Tutorial: INVO

In this second tutorial, we’ll explain a more complete application in order to gain a deeper understanding of developing with Phalcon. INVO is one of the sample applications we have created. INVO is a small website that allows users to generate invoices and do other tasks such as manage customers and products. You can clone its code from Github.

INVO was made with the client-side framework Bootstrap. Although the application does not generate actual invoices, it still serves as an example showing how the framework works.

Project Structure

Once you clone the project in your document root you’ll see the following structure:

As you know, Phalcon does not impose a particular file structure for application development. This project has a simple MVC structure and a public document root.

Once you open the application in your browser http://localhost/invo you’ll see something like this:

The application is divided into two parts: a frontend and a backend. The frontend is a public area where visitors can receive information about INVO and request contact information. The backend is an administrative area where registered users can manage their products and customers.

Routing

INVO uses the standard route that is built-in with the Router component. These routes match the following pattern: /:controller/:action/:params. This means that the first part of a URI is the controller, the second the controller action and the rest are the parameters.

The following route /session/register executes the controller SessionController and its action registerAction.

Configuration

INVO has a configuration file that sets general parameters in the application. This file is located at app/config/config.ini and is loaded in the very first lines of the application bootstrap (public/index.php):

Phalcon Config (Phalcon\Config) allows us to manipulate the file in an object-oriented way. In this example, we’re using an ini file for configuration but Phalcon has adapters for other file types as well. The configuration file contains the following settings:

Autoloaders

The autoloader registers a set of directories in which the application will look for the classes that it will eventually need.

<?php$loader=newPhalcon\Loader();// We're a registering a set of directories taken from the configuration file$loader->registerDirs([APP_PATH.$config->application->controllersDir,APP_PATH.$config->application->pluginsDir,APP_PATH.$config->application->libraryDir,APP_PATH.$config->application->modelsDir,APP_PATH.$config->application->formsDir,]);$loader->register();

Note that the above code has registered the directories that were defined in the configuration file. The only directory that is not registered is the viewsDir because it contains HTML + PHP files but no classes. Also, note that we use a constant called APP_PATH. This constant is defined in the bootstrap (public/index.php) to allow us to have a reference to the root of our project:

<?php// ...define('APP_PATH',realpath('..').'/');

Registering services

Another file that is required in the bootstrap is (app/config/services.php). This file allows us to organize the services that INVO uses.

Service registration is achieved with closures for lazy loading the required components:

<?phpusePhalcon\Mvc\UrlasUrlProvider;// .../**
* The URL component is used to generate all kind of URLs in the application
*/$di->set('url',function()use($config){$url=newUrlProvider();$url->setBaseUri($config->application->baseUri);return$url;});

We will discuss this file in depth later.

Handling the Request

If we skip to the end of the file (public/index.php), the request is finally handled by Phalcon\Mvc\Application which initializes and executes all that is necessary to make the application run:

Dependency Injection

In the first line of the code block above, the Application class constructor is receiving the variable $di as an argument. What is the purpose of that variable? Phalcon is a highly decoupled framework so we need a component that acts as glue to make everything work together. That component is Phalcon\Di. It’s a service container that also performs dependency injection and service location, instantiating all components as they are needed by the application.

There are many ways of registering services in the container. In INVO, most services have been registered using anonymous functions/closures. Thanks to this, the objects are instantiated in a lazy way, reducing the resources needed by the application.

For instance, in the following excerpt the session service is registered. The anonymous function will only be called when the application requires access to the session data:

<?phpusePhalcon\Session\Adapter\FilesasSession;// ...// Start the session the first time a component requests the session service$di->set('session',function(){$session=newSession();$session->start();return$session;});

Here, we have the freedom to change the adapter, perform additional initialization and much more. Note that the service was registered using the name session. This is a convention that will allow the framework to identify the active service in the services container.

A request can use many services and registering each service individually can be a cumbersome task. For that reason, the framework provides a variant of Phalcon\Di called Phalcon\Di\FactoryDefault whose task is to register all services providing a full-stack framework.

It registers the majority of services with components provided by the framework as standard. If we need to override the definition of some service we could just set it again as we did above with session or url. This is the reason for the existence of the variable $di.

Log into the Application

A log in facility will allow us to work on backend controllers. The separation between backend controllers and frontend ones is only logical. All controllers are located in the same directory (app/controllers/).

To enter the system, users must have a valid username and password. Users are stored in the table users in the database invo.

Before we can start a session, we need to configure the connection to the database in the application. A service called db is set up in the service container with the connection information. As with the autoloader, we are again taking parameters from the configuration file in order to configure a service:

<?phpusePhalcon\Db\Adapter\Pdo\MysqlasDbAdapter;// ...// Database connection is created based on parameters defined in the configuration file$di->set('db',function()use($config){returnnewDbAdapter(['host'=>$config->database->host,'username'=>$config->database->username,'password'=>$config->database->password,'dbname'=>$config->database->name,]);});

Here, we return an instance of the MySQL connection adapter. If needed, you could do extra actions such as adding a logger, a profiler or change the adapter, setting it up as you want.

The following simple form (app/views/session/index.volt) requests the login information. We’ve removed some HTML code to make the example more concise:

Instead of using raw PHP as the previous tutorial, we started to use Volt. This is a built-in template engine inspired by Jinja_ providing a simpler and friendly syntax to create templates. It will not take too long before you become familiar with Volt.

The SessionController::startAction function (app/controllers/SessionController.php) has the task of validating the data entered in the form including checking for a valid user in the database:

<?phpclassSessionControllerextendsControllerBase{// ...privatefunction_registerSession($user){$this->session->set('auth',['id'=>$user->id,'name'=>$user->name,]);}/**
* This action authenticate and logs a user into the application
*/publicfunctionstartAction(){if($this->request->isPost()){// Get the data from the user$email=$this->request->getPost('email');$password=$this->request->getPost('password');// Find the user in the database$user=Users::findFirst(["(email = :email: OR username = :email:) AND password = :password: AND active = 'Y'",'bind'=>['email'=>$email,'password'=>sha1($password),]]);if($user!==false){$this->_registerSession($user);$this->flash->success('Welcome '.$user->name);// Forward to the 'invoices' controller if the user is validreturn$this->dispatcher->forward(['controller'=>'invoices','action'=>'index',]);}$this->flash->error('Wrong email/password');}// Forward to the login form againreturn$this->dispatcher->forward(['controller'=>'session','action'=>'index',]);}}

For the sake of simplicity, we have used sha1 to store the password hashes in the database, however, this algorithm is not recommended in real applications, use bcrypt instead.

Note that multiple public attributes are accessed in the controller like: $this->flash, $this->request or $this->session. These are services defined in the services container from earlier (app/config/services.php). When they’re accessed the first time, they are injected as part of the controller. These services are shared, which means that we are always accessing the same instance regardless of the place where we invoke them. For instance, here we invoke the session service and then we store the user identity in the variable auth:

Note, the use of ‘bound parameters’, placeholders :email: and :password: are placed where values should be, then the values are ‘bound’ using the parameter bind. This safely replaces the values for those columns without having the risk of a SQL injection.

If the user is valid we register it in session and forwards him/her to the dashboard:

Securing the Backend

The backend is a private area where only registered users have access. Therefore, it is necessary to check that only registered users have access to these controllers. If you aren’t logged into the application and you try to access, for example, the products controller (which is private) you will see a screen like this:

Every time someone attempts to access any controller/action, the application verifies that the current role (in session) has access to it, otherwise it displays a message like the above and forwards the flow to the home page.

Now let’s find out how the application accomplishes this. The first thing to know is that there is a component called Dispatcher. It is informed about the route found by the Routing component. Then, it is responsible for loading the appropriate controller and execute the corresponding action method.

Normally, the framework creates the Dispatcher automatically. In our case, we want to perform a verification before executing the required action, checking if the user has access to it or not. To achieve this, we have replaced the component by creating a function in the bootstrap:

We now have total control over the Dispatcher used in the application. Many components in the framework trigger events that allow us to modify their internal flow of operation. As the Dependency Injector component acts as glue for components, a new component called EventsManager allows us to intercept the events produced by a component, routing the events to listeners.

Events Management

The EventsManager allows us to attach listeners to a particular type of event. The type that interests us now is ‘dispatch’. The following code filters all events produced by the Dispatcher:

<?phpusePhalcon\Mvc\Dispatcher;usePhalcon\Events\ManagerasEventsManager;$di->set('dispatcher',function(){// Create an events manager$eventsManager=newEventsManager();// Listen for events produced in the dispatcher using the Security plugin$eventsManager->attach('dispatch:beforeExecuteRoute',newSecurityPlugin());// Handle exceptions and not-found exceptions using NotFoundPlugin$eventsManager->attach('dispatch:beforeException',newNotFoundPlugin());$dispatcher=newDispatcher();// Assign the events manager to the dispatcher$dispatcher->setEventsManager($eventsManager);return$dispatcher;});

When an event called beforeExecuteRoute is triggered the following plugin will be notified:

<?php/**
* Check if the user is allowed to access certain action using the SecurityPlugin
*/$eventsManager->attach('dispatch:beforeExecuteRoute',newSecurityPlugin());

The hook events always receive a first parameter that contains contextual information of the event produced ($event) and a second one that is the object that produced the event itself ($dispatcher). It is not mandatory that plugins extend the class Phalcon\Mvc\User\Plugin, but by doing this they gain easier access to the services available in the application.

Now, we’re verifying the role in the current session, checking if the user has access using the ACL list. If the user does not have access we redirect to the home screen as explained before:

<?phpusePhalcon\Acl;usePhalcon\Events\Event;usePhalcon\Mvc\User\Plugin;usePhalcon\Mvc\Dispatcher;classSecurityPluginextendsPlugin{// ...publicfunctionbeforeExecuteRoute(Event$event,Dispatcher$dispatcher){// Check whether the 'auth' variable exists in session to define the active role$auth=$this->session->get('auth');if(!$auth){$role='Guests';}else{$role='Users';}// Take the active controller/action from the dispatcher$controller=$dispatcher->getControllerName();$action=$dispatcher->getActionName();// Obtain the ACL list$acl=$this->getAcl();// Check if the Role have access to the controller (resource)$allowed=$acl->isAllowed($role,$controller,$action);if(!$allowed){// If he doesn't have access forward him to the index controller$this->flash->error("You don't have access to this module");$dispatcher->forward(['controller'=>'index','action'=>'index',]);// Returning 'false' we tell to the dispatcher to stop the current operationreturnfalse;}}}

Getting the ACL list

In the above example we have obtained the ACL using the method $this->getAcl(). This method is also implemented in the Plugin. Now we are going to explain step-by-step how we built the access control list (ACL):

<?phpusePhalcon\Acl;usePhalcon\Acl\Role;usePhalcon\Acl\Adapter\MemoryasAclList;// Create the ACL$acl=newAclList();// The default action is DENY access$acl->setDefaultAction(Acl::DENY);// Register two roles, Users is registered users// and guests are users without a defined identity$roles=['users'=>newRole('Users'),'guests'=>newRole('Guests'),];foreach($rolesas$role){$acl->addRole($role);}

Now, we define the resources for each area respectively. Controller names are resources and their actions are accesses for the resources:

<?phpusePhalcon\Acl\Resource;// ...// Private area resources (backend)$privateResources=['companies'=>['index','search','new','edit','save','create','delete'],'products'=>['index','search','new','edit','save','create','delete'],'producttypes'=>['index','search','new','edit','save','create','delete'],'invoices'=>['index','profile'],];foreach($privateResourcesas$resourceName=>$actions){$acl->addResource(newResource($resourceName),$actions);}// Public area resources (frontend)$publicResources=['index'=>['index'],'about'=>['index'],'register'=>['index'],'errors'=>['show404','show500'],'session'=>['index','register','start','end'],'contact'=>['index','send'],];foreach($publicResourcesas$resourceName=>$actions){$acl->addResource(newResource($resourceName),$actions);}

The ACL now knows about the existing controllers and their related actions. Role Users has access to all the resources of both frontend and backend. The role Guests only has access to the public area:

<?php// Grant access to public areas to both users and guestsforeach($rolesas$role){foreach($publicResourcesas$resource=>$actions){$acl->allow($role->getName(),$resource,'*');}}// Grant access to private area only to role Usersforeach($privateResourcesas$resource=>$actions){foreach($actionsas$action){$acl->allow('Users',$resource,$action);}}

Working with the CRUD

Backends usually provide forms to allow users to manipulate data. Continuing the explanation of INVO, we now address the creation of CRUDs, a very common task that Phalcon will facilitate you using forms, validations, paginators and more.

Most options that manipulate data in INVO (companies, products and types of products) were developed using a basic and common CRUD (Create, Read, Update and Delete). Each CRUD contains the following files:

<?phpclassProductsControllerextendsControllerBase{/**
* The start action, it shows the 'search' view
*/publicfunctionindexAction(){// ...}/**
* Execute the 'search' based on the criteria sent from the 'index'
* Returning a paginator for the results
*/publicfunctionsearchAction(){// ...}/**
* Shows the view to create a 'new' product
*/publicfunctionnewAction(){// ...}/**
* Shows the view to 'edit' an existing product
*/publicfunctioneditAction(){// ...}/**
* Creates a product based on the data entered in the 'new' action
*/publicfunctioncreateAction(){// ...}/**
* Updates a product based on the data entered in the 'edit' action
*/publicfunctionsaveAction(){// ...}/**
* Deletes an existing product
*/publicfunctiondeleteAction($id){// ...}}

The Search Form

Every CRUD starts with a search form. This form shows each field that the table has (products), allowing the user to create a search criteria for any field. The products table has a relationship with the table products_types. In this case, we previously queried the records in this table in order to facilitate the search by that field:

An instance of the ProductsForm form (app/forms/ProductsForm.php) is passed to the view. This form defines the fields that are visible to the user:

<?phpusePhalcon\Forms\Form;usePhalcon\Forms\Element\Text;usePhalcon\Forms\Element\Hidden;usePhalcon\Forms\Element\Select;usePhalcon\Validation\Validator\Email;usePhalcon\Validation\Validator\PresenceOf;usePhalcon\Validation\Validator\Numericality;classProductsFormextendsForm{/**
* Initialize the products form
*/publicfunctioninitialize($entity=null,$options=[]){if(!isset($options['edit'])){$element=newText('id');$element->setLabel('Id');$this->add($element);}else{$this->add(newHidden('id'));}$name=newText('name');$name->setLabel('Name');$name->setFilters(['striptags','string',]);$name->addValidators([newPresenceOf(['message'=>'Name is required',])]);$this->add($name);$type=newSelect('profilesId',ProductTypes::find(),['using'=>['id','name',],'useEmpty'=>true,'emptyText'=>'...','emptyValue'=>'',]);$this->add($type);$price=newText('price');$price->setLabel('Price');$price->setFilters(['float',]);$price->addValidators([newPresenceOf(['message'=>'Price is required',]),newNumericality(['message'=>'Price is required',]),]);$this->add($price);}}

The form is declared using an object-oriented scheme based on the elements provided by the forms component. Every element follows almost the same structure:

<?php// Create the element$name=newText('name');// Set its label$name->setLabel('Name');// Before validating the element apply these filters$name->setFilters(['striptags','string',]);// Apply this validators$name->addValidators([newPresenceOf(['message'=>'Name is required',])]);// Add the element to the form$this->add($name);

Other elements are also used in this form:

<?php// Add a hidden input to the form$this->add(newHidden('id'));// ...$productTypes=ProductTypes::find();// Add a HTML Select (list) to the form// and fill it with data from 'product_types'$type=newSelect('profilesId',$productTypes,['using'=>['id','name',],'useEmpty'=>true,'emptyText'=>'...','emptyValue'=>'',]);

Note that ProductTypes::find() contains the data necessary to fill the SELECT tag using Phalcon\Tag::select(). Once the form is passed to the view, it can be rendered and presented to the user:

When the form is submitted, the search action is executed in the controller performing the search based on the data entered by the user.

Performing a Search

The search action has two behaviors. When accessed via POST, it performs a search based on the data sent from the form but when accessed via GET it moves the current page in the paginator. To differentiate HTTP methods, we check it using the Request component:

<?php/**
* Execute the 'search' based on the criteria sent from the 'index'
* Returning a paginator for the results
*/publicfunctionsearchAction(){if($this->request->isPost()){// Create the query conditions}else{// Paginate using the existing conditions}// ...}

With the help of Phalcon\Mvc\Model\Criteria, we can create the search conditions intelligently based on the data types and values sent from the form:

This method verifies which values are different from ‘’ (empty string) and null and takes them into account to create the search criteria:

If the field data type is text or similar (char, varchar, text, etc.) It uses an SQL like operator to filter the results.

If the data type is not text or similar, it’ll use the operator =.

Additionally, Criteria ignores all the $_POST variables that do not match any field in the table. Values are automatically escaped using bound parameters.

Now, we store the produced parameters in the controller’s session bag:

<?php$this->persistent->searchParams=$query->getParams();

A session bag, is a special attribute in a controller that persists between requests using the session service. When accessed, this attribute injects a Phalcon\Session\Bag instance that is independent in each controller.

Then, based on the built params we perform the query:

<?php$products=Products::find($parameters);if(count($products)===0){$this->flash->notice('The search did not found any products');return$this->dispatcher->forward(['controller'=>'products','action'=>'index',]);}

If the search doesn’t return any product, we forward the user to the index action again. Let’s pretend the search returned results, then we create a paginator to navigate easily through them:

<?phpusePhalcon\Paginator\Adapter\ModelasPaginator;// ...$paginator=newPaginator(['data'=>$products,// Data to paginate'limit'=>5,// Rows per page'page'=>$numberPage,// Active page]);// Get active page in the paginator$page=$paginator->getPaginate();

Finally we pass the returned page to view:

<?php$this->view->page=$page;

In the view (app/views/products/search.volt), we traverse the results corresponding to the current page, showing every row in the current page to the user:

{%forproductinpage.items%}{%ifloop.first%}<table><thead><tr><th>Id</th><th>Product Type</th><th>Name</th><th>Price</th><th>Active</th></tr></thead><tbody>{%endif%}<tr><td>{{product.id}}</td><td>{{product.getProductTypes().name}}</td><td>{{product.name}}</td><td>{{'%.2f'|format(product.price)}}</td><td>{{product.getActiveDetail()}}</td><tdwidth='7%'>{{link_to('products/edit/'~product.id,'Edit')}}</td><tdwidth='7%'>{{link_to('products/delete/'~product.id,'Delete')}}</td></tr>{%ifloop.last%}</tbody><tbody><tr><tdcolspan='7'><div>{{link_to('products/search','First')}}{{link_to('products/search?page='~page.before,'Previous')}}{{link_to('products/search?page='~page.next,'Next')}}{{link_to('products/search?page='~page.last,'Last')}}<spanclass='help-inline'>{{page.current}} of {{page.total_pages}}</span></div></td></tr></tbody></table>{%endif%}{%else%}
No products are recorded
{%endfor%}

There are many things in the above example that worth detailing. First of all, active items in the current page are traversed using a Volt’s for. Volt provides a simpler syntax for a PHP foreach.

{%forproductinpage.items%}

Which in PHP is the same as:

<?phpforeach($page->itemsas$product){?>

The whole for block provides the following:

{%forproductinpage.items%}{%ifloop.first%}
Executed before the first product in the loop
{%endif%}
Executed for every product of page.items
{%ifloop.last%}
Executed after the last product is loop
{%endif%}{%else%}
Executed if page.items does not have any products
{%endfor%}

Now you can go back to the view and find out what every block is doing. Every field in product is printed accordingly:

As we seen before using product.id is the same as in PHP as doing: $product->id, we made the same with product.name and so on. Other fields are rendered differently, for instance, let’s focus in product.productTypes.name. To understand this part, we have to check the Products model (app/models/Products.php):

A model can have a method called initialize(), this method is called once per request and it serves the ORM to initialize a model. In this case, ‘Products’ is initialized by defining that this model has a one-to-many relationship to another model called ‘ProductTypes’.

Which means, the local attribute product_types_id in Products has an one-to-many relation to the ProductTypes model in its attribute id. By defining this relationship we can access the name of the product type by using:

<td>{{product.productTypes.name}}</td>

The field price is printed by its formatted using a Volt filter:

<td>{{'%.2f'|format(product.price)}}</td>

In plain PHP, this would be:

<?phpechosprintf('%.2f',$product->price)?>

Printing whether the product is active or not uses a helper implemented in the model:

<td>{{ product.getActiveDetail() }}</td>

This method is defined in the model.

Creating and Updating Records

Now let’s see how the CRUD creates and updates records. From the new and edit views, the data entered by the user is sent to the create and save actions that perform actions of creating and updating products, respectively.

In the creation case, we recover the data submitted and assign them to a new Products instance:

<?php/**
* Creates a product based on the data entered in the 'new' action
*/publicfunctioncreateAction(){if(!$this->request->isPost()){return$this->dispatcher->forward(['controller'=>'products','action'=>'index',]);}$form=newProductsForm();$product=newProducts();$product->id=$this->request->getPost('id','int');$product->product_types_id=$this->request->getPost('product_types_id','int');$product->name=$this->request->getPost('name','striptags');$product->price=$this->request->getPost('price','double');$product->active=$this->request->getPost('active');// ...}

Remember the filters we defined in the Products form? Data is filtered before being assigned to the object $product. This filtering is optional; the ORM also escapes the input data and performs additional casting according to the column types:

<?php// ...$name=newText('name');$name->setLabel('Name');// Filters for name$name->setFilters(['striptags','string',]);// Validators for name$name->addValidators([newPresenceOf(['message'=>'Name is required',])]);$this->add($name);

When saving, we’ll know whether the data conforms to the business rules and validations implemented in the form ProductsForm form (app/forms/ProductsForm.php):

Finally, if the form does not return any validation message we can save the product instance:

<?php// ...if($product->save()===false){$messages=$product->getMessages();foreach($messagesas$message){$this->flash->error($message);}return$this->dispatcher->forward(['controller'=>'products','action'=>'new',]);}$form->clear();$this->flash->success('Product was created successfully');return$this->dispatcher->forward(['controller'=>'products','action'=>'index',]);

Now, in the case of updating a product, we must first present the user with the data that is currently in the edited record:

<?php/**
* Edits a product based on its id
*/publicfunctioneditAction($id){if(!$this->request->isPost()){$product=Products::findFirstById($id);if(!$product){$this->flash->error('Product was not found');return$this->dispatcher->forward(['controller'=>'products','action'=>'index',]);}$this->view->form=newProductsForm($product,['edit'=>true,]);}}

The data found is bound to the form by passing the model as first parameter. Thanks to this, the user can change any value and then sent it back to the database through to the save action:

<?php/**
* Updates a product based on the data entered in the 'edit' action
*/publicfunctionsaveAction(){if(!$this->request->isPost()){return$this->dispatcher->forward(['controller'=>'products','action'=>'index',]);}$id=$this->request->getPost('id','int');$product=Products::findFirstById($id);if(!$product){$this->flash->error('Product does not exist');return$this->dispatcher->forward(['controller'=>'products','action'=>'index',]);}$form=newProductsForm();$data=$this->request->getPost();if(!$form->isValid($data,$product)){$messages=$form->getMessages();foreach($messagesas$message){$this->flash->error($message);}return$this->dispatcher->forward(['controller'=>'products','action'=>'new',]);}if($product->save()===false){$messages=$product->getMessages();foreach($messagesas$message){$this->flash->error($message);}return$this->dispatcher->forward(['controller'=>'products','action'=>'new',]);}$form->clear();$this->flash->success('Product was updated successfully');return$this->dispatcher->forward(['controller'=>'products','action'=>'index',]);}

User Components

All the UI elements and visual style of the application has been achieved mostly through Bootstrap. Some elements, such as the navigation bar changes according to the state of the application. For example, in the upper right corner, the link Log in / Sign Up changes to Log out if a user is logged into the application.

This part of the application is implemented in the component Elements (app/library/Elements.php).

This class extends the Phalcon\Mvc\User\Component. It is not imposed to extend a component with this class, but it helps to get access more quickly to the application services. Now, we are going to register our first user component in the services container:

As controllers, plugins or components within a view, this component also has access to the services registered in the container and by just accessing an attribute with the same name as a previously registered service: