There are plenty of excellent MVC's out there. So why the need for yet another? Simply because they are big and complex. Why use a sledgehammer to crack a nut?
To provide a compact codebase providing the basics for any application. It's designed to be fast. Really fast. With the built in profiler you can easily see how fast each fragment of your site is, how much memory it consumes, and how each fragment was initiated.
We provide the basics of the MVC, everything from the router, dispatcher, controllers, views, and view helpers. We even have a few components you can make use of, including:
- Model ORM
- Cache
- Event listeners
- Formatting of data
- Notice messages
- Data validation
- Store (APC, Cookie, Memcache(d), Request, and Session)
This project contains a PHPUnit test suite with 64 tests and 194 assertions (all passing).
Checkout a copy of the source files and head over to your app's config in /Library/MyProject/config.ini
and update the path
's to your specific directory structure. You can rename the MyProject
in the Library
directory, just make sure you also update the namespace definition in each of your .php
files.
We parse URI's such as /index/hello/my/variables/go/here/foobar
and place it into the GET array. Dumping the will give you:
array(
'controller' => 'index',
'action' => 'hello',
'my' => 'variables',
'go' => 'here',
'foobar' => true
)
This will forward the request onto the Index
controller and into the hello
action.
If you want to customise your URL's as such that the basic /controller/action/my/variables/go/here
will not suffice, then you can use the built in Router
.
// Global configurations
include dirname(__FILE__) . '/../Library/global.php';
// Create new Router instance
$router = new Core\Router();
$router
->addRoute('Foo')
->setRoute('foo/:bar/:acme')
->setFormat(array(
'bar' => '\d+',
'acme' => '[a-z0-9]+')
)
->setEndpoint(array(
'controller' => 'Foo',
'action' => 'bar')
);
// Start the application, passing in the Router
new Core\Front('MyProject', $router);
You can add as many routes as you like. Any variables that you do not specify in the setFormat
method (which is optional) will use the regex of [\w\-]+
. The setEndpoint
method does not require an action
parameter, but if none is declared then it will use the default index
action of that controller.
This advanced routing system will use the first declared route that it finds matching the request URL.
URL's will often change. Defining them in a single place (the router) will save you having to rewrite them in your View Helpers/Partials. It is also safer because URL encoding will be taken care for you. You can call the Route
View Helper via:
$this->view->route(array(
'route' => 'Foo',
'params' => array(
'bar' => 1234,
'acme' => 'foobar'
)
);
Controllers are created in /Library/MyProject/Controller
, the file name begins with an uppercase letter and ends in a .php
extension, so index
would be called Index.php
.
You can pass variables from the Controller
to the View
via:
public function indexAction() {
$this->view->addVariable($name = 'foo', $value = 'bar');
}
Which you can then access in the Layout and View Script by simple using the name of the variable you added to the view, such as in the above example: $foo
You can interact directly with the Request
object (data passed by the URL and forms, server variables, and the URL object):
public function indexAction() {
if ($getVariable = $this->request->get('foo')) {
echo "Foo is: {$getVariable}";
}
if ($postVariable = $this->request->post('bar')) {
echo "Bar is: {$postVariable}";
}
}
Actions are named the same as specified in the URI, are lowercase, and end in Action
. So the index
action will be named indexAction()
.
Caching can be turned on or off from your projects configuration file (/Library/MyProject/config.ini
), and you can set how long you want before the cache is invalidated. You can also select how you would like your cache to be stored: "File", "Apc", or "Memcache".
[cache]
interface = "File"
enable = true
life = 60
You can forward to another action (or controller) via the return $this->forward('action', 'controller')
command in a controller.
You can change the layout (layouts are stored in /Library/MyProject/Layout
, are lowercase, and end with a .phtml
extension) that will wrap the View by calling the $this->setLayout('layout')
method in a controllers action.
class Index extends Core\Controller
{
public function indexAction() {
$this->setLayout('new-layout');
}
}
Views are stored in the /Library/MyProject/View/Script
directory, and each controller has their own directory. So the Index
controller's views will be stored in /Library/MyProject/View/Script/Index
. Each of the controllers actions have a separate view, so the Index
controller's hello
action will be stored in /Library/MyProject/View/Script/Index/hello.phtml
.
The view has a built in method to generate URL's. You can specify the controller, action and any variables. You can also state whether you want to retain the current pages URL variables (disabled by default). This is called via:
echo $this->url(
array(
'controller' => 'index',
'action' => 'hello',
'variables' => array('foo' => 'bar'),
'variable_retain' => true
)
);
You can output HTML to the browser safely by using the $this->safe(array('string' => 'Evil string'))
method.
Your View Scripts can easily direct logic away from themselves into View Helpers. View helpers can have their own template files, called Partials. For example, the Test
View Helper:
return $this->renderPartial('test', array(
'testVar' => $params['testVar']
));
Every storage method has exactly the same interface; has
, put
, get
, and remove
. You pass a storage method into the Store
class and you can then interact with it.
$store = new Store(new Store\File());
$store->put('foo', 'bar');
$store->get('foo'); // Echo's "bar"
Some storage methods might require setting up (such as Memcache
), you can easily do this before passing it into the Store
.
$memcache = new Store\Memcache();
$memcache->setup($server, $host, $port);
$store = new Store($memcache);
$store->put('foo', 'bar');
$store->get('foo'); // Echo's "bar"
The advantages of a single interface is you can very easily switch your storage mechanism without having to change any of the logic.
A powerful in-built profiler will let you know exactly where your application is expending time and additional memory. Its waterfall display allows you to see which functions have been called by whom.