-
Notifications
You must be signed in to change notification settings - Fork 8
#SamsonPHP Framework
SamsonPHP uses convention-over-configuration(COC) principle. Application bootstrap has defined catalog structure:
-
/appDefault application folder -
/controllerFolder for storing local modules controllers files -
/modelFolder for storing local modules model files -
/viewFolder for storing local modules views files -
/view/index.phpDefault application template -
/cssFolder for CSS/LESS files -
/jsFolder fo Javascript/Coffee files -
/imgFolder for images -
index.phpInit script -
config.phpMain configuration file -
.htaccessApache parameters for rewriting -
composer.jsonComposer configuration file
##URL routing
Creating route is very simple, for example lets create a Contacts page, we want this page to be accessible by URL /contacts. First of all we must create a local module controller file whitch should be located in app/controller folder. The name of this file will define SamsonPHP local module name. So we must create a new "Contacts" SamsonPHP local module controller file app/controller/contacts.php
This is all available routes supported by contoller:
| URL | Description | PHP Function |
|---|---|---|
GET /contacts |
default GET controller | function contacts() |
POST /contacts |
default POST controller | function contacts__POST() |
PUT /contacts |
default PUT controller | function contacts__PUT() |
UPDATE /contacts |
default UPDATE controller | function contacts__UPDATE() |
DELETE /contacts |
default DELETE controller | function contacts__DELETE() |
GET /contacts/[action] |
default controller action | function contacts_[action]() |
GET /contacts/[parameter] |
universal controller | function contacts__HANDLER([parameter]) |
If you want to pass any parameters to controllers, they must be specified splitted by / sign, for example: /contacts/location/usa/la will be passed to controller action function contacts_location($country = [usa], $state = [la]).
Controller file is consider valid only if it has at least one correctly defined controller function or controller action function, otherwise this file is ignored.
If you called not defined controller action /contacts/zombie/zombieparameter, this action call will be handled by universal controller if it is defined: function contacts__HANDLER($param1 = [zombie], $param2 = [zombieparameter]).
If cotroller action function contacts_zombie($param = [zombieparameter]) is defined, universal controller would not be called.
Universal controller can also handle default GET controller GET /contacts request as well.
If you want to handle 2nd URL argument as parameter, you should define universal controller and handle it in its body.
Router parsing logic is very simple and consists of 4 simple steps:
####Step #1 - Analyze module /dog[...]
This step analyzes first URL argument, which follows after first / sign in URL string. If framework does not has loaded module with name Dog we go to step #404. If module Dog is loaded and we have second URL argument defined then we go to step #2, otherwise we go to step #3
####Step #2 - Analyze controller action /dog/bark[...]
This step analyzes second URL argument, which follows after second / sign in URL string. If module Dog has defined controller action Bark function dog_bark($param1,...) Then this function will be called with passed parameters.
If controller action Bark is not defined than router will search for universal controller function dog__HANDLER($param,...) to execute it.
If none of described controllers existed we go to step #404
####Step #3 - Call default GET /dog controller
If default GET controller is defined function dog() then execute it, otherwise call universal controller if it is defined function dog__HANDLER($param,...)
If none of this controllers if defined, then this controller file is not valid and we go to step #404
####Step #404
Call e404 handler if it is defined, otherwise return e404 HTTP response status.
###Operating in a controller function
The main purpose of any controller or controller action function is to prepare output data for request. This is usually acomplished by setting the view to render and passing necessary parameters to it.
First of all we must create a view file which are situated at /app/view folder. For example we create a Contacts view file /app/view/contacts.php.
Views in
/app/viewfolder can have any sub level nesting structure as needed.
If our module has only one view, its a good practice to create only one view file for it /app/view/contacts.php, but if our module will have several views, we recommend creating /app/view/contacts folder and place base view in /app/view/contacts/index.php.
Accelerator function
s()returns pointer to SamsonPHP core object
When router is executing controller function it switches framework context to this module and current module object can be accessed by m() function, which returns pointer to current module object. All further actions related to module must be done throw this object pointer.
Function
m()is an accelerator function for callings()->module([module])for getting current module if no parameters passed or trying to find module by specified name.
If we want to set view for further rendering, we must call m()->view([path_to_view]) method.
When we call module
view([path_to_view])method, module automatically scans for all views located in/app/view/folder to find the shortest appliable match. If we have/app/view/dog/dog/index.phpandapp/view/dog/index.phpthenm()->view('dog/index')will matchapp/view/dog/index.phpview file
If matching view file is not found then the error will be signaled.
You must understand that this view setting is actually a delayed event, and this view will really render only when it will be called in main template renderer.
If we want to pass any parameter to view for rendering, we should use m()->set([parameter_name],[parameter_value]) module method. If we want to pass [heading text] to and make it accessible in view under $heading variable, we should use m()->set('heading', 'I\'m a heading').
Method set() can be used to pass array of parameters to view, if we pass associative array $arr = array('heading'=>'I\'m a heading', 'text' => 'This is text') then we can use special set() function signature m()->set($arr) without specifing second argument. After this call module will automatically create variables $heading and $text which will be available in rendered view.
If we want to add special prefix to all view variables passed from array to avoid collisions, we can use m()->set() second parameter as prefix m()->set($arr, 'prefix'). All variables from passed array will be available as $prefix_heading and $prefix_text in a view file.
If you want to pass an object to view for rendering there is one limitation for this, it must implement \samson\core\iModuleViewable interface class MyObject implements iModuleViewable and must implement one its function function toView( $prefix = NULL, array $restricted = array() ) and return an associative array with $key => $value pairs to imitate array behavior as described earlier.
If we have class $cat = new Cat() with field public $age and want to pass it to view m()->set($cat) we should implement iModuleViewable and in function toView() use return array($prefix.$age => $this->age).
Why we concat $prefix variable? Because of another super usefull m()->set() feature - for example we have two different cats $cat = new Cat(); $cat2 = new Cat() instances that we want to pass to a view file for rendering, they are absolutely equal and has equeal fields, so if we pass them to a view m()->view([path])->set($cat)->set($cat2) what will happen?
the $age field will be erased, and we cannot show information about both cats.
To avoid this we can use $prefix to to split cats ages in a view m()->view([path])->set($cat, 'first')->set($cat2, 'second') and we can then simply get their ages $first_age and $second_age in a view file and use them.
But we have another surprise for you, this is PHP and it is marvelous! It has magic methods, and we present super beatiful syntax - m()->view([path])->first($cat)->second($cat), yes you are right! You set the variable name or prefix by using function call.
Setting
m()->view([path])->[name/prefix]([$variable])is not actually rendering the view, this is just seetings for rendering. The actual rendering will occur when them([module])->render([controller])will be called
###Rendering smaller views If we want to render catalog list which consists of items we would probably create two type of views:
- View for whole catalog page
/app/view/catalog/index.php<div><?php echo $items?></div> - View for good block
/app/view/catalog/item.php<div><h2><?php echo $item?></h2></div>
To render this smaller parts of particular page we need to use special module function m()->output() which actually performs view rendering. Let's create default GET controller /app/controller/catalog.php
function catalog()
{
$html = '';
foreach (array('apple', 'banana', 'cucumber') as $item) {
$html .= m()->view('catalog/item')->item($item)->output();
}
m()->view('catalog/index')->items($html)->title('Catalog');
}##Asynchronous reponse
If we want to generate ajax response then we should avoid rendering default template by using special core function s()->async(true). And after this call all response output must be generate by developer because SamsonPHP wont output anything. Lets create asynchonous response based on catalog example, create controller action /catalog/asyncresponse
function catalog_asyncresponse()
{
s()->async(true);
$html = '';
foreach (array('apple', 'banana', 'cucumber') as $item) {
$html .= m()->view('catalog/item')->item($item)->output();
}
echo m()->view('catalog/index')->items($html)->title('Catalog')->output();
}As you can see we manually called m()->output() method and echo it to current output stream context.
###Asynchronous controller Building convenient web application is connected with fully asynchronous user interface(UI) actions, so we have build ability to build special type of contollers that are executed sepately in SamsonPHP core, not like standard controllers we call them asynchronous controllers. \
This controller must have _async_ prefix for example function catalog_async_list($param,...), each async controller by ideal must implement one UI action, in our example it is rendering list of catalog items, without rendering whole catalog page, only items list. The main idea of async cotroller is that it must return asynchronous response associative array which must have one required key definde status, there are two possible values array('status' => '1') or array('status' => '0').
In asynchronous controllers we dont need to call
s()->async(true), this is processed automatically
function catalog_async_list()
{
$result = array('status' => '1', 'html_list' => '');
foreach (array('apple', 'banana', 'cucumber') as $item) {
$result['html_list'] .= m()->view('catalog/item')->item($item)->output();
}
return $result;
}All asynchronous controllers are accessible via HTTP GET/POST/DELETE/PUT/UPDATE requests without
_async_prefix but must have special HTTP request header set or POST/GET fieldSJSAsync:true. If we have asynchronous controller actionfunction catalog_async_list()then it will be accessible viaHTTP GET /catalog/list
If asynchronous controller returns status == '1' then system automatically encodes it response status array into JSON using json_encode()
###Asynchronous controller chain
Now lets imagine then we have another UI action /catalog/delete/[identifier] for deleting item from catalog list. We are creating fully asynchronous UI so the logical way to delete item from catalog would be
- Call
/catalog/delete/[identifier] - Call
/catalog/listand update DOM
But with this approach we have to write two ajax request in clientside javascript code, analyze if delete action performed sucessfully and something changed and only then perform second ajax request to then server and rerender the list. To solve this and similar tasks more beatifull and optimized we use asynchoronous controller chain
To use it we just form HTTP request consisted of both conrtoller calls
GET /catalog/delete/[identifier]/list
And what will happen? System automatically parses URL and match every asynchronous controller action call and executes each controller one by one. If one of the controllers returns status == '0' chain fails and response is generated returning HTTP 404 status. All of the asynchronous response status arrays returned by each executed controllers are merged into one sigle array.
If several controllers add data to asynchronous response status array then all this data will be returned to client as JSON object. If two controllers return the same key then the last value for this key will be reterned.
Developed by SamsonOS. Feel free to contact us at any time