In this chapter, we will cover the following topics:
Getting your IDE to work nicely with Phalcon
Creating the application directory structure
Setting up your request entry point
Easily loading code on demand
Initializing Phalcon to handle a request
Understanding the request life cycle
Despite the fact that Phalcon is a framework for PHP application development, it isn't itself PHP-based. Therefore, some difficulties may appear when using syntax highlighting or code autocompletion. In this chapter, we will try to study how to set up autocompletion in a modern Integrated Development Environment (IDE) and how to enable code autocompletion.
An essential step in the architecture process when developing an application is a clear understanding of the project directory structure and the role of each of the directories. We will use a variation of a Phalcon project directory structure, which is easy to deploy and upgrade. The directory structure, which we work with in this chapter, isn't a rule set in stone. In future you will, time after time, use different directory structures, which will be different than the one we will be using. That structure is only one of the options and a starting point for your needs, and it easily responds to changes caused by certain technical conditions.
Phalcon is not only the fastest framework in the world, it is one of the easiest frameworks. As a rule, you don't need any special knowledge or additional software. However, when developing an application with Phalcon, you will need to create a base system configuration and application skeleton. In this chapter, we will learn how to deploy an application with the use of Phalcon, how to make its base configuration, and how to create a flexible and efficient application skeleton, which you can use in future.
Any object-oriented PHP application needs classes. You have to tell your class autoloader where your classes are situated and which way they can be searched for. We will look at some possible options of class auto-loading in Phalcon and their usage methods.
Many developers, despite their experience, have difficulty when working with a Request
component. Phalcon isn't an exception. We will discover how to make a base application configuration so that Phalcon starts handling requests. We will learn how Phalcon handles these requests and in what sequence they are executed. We will learn which components take part in a request life cycle, where it is possible to catch the request, how to specify its state, as well as how to take any suitable actions.
Most IDEs have some form of code completion as part of the program. To enable auto-completion for Phalcon's namespaces, we will need to help the IDE recognize those namespaces. We will also see how to enable Volt syntax highlighting and Phalcon API auto-completion for major IDEs.
To get started, we should have Git installed. To enable auto-completion in your IDE, you need to get the Phalcon stubs. For these purposes, clone the Phalcon Developer Tools
repository:
git clone git@github.com:phalcon/phalcon-devtools.git
The following are the steps needed to complete this recipe.
Using the Project Tool window in PhpStorm, select External Libraries, right-click on the section, and select Configure PHP Include Paths... from the drop-down menu.
Then, using the External Libraries dialog box, click the + button. Next, select the Specify Other... option in the drop-down menu and specify the location of the Phalcon stubs. The stubs are located in the
phalcon-devtools
repository you just cloned under the/ide
subfolder. You should specify the pathphalcon-devtools/ide/stubs/Phalcon
and click on Apply.
In NetBeans, you need to open the Projects window (Ctrl + F1) and select Include Path. Right-click on the section and select Properties from the drop-down menu.
Then, open the Project Properties window and click on the Add Folder... button. Next, specify the location of the Phalcon stubs. The stubs are located in the
phalcon-devtools
repository you just cloned under the/ide
subfolder. You should specify the pathphalcon-devtools/ide/stubs/Phalcon
and click on the OK button.
In the opened window, select Miscellaneous | Files.
In the File Associations group box, click on the button New… next to the File Extensions option, enter the extension
volt,
and click on OK.Select TWIG (text/x-twig) in the Associated File Type (MIME) drop-down menu.
If you have Sublime Package Control, you know what to do. If not, well, it's a package manager for Sublime Text, and it's awesome!
After installing Sublime Package Control and restarting the editor:
For manual installation, clone the Volt
syntax highlight for the Sublime Text 2/Textmate
repository:
git@github.com:phalcon/volt-sublime-textmate.git
Depending on your OS, copy the volt-sublime-textmate/Volt
directory to any of the following locations:
Sublime Text 2:
Mac OS X:
~/Library/Application Support/Sublime Text 2/Packages
Linux:
~/.Sublime Text 2/Packages
Windows:
%APPDATA%/Sublime Text 2/Packages/
Sublime Text 3:
Mac OS X:
~/Library/Application Support/Sublime Text 3/Packages
Linux:
~/.Sublime Text 3/Packages
Windows:
%APPDATA%/Sublime Text 3/Packages/
TextMate:
/Library/Application Support/TextMate/Bundles
Some IDEs need help understanding the framework syntax. To get the IDE to understand, we download a list of all the Phalcon stubs. Then, when we add it to the include
path, NetBeans (or PhpStorm) will automatically check the file and show us the autocomplete options. To enable the Volt
or Zephir
syntax highlight, we have to configure our IDE or text editor accordingly.
In this recipe, we will take a brief look at the commonly used directory structure of a single module application. A structure of this type is aimed at providing a great starting point for different applications.
We don't need any special means to implement this recipe. We will create the project structure on site, at the development stage. Important points of this recipe are the names of directories and their location.
Follow these steps to complete this recipe:
Create a root directory for your project, where
myprojectname
is the name of your project:/var/www/myprojectname
Create three second-level directories in the root directory:
/var/www/myprojectname/app /var/www/myprojectname/public /var/www/myprojectname/.phalcon
Create the following subdirectories in the
public
directory:/var/www/myprojectname/public/css /var/www/myprojectname/public/img /var/www/myprojectname/public/js
Create the following subdirectories in the
app
directory:/var/www/myprojectname/app/cache /var/www/myprojectname/app/config /var/www/myprojectname/app/controllers /var/www/myprojectname/app/library /var/www/myprojectname/app/logs /var/www/myprojectname/app/models /var/www/myprojectname/app/tasks /var/www/myprojectname/app/views
Create the following subdirectories in the
cache
directory:/var/www/myprojectname/app/cache/annotations /var/www/myprojectname/app/cache/data /var/www/myprojectname/app/cache/metadata /var/www/myprojectname/app/cache/volt
Make sure that the just created structure eventually appears as follows:
myprojectname ├── app │ │── cache │ │ ├── annotations │ │ ├── data │ │ ├── metadata │ │ └── volt │ ├── config │ ├── controllers │ ├── library │ ├── logs │ ├── models │ ├── tasks │ └── views ├── .phalcon └── public ├── css ├── img └── js
The project root directory contains the following directories: app
, public
, and .phalcon
(note that the last one has a point at the beginning of its name).
In the app
directory, there will be, as you would expect, the main application code. We will describe this folder in detail shortly.
The public
directory will contain the entry point of your application and directories for various static assets (JavaScript files, images, and CSS files).
The .phalcon
directory will be used by the Phalcon developer tools for internal purposes (migration creation, code generation, and so on). This is the Phalcon system directory. There is no need to create something in it.
The app
directory contains a subdirectory for controllers as well as their views and models. Additionally, there is the tasks
directory, which is intended for cli tasks, the library
directory for the general application components (plug-ins, helpers, and other custom classes), the logs
directory, where we will store our log
files, and the cache
directory for storing cached data of different application components.
In the cache
directory, there are the following directories: annotations
to store the file cache of model annotations, data to store common file cache, metadata to store the model structure cache, and volt to store the view cache.
Note that we have created different directories to store screenshots. These directories are utilized when we are using the File Cache. If you will be using a cache adapter other than the File one, you don't need to create the directory structure here.
After installing Phalcon, we need to create a Bootstrap class in which we'll set about our application tuning. We will create a flexible yet robust Bootstrap file to receive requests for the application.
The first and most important file you need to create for the initial loading of your application is index.php
. This is the file in which all your HTTP requests will be redirected. Create this file in the public
directory of your project, if you haven't got one:
/var/www/myBlog/public/index.php
Follow these steps to complete this recipe:
Put the following code into the
index.php
file:<?php define('APP_PATH', realpath(dirname(__DIR__))); try { include APP_PATH . '/app/library/Bootstrap.php'; $di = new \Phalcon\Di\FactoryDefault(); $bootstrap = new \MyBlog\Bootstrap($di); echo $application->run(); } catch (\Exception $e) { $logger = $di->getShared('logger'); $logger->error($e->getMessage()); $logger->error($e->getTraceAsString()); }
Create a
Bootstrap
class skeleton in yourapp/library/Bootstrap.php
file:<?php namespace MyBlog; class Bootstrap extends \Phalcon\Mvc\Application { protected $loaders = [ 'environment', 'logger', 'loader', 'database', 'views', // other loaders ... ]; protected $config; public function __construct(\Phalcon\DiInterface $di = null) { $di = $di ?: new \Phalcon\Di\FactoryDefault(); $this->config = include APP_PATH . '/app/config/config.php'; // Store config in the DI container $di->setShared('config', $this->config); parent::__construct($di); } }
Create an application
config
file in theapp/config/config.php
file:<?php return new \Phalcon\Config([ 'application' => [ 'controllersDir' => APP_PATH . '/app/controllers/', 'modelsDir' => APP_PATH . '/app/models/', 'viewsDir' => APP_PATH . '/app/views/', 'libraryDir' => APP_PATH . '/app/library/', 'logsDir' => APP_PATH . '/app/logs/', 'baseUri' => '/', 'debug' => true ], 'database' => [ 'adapter' => 'Mysql', 'host' => 'localhost', 'username' => 'root', 'password' => '', 'dbname' => 'my_blog', 'charset' => 'utf8' ], 'metaData' => [ 'metaDataDir' => APP_PATH . '/app/cache/metaData/', ], 'modelsCache' => [ 'lifetime' => 86400 * 30, 'cacheDir' => APP_PATH . '/app/cache/data/', 'prefix' => 'myblog-cache-data-' ], 'volt' => [ 'cacheDir' => APP_PATH . '/app/cache/volt/', 'compiledExt' => '.php', 'separator' => '_', ] ]);
Create an application initialization method,
Bootstrap::run
, in theapp/library/Bootstrap.php
file:public function run() { $em = new \Phalcon\Events\Manager(); $this->setEventsManager($eventsManager); $di = $this->_dependencyInjector; foreach ($this->loaders as $service) { $serviceName = ucfirst($service); $this->{'init' . $serviceName}($di, $this->config, $em); } $di->setShared('eventsManager', $em); return $this->handle()->getContent(); }
Next, create all the methods required for the application service initialization. Use the
Bootstrap::loaders
class property, which was created in Step 2, to define the methods. Use the CamelCase style, adding theinit
prefix to the name of every method:protected initEnvironment($di, $config, $em) { if (true == $config->application->debug) { ini_set('display_errors', 1); ini_set('display_startup_errors', 1); error_reporting(E_ALL | E_STRICT); if (extension_loaded('xdebug')) { ini_set('xdebug.collect_params', 4); } } else { ini_set('display_errors', 0); ini_set('display_startup_errors', 0); error_reporting(E_ALL ^ E_NOTICE); } if (is_readable(APP_PATH . '/vendor/autoload.php')) { include APP_PATH . '/vendor/autoload.php'; } } protected initLogger($di, $config, $em) { $di->set('logger', function ($file = 'main', $format = null) use ($config) { $path = $config->application->logsDir . $file . '.log'; $format = $format ?: $config->application->logFormat; $logger = new \Phalcon\Logger\Adapter\File($path); if ($format) { $formatter = new \Phalcon\Logger\Formatter\Line($format); $logger->setFormatter($formatter); } return $logger; }); } protected initLoader($di, $config, $em) { $di->setShared('loader', function () use ($config, $em) { $loader = new \Phalcon\Loader(); $namespaces = [ 'MyBlog\Models' => $config->application->modelsDir, 'MyBlog\Controllers' => $config->application->controllersDir, 'MyBlog' => $config->application->libraryDir ]; $loader->registerNamespaces($namespaces); $loader->setEventsManager($em); $loader->register(); return $loader; }); }
Use the implementation just described to create all the methods, which you need for service initialization. It is evident that all the values of the loaders array are in actual fact the initializer names, which we call sequentially. The order of these values defines the method call order. You can use the following code template as an example:
protected initMethodName($di, $config, $eventsManager) { // Some initialization return ...; // may be omitted to return null }
The first thing that should be observed is that we have used a Bootstrap
class. Phalcon uses a
Dependency Injection (DI) container to handle all the services you may need to use in your application. We have passed it as a parameter to the Bootstrap
class constructor, in which we will set up our application.
In the class constructor, we create a DI container. If it wasn't passed as a parameter, include the application configuration by placing it into a container for future use and create an instance, Phalcon\Mvc\Application
. Note that we already use the APP_PATH
constant, previously defined. Let's remember how it was defined:
define('APP_PATH', realpath(dirname(__DIR__)));
We have also created a loader
class property, which we will discuss later.
We have added the application configuration in a separate file. Usually, it is worthwhile due to the fact that according to the application growth, the configuration of the application elements and services will grow. It is much more convenient to have the whole configuration in one place, rather than scattered all over the application. Here we have specified the main application paths and parameters of the database connection, metadata cache, models, and views. We have defined five directories for our application; these are controllers
, models
, views
, class library
, and logs
. Besides this, we have specified directories for the metadata cache of models and for the data itself, as well as a view cache directory. If these directories do not exist, it is time to create them. All we need for our application is presented here.
In the Bootstrap::run
method we have created the event manager, which we can use at any point, subscribed to its events, and passed it to the application. You must not forget the loaders field we have already defined. We will use it right at the present moment. When iterating through the array, we form the method name, ucfirst($service)
. Then we register a new event in the event manager (of course, we can subscribe to this event now) and call this method by passing the DI container, the application configuration, and the event manager into it.
Next, let's have a look at the most important of initializer methods. The first one is the application environment configuration. There is nothing out of the ordinary here; we define the application error reporting level in an emergency. Note that error output enabling is designed and suitable only for development and must not be used in ready-made production systems. Finally, we include the Composer autoloader, if there is one, and configure the xdebug extension to make our application debugging more convenient. Note that it is highly recommended to use at least Xdebug v2.2.3 for better compatibility with Phalcon.
Next, we've just created a quite powerful logger, which we can use in different ways. The code snippet is as follows:
$this->di->get('logger', ['some_file'])->debug('Some text ...');
This code, which if used, for example, in a controller, will tell the logger to create the following file on the disk if it is missing there:
/var/www/myBlog/app/logs/some_file.log
The path for the log creation is formed according to the configuration just defined. The logger will write the following to that file:
[Tue, 28 Jul 15 22:09:02 -0500][DEBUG] Some text ...
And the following code:
$this->di->get('logger')->debug('Some text ...');
We will use the main.log
file. Additionally, we are able to define the message format in our log files. In order to get that done, we need to pass the format as the second array element on the get
method, like this:
get('logger', ['some_file', "%date% - %message%"])
Also, we can define the format in our configuration file.
The next method we need is the autoloader initialization method. We have created one more method in our Bootstrap
class and initialized the class autoloading
component in this method. It means that we have registered the necessary namespaces there. We have defined the namespace as MyBlog
for all future library classes, located in app/library/
, in relation to the application root (think back to our configuration file), and have added paths for the namespaces of models and controllers, which are located in app/models/
and app/controllers/
, respectively, to the autoloading. Phalcon Loader will register the namespaces we may need, and PHP will search for some classes within the MyBlog\Models
namespace in /var/www/myBlog/app/models/
, within the MyBlog\Controllers
namespace in /var/www/myBlog/app/controllers/
, and any other classes contained within the MyBlog
namespace in /var/www/myBlog/app/library/
(for example, MyBlog\Acl
or MyBlog\Utils\Backup
). In future, as the structure of our application grows, we will always be able to enhance or modify this method.
Finally, when looking at the Bootstrap::run
method, it becomes apparent that after the successful initialization of all the necessary services, the following parent method, $this->handle()->getContent(),
is called and returns its result back to the caller. That's just the thing.
The line in the entry point (index.php
) will display the application output:
echo $application->run();
If the application throws an exception, we'll write a detailed report into our log file in the catch
block. We may take it a step further by defining the errorHandler
method in the loaders
field of the Bootstrap
class, and implement the initErrorHandler
method.
Here you will learn how to load code on demand by using the PSR-4 compatible autoloading strategy. We notify the autoloader where the classes are located to load them on demand.
If you are familiar with PHP, you've surely heard of PSR-0 and PSR-4 standards, which make it possible to load the required classes automatically at the moment of their call without using such instructions as require and include.
The behavior of Phalcon\Loader
is based on the PHP's capability of autoloading classes; if any class used in code doesn't exist, a special handler will try to find and load it. Phalcon\Loader
is designed just for that operation. The loading of only the files needed for a particular class to operate has a positive impact on the application's performance. It helps to avoid wasteful computation, and reduces program memory requirements. This technology is called
Lazy Initialization.
As of October 21, 2014, PSR-0 has been marked as deprecated. PSR-4 is now recommended as an alternative. Starting from version 3.0.0, the support for prefixes strategy in Phalcon\Loader
is removed from Phalcon. For that very reason, loading with the use of the PSR-0 standard will be omitted here.
Follow these steps to complete this recipe:
Create an autoloader instance in the following way:
define('APP_PATH', realpath(dirname(dirname(__DIR__)))); $loader = new \Phalcon\Loader(); $loader->registerNamespaces([ 'MyBlog\Models' => APP_PATH . '/app/models/', 'MyBlog\Controllers' => APP_PATH . '/app/controllers/', 'MyBlog\Library' => APP_PATH . '/engine/', ]); $loader->register();
Now, after configuring the loader in the way shown in the earlier code instance, create the class
MyBlog\Models\Users
, located inapp/models/Users.php
:<?php namespace MyBlog\Models; use Phalcon\Mvc\Model; class User extends Model { }
Edit
MyBlog\Controllers\IndexController
inapp/controllers/IndexController.php
:<?php namespace MyBlog\Controllers; use Phalcon\Mvc\Controller; class IndexController extends Controller { }
Edit
MyBlog\Library\SomeClass
in theengine/SomeClass.php
:<?php namespace MyBlog\Library; class SomeClass { }
The Phalcon\Loader::registerNamespaces
method gets an associative array, identifying which keys are namespace prefixes and their values are directories where the classes are located in. For instance, after configuring the loader as shown earlier, we can use the following class, MyBlog\Models\Users
, located in app/models/Users.php
, MyBlog\Controllers\IndexController
in app/controllers/IndexController.php
, and MyBlog\Library\SomeClass
in engine/SomeClass.php
.
Let's consider in depth how the class search is carried out when using the namespaces strategy. Let's assume that you have registered the MyBlog\Library
namespace for the engine
directory, and then called the class MyBlog\Library\Some\Example
. After registration, the autoloader knows that the MyBlog\Library
namespace belongs to the engine
directory. Next, in the remaining part of the class name (\Some\Example
), the namespace separator (\
) will be replaced with the directory separator (/
), and the file extension will be added. Eventually, this will result in forming the path engine/Some/Example.php
. So, in such a simple and quite fast way, the class loading with the use of the namespace strategy is performed.
It is defined in the PSR-4 standard that the vendor/package pair can refer to any directory or even more. That's why you can easily register the same namespace to be served by several directories:
define('ROOT_PATH', realpath(dirname(dirname(__DIR__)))); $loader = new \Phalcon\Loader(); $loader->registerNamespaces([ 'Phalcon' => ROOT_PATH . '/app/library/Phalcon/', ]); $loader->register();
We have used the Phalcon
namespace here, regardless of the fact that it is already registered by the framework. There is no conflict here. Using the Phalcon
namespace, PHP will try to find the class among the classes provided by the framework, and after failing to find it, it will try to find it in the directory app/library/Phalcon/
.
Note that when registering a namespace, which already exists, and an identical class name in this namespace, you will not be able to call this class. For example, if you register the namespace as previously described, and create the Phalcon\Crypt
class, located in app/library/Phalcon/Crypt.php
, you will not be able to call it in the following way: $cryp = new Phalcon\Crypt()
. This is due to the fact that the Phalcon PHP extension is initialized at the earlier stage and PHP knows already about the Phalcon\Crypt
class.
But let's return to our Phalcon
namespace, which we have registered before. Now, if we try to create a new class:
$myHelper = new Phalcon\MyHelper();
Phalcon will search for it in the file app/library/Phalcon/MyHelper.php
. Just due to the fact that Phalcon\Loader
uses the fully PSR-4 compatible autoloader, we have no problems with class autoloading when applying such libraries as Phalcon Incubator
and Phalcon Developer Tools, using the Phalcon
namespace.
Furthermore, Phalcon\Loader
provides other class-loading strategies, which are not PSR-4 compatible. We'll consider them briefly later.
Phalcon\Loader
, as with most autoloaders, provides class loading with the use of directories. This class loading strategy isn't PSR-4 compatible, but it is efficient, and in certain situations, adequate. The loading with the use of directories comes down to the enumeration of all possible directories in an attempt to search for your classes. The Phalcon\Loader::registerDirs
method receives the array of directories, in which the search will perform:
define('ROOT_PATH', realpath(dirname(dirname(__DIR__)))); $loader = new \Phalcon\Loader(); $loader->registerDirs([ ROOT_PATH . '/components/', ROOT_PATH . '/adapters/', ROOT_PATH . '/engine/', ]); $loader->register();
We have told the autoloader that our classes are located in three directories: components
, adapters
, and engine
. Note that this strategy is the slowest. Class registering with the use of directories means that, by calling any class, Phalcon will search through these directories to find a class with the same name as the required class. As our project grows, this type of search will have an impact on performance. When using this class loading strategy, it is important to be mindful of the order of your directories, because you could create two classes with the same name in two different directories. If there is one class named Example
in each of the following directories, components and engine, then, by calling the class:
$myComponent = new Example();
The first found class will be used which is the one from the components
directory.
The third option, which can help you register your classes with the use of the Phalcon\Loader
component, is registering classes. This autoloading method is not PSR-4 compatible, but in some cases it is the fastest. This solution may be efficient when using strategies which don't allow for easy retrieval of the file using the namespace and the class
directory. The following describes how we can register classes in this way:
define('ROOT_PATH', realpath(dirname(dirname(__DIR__)))); $loader = new \Phalcon\Loader(); $loader->registerClasses([ ROOT_PATH . '/components/Awesome/Example.php', ROOT_PATH . '/adapters/Base/BaseAdaper.php' ]); $loader->register();
The Phalcon\Loader:registerClasses
method gets the array of files, among which the search will be performed. Here we have told the autoloader about two classes, Example
and BaseAdapter
, and specified the full path to them. For example, now when calling the class $example = new Example()
, the class located in components/Awesome/Example.php
will be used. Although this method is the fastest, your file list will grow significantly, and with it the time it takes for the search to be carried out, so it will reduce the performance.
The Phalcon\Loader
component allows you to combine autoloading options. There is no reason why you shouldn't use them all together, if you needed:
define('ROOT_PATH', realpath(dirname(dirname(__DIR__)))); $loader = new \Phalcon\Loader(); $loader->registerNamespaces([ 'MyBlog\Models' => ROOT_PATH . '/app/models/', 'MyBlog\Controllers' => ROOT_PATH . '/app/controllers/', 'MyBlog\Library' => ROOT_PATH . '/engine/', ]); $loader->registerDirs([ ROOT_PATH . '/components/', ROOT_PATH . '/adapters/', ROOT_PATH . '/engine/', ]); $loader->registerClasses([ ROOT_PATH . '/vendor/awesome/plugins/Example.php', ROOT_PATH . '/adapters/Base/BaseAdaper.php' ]); $loader->register();
In such a manner, we can register the namespaces, the directories, and the classes.
In summary, it should be mentioned that the strategies that are based on the namespaces are faster than those based on the directories. In some cases, the strategy based on the class registering is faster, but only if your project has a small number of classes. In a relatively large project, the fastest strategy is to register the namespaces.
Finally, if the APC is enabled, it will be used for the requested file (and this file will be cached).
For more information about Lazy Initialization, go to:
https://en.wikipedia.org/wiki/Lazy_initialization.
For more detailed information about the PSR-0 standard refer to:
http://www.php-fig.org/psr/psr-0/, about the PSR-4 standard refer to
http://www.php-fig.org/psr/psr-4/.
Before you start handling requests, you need to configure your application work environment and prepare all the required components. In this recipe, you will learn what should be done before asking Phalcon to handle an incoming request.
To embark on this recipe, you need to have the web server Apache or Nginx + PHP-FPM installed. Besides this, you should be able to create or change the configuration of the virtual host for your web server and have the proper authority to do so.
Follow these steps to complete this recipe:
Create the Nginx virtual host setting as follows:
upstream backend { server unix:/var/run/php5-fpm.sock; } # redirect the request to the non-www domain # to choose which domain you prefer server { server_name www.mysite.com; return 301 $scheme://mysite.com$request_uri; } server { listen 80; server_name mysite.com; error_log /var/log/nginx/mysite.error.log; access_log /var/log/nginx/mysite.access.log; index index.php; root /var/www/mysite/public; try_files $uri $uri/ @rewrite; location @rewrite { rewrite ^(.*)$ /index.php?_url=/$1 last; } location ~ \.php$ { try_files $uri =404; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass backend; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info; fastcgi_param HTTP_REFERER $http_referer; # production | development | staging | testing fastcgi_param APP_ENV development; fastcgi_buffers 16 16k; fastcgi_buffer_size 32k; include fastcgi_params; } location ~* ^/(css|img|js|flv|swf|download)/(.+)$ { root /var/www/mysite/public; } location ~ /\. { return 403; } }
If you have the Apache web server installed, create the virtual host configuration as follows:
<VirtualHost *:80> ServerAdmin admin@example.host DocumentRoot "/var/www/mysite/public" DirectoryIndex index.php ServerName mysite.com ServerAlias www.mysite.com # production | development | staging | testing SetEnv APP_ENV development <Directory "/var/www/mysite/public"> Options All AllowOverride All Allow from all RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^((?s).*)$ index.php?_url=/$1 [QSA,L] </Directory> <Files .*> Order Deny,Allow Deny From All </Files> </VirtualHost>
Open your
bootstrap
file and add the URL manager configuration to it:$di->set('url', function () { $url = new \Phalcon\Mvc\Url(); $url->setBaseUri('/'); return $url; });
Then, add the primary routing configuration:
$di->setShared('router', function () { $router = new \Phalcon\Mvc\Router(); if (!isset($_GET['_url'])) { $router->setUriSource(Router::URI_SOURCE_SERVER_REQUEST_URI); } $router->removeExtraSlashes(true); $router->add( '/', [ 'controller' => 'index', 'action' => 'index' ] ); return $router; });
Create a controller, associating it with the routing map defined in the preceding code:
use Phalcon\Mvc\Controller; class IndexController extends Controller { public function indexAction() { var_dump($_SERVER); return; } }
We configure the virtual host for our web server. Then, we configure the component Phalcon\Mvc\Url
. Note that, depending on the installed application root, there may emerge a need to define the base URL. For example, if the document root is /var/www/mysite
, and your application is installed at /var/www/mysite/phalcon
, then your base URI (baseUri
) is /phalcon/
. When using virtual hosts or if the application is installed in the root directory, the baseUri
parameter is /
. By default, Phalcon detects the necessary baseUri
by itself, but in order to improve performance we recommend to specify it manually.
For creating URIs, the Phalcon\Mvc\Router
component is used by default. Your application can run with the following default routing pattern:
/:controller/:action/:params.
Therefore, it's easy to create URIs on this model or by other rules, set in the routing map. When creating an object of the type Phalcon\Mvc\Router
, you can tell it not to create the default routing. For that purpose, you need to pass false
to the constructor:
$router = new \Phalcon\Mvc\Router(false)
Next, we create a router with the default configuration and add a pattern to it, which refers to the site root. By default, the current handling URI is taken from the variable $_GET['_url']
, Phalcon is wired that way, as are the standard mod-rewrite rules. Here we use a little trick—we check up whether there is the _url
($_GET['_url']
) key in the superglobal array $_GET
. In case it is missing, the virtual host configuration doesn't redirect the type index.php?_url=
. Thus, it's very easy to create a flexible component configuration.
Finally, we create a controller to test our routing with an action, which coincides with the added routing pattern.
You can learn about the Apache setup instructions at http://httpd.apache.org/docs/, and the complete Nginx documentation at http://nginx.org/en/docs/. For the PHP-FPM configuration directives description, refer to http://php.net/manual/en/install.fpm.configuration.php.
For Phalcon installation notes, routing documentation, and generating URLs, refer to https://docs.phalconphp.com/.
A deeper understanding of the request life cycle will lift the veil on the main application mechanisms and offers you the possibility to handle requests more flexibly. This recipe demonstrates how each request usually flows within the framework.
To complete this recipe, you need to have a web server installed and, what is more, you must make the base configuration of your application. Usually, by base configuration, we mean that you have your application deployed and have the services configured in all the ways available. You should understand the main class autoloading principles in Phalcon and know how to configure Phalcon Loader. Besides, you need to configure logging in PHP into a readable file.
Follow these steps to complete this recipe:
Open the application service configuration and create an event manager, which we will use during further steps:
$em = new Phalcon\Events\Manager();
Then, add the following routing setting:
$di->setShared('router', function () use($em) { $router = new \Phalcon\Mvc\Router(); $router->add( '/:controller/:action/:params', [ 'controller' => 1, 'action' => 2, 'params' => 3 ] ); $em->attach('router', new RouterListener); $router->setEventsManager($em); return $router; });
Now we need the router event listener. Create a directory with the name
library
in your project root directory. Add that directory into the Phalcon autoloader and create a class namedRouterListener
in it, inserting the following content:<?php use Phalcon\Mvc\Router; use Phalcon\Mvc\Router\Route; use Phalcon\Events\Event; use Phalcon\Di; class RouterListener { public function beforeCheckRoute(Event $event, Router $router) { $request = Di::getDefault()->getShared('request'); error_log('[REQUEST] ' . $request->getMethod() . ": " . $request->getURI()); } public function matchedRoute(Event $event, Router $router, Route $route) { error_log('[ROUTE] matched pattern: ' . $route->getPattern()); }
Create a
ProductsController
in theapp/controllers
file and place the following code there:<?php class ProductsController extends Phalcon\Mvc\Controller { public function viewAction($id) { $this->tag->setTitle('View Product'); $this->view->setVars([ 'product_id' => $id, 'product_name' => 'Pizza', 'product_price' => 12, ]); } }
Then, create a view in
app/views/products/view.volt
and place the following code there:{{ content() }} <h1>{{ product_name }}</h1> <p> <strong>ID:</strong> {{ product_id }}<br> <strong>Price:</strong> <span style="text-decoration: line-through">{{ product_price / 0.25 }}</span> <strong style="color:darkred">{{ product_price }}</strong> </p>
Add an initial dispatcher setting into the service configuration, as follows:
$di->setShared('dispatcher', function() use ($em) { $dispatcher = new Phalcon\Mvc\Dispatcher; $em->attach('dispatch', new ActionListener); $dispatcher->setEventsManager($em); return $dispatcher; });
Then, we have to create the Event Listener. For this step, create an
ActionListener
class in thelibrary
directory and put the following content into this class:<?php use Phalcon\Events\Event; use Phalcon\Mvc\Dispatcher; class ActionListener { public function afterExecuteRoute(Event $event, Dispatcher $dispatcher) { $report = [ $dispatcher->getControllerName() => $dispatcher->getControllerClass(), $dispatcher->getActionName() => $dispatcher->getActiveMethod(), 'params' => $dispatcher->getParams() ]; error_log('[ACTION] ' . json_encode($report)); } }
Open the application services definition and add a view service into the DI container:
$di->setShared('view', function () use ($config, $em) { $view = new \Phalcon\Mvc\View(); $view->setViewsDir($config->get('application')->viewsDir); $view->registerEngines([ '.volt' => function ($view, $di) use ($config) { $volt = new \Phalcon\Mvc\View\Engine\Volt($view, $di); return $volt; } ]); $em->attach('view', new ViewListener()); $view->setEventsManager($em); return $view; });
Finally, create the listener named
ViewListener
in thelibrary
directory and put the following code into it:<?php use Phalcon\Events\Event; use Phalcon\Mvc\View; class ViewListener { public function afterRender(Event $event, View $view) { error_log('[VIEW] ' . $view->getActiveRenderPath()); return true; } }
If you've done it all correctly, after you go to http://your_site/products/view/1 in your browser, you will see the product card. Note that we use the hostname your_site
in our example; however, you should use your real project hostname instead.
Additionally, if you look at the content of your php-log
file, you will see something like this:
[30-Sep-2015 02:06:48 Europe/Berlin] [REQUEST] GET: /products/view/1 [30-Sep-2015 02:06:48 Europe/Berlin] [ROUTE] matched pattern: /:controller/:action/:params [30-Sep-2015 02:06:48 Europe/Berlin] [ACTION] {"products":"ProductsController","view":"viewAction","params":["1"]} [30-Sep-2015 02:06:48 Europe/Berlin] [VIEW] /var/www/your_site/app/views/index.volt
A request life cycle starts with an entry point (for example, index.php
). All requests are directed to it by the web server (such as Apache, Nginx, and others). The entry point usually doesn't contain much code; it defines only the application object and delegates the control to the last one. The application instance, in turn, uses the Dependency Injection Container (DIC) and thereby calls different application components one after the other, delegating them the control and if necessary the request context or its current handling result.
The default request life cycle consists of the following stages:
An HTTP Request is handled by the dispatcher and routed to the controller by means of the router pattern.
The controller performs the action defined in it and passes data to the View.
The View transforms and/or formats the data as appropriate and provides it in a format that is required for the HTTP Response.
There are many ways to change the default request handling logic, including:
Multi module applications
Halting view rendering
Non-use of controllers (for example, in a RESTful application)
Working directly with HTTP requests or routers with the immediate return from anonymous functions bound with the defined URIs and even throwing an exception in any of these steps
However, the default request life cycle stages just listed outline the concept of the three main places where we can begin our research.
To demonstrate, we create an event manager, and in it register the listeners of the events we are interested in:
RouterListener
ActionListener
ViewListener
In each listener
method, we log the current request state, that is, which line of the URL request is handled by the Request
component, which router pattern matches for the address handling, which controller and action are selected by the dispatcher for the request handling, and which view is involved eventually.
Note that we define the routing pattern clearly. We couldn't define the pattern for the handling of the request /products/view/1
in this recipe, because we've created a routing component with default settings:
$router = new \Phalcon\Mvc\Router();
Try to comment the adding of your pattern into the routing component and refresh the page. Then, you'll see a matched pattern log entry, like this:
[ROUTE] matched pattern: #^/([\w0-9\_\-]+)/([\w0-9\.\_]+)(/.*)*$#u
If you take a detailed look at the request Uniform Resource Identifier (URI) and refer it to this regular expression, it will fall in place that those are a perfect match. But we recommend you to define the pattern clearly and not rely on the default one, since it helps with performance and avoids undesired behaviors.