Midgard MVC is a web framework for PHP. Being a framework, it provides a standard way to build and deploy web applications. Your applications provide a set of interfaces that Midgard MVC calls when a matching HTTP request is made.
For serving the HTTP requests, Midgard MVC uses an application server written in PHP. This allows for persistence and advanced caching on the PHP level.
Midgard MVC can be used with the Midgard2 Content Repository, but it also works without it.
Downloads
Source
The easiest way to install Midgard MVC is by using the midgardmvc_installer tool. You can install the tool with PEAR by running the following:
$ sudo pear channel-discover pear.indeyets.pp.ru
$ sudo pear install indeyets/midgardmvc_installer
After this you can install Midgard MVC applications by pointing the Midgard MVC installer to a application configuration YAML file and a target directory. For example:
$ midgardmvc install https://github.com/bergie/org_midgardproject_productsite/raw/master/rdf.yml ~/midgardexample
The Midgard MVC installer will install Midgard MVC, and all components and libraries required by the application. It will also generate a Midgard2 database for the application. Now running the application is easy:
$ ~/midgardexample/run
By default the AppServer in PHP used for running Midgard MVC will be available in http://localhost:8001.
Midgard MVC request process
Basic building blocks:
- Application: a configuration file describing the components and other settings used in a Midgard MVC website
- Component: a PHP module intended to run as part of a web application. For example: a news listing. Provides routes, controllers and templates
- Library: a PHP module that can be called by other modules to perform some task. Does not run as part of a website. For example: a form validation library
- Service: a standardized interface to access a given functionality. For example: authentication
- Provider: a standardized interface to access given information. For example: hierarchy. The difference between Services and Providers is that Services perform tasks while Providers merely provide access to a particular type of data
Bootstrapping
- Midgard MVC bootstrap PHP file (
framework.php
) is called - Midgard MVC bootstrap registers an autoloader (
midgardmvc_core::autoload()
) that will be used for loading the necessary PHP files - Front controller (
midgardmvc_core::get_instance()
) starts - Front controller loads configuration from a configuration provider
- Front controller loads component providers specified in configuration
- Front controller loads hierarchy providers specified in configuration
Request processing
- A request object gets populated with the current HTTP request parameters
- Process injectors are called, if the loaded components registered any
- Request object uses hierarchy providers to determine what components handle the request
- Request object loads the necessary component
- Front controller passes the request object to the Dispatcher
- Dispatcher dispatches the request to the component controller class, passing it the request object
- Component controller class executes and sets data to the request object
Templating
- Front controller loads template providers specified in configuration
- Template injectors are called, if the loaded components registered any
- Front controller determines template to be used with the request
- Front controller uses a template provider to generate request output
- Request output is sent to browser
Midgard MVC Front Controller
The Midgard MVC Front Controller midgardmvc_core is responsible for catching a HTTP request and ensuring it gets processed and templated. The Front Controller implements a Singleton pattern, meaning that only a single Midgard MVC Front Controller is available at a time.
The Front Controller is accessible via:
$mvc = midgardmvc_core::get_instance();
Midgard MVC Dispatcher
The Midgard MVC Dispatcher receives a Request object and instantiates and calls the necessary Components and Controllers, and calls their action methods.
The Dispatcher is accessible via:
$dispatcher = midgardmvc_core::get_instance()->dispatcher;
$dispatcher->dispatch($request);
Depending on what Controllers and action methods were called (if any), this will either return the Request object with some new data populated or cause an Exception to be thrown.
Application configuration
Application configuration is a configuration file read before Midgard MVC starts where you can define application-wide shared configuration settings, including overriding Midgard MVC default configurations. The components used with an application are defined in the application configuration file.
The application configuration is by default located in the root of your Midgard MVC installation directory in a YAML file called application.yml. Example:
name: Example Blog
components:
midgardmvc_core:
- {type: github, user: midgardproject, repository: midgardmvc_core, branch: master}
org_midgardproject_projectsite:
- {type: github, user: midgardproject, repository: org_midgardproject_projectsite, branch: master}
services_dispatcher: midgard2
providers_component: midgardmvc
You can also define a custom location for your application configuration file by setting midgardmvc.application_config in your php.ini. Example:
midgardmvc.application_config=/etc/midgard2/example.yml
Please note that only the components specified in your Application YAML file (and their dependencies) will be available in this Midgard MVC instance.
Structure of a component
A component is a functional module that runs inside Midgard MVC. It is usually run associated to a particular Midgard MVC Node, but can also tack itself to be run alongside another component's Node.
componentName
- manifest.yml: Component's package manifest, routes and signal listener registration
configuration
- defaults.yml: Component's default configuration, as name-value pairs
controllers
- controllername.php: A controller class for the component
models
- classname.xml: Midgard Schema used by the component, registers type classname
- classname.php: PHP class that extends a Midgard Schema
views
- viewname.xml: Midgard View used by the component, registers view viewname
services
- authentication.php: component-specific implementation of Midgard MVC Authentication Service (these are rarely needed)
templates
- templatename.xhtml: A TAL template used by the component, named templatename
Defining routes
Individual routes (or views) of a component are defined in the component manifest. Midgard MVC takes the route definitions and constructs Route objects out of them.
Routes map between an URL and the corresponding controller class and an action method.
Minimal route definition:
route_identifier:
- path: '/some/url'
- controller: controller class
- action: action name
- template_aliases:
- root: name of template used when "root" is included
- content: name of template used when "content" is included
Route matching
There are several ways Midgard MVC matches Requests to Routes. The matching is handled by providing an Intent to the factory method of the Request class:
- Explicit matching
- In an explicit match we know the component instance, route identifier and arguments
- Implicit matching
- In implicit matching we know one or multiple of:
- Route identifier and arguments
- URL
- Component name
- Existing request object
- URL patterns
- In implicit matching we know one or multiple of:
Route path (under a given hierarchy node) is defined with the path property of the route. The paths follow the URL pattern specification.
Variables can be used with URL patterns in the following way:
- Named variable
-path: '/{$foo}'
- With request /bar the controller would be called with
$args['foo'] = 'bar'
- Named and typed variable
-path: '/latest/{int:$number}'
- With request /latest/5 the controller would be called with
$args['number'] = 5
- Unnamed arguments
-path '/file/@'
- With request /file/a/b the controller would be called with
$args['variable_arguments'] = array('a', 'b')
Limiting route availability
If you want routes to be accessible only when run on the root folder of the website (/), you can add the following to that route definition:
routes:
some_route:
root_only: true
Another option is to ensure a route is accessible only when used in subrequests (dynamic_load
and dynamic_call
) and not accessible directly by browser. This can be achieved by the following in route definition:
routes:
some_route:
subrequest_only: true
The component provider handling route registration will ensure that routes not fitting these limitations will not be registered for the Request.
Workings of a controller
Controller is a PHP class that contains one or more actions matching route definitions of a component. When invoked, Midgard MVC dispatcher will load the component, instantiate the controller class specified in route definition, passing it the Request object and a reference to request data array, and finally call the action method corresponding to the route used, passing it the arguments from the request.
The controller will then do whatever processing or data fetching it needs to do. Any content the controller wants to pass to a template should be added to the data array. If any errors occur, the controller should throw an exception.
Actions are public methods in a controller class. Action methods are named using pattern <HTTP verb>_<action name>
, for example get_article()
or post_form()
. Action methods will receive the possible URL arguments as an argument containing an array.
Here is a simple example. Route definition from net_example_calendar/manifest.yml
:
show_date:
- path: '/date'
- controller: net_example_calendar_controllers_date
- action: date
- template_aliases:
- content: show-date
Controller class net_example__calendar/controllers/date.php
:
<?php
class net_example_calendar_controllers_date
{
public function __construct(midgardmvc_core_request $request)
{
// All controllers receive the Request instance when invoked
$this->request = $request;
}
public function get_date(array $args)
{
// Information we want to pass to the template should be set into the data array
$this->data['date'] = strftime('%x %X');
}
}
Showtime
Once a controller has been run, the next phase in MVC execution is templating. There are two levels of templates used:
- Template entry point: the "whole page" template, which includes a content area by having a
<mgd:include>content</mgd:include>
- Content entry point: the "content area" of a page, as defined in the main template
Each route definition can decide what templates to use in each of these. If a route wants to override the whole site template, then the route should define its own template entry point, and if it only wants to show something in the content area, then it should define its own content entry point.
Templates are defined by giving them a name. For example, a template for displaying the current date could be called show-date. When MVC gets into the templating stage. This is defined in the route:
show_date:
- path: '/date'
- controller: net_example_calendar_date
- action: date
- template_aliases:
- content: show-date
When the templating phase of the route happens, MVC will look for such element from the template stack. Template stack is a list of components running with the current request. First MVC looks for the element in the current component, and if it can't be found there it goes looking for it down the stack:
- Current running component's templates directory
- templates directories of any components injected to the template stack
- Midgard MVC core templates directory
The first matching template element will be used and executed via TAL. The data returned by the component will be exposed into TAL as a current_component
variable. In case of our date example the template could simply be a net_example_calendar/templates/show-date.xhtml
file with following contents:
<p>Current date is <span tal:content="current_component/date">5/8/1999 01:00</span></p>
Please consult the PHPTAL manual for more information on using TAL templating commands.
Request isolation and making of sub-requests
Midgard MVC supports handling multiple requests within same web page. For example, the main content of your page can be served from a request, and then a news listing in a sidebar can be handled from a sub-request.
Since this means that potentially multiple routes, controllers and templates will be run within the same PHP execution, every request must be isolated within the PHP variable scope. To accomplish this, the principle is that all request-specific information is stored within the Request object that gets passed around between the front controller, dispatcher and actual controllers, and all of them are actually stateless. For example, the dispatch method of a dispatcher, or the template method of the front controller may be run multiple times within same PHP execution.
Within any stage of Midgard MVC execution you can make a sub-request in the following way:
<?php
// Set up intent, for example a hierarchy node, node URL or component name
$intent = '/myfolder/date';
// Get a Request object based on the intent
$request = midgardmvc_core_request::get_for_intent($intent);
// Process the Request
midgardmvc_core::get_instance()->dispatcher->dispatch($request);
// Use the resulting data
$component_data = $request->get_data_item('current_component');
echo $component_data['date'];
?>
For convenience purposes there are two helpers for sub-request handling in the templating class. Dynamic call will perform the sub-request and return its data:
$data = midgardmvc_core::get_instance()->templating->dynamic_call($intent, $route_id, $route_args);
Dynamic load will perform the subrequest and return its templated output:
$content = midgardmvc_core::get_instance()->templating->dynamic_load($intent, $route_id, $route_args, true);
These same calls can also be used inside templates. For example, to display news items returned by another route, you can:
<ol tal:define="news php:midgardmvc.templating.dynamic_call('/news/', 'index', array())">
<li class="news" tal:repeat="item news/items">
<h3>
<a tal:content="item/title" tal:attributes="href item/url">Blog post</a>
</h3>
<div tal:content="structure item/abstract">
Lorem ipsum
</div>
</li>
</ol>
If you want the delegate the template of that sub-request to the component being called, then use dynamic_load.