• 2016-07-29

    Phalcon 3.0.0 released

    This article is cross posted from the Phalcon Blog

    Phalcon 3.0.0 final (LTS) released

    The Phalcon team is very excited to share some news with our community!

    The last few months, we have been working hard to push 2.1 out, which contains significant enhancements as well as some API changes that require attention so as not to break compatibility with your application.
    On top of that we have been working in making Zephir PHP7 compatible so that you can enjoy Phalcon in your PHP7 application. Some news first though:

    Versioning

    For any future Phalcon releases we are adopting SemVer (http://semver.org). In short:

    Given a version number MAJOR.MINOR.PATCH, increment the:
    * MAJOR version when you make incompatible API changes,
    * MINOR version when you add functionality in a backwards-compatible manner, and
    * PATCH version when you make backwards-compatible bug fixes.
    * Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format.

    Since 2.1 has many API changes, we decided that it would be best to not release it as is and start using SemVer to better communicate with the community and keep track of our releases.

    2.1 is dead, all hail 3.0

    As mentioned above, 2.1 will not be fully backwards compatible. As a result, we are changing the version number to 3.0.

    PHP version support

    The Phalcon team takes security very seriously and thus have decided to provide support to PHP versions that are supported. As of 3.0, PHP 5.3 and 5.4 will be deprecated. We are making a small exception to this rule and will continue to support 5.5 for a little while, but since its support has expired a few days ago, it will too be deprecated in a future release.

    The goodie bag

    So what does 3.0 offer? The changelog is extensive as you can see. Below are highlights of the changes as well as areas you need to concentrate.

    • PHP 5.3 and 5.4 are fully deprecated.
    You can compile the code on your own, but we will not be able to support it nor can we guarantee that it will work as you expect it to. PHP 5.3 support expired mid 2014 and 5.4 expired mid 2015. We need to ensure our applications have all known vulnerabilities on the PHP side fixed and patched, thus we will not support any unsupported PHP version. This excludes PHP 5.5, whose support expired a few days ago. We will deprecate 5.5 in a future release but will make sure that you all know beforehand so that you can prepare.

    INCOMPATIBLE: You will need to upgrade your PHP installation to 5.6. You can always continue to use the Phalcon version you are using, but in 3.0 support for PHP 5.4 has been deprecated and we cannot guarantee that PHP 5.5 will be fully functional.

    APPLICATION

    Phalcon\Cli\Console and Phalcon\Mvc\Application now inherits Phalcon\Application.
    This change makes the interfaces more uniformed and offers additional functionality to the respective applications (cli/mvc)

    BEANSTALK

    • Added \Phalcon\Queue\Beanstalk::ignore().
    Removes the named tube from the watch list for the current connection.

    • Added \Phalcon\Queue\Beanstalk::pauseTube().
    Can delay any new job being reserved for a given time.

    • Added \Phalcon\Queue\Beanstalk::kick().
    It moves jobs into the ready queue. If there are any buried jobs, it will only kick buried jobs. Otherwise it will kick delayed jobs.

    // Kick the job, it should move to the ready queue again
    if (false !== $job->kick()) {
        $job = $this->client->peekReady();
    }
    

    • Added \Phalcon\Queue\Beanstalk::listTubeUsed().
    Returns the tube currently being used by the client.

    • Added \Phalcon\Queue\Beanstalk::listTubesWatched().
    Returns a list tubes currently being watched by the client.

    • Added \Phalcon\Queue\Beanstalk::peekDelayed().
    Return the delayed job with the shortest delay left.

    $this->client->put('testPutInTube', ['delay' => 2]);
    $job = $this->client->peekDelayed();
    

    • Added \Phalcon\Queue\Beanstalk::jobPeek().
    Returns the next available job.

    $this->client->choose(self::TUBE_NAME_1);
    $jobId = $this->client->put('testPutInTube');
    $job   = $this->client->jobPeek($jobId);
    $this->assertEquals($jobId, $job->getId());
    

    CACHE

    • The cache backend adapters now return boolean when calling Phalcon\Cache\BackendInterface::save

    // Returns true/false
    $result = $backendCache->save('my_key', $content);
    

    • Added Phalcon\Cache\Frontend\Msgpack. MsgPack is a new frontend cache. It is an efficient binary serialization format, which allows exchanging data among multiple languages like JSON.

    use Phalcon\Cache\Backend\File;
    use Phalcon\Cache\Frontend\Msgpack;
    
    // Cache the files for 2 days using Msgpack frontend
    $frontCache = new Msgpack(
        [
            'lifetime' => 172800,
        ]
    );
    
    // Create the component that will cache 'Msgpack' to a 'File' backend
    // Set the cache file directory - important to keep the '/' at the end of
    // of the value for the folder
    $cache = new File(
        $frontCache, 
        [
            'cacheDir' => '../app/cache/',
        ]
    );
    
    // Try to get cached records
    $cacheKey = 'robots_order_id.cache';
    $robots   = $cache->get($cacheKey);
    
    if ($robots === null) {
        // $robots is null due to cache expiration or data do not exist
        // Make the database call and populate the variable
        $robots = Robots::find(['order' => 'id']);
    
        // Store it in the cache
        $cache->save($cacheKey, $robots);
    }
    
    // Use $robots
    foreach ($robots as $robot) {
        echo $robot->name, "\n";
    }
    

    • Fixed bug of destroy method of Phalcon\Session\Adapter\Libmemcached

    • Added Phalcon\Cache\Backend\Memcache::addServers to enable pool of servers for memcache

    $memcache->addServers('10.4.6.10', 11000, true);
    $memcache->addServers('10.4.6.11', 11000, true);
    $memcache->addServers('10.4.6.12', 11000, true);
    

    CRYPT

    • Mcrypt is replaced with openssl in Phalcon\Crypt #11530#phalcon
    Due to the lack of updates for mcrypt for a number of years, its slow performance and the fact that the PHP core team decided to deprecate mcrypt as soon as possible (version 7.1 onward), we have replaced it with the much faster and supported openssl.

    • Default encrypt algorithm in Phalcon\Crypt is now changed to AES-256-CFB

    • Removed methods setMode(), getMode(), getAvailableModes() in Phalcon\CryptInterface (no longer apply with openssl)

    BACKWARDS INCOMPATIBLE: Backwards compatibility from openssl to mcrypt is problematic if not impossible. We had to remove several methods that are no longer applicable. Additionally the rijndael-256 from mcrypt is no longer valid in openssl. The default encryption algorithm is AES-256-CFB

    If you have data that has already been encrypted with mcrypt, you will need first to decrypt it before upgrading to 3.0 and then encrypt it again using 3.0 and therefore openssl. Failure to do so will result in loss of data. A port is available in the incubator. Please see the code here

    DATABASE

    • Dropped support of Oracle #phalcon#12009
    Support of Oracle has been dropped from the Phalcon Core for the following reasons:
    •• The lack of Oracle maintainer
    •• The lack of relevant experience among the Phalcon Core Team
    •• Weak support or interest from the community
    •• Incomplete implementation that creates only the illusion of support for Oracle
    •• Some issues hampering for the support of PHP 7 in Phalcon

    Oracle components will be ported to the Phalcon Incubator. If the adapter receives support and enhancements from the community, we will consider making it part of the core again.

    DI

    Phalcon\Di is now bound to services closures allowing use Phalcon\Di as $this to access services within them. Additionally, closures used as handlers inMvc\Micro are now bound to the $app instance

    Old way:

    $diContainer->setShared(
        'modelsCache',
        function () use ($config) {
            $frontend = '\Phalcon\Cache\Frontend\\' . $config->get('modelsCache')->frontend;
            $frontend = new $frontend(
                [
                    'lifetime' => $config->get('modelsCache')->lifetime,
                ]
            );
            $config   = $config->get('modelsCache')->toArray();
            $backend  = '\Phalcon\Cache\Backend\\' . $config['backend'];
    
            return new $backend($frontend, $config);
        }
    );
    

    New way:

    $diContainer->setShared(
        'modelsCache',
        function () {
            $frontend = '\Phalcon\Cache\Frontend\\' . $this->config->get('modelsCache')->frontend;
            $frontend = new $frontend(
                [
                    'lifetime' => $this->config->get('modelsCache')->lifetime,
                ]
            );
            $config   = $this->config->get('modelsCache')->toArray();
            $backend  = '\Phalcon\Cache\Backend\\' . $config['backend'];
    
            return new $backend($frontend, $config);
        }
    );
    

    Also note the nested DI behavior:

    $foo = function() {
        get_class($this); // DI
        $bar = function () {
            get_class($this); // DI
            $baz = function () {
                // etc
            }
        }
    }
    

    • If an object is returned after firing the event beforeServiceResolve in Phalcon\Di it overrides the default service localization process

    DISPATCHER

    • Added Phalcon\Dispatcher::hasParam().

    public function testAction() 
    {    
        if (true === $this->dispatcher->hasParam('foo')) {
            // Parameter exists
        }
    }
    

    • Added method getActionSuffix() in Phalcon\DispatcherInterface. This allows you change the 'Action' suffix in controller actions.

    • Corrected behavior to fire the dispatch:beforeException event when there is any exception during dispatching #phalcon

    • CLI parameters are now handled consistently.

    • Added Phalcon\Mvc\Controller\BindModelInterface and associated model type hint loading through dispatcher.

    • Added Phalcon\Mvc\Collection::update, Phalcon\Mvc\Collection::create and Phalcon\Mvc\Collection::createIfNotExist

    public function createAction() 
    {
        /**
         * Creates a document based on the values in the attributes, if not found by criteria
         */
        $robot = new Robot();
        $robot->name = 'MyRobot';
        $robot->type = 'Droid';
        $robot->create();
    }
    
    public function createOverrideAction() 
    {
        /**
         * Create a document
         */
        $robot = new Robot();
        $robot->name = 'MyRobot';
        $robot->type = 'Droid';
        //create only if robot with same name and type does not exist
        $robot->createIfNotExist( array( 'name', 'type' ) );
    }
    
    public function updateAction() 
    {
        /**
         * Update a document
         */
        $robot = Robots::findFirst(['id' => 1]);
        $robot->name = 'MyRobot';
        $robot->type = 'Droid';
        $robot->update();
    }
    

    EVENTS

    • Now Phalcon\Events\Event implements Phalcon\Events\EventInterface

    Phalcon\Events\Event::getCancelable renamed to Phalcon\Events\Event::isCancelable

    BACKWARDS INCOMPATIBLE: Any references to getCancelable will stop working. You will need to rename the function to isCancelable

    Old way:

    public function cancelAction()
    {
        if (true === $this->eventsManager->getCancelable()) {
            // do something here
        }
    }
    

    New way:

    public function cancelAction()
    {
        if (true === $this->eventsManager->isCancelable()) {
            // do something here
        }
    }
    

    • Removed Phalcon\Events\Manager::dettachAll in favor of Phalcon\Events\Manager::detachAll

    BACKWARDS INCOMPATIBLE: Any references to dettachAll will stop working. You will need to rename the function to detachAll

    Old way:

    public function destroyAction()
    {
        $this->eventsManager->dettachAll()
    }
    

    New way:

    public function destroyAction()
    {
        $this->eventsManager->detachAll()
    }
    

    FLASH

    • Added ability to autoescape Flash messages #phalcon

    $flash = new Phalcon\Flash\Session;
    $flash->setEscaperService(new Phalcon\Escaper);
    
    $flash->success("<script>alert('This will execute as JavaScript!')</script>");
    echo $flash->output();
    // <div class="successMessage"><script>alert('This will execute as JavaScript!')</script></div>
    

    • Fixed Phalcon\Session\Flash::getMessages.
    Now it returns an empty array in case of non existent message type request #phalcon

    Old result:

    use Phalcon\Session\Flash as FlashSession;
    
    $flash = new FlashSession();
    $flash->error('Error Message');
    var_dump($flash->getMessages('success', false));
    
    array (size=1)
      'error' => 
        array (size=1)
          0 => string 'Error Message' (length=13)
    

    New result:

    use Phalcon\Session\Flash as FlashSession;
    
    $flash = new FlashSession();
    $flash->error('Error Message');
    var_dump($flash->getMessages('success', false));
    
    array (size=0)
      empty
    

    HTTP REQUEST/RESPONSE

    • Added default header: Content-Type: "application/json; charset=UTF-8" in method Phalcon\Http\Response::setJsonContent

    Old way:

    use Phalcon\Http\Response;
    
    $data     = 'Phlying with Phalcon';
    $response = new Response();
    $response->setContentType('application/json;');
    $response->setJsonContent($data)
    $response->send();
    

    New way:

    $data     = 'Phlying with Phalcon';
    $response = new Response();
    $response->setJsonContent($data)
    $response->send();
    

    • Added ability to spoof the HTTP request method.
    Most browsers do not support sending PUT and DELETE requests via the method attribute in an HTML form. If the X-HTTP-Method-Override header is set, and if the method is a POST, then it is used to determine the 'real' intended HTTP method. The _method request parameter can also be used to determine the HTTP method, but only if setHttpMethodParameterOverride(true) has been called. By including a _method parameter in the query string or parameters of an HTTP request, Phalcon will use this as the method when matching routes. Forms automatically include a hidden field for this parameter if their submission method is not GET or POST.

    • Added support of CONNECT, TRACE and PURGE HTTP methods.
    - CONNECT: A variation of HTTP tunneling when the originating request is behind a HTTP proxy server. With this mechanism, the client first requests the HTTP proxy server to forward the TCP connection to the final endpoint. The HTTP proxy server then establishes the connection on behalf of the client.
    - TRACE: A method used for debugging which echoes input back to the user. Note that this method is dangerous, since it introduces a risk whereby an attacker could steal information such as cookies and possibly server credentials.
    - PURGE: Although not defined in the HTTP RFCs, some HTTP servers and caching systems implement this method and use it to purge cached data.

    • Refactored Phalcon\Http\Request::getHttpHost.
    Now it always returns the hostname or empty an string. Optionally validates and cleans host name #phalcon#11921

    • Renamed Phalcon\Http\Request::isSoapRequest to Phalcon\Http\Request::isSoap and Phalcon\Http\Request::isSecureRequest to Phalcon\Http\Request::isSecure.
    Left the originals functions as aliases and marked them deprecated.

    CAUTION: Any references to isSoapRequest need to be renamed to isSoap. Any references to isSecureRequest need to be renamed to isSecure.

    Old way:

    public function testAction()
    {
        if (true === $this->request->isSoapRequest()) {
            //
        }
    
        if (true === $this->request->isSecureRequest()) {
            //
        }
    }
    

    New way:

    public function testAction()
    {
        if (true === $this->request->isSoap()) {
            //
        }
    
        if (true === $this->request->isSecure()) {
            //
        }
    }
    

    • Added Phalcon\Http\Request::setStrictHostCheck and Phalcon\Http\Request::isStrictHostCheck to manage strict validation of the host name.

    use Phalcon\Http\Request;
    
    $request = new Request;
    
    $_SERVER['HTTP_HOST'] = 'example.com';
    $request->getHttpHost(); // example.com
    
    $_SERVER['HTTP_HOST'] = 'example.com:8080';
    $request->getHttpHost(); // example.com:8080
    
    $request->setStrictHostCheck(true);
    $_SERVER['HTTP_HOST'] = 'ex=am~ple.com';
    $request->getHttpHost(); // UnexpectedValueException
    
    $_SERVER['HTTP_HOST'] = 'ExAmPlE.com';
    $request->getHttpHost(); // example.com
    

    • Added Phalcon\Http\Request::getPort.
    Returns the port on which the request is made i.e. 80, 8080, 443 etc.

    • Added setLastModified method to Phalcon\Http\Response
    Sets the Last-Modified header

    public function headerAction()
    {
        $this->response->setLastModified(new DateTime());
    }
    

    • Add setContentLength method to Phalcon\Http\Response
    Sets the response content-length

    public function headerAction()
    {
        $this->response->setContentLength(2048);
    }
    

    LOADER

    • Removed support for prefixes strategy in Phalcon\Loader

    BACKWARDS INCOMPATIBLE: In Phalcon 2, you could load classes using a specific prefix. This method was very popular before namespaces were introduced. For instance:

    <?php
    
    use Phalcon\Loader;
    
    // Creates the autoloader
    $loader = new Loader();
    
    // Register some prefixes
    $loader->registerPrefixes(
        array(
            "Example_Base"    => "vendor/example/base/",
            "Example_Adapter" => "vendor/example/adapter/",
            "Example_"        => "vendor/example/"
        )
    );
    
    // Register autoloader
    $loader->register();
    
    // The required class will automatically include the
    // file vendor/example/adapter/Some.php
    $some = new Example_Adapter_Some();
    

    This functionality is no longer supported

    • Added \Phalcon\Loader::registerFiles and \Phalcon\Loader::getFiles. registerFiles registers files that are "non-classes" hence need a "require". This is very useful for including files that only have functions. getFiles
    returns the files currently registered in the autoloader

    $loader->registerFiles(
        [
            'fuctions.php',
            'arrayFunctions.php',
        ]
    );
    

    MODELS

    • Changed constructor of Phalcon\Mvc\Model to allow pass an array of initialization data

    $customer = new Customer(
        [
            'Name'   => 'Peter',
            'Status' => 'active',
        ]
    );
    $customer->save();
    

    Phalcon\Mvc\Model now implements JsonSerializable making easy serialize model instances

    $customers = Customers::find();
    echo json_encode($customers); // {['id':1,...],['id':2,...], ...}
    

    Phalcon\Mvc\Model\Criteria::getOrder renamed to Phalcon\Mvc\Model\Criteria::getOrderBy

    BACKWARDS INCOMPATIBLE: Any references to getOrder will stop working. You will need to rename the function to getOrderBy

    • Added method getOption() in Phalcon\Mvc\Model\RelationInterface
    Returns an option by the specified name. If the option does not exist null is returned

    • Added OR operator for Phalcon\Mvc\Model\Query\Builder methods: betweenWhere, notBetweenWhere, inWhere and notInWhere

    $builder->betweenWhere('price', 100.25, 200.50);     // Appends a BETWEEN condition
    $builder->notBetweenWhere('price', 100.25, 200.50);  // Appends a NOT BETWEEN condition
    $builder->inWhere('id', [1, 2, 3]);                  // Appends an IN condition
    $builder->notInWhere('id', [1, 2, 3]);               // Appends an NOT IN condition
    

    • Added new getter Phalcon\Mvc\Model\Query\Builder::getJoins()
    Returns the join parts from query builder

    • When destructing a Mvc\Model\Manager PHQL cache is cleaned

    • Added FULLTEXT index type to Phalcon\Db\Adapter\Pdo\Mysql

    • Fixed afterFetch event not being sent to behaviors

    • Fixed issue with Model::__set that was bypassing setters #phalcon

    • Fixed issue with Model::__set setting hidden attributes directly when setters are not declared #phalcon

    Phalcon\Mvc\Model\Manager::load() now can load models from aliased namespaces

    Phalcon\Mvc\Model\Transaction\Manager now correctly keeps account of transactions #phalcon

    Phalcon\Db\Dialect\Sqlite now maps additional column types to SQLite columns equivalents.

    • Fixed Phalcon\Mvc\Model\Resultset::update() - Removed endless loop queries

    • Fixed Phalcon\Mvc\Model\Manager::_mergeFindParameters - Merging conditions fix

    ROLES

    • Added Phalcon\Acl\RoleAware and Phalcon\Acl\ResourceAware Interfaces. Now you can pass objects to Phalcon\Acl\AdapterInterface::isAllowed as roleName and resourceName, also they will be automatically passed to function defined in Phalcon\Acl\AdapterInterface::allow or Phalcon\Acl\AdapterInterface::deny by type

    use UserRole;       // Class implementing RoleAware interface
    use ModelResource;  // Class implementing ResourceAware interface
    
    // Set access level for role into resources
    $acl->allow('Guests', 'Customers', 'search');
    $acl->allow('Guests', 'Customers', 'create');
    $acl->deny('Guests', 'Customers', 'update');
    
    // Create our objects providing roleName and resourceName
    $customer     = new ModelResource(1, 'Customers', 2);
    $designer     = new UserRole(1, 'Designers');
    $guest        = new UserRole(2, 'Guests');
    $anotherGuest = new UserRole(3, 'Guests');
    
    // Check whether our user objects have access to the operation on model object
    $acl->isAllowed($designer, $customer, 'search')     // Returns false
    $acl->isAllowed($guest, $customer, 'search')        // Returns true
    $acl->isAllowed($anotherGuest, $customer, 'search') // Returns true
    

    Phalcon\Acl\AdapterInterface::allow and Phalcon\Acl\AdapterInterface::deny have 4th argument - function. It will be called when using Phalcon\Acl\AdapterInterface::isAllowed

    Phalcon\Acl\AdapterInterface::isAllowed have 4th argument - parameters. You can pass arguments for a function defined in Phalcon\Acl\AdapterInterface:allow or Phalcon\Acl\AdapterInterface::deny as associative array where key is argument name

    // Set access level for role into resources with custom function
    $acl->allow(
        'Guests', 
        'Customers', 
        'search',
        function ($a) {
            return $a % 2 == 0;
        }
    );
    
    // Check whether role has access to the operation with custom function
    $acl->isAllowed('Guests', 'Customers', 'search', ['a' => 4]); // Returns true
    $acl->isAllowed('Guests', 'Customers', 'search', ['a' => 3]); // Returns false
    

    • Fixed wildcard inheritance in Phalcon\Acl\Adapter\Memory #phalcon#12006

    use Phalcon\Acl;
    use Phalcon\Acl\Adapter\Memory as MemoryAcl;
    
    $acl = new MemoryAcl();
    
    $acl->setDefaultAction(Acl::DENY);
    
    $roleGuest      = new Role("guest");
    $roleUser       = new Role("user");
    $roleAdmin      = new Role("admin");
    $roleSuperAdmin = new Role("superadmin");
    
    $acl->addRole($roleGuest);
    $acl->addRole($roleUser, $roleGuest);
    $acl->addRole($roleAdmin, $roleUser);
    $acl->addRole($roleSuperAdmin, $roleAdmin);
    
    $acl->addResource("payment", ["paypal", "facebook",]);
    
    $acl->allow($roleGuest->getName(), "payment", "paypal");
    $acl->allow($roleGuest->getName(), "payment", "facebook");
    
    $acl->allow($roleUser->getName(), "payment", "*");
    
    echo $acl->isAllowed($roleUser->getName(), "payment", "notSet");  // true
    echo $acl->isAllowed($roleUser->getName(), "payment", "*");       // true
    echo $acl->isAllowed($roleAdmin->getName(), "payment", "notSet"); // true
    echo $acl->isAllowed($roleAdmin->getName(), "payment", "*");      // true
    

    ROUTES

    • Routes now can have an associated callback that can override the default dispatcher + view behavior

    • Amended Phalcon\Mvc\RouterInterface and Phalcon\Mvc\Router. Added missed addPurge, addTrace and addConnect methods.
    Added addConnect for the CONNECT HTTP method, addPurge for the PURGE HTTP method and addTrace for the TRACE HTTP method

    • Placeholders :controller and :action in Mvc\Router now defaults to /([\\w0-9\_\-]+) instead of /([\\a-zA-Z0-9\_\-]+)

    • Modifier #u (PCRE_UTF8) is now default in regex based routes in Mvc\Router

    Mvc\Router\Route now escapes characters such as . or + to avoid unexpected behaviors

    • Fixed the use of the annotation router with namespaced controllers

    • Fixed matching host name by Phalcon\Mvc\Route::handle when using port on current host name #phalcon

    SECURITY

    • Added Phalcon\Security::hasLibreSsl and Phalcon\Security::getSslVersionNumber
    Mostly these are used internally but can be used to get information about libreSsl.

    • Changed default hash algorithm in Phalcon\Security to CRYPT_BLOWFISH_Y

    Phalcon\Security is using now Phalcon\Security\Random

    • Enforced that Phalcon\Security::getToken() and Phalcon\Security::getTokenKey() return a random value per request not per call

    Phalcon\Security::getToken() and Phalcon\Security::getTokenKey() are using now Phalcon\Security::_numberBytes instead of passed as argument or hard coded value

    Phalcon\Security::hash() corrected not working CRYPTSTDDES, CRYPTEXTDES, MD5, CRYPT_SHA256

    Phalcon\Security::hash() CRYPT_SHA512 fixed wrong salt length

    • Added missing unit-tests for Phalcon\Security

    SESSION

    • Removed Phalcon\Session #phalcon

    BACKWARDS INCOMPATIBLE: Any references to Phalcon\Session have to be removed and replaced with the relevant adapter class

    • Fixed the Session write callback #phalcon

    TEXT

    • Added ability to use custom delimiter for Phalcon\Text::camelize and Phalcon\Text::uncamelize #phalcon

    use Phalcon\Text;
    
    public function displayAction()
    {
        echo Text::camelize('c+a+m+e+l+i+z+e', '+'); // CAMELIZE
    }
    

    • Fixed Phalcon\Text:dynamic() to allow custom separator #phalcon

    VIEW

    • An absolute path can now be used to Mvc\View::setLayoutsDir
    You can now use one layout path for all the landing pages of your application for instance, even from separate projects

    • Now Phalcon\Mvc\View supports many views directories at the same time

    • Return false from an action disables the view component (same as $this->view->disable())

    public function displayAction()
    {
        // Do some stuff here
    
        return false; // Same as $this->view->disable();
    }
    

    • Return a string from an action takes it as the body of the response

    • Return a string from an Mvc\Micro handler takes it as the body of the response

    public function displayAction()
    {
        // Do some stuff here
    
        // $this->response->setContent('<h1>Hello World</h1>');
        return '<h1>Hello World</h1>';
    }
    

    • Fixed odd view behavior #phalcon related to setLayout() and pick()

    VALIDATION

    Phalcon\Mvc\Model\Validation is now deprecated in favor of Phalcon\Validation
    The functionality of both components is merged into one, allowing us to reduce the codebase while offering the same functionality as before.

    Old way:

    namespace Invo\Models;
    
    use Phalcon\Mvc\Model;
    use Phalcon\Mvc\Model\Validator\Email as EmailValidator;
    use Phalcon\Mvc\Model\Validator\Uniqueness as UniquenessValidator;
    
    class Users extends Model
    {
        public function validation()
        {
            $this->validate(
                new EmailValidator(
                    [
                        'field' => 'email',
                    ]
                )
            );
    
            $this->validate(
                new UniquenessValidator(
                    [
                        'field'   => 'username',
                        'message' => 'Sorry, That username is already taken',
                    ]
                )
            );
    
            if ($this->validationHasFailed() == true) {
                return false;
            }
        }
    }
    

    New way:

    namespace Invo\Models;
    
    use Phalcon\Mvc\Model;
    use Phalcon\Validation;
    use Phalcon\Validation\Validator\Email as EmailValidator;
    use Phalcon\Validation\Validator\Uniqueness as UniquenessValidator;
    
    class Users extends Model
    {
        public function validation()
        {
            $validator = new Validation();
    
            $validator->add(
                'email', //your field name
                new EmailValidator([
                    'model' => $this,
                    'message' => 'Please enter a correct email address'
                ])
            );
    
            $validator->add(
                'username',
                new UniquenessValidator([
                    'model' => $this,
                    'message' => 'Sorry, That username is already taken',
                ])
            );
    
            return $this->validate($validator);
        }
    }
    

    • Method isSetOption in Phalcon\Validation\ValidatorInterface marked as deprecated, please use hasOption

    CAUTION: Any references to isSetOption need to be renamed to hasOption

    Old way:

    if (true === $validation->isSetOption('my-option')) {
        //
    }
    

    New way:

    if (true === $validation->hasOption('my-option')) {
        //
    }
    

    • Added internal check allowEmpty before calling a validator. If it option is true and the value of empty, the validator is skipped

    • Added option to validate multiple fields with one validator (fix uniqueness validator as well), also removes unnecessary model => $this in Phalcon\Validation\Validator\Uniqueness.

    Phalcon\Validation\Validator\Alpha now correctly validates non-ASCII characters #phalcon

    • Added Phalcon\Validation\CombinedFieldsValidator, validation will pass array of fields to this validator if needed

    Phalcon\Validation\Validator\Digit now correctly validates digits #phalcon

    use Phalcon\Validation\Validator\Digit as DigitValidator;
    
    $validator->add(
        'height', 
        new DigitValidator(
            [
                'message' => ':field must be numeric',
            ]
        )
    );
    
    $validator->add(
        [
            'height', 
            'width',
        ], 
        new DigitValidator(
            [
                'message' => [
                    'height' => 'height must be numeric',
                    'width'  => 'width must be numeric',
                ]
            ]
        )
    );
    

    • Added Phalcon\Validation\Validator\Date

    use Phalcon\Validation\Validator\Date as DateValidator;
    
    $validator->add(
        'date', 
        new DateValidator(
            [
                'format'  => 'd-m-Y',
                'message' => 'The date is not valid',
            ]
        )
    );
    
    $validator->add(
        [
            'date',
            'anotherDate',
        ], 
        new DateValidator(
            [
                'format'  => [
                    'date'        => 'd-m-Y',
                    'anotherDate' => 'Y-m-d',
                ],
                'message' => [
                    'date'        => 'The date is invalid',
                    'anotherDate' => 'The another date is invalid',
                ]
            ]
        )
    );
    

    • Fixed Phalcon\Validation::appendMessage to allow append message to the empty stack #phalcon

    • Added convert option to the Phalcon\Validation\Validator\Uniqueness to convert values to the database lookup #phalcon#12030

    use Phalcon\Validation\Validator\Uniqueness;
    
    $validator->add(
        'username', 
        new Uniqueness(
            [
                'convert' => function (array $values) {
                    $values['username'] = strtolower($values['username']);
    
                    return $values;
                }
            ]
        )
    );
    

    INTERFACES

    • Removed __construct from all interfaces #phalcon#11441

    • Added Phalcon\Cli\DispatcherInterface, Phalcon\Cli\TaskInterface, Phalcon\Cli\RouterInterface and Phalcon\Cli\Router\RouteInterface.

    DOCUMENTATION

    • Added Indonesian translation #840

    VARIOUS

    • Added Phalcon\Assets\Manager::exists() to check if collection exists

    • Fixed Filter::add method handler #phalcon

    • Fixed issue with radio not being checked when default value is 0 #phalcon

    • Phalcon\Tag::getTitle() shows a title depending on prependTitle and appendTitle

    • Using a settable variable for the Mongo Connection Service name instead of a hard coded string #phalcon

    Phalcon\Debug\Dump skip debugging di, fix detecting private/protected properties

    • Added new setter Phalcon\Escaper::setDoubleEncode() - to allow setting/disabling double encoding

    • Fixed Phalcon\Config::merge for working with php7

    PHP7

    Phalcon 3.0 supports PHP7! In subsequent releases we will focus on the development of the framework to implove the compatibility and take advantage of the performance enhancements that PHP7 offers. You can install the framework in php7 using the usual installation instructions.

    Support

    Phalcon 3.0 Long Term Support (LTS) version is out, and it’s packed with new features to help you better create web applications with PHP. This version of the framework will be maintained for 3 years from now.

    Acknowledgments

    We want to greatly thank everyone who has contributed to accomplish and achieve the completion of this release. Special thanks to our friends around the world that have made possible this release:

    Conclusion

    Phalcon 3.0 takes a step forward towards a modern framework for PHP. We'll continue working making it more useful and performant for developers. Thank you once more to our wonderful community and users!

    Installation

    You can install Phalcon 3.0 for either PHP 5.5/5.6/7.0 using the following instructions:

    git clone --depth=5 https://github.com/phalcon/cphalcon
    cd cphalcon/build
    sudo ./install
    

    Windows DLLs are available in the download page.

    As always, many thanks to everyone involved in this release and thanks for choosing Phalcon!

    <3 Phalcon Team

  • 2015-10-18 13:09:00

    Using Traits to add more functionality to your classes in PHP

    Traits are a mechanism for code reuse in single inheritance languages such as PHP.

    A Trait is similar to a class, but only intended to group functionality in a fine-grained and consistent way. It is not possible to instantiate a Trait on its own. It is an addition to traditional inheritance and enables horizontal composition of behavior; that is, the application of class members without requiring inheritance. Source

    Traits have been introduced in PHP 5.4.0. However, a lot of developers have not yet embraced them and taken advantage of the power that they offer.

    As mentioned above in the snippet of the PHP manual, Traits are a mechanism to reuse code, making your code more DRY.

    Let's have a look at a real life example of how Traits can help you with your Phalcon project, or any project you might have.

    Models

    With Phalcon, we have model classes which represent pretty much a table in our database, and allows us to interact with a record or a resultset for the needs of our application.

    Scenario

    We have an application where we need to store information about Companies. Each Company can have one or more Customers as well as one or more Employees. We chose to store that information in three different tables.

    For each Employee or Customer, we need to store their first name, middle name and last name. However we also need to be able to show the full name in this format:

    <Last name>, <First Name> <Middle Name>
    

    Using custom getter

    In each model we can use a custom getter method in the Phalcon model to calculate the full name of the record.

    Employee
    namespace NDN\Models;
    
    class Employee
    {
        ...
        public function getFullName()
        {
            return trim(
                sprintf(
                    '%s, %s %s',
                    $this->getLastName(),
                    $this->getFirstName(),
                    $this->getMiddleName()
                )
            );
        }
    }
    
    Customer
    namespace NDN\Models;
    
    class Customer
    {
        ...
        public function getFullName()
        {
            return trim(
                sprintf(
                    '%s, %s %s',
                    $this->getLastName(),
                    $this->getFirstName(),
                    $this->getMiddleName()
                )
            );
        }
    }
    

    The above introduces a problem. If we want to change the behavior of the getFullName we will have to visit both models and make changes to the relevant methods in each model. In addition, we are using the same code in two different files i.e. duplicating code and effort.

    We could create a base model class that our Customer and Employee models extend and put the getFullName function in there. However that increases the class extensions and could lead to maintenance nightmares.

    For instance we will have to create the base model class that only Customer and Employee models extend but what would happen if we need common functionality for other models? We will need to then create another base model class and so on and so forth. If we end up piling all the common functionality into one base model class then we will end up with functions that would not apply to all of our models and thus a maintenance nightmare.

    NOTE: We can also use the afterFetch method to create a calculated field which will be available for us to use. We can use either the getter or the afterFetch like so:

    namespace NDN\Models;
    
    class Customer
    {
        ...
        public function afterFetch()
        {
            $this->full_name = trim(
                sprintf(
                    '%s, %s %s',
                    $this->getLastName(),
                    $this->getFirstName(),
                    $this->getMiddleName()
                )
            );
        }
    }
    

    Traits

    We can use a trait to offer the same functionality, keeping our code DRY. Since a Trait is not a class that can be instantiated by itself, we attach it to wherever we need to, in this case the Employee and Customer models.

    namespace NDN\Traits;
    
    trait FullNameTrait
    {
        /**
         * Gets the user first/last/med name and formats it in a readable format
         *
         * @return  string
         */
        public function getFullName()
        {
            return trim(
                sprintf(
                    '%s, %s %s',
                    $this->getLastName(),
                    $this->getFirstName(),
                    $this->getMiddleName()
                )
            );
        }
    }
    

    We can attach now this trait to the relevant models

    Employee
    namespace NDN\Models;
    
    use NDN\Traits\FullNameTrait;
    
    class Employee
    {
        use FullNameTrait;
    }
    
    Customer
    namespace NDN\Models;
    
    use NDN\Traits\FullNameTrait;
    
    class Customer
    {
        use FullNameTrait;
    }
    

    Now we can use the getFullName() function in our two models to get the full name of the Employee or Customer calculated by the relevant model fields.

    // Customer:
    // first_name:  John
    // middle_name: Mark
    // last_name:   Doe
    
    // Prints: Doe, John Mark
    echo $customer->getFullName();
    
    // Employee:
    // first_name:  Stanley
    // middle_name: Martin
    // last_name:   Lieber
    
    // Prints: Lieber, Stanley Martin
    echo $employee->getFullName();
    

    Conclusion

    Traits can be very powerful and helpful allies, keeping our code very flexible and reusable.

    Give it a try!

  • 2015-10-04 13:26:00

    Building the Phalcon Blog (i)

    This is the first of a series of posts, describing how we built the Phalcon Blog (and this one of course). The intention is to showcase some of the features of Phalcon and discuss the reasons behind implementing the code in such a way. I will amend this post with the links of the future posts once I post them.

    These series will focus initially on the Phalcon blog (Github) and will then expand on this blog (Github). In the very near future all the features available in this blog will be available in the Phalcon one :)

    As I mentioned in a previous post, Andres and I were not 100% satisfied with Tumblr, the blogging platform that we have used for a few years for the purposes of the Phalcon blog. So we decided that it would not only be beneficial for us to build something of our own, but also for the community, since the software is open sourced and available for everyone to use.

    Bootstrapping process

    In this post I am going to concentrate on bootstrapping the application. By bootstrapping I do not mean using the Bootstrap open source library, despite the probably misleading image on the right.

    Bootstrapping is the class (in our case) that handles pretty much everything that our application needs to run prior to executing actions. This entails

    • Conditional execution between the normal app and the CLI one
    • Application Paths
    • Configuration Files
    • Loader (and composer autoloader) setup
    • Error handling
    • Routes
    • Dispatcher
    • Url
    • Views
      • Main View
      • Simple View (for emails, RSS/Sitemap etc.)
    • Cache
    • Utils
    • Post Finder class

    Some of the above components are also registered in the DI container for further use in the application.

    Implementation

    In other applications we have open sourced such as Vokuro, we have pretty much always included a couple of files in our index.php; one for the loader and one for the services as demonstrated here.

    <?php
    
    error_reporting(E_ALL);
    
    try {
        /**
         * Define some useful constants
         */
        define('BASE_DIR', dirname(__DIR__));
        define('APP_DIR', BASE_DIR . '/app');
        /**
         * Read the configuration
         */
        $config = include APP_DIR . '/config/config.php';
        /**
         * Read auto-loader
         */
        include APP_DIR . '/config/loader.php';
        /**
         * Read services
         */
        include APP_DIR . '/config/services.php';
        /**
         * Handle the request
         */
        $application = new \Phalcon\Mvc\Application($di);
        echo $application->handle()->getContent();
    } catch (Exception $e) {
        echo $e->getMessage(), '<br>';
        echo nl2br(htmlentities($e->getTraceAsString()));
    }
    

    There is nothing wrong with the above approach. We did however consider the fact that the particular index.php file has 3 different file inclusions and if we wanted to tinker with the setup of the application we would have to open all three.

    We opted for one file containing all of our services and application bootstrap. In addition to that, we altered the design so that later on we can add a CLI application without much effort and heavy refactoring.

    NOTE: The CLI application has been implemented on this blog and will very soon be merged to the Phalcon repository. We will cover that functionality in a future post.

    index.php

    Having one file that performs all the necessary initialization tasks a.k.a. bootstrapping our application allows us to have a much smaller index.php file. (comments removed to preserve space)

    <?php
    
    use \Phalcon\Di\FactoryDefault as PhDI;
    use \Kitsune\Bootstrap;
    
    error_reporting(E_ALL);
    
    try {
        require_once '../library/Kitsune/Bootstrap.php';
    
        $di = new PhDI();
        $bootstrap = new Bootstrap();
    
        echo $bootstrap->run($di, []);
    } catch (\Exception $e) {
        if ($di->has('logger')) {
            $logger = $di->getShared('logger');
            $logger->error($e->getMessage());
            $logger->error('<pre>' . $e->getTraceAsString() . '</pre>');
        }
    }
    

    We create a new bootstrap application and pass in it a DI container. For this part of the application the FactoryDefault DI container is used. However we will be able to inject a Phalcon CLI DI container for the CLI application we will discuss later on.

    Bootstrap.php

    Our bootstrap class contains all the code we need to run the application. It is a bit shy of 400 lines which according to PHP Mess Detector is not something we want to be doing because it increases complexity and if we are not careful it will create a mess :). We opted to ignore that rule and left the file as is because once we had everything working as we wanted, we were not going to be messing with that file again.

    Constants

    We use several constants throughout the application.

    • K_PATH - the top folder path of our installation
    • K_CLI - whether this is a CLI application or not
    • K_DEBUG - whether we are in debug/development mode. In this mode all volt templates are being created at every request and cache is not used.
    • K_TESTS - whether we are running the test suite or not (test suite is not implemented yet)
            /**
             * The app path
             */
            if (!defined('K_PATH')) {
                define('K_PATH', dirname(dirname(dirname(__FILE__))));
            }
    
            ....
    
            /**
             * Check if this is a CLI app or not
             */
            $cli   = $utils->fetch($options, 'cli', false);
            if (!defined('K_CLI')) {
                define('K_CLI', $cli);
            }
    
            $tests = $utils->fetch($options, 'tests', false);
            if (!defined('K_TESTS')) {
                define('K_TESTS', $tests);
            }
    
            ....
    
            /**
             * Check if we are in debug/dev mode
             */
            if (!defined('K_DEBUG')) {
                $debugMode = boolval($utils->fetch($config, 'debugMode', false));
                define('K_DEBUG', $debugMode);
            }
    
    Configuration

    The configuration is split into two files. The git tracked base.php (under /var/config/) contains an array of elements that are needed throughout the application, such as cache settings, routes etc. The config.php located in the same folder is installation dependent and is not tracked in git. You can override every element that exists in base.php.

            /**
             * The configuration is split into two different files. The first one
             * is the base configuration. The second one is machine/installation
             * specific.
             */
            if (!file_exists(K_PATH . '/var/config/base.php')) {
                throw new \Exception('Base configuration files are missing');
            }
    
            if (!file_exists(K_PATH . '/var/config/config.php')) {
                throw new \Exception('Configuration files are missing');
            }
    
            /**
             * Get the config files and merge them
             */
            $base     = require(K_PATH . '/var/config/base.php');
            $specific = require(K_PATH . '/var/config/config.php');
            $combined = array_replace_recursive($base, $specific);
    
            $config = new Config($combined);
            $di->set('config', $config, true);
    
    Loader

    The loader uses the namespaces defined in the base.php and config.php. Additionally the composer autoloader is included to offer functionality needed from the composer components we have.

            /**
             * We're a registering a set of directories taken from the
             * configuration file
             */
            $loader = new Loader();
            $loader->registerNamespaces($config->namespaces->toArray());
            $loader->register();
    
            require K_PATH . '/vendor/autoload.php';
    
    Logger

    The logger is set to create a log file every day (with the date as the prefix).

            /**
             * LOGGER
             *
             * The essential logging service
             */
            $format    = '[%date%][%type%] %message%';
            $name      = K_PATH . '/var/log/' . date('Y-m-d') . '-kitsune.log';
            $logger    = new LoggerFile($name);
            $formatter = new LoggerFormatter($format);
            $logger->setFormatter($formatter);
            $di->set('logger', $logger, true);
    
    Error handler

    We decided to have no errors thrown in the application even if those are E_NOTICE. A simple isset() in most cases is more than enough to ensure that there are no E_NOTICE errors thrown in our log. Any errors thrown in the log files slow our application down, even if the errors are suppressed using the php.ini directives. We also set the timezone to US/Eastern in that file. That particular piece could become configurable and stored in the config.php. Finally we specify a custom error handler, to offer verbosity in errors thrown as well as log metrics when in debug mode.

            /**
             * ERROR HANDLING
             */
            ini_set('display_errors', boolval(K_DEBUG));
    
            error_reporting(E_ALL);
    
            set_error_handler(
                function ($exception) use ($logger) {
                    if ($exception instanceof \Exception) {
                        $logger->error($exception->__toString());
                    } else {
                        $logger->error(json_encode(debug_backtrace()));
                    }
                }
            );
    
            set_exception_handler(
                function (\Exception $exception) use ($logger) {
                    $logger->error($exception->getMessage());
                }
            );
    
            register_shutdown_function(
                function () use ($logger, $memoryUsage, $currentTime) {
                    $memoryUsed = number_format(
                        (memory_get_usage() - $memoryUsage) / 1024,
                        3
                    );
                    $executionTime = number_format(
                        (microtime(true) - $currentTime),
                        4
                    );
                    if (K_DEBUG) {
                        $logger->info(
                            'Shutdown completed [Memory: ' . $memoryUsed . 'Kb] ' .
                            '[Execution: ' . $executionTime .']'
                        );
                    }
                }
            );
    
            $timezone = $config->get('app_timezone', 'US/Eastern');
            date_default_timezone_set($timezone);
    
    Routes

    Our routes are stored in the base.php. Additional routes can be set in the config.php. The router is not initialized if this is a CLI application.

            /**
             * Routes
             */
            if (!K_CLI) {
                $di->set(
                    'router',
                    function () use ($config) {
                        $router = new Router(false);
                        $router->removeExtraSlashes(true);
                        $routes = $config->routes->toArray();
                        foreach ($routes as $pattern => $options) {
                            $router->add($pattern, $options);
                        }
    
                        return $router;
                    },
                    true
                );
            }
    
    Dispatcher

    The dispatcher is instantiated with a listener, attaching to the beforeException event of the dispatcher. A custom plugin NotFoundPlugin is used to send output to the 404 page. Using the plugin allows us to reuse it anywhere in the application. This implementation is very beneficial when developing multi module applications.

    NOTE: For the CLI application later on, we will need the CLI dispatcher.

            /**
             * We register the events manager
             */
            $di->set(
                'dispatcher',
                function () use ($di) {
                    $eventsManager = new EventsManager;
    
                    /**
                     * Handle exceptions and not-found exceptions using NotFoundPlugin
                     */
                    $eventsManager->attach('dispatch:beforeException', new NotFoundPlugin);
    
                    $dispatcher = new Dispatcher;
                    $dispatcher->setEventsManager($eventsManager);
    
                    $dispatcher->setDefaultNamespace('Kitsune\Controllers');
    
                    return $dispatcher;
                }
            );
    
    Views

    The views are being initialized using Volt as the template engine. The main view is set up with the expected options.

            $di->set(
                'view',
                function () use ($config) {
                    $view = new View();
                    $view->setViewsDir(K_PATH . '/app/views/');
                    $view->registerEngines([".volt" => 'volt']);
                    return $view;
                }
            );
    
            /**
             * Setting up volt
             */
            $di->set(
                'volt',
                function ($view, $di) {
                    $volt = new VoltEngine($view, $di);
                    $volt->setOptions(
                        [
                            "compiledPath"  => K_PATH . '/var/cache/volt/',
                            'stat'          => K_DEBUG,
                            'compileAlways' => K_DEBUG,
                        ]
                    );
                    return $volt;
                },
                true
            );
    
    Cache

    The cache component is configured using the config.php. We can define the parameters in that file and thus use say the File cache for our local/development machine and a more advanced cache (Memcached for instance) for the production system.

            /**
             * Cache
             */
            $frontConfig = $config->cache_data->front->toArray();
            $backConfig  = $config->cache_data->back->toArray();
            $class       = '\Phalcon\Cache\Frontend\\' . $frontConfig['adapter'];
            $frontCache  = new $class($frontConfig['params']);
            $class       = '\Phalcon\Cache\Backend\\' . $backConfig['adapter'];
            $cache       = new $class($frontCache, $backConfig['params']);
            $di->set('cache', $cache, true);
    
            /**
             * viewCache
             */
            $frontConfig = $config->cache_view->front->toArray();
            $backConfig  = $config->cache_view->back->toArray();
            $class       = '\Phalcon\Cache\Frontend\\' . $frontConfig['adapter'];
            $frontCache  = new $class($frontConfig['params']);
            $class       = '\Phalcon\Cache\Backend\\' . $backConfig['adapter'];
            $cacheView   = new $class($frontCache, $backConfig['params']);
            $di->set('viewCache', $cacheView, true);
    
    Markdown Renderer

    We use Ciconia for the rendering of markdown with several plugins, existing and user defined. The registration is pretty straight forward.

            /**
             * Markdown renderer
             */
            $di->set(
                'markdown',
                function () {
                    $ciconia = new Ciconia();
                    $ciconia->addExtension(new FencedCodeBlockExtension());
                    $ciconia->addExtension(new TaskListExtension());
                    $ciconia->addExtension(new InlineStyleExtension());
                    $ciconia->addExtension(new WhiteSpaceExtension());
                    $ciconia->addExtension(new TableExtension());
                    $ciconia->addExtension(new UrlAutoLinkExtension());
                    $ciconia->addExtension(new MentionExtension());
    
                    $extension = new IssueExtension();
                    $extension->setIssueUrl(
                        '[#%s](https://github.com/phalcon/cphalcon/issues/%s)'
                    );
                    $ciconia->addExtension($extension);
    
                    $extension = new PullRequestExtension();
                    $extension->setIssueUrl(
                        '[#%s](https://github.com/phalcon/cphalcon/pull/%s)'
                    );
                    $ciconia->addExtension($extension);
                    return $ciconia;
                },
                true
            );
    
    Posts Finder

    This is a class we came up with, which is used to give us an easy way to get information about a specific post, the tag cloud, the index page etc. It is utilizing cache a lot!

            /**
             * Posts Finder
             */
            $di->set(
                'finder',
                function () use ($utils, $cache) {
                    $key        = 'post.finder.cache';
                    $postFinder = $utils->cacheGet($key);
                    if (null === $postFinder) {
                        $postFinder = new PostFinder();
                        $cache->save($key, $postFinder);
                    }
                    return $postFinder;
                },
                true
            );
    

    Conclusion

    In the next post of these series we will take a look at the router and discuss what each route means to our application.

    Comments are more than welcome. If you have any questions on the implementation, feel free to ask in the comments below.

    References

  • 2015-10-02 22:42:39

    Every microsecond counts!

    Preface

    One of the primary factors that always needs to be taken into consideration when designing and implementing an application is performance. In this day and age of information overload, everything is about speed. If your website is slow (more than a second or two) to appear on the browser, most likely your visitors will leave and go elsewhere. If the application you designed/implemented is slow, it will use more resources (memory/cpu) and thus cost more money to run. Time is money.

    The Nanosecond

    I have the highest admiration for Grace Hopper, a pioneer computer scientist who invented the first compiler for a computer language and paved the way for the evolution of programming languages in general. In a video she describes the value of a nanosecond in computer terms. I encourage you to click the link and watch it, it might give you a better perspective on why your application must be as fast as possible.

    This Blog

    As I wrote in a previous post, a new version of this blog has been launched, based on work done for for the Phalcon blog. While in development mode, metrics that are printed in the logger. The output looks something like this:

    [Fri, 02 Oct 15 20:10:27 -0400][INFO] Shutdown completed [2.798s] - [4,134.16 KB] 
    [Fri, 02 Oct 15 20:11:01 -0400][INFO] Shutdown completed [2.979s] - [4,134.00 KB] 
    [Fri, 02 Oct 15 20:14:43 -0400][INFO] Shutdown completed [2.891s] - [4,142.60 KB] 
    [Fri, 02 Oct 15 20:26:10 -0400][INFO] Shutdown completed [2.721s] - [1,075.02 KB] 
    [Fri, 02 Oct 15 20:30:16 -0400][INFO] Shutdown completed [2.735s] - [1,002.25 KB] 
    [Fri, 02 Oct 15 20:30:29 -0400][INFO] Shutdown completed [2.708s] - [1,002.29 KB] 
    [Fri, 02 Oct 15 20:54:04 -0400][INFO] Shutdown completed [2.674s] - [1,003.43 KB] 
    [Fri, 02 Oct 15 20:55:28 -0400][INFO] Shutdown completed [2.677s] - [1,003.31 KB] 
    [Fri, 02 Oct 15 21:12:33 -0400][INFO] Shutdown completed [2.013s] - [913.81 KB] 
    [Fri, 02 Oct 15 21:14:11 -0400][INFO] Shutdown completed [2.002s] - [895.35 KB] 
    [Fri, 02 Oct 15 21:32:48 -0400][INFO] Shutdown completed [2.054s] - [894.71 KB] 
    [Fri, 02 Oct 15 21:39:02 -0400][INFO] Shutdown completed [2.028s] - [894.04 KB]
    [Fri, 02 Oct 15 21:44:19 -0400][INFO] Shutdown completed [2.046s] - [895.59 KB]
    [Fri, 02 Oct 15 21:45:55 -0400][INFO] Shutdown completed [2.023s] - [893.75 KB]
    

    As you can see there is room for improvement. Granted these results come from my local installation, where the debugMode is set to 1, which means that there is no caching and everything gets recalculated on every request. Still, if I can make this local installation perform as fast as possible, then on the production server it will be even faster.

    The first few lines show a relatively OK response (2.7-3.0 seconds) but a high usage in memory. This had to be rectified and looking at the code, I managed to refactor the PostFinder class and reduce the memory consumption significantly. Removing objects and referenced objects in them made a huge difference. Arrays work just fine for my purposes.

    Additional optimizations led to dropping the execution time to just above 2.0 seconds and the memory consumption below 1Mb.

    There are still a lot of things I can try (and will) both on the application level as well as the server level. I am aiming to reduce the execution time to below 1 second and I will for sure share the results and the tools/techniques used.

    I hope you enjoy the performance increase (however noticeable). More to come in the near future.

    References

  • 2015-09-28 20:06:28

    New Blog (again), new look, hopefully more posts

    The excuse

    It has definitely been a very long time since I last blogged. Just like my previous hiatus, a lot of personal issues arose that would not allow me the luxury to devote time for this blog.

    Things managed to settle down for a bit, so hopefully I can start posting more frequently.

    New look

    For those that have been following this blog (all 3 of you), you will notice that the look and feel has changed significantly. I decided that I needed a fresh look so after evaluating many platforms and blog related software, I opted for the in house version.

    Many of you know that I have been heavily involved in the Phalcon Framework, where I serve as a member of the core team. For the blogging needs of the project, we were using Tumblr but were not 100% satisfied. So a couple months back we decided to write our own blogging software based on Phalcon!

    The results can be seen on our blog, which is also open sourced on Github.

    For my own purposes I modified the codebase, adding new functionality and modifying it somewhat for my needs. The changes are also on Github and will soon be propagated to the Phalcon repository for everyone to use. This software requires some libraries loaded by composer and of course Phalcon loaded on your web server. The content is served using Markdown.

    As far as the look is concerned, I tried (really did) but nothing came close to what I wanted. Since I am not a CSS developer by any means, I purchased this template by the guys that created Bootstrap. The result IMHO is really nice, responsive and clean. I hope you like it too!

    Future

    I intend on posting more content in the next few weeks. The goal is to post one article (minimum) per week. Other than technology, I am going to focus on Phalcon related posts, sharing experiences, tips and tricks.

    Missing...

    This blog software is still under construction. I have a few things that I need to address such as:

    • Pagination
    • RSS feeds
    • Posts by tag
    • Posts by archive
    • Comments - disqus integration
    • Tests!!!!

    Some of those are already in place but I need to make sure that they work as expected

    I hope you like the new look and stay tuned for more content.

  • 2013-09-15 12:00:00

    Let the RDBMS do more than just store data

    One of the common "mistakes" that programmers (and have been guilty as charged many a times in the past) is not to use the tools that are available to them to the maximum extent possible.

    A common example is using the RDBMS of your choice to only store and retrieve data, without taking advantage of its power and its features to the full extent.

    A RDBMS can do much, much more. One can use triggers that can auto update fields (as I will demonstrate in this blog post), log data into tables, trigger cascade deletes etc.; stored procedures can compute complex data sets, joining tables and transforming data; views can offer easier representations of data, hiding complex queries from the actual application. In addition, such features, like stored procedures/views, can offer security enhancements as well as maintainability to an application. Execution for instance can be restricted to particular groups/logins, while changing the stored procedure/view only requires a change on the database layer and not the application itself.

    In this blog post I will show you a simple example on how one can transfer some of the processing of an application to the RDBMS. I am using MariaDB as the RDBMS and PhalconPHP as the PHP framework.

    The RDBMS

    Each table of my database has several common fields that are used for logging and reporting as well as recording status.

    An example table is as follows

    CREATE TABLE IF NOT EXISTS co_address (
      id             int(11)      unsigned NOT NULL AUTO_INCREMENT,
      address_line_1 varchar(150) COLLATE utf8_unicode_ci DEFAULT NULL,
      address_line_2 varchar(150) COLLATE utf8_unicode_ci DEFAULT NULL,
      address_line_3 varchar(150) COLLATE utf8_unicode_ci DEFAULT NULL,
      region         varchar(6)   COLLATE utf8_unicode_ci DEFAULT NULL,
      post_code      varchar(24)  COLLATE utf8_unicode_ci DEFAULT NULL,
      country        varchar(2)   COLLATE utf8_unicode_ci DEFAULT NULL,
      created_id     int(11)      unsigned NOT NULL DEFAULT '0',
      created_date   datetime              NOT NULL,
      updated_id     int(11)      unsigned NOT NULL DEFAULT '0',
      updated_date   datetime              NOT NULL,
      deleted        tinyint(1)   unsigned NOT NULL DEFAULT '0',
      PRIMARY KEY (id),
      KEY created_id (created_id),
      KEY created_date (created_date),
      KEY updated_id (updated_id),
      KEY updated_date (updated_date),
      KEY deleted (deleted)
    ) ENGINE=InnoDB  
    DEFAULT CHARSET=utf8
    COLLATE=utf8_unicode_ci
    COMMENT='Holds addresses for various entities' AUTO_INCREMENT=1 ;
    

    The fields are:

    Field Name Description
    created_id The id of the user that created the record
    created_date The date/time that the record was created
    updated_id The id of the user that last updated the record
    updated_date The date/time that the record was last updated
    deleted A soft delete flag

    There is not much I can do with the user ids (created/updated) or the deleted column (see also notes below regarding this). However as far as the dates are concerned I can definitely let MariaDB handle those updates.

    Triggers

    The work is delegated to triggers, attached to each table.

    --
    -- Triggers address
    --
    DROP TRIGGER IF EXISTS trg_created_date;
    DELIMITER //
    CREATE TRIGGER trg_created_date BEFORE INSERT ON address
     FOR EACH ROW SET NEW.created_date = NOW(), NEW.updated_date = NOW()
    //
    DELIMITER ;
    DROP TRIGGER IF EXISTS trg_updated_date;
    DELIMITER //
    CREATE TRIGGER trg_updated_date BEFORE UPDATE ON address
     FOR EACH ROW SET NEW.updated_date = NOW()
    //
    DELIMITER ;
    

    The triggers above update the created_date and updated_date fields automatically upon insert/update.

    Phalcon Model

    I needed to make some changes to my model Address, in order to allow the triggers to work without interference from the model.

    class Model extends PhModel
    {
        public function initialize()
        {
            // Disable literals
            $this->setup(['phqlLiterals' => false]);
    
            // We skip these since they are handled by the RDBMS
            $this->skipAttributes(
                [
                    'created_date',
                    'updated_date',
                ]
            );
    
        }
    
        public function getSource()
        {
            return 'address';
        }
    
        public function getCreatedDate()
        {
            return $this->created_date;
        }
    
        public function getUpdatedDate()
        {
            return $this->updated_date;
        }
    }
    

    By using skipAttributes, I am instructing the Phalcon model not to update those fields. By doing so, I am letting my triggers worry about that data.

    Conclusion

    It might seem a very trivial task that I am delegating but in the grand scheme of things, the models of an application can be very complex and have a lot of logic in them (and so might controllers). Delegating some of that logic in the RDBMS simplifies things and also increases performance of the application, which now requires just a bit less computational power.

    NOTES

    For a soft delete feature i.e. automatically updating the deleted field when a DELETE is called, a trigger will not work. Instead one can use a stored procedure for it. See this Stack Overflow answer.

  • 2013-06-25 12:00:00

    Voting for Phalcon as a cPanel feature woes

    Those that have been following my blog and posts on Google+ know that for the last year or so I have been involved heavily in PhalconPHP, a C based PHP framework, which delivers its functionality as an extension loaded on the web server.

    I was honored a few months ago when I became a member of the Phalcon Team and have since tried my best to evangelize Phalcon and to help as much as possible with scheduling NFRs for development, helping in the forum, unit tests, blog posts etc.

    One of the most difficult issues that Phalcon is facing is helping developers installing it on their machines. This of course does not mean that it is really difficult to install. The installation is basically three commands (or if you are on a Windows box you just download the DLL, add the relevant directive in the php.ini and restart the web server).

    However since a lot of people are using shared hosting, they do not have access to the command line where sudo or su are available so that that Phalcon can be installed. This is left to the hosting company and some are very reluctant to install anything at all. I used to own a hosting company and I can assure you that it is indeed a hassle if a handful of clients ask for a library or an installation that is not part of the "norm". You have to maintain it, you have to ensure that it will not interfere with other packages on the server or hinder other clients that reside on the same hosting box.

    A few months ago I approached cPanel through their ticketing system, in an effort to make Phalcon an option for the extensions that can be installed and loaded through their EasyApache application.

    At the time I was granted a development account for the software so that I can try and create an installation script and also was prompted to go to http://features.cpanel.net/ and open a new feature request regarding this (the request is here). The purpose of this exercise is for cPanel to get a feel of what features the community needs and address them. In my communications with them I received the following (emphasis mine):

    Our EasyApache has multiple locations to add includes, or post hooks to compile third party libraries/software during a build, or after a build has completed. That being said, the extensions that we ship to be configured are maintained by the PHP & PECL groups. To answer the question as far as integration on all servers running cPanel, I would recommend creating a feature request for this, and allowing the community to vote for this:

    http://features.cpanel.net/

    And so I did. I created the feature request and we also advertised this in our community via our forum and our blog post.

    The feature received well over 180 votes, making it the third most requested feature in cPanel. Also if you follow the link, one of the engineers of cPanel requested additional information which was provided by myself and others.

    As time went by, I visited the feature and requested an update but never got a reply back. More and more votes kept on coming in so it was really a waiting game at that time.

    All of a sudden though a week ago, one of our users in the forum asked in our Forum why the feature request in cPanel has only 7 votes. This came to us as a surprise so I went and checked it out. Lo and behold the vote counter was at 7 and not at 180+. Assuming that this was some sort of a glitch, I opened a ticket with cPanel and inquired about this.

    A short time later I received a reply from a Vice President of Operations stating (emphasis mine):

    Our feature request system was designed for features requests from our customers and in reviewing this feature it was determined that most (if not all of the votes) came from an outside source. In an effort to validate this we polled a number of shared hosting providers that we work closely with and this was not a feature they wanted.

    We traced most of the votes down to this single source:

    https://blog.phalconphp.com/post/help-the-community-to-make-phalcon-available-on-cpanel

    While cPanel wants feedback from the general community, our focus is to deliver new features that our Partners and customers are asking for.

    We always appreciate community support and will keep an eye on this feature. Decisions for new features are made by both reviewing this feature request system and talking with Partners and customers. To date out of about 15 conversations, not a single Partner or customer told us this was something that was important to them and thus we adjusted the votes to what we felt was more in line with the community using our system.

    I have a couple of issues with the above reply.

    • For starters there was no communication and no warning that would have indicated what was going to happen with our votes. cPanel decided on this on their own. They do have the right to do so, it's their software after all, but a little courtesy would have gone a long way.
    • The definition of "customers" is different to me than it is to cPanel. As customers cPanel defines their partners and hosting companies, the ones that purchase their software. Myself on the other hand considers customers also the end user, me and you who visit a hosting company and purchase services from them. If we do not exist, then a hosting company and subsequently cPanel does not exist either.
    • cPanel asked a number of their partners who have not heard of Phalcon before, and as such they acted based on that premise. What would have happened if their sample was a different one? Say they asked partner X instead of partner Y? The bias of statistics is based on the sample one chooses and at times (such as this one) it could lead to false results.

    In my reply I pointed out the above, stating that a customer should effectively be the end user and if not, at least their voice should be heard. The reply that I got was as follows:

    Thanks for the reply and understanding. In reviewing the votes that came in, most appeared to be brand advocates of Phalcon and it was very difficult to discern the legitimacy of the votes. Had the votes come from active users of forums.cpanel.net (where most of the users originate from) or we were able to relate them back to some sort of hosting entity, we would have left the votes.

    From the outside looking in the votes just appeared to come from Phalcon users without ties to cPanel & WHM. What I would encourage you to do is the following:

    • Have your users ask their hosting provider about it.
    • Remove the blog links and tweets and allow the feature to grow it's own set of wheels.

    If this feature is truly in high demand from customers of cPanel, Inc. they will naturally vote for it.

    We do appreciate your support and ongoing efforts to get this in front of us. The massive amounts of votes and comments it received, put it on our developers radars and we will continue to monitor the situation.

    So in essence, if we want to achieve our objective, i.e. get Phalcon as an available extension for cPanel, we need to advise the community (but be careful to Remove the blog links and tweets and allow the feature to grow it's own set of wheels.) to contact their hosting companies (that use cPanel) to in turn contact cPanel and request Phalcon to be included as an extension.

    The fallacy of the above is that developers of Phalcon will not choose hosts that offer cPanel because they cannot install the application. If they cannot install the application they will not use hosts that offer cPanel, thus they cannot ask their hosts to include Phalcon as a cPanel extension, and so goes the chicken and egg situation.

    I totally respect cPanel's decisions - I don't agree with them but I do respect them. It's their house their rules as they say. I am however saddened by the fact that we never got any communication or warning that our votes were removed. A bit of communication there would have definitely saved a lot of frustration at least for our community.

    Concluding, if anyone has a cPanel hosting account and wants to see Phalcon available as an extension, feel free to contact your host and request it to be included as an available extension.

    2013-07-25 Update: A great analogy and reply has been posted by Andres in the Phalcon Forum.

  • 2012-11-25 12:00:00

    Building a web app with PhalconPHP and AngularJS Update

    It's been a while since I last wrote a blog post, so I wanted to touch on the effort to upgrade the application that I wrote for Harry Hog Fottball using PhalconPHP and AngularJS

    If you haven't read it, the first two blog posts were here and here.

    The application was written using the 0.4.5 version of PhalconPHP. Since then there have been significant changes to the framework, such as the introduction of a DI container, injectable objects and lately interfaces (in 0.7.0, to be released in a couple of days), I had to make some changes.

    There are a couple of things that I as a developer would like to see in PhalconPHP, which I am pretty sure will appear later on, since let's face it the framework is still very young (not even 1.0 version yet). Despite its "youth" it is a robust framework with excellent support, features and a growing community. One of these features is behaviors which I had to implement myself, and this was something new that came with this upgrade.

    Recently a new repo has been created on Github called the incubator, where developers can share implementations of common tasks, that act as drop ins to the framework and extend it. These implementations are all written in PHP so everyone can just download them and use them. The more submissions come in, the more the framework will grow and eventually these submissions will become part of the framework itself.

    Converting the 0.4.x application to 0.5.x

    The task of converting everything from 0.4 to 0.5 was a bit challenging. The reason behind it was the DI container and how best to use it to suit the needs of the current application. Now these challenges would not even be an issue if one started writing their application from scratch, but since I had everything in place, I ventured into upgrading vs. rewriting. Note that this kind of upgrade will most likely never happen again, since the framework has been changed accordingly so that future upgrades will not require developers to rewrite their code (like I did now). From 0.5.x onward the framework design has been kind of "frozen".

    I decided to create a new library that will help me with my tasks. I therefore created a custom bootstrap class, that would instantiate everything I wanted in my code. A short snippet of the class is below (the full code of course is in my Github repo which you are more than welcome to download and modify to suit your needs)

    namespace NDN;
    
    use \Phalcon\Config\Adapter\Ini as PhConfig;
    use \Phalcon\Loader as PhLoader;
    ....
    use \Phalcon\Exception as PhException;
    
    class Bootstrap
    {
        private $_di;
    
        /**
         * Constructor
         * 
         * @param $di
         */
        public function __construct($di)
        {
            $this->_di = $di;
        }
    
        /**
         * Runs the application performing all initializations
         * 
         * @param $options
         *
         * @return mixed
         */
        public function run($options)
        {
            $loaders = array(
                'config',
                'loader',
                'environment',
                'timezone',
                'debug',
                'flash',
                'url',
                'dispatcher',
                'view',
                'logger',
                'database',
                'session',
                'cache',
                'behaviors',
            );
    
    
            try {
                foreach ($loaders as $service)
                {
                    $function = 'init' . ucfirst($service);
    
                    $this->$function($options);
                }
    
                $application = new PhApplication();
                $application->setDI($this->_di);
    
                return $application->handle()->getContent();
    
            } catch (PhException $e) {
                echo $e->getMessage();
            } catch (\PDOException $e) {
                echo $e->getMessage();
            }
        }
    
        // Protected functions
    
        /**
         * Initializes the config. Reads it from its location and
         * stores it in the Di container for easier access
         *
         * @param array $options
         */
        protected function initConfig($options = array())
        {
            $configFile = ROOT_PATH . '/app/var/config/config.ini';
    
            // Create the new object
            $config = new PhConfig($configFile);
    
            // Store it in the Di container
            $this->_di->set('config', $config);
        }
    
        /**
         * Initializes the loader
         *
         * @param array $options
         */
        protected function initLoader($options = array())
        {
            $config = $this->_di->get('config');
    
            // Creates the autoloader
            $loader = new PhLoader();
    
            $loader->registerDirs(
                array(
                    ROOT_PATH . $config->app->path->controllers,
                    ROOT_PATH . $config->app->path->models,
                    ROOT_PATH . $config->app->path->library,
                )
            );
    
            // Register the namespace
            $loader->registerNamespaces(
                array("NDN" => $config->app->path->library)
            );
    
            $loader->register();
        }
    
        ....
    
        /**
         * Initializes the view and Volt
         *
         * @param array $options
         */
        protected function initView($options = array())
        {
            $config = $this->_di->get('config');
            $di     = $this->_di;
    
            $this->_di->set(
                'volt',
                function($view, $di) use($config)
                {
                    $volt = new PhVolt($view, $di);
                    $volt->setOptions(
                        array(
                            'compiledPath'      => ROOT_PATH . $config->app->volt->path,
                            'compiledExtension' => $config->app->volt->extension,
                            'compiledSeparator' => $config->app->volt->separator,
                            'stat'              => (bool) $config->app->volt->stat,
                        )
                    );
                    return $volt;
                }
            );
        }
        ....
    
        /**
         * Initializes the model behaviors
         *
         * @param array $options
         */
        protected function initBehaviors($options = array())
        {
            $session = $this->_di->getShared('session');
    
            // Timestamp
            $this->_di->set(
                'Timestamp',
                function() use ($session)
                {
                    $timestamp = new Models\Behaviors\Timestamp($session);
                    return $timestamp;
                }
            );
        }
    }
    

    I chose to show a few sections of this bootstrap which I will explain shortly. What this bootstrap class does is it initializes my whole environment and keeps my index.php file small.

    error_reporting(E_ALL);
    
    try {
    
        if (!defined('ROOT_PATH')) {
            define('ROOT_PATH', dirname(dirname(__FILE__)));
        }
    
        // Using require once because I want to get the specific
        // bootloader class here. The loader will be initialized
        // in my bootstrap class
        require_once ROOT_PATH . '/app/library/NDN/Bootstrap.php';
        require_once ROOT_PATH . '/app/library/NDN/Error.php';
    
        // Instantiate the DI container
        $di  = new \Phalcon\DI\FactoryDefault();
    
        // Instantiate the boostrap class and inject the DI container 
        // in it so that services can be registered
        $app = new \NDN\Bootstrap($di);
    
        // Here we go!
        echo $app->run(array());
    
    } catch (\Phalcon\Exception $e) {
        echo $e->getMessage();
    }
    

    As you can see the index.php is very small in terms of code.

    Let's have a look at a couple of the functions that are in the bootstrap.

        /**
         * Initializes the config. Reads it from its location and
         * stores it in the Di container for easier access
         *
         * @param array $options
         */
        protected function initConfig($options = array())
        {
            $configFile = ROOT_PATH . '/app/var/config/config.ini';
    
            // Create the new object
            $config = new PhConfig($configFile);
    
            // Store it in the Di container
            $this->_di->set('config', $config);
        }
    

    Pretty straight forward. The config INI file is read from its location and stored in the DI container. I need to do this first, since a lot of the parameters of the application are controlled from that file.

        /**
         * Initializes the loader
         *
         * @param array $options
         */
        protected function initLoader($options = array())
        {
            $config = $this->_di->get('config');
    
            // Creates the autoloader
            $loader = new PhLoader();
    
            $loader->registerDirs(
                array(
                    ROOT_PATH . $config->app->path->controllers,
                    ROOT_PATH . $config->app->path->models,
                    ROOT_PATH . $config->app->path->library,
                )
            );
    
            // Register the namespace
            $loader->registerNamespaces(
                array("NDN" => $config->app->path->library)
            );
    
            $loader->register();
        }
    

    The loader is what does all the discovery of classes for me. As you can see I store a lot of the paths in the config INI file, and I register my custom namespace NDN.

        /**
         * Initializes the view and Volt
         *
         * @param array $options
         */
        protected function initView($options = array())
        {
            $config = $this->di->get('config');
            $di     = $this->_di;
    
            $this->_di->set(
                'volt',
                function($view, $di) use($config)
                {
                    $volt = new PhVolt($view, $di);
                    $volt->setOptions(
                        array(
                            'compiledPath'      => ROOT_PATH . $config->app->volt->path,
                            'compiledExtension' => $config->app->volt->extension,
                            'compiledSeparator' => $config->app->volt->separator,
                            'stat'              => (bool) $config->app->volt->stat,
                        )
                    );
                    return $volt;
                }
            );
        }
    

    This is an interesting one. Registering the view and Volt. Volt is the template engine that comes with Phalcon. It is inspired by Twig and written in C, thus offering maximum performance. I set the compiled path, extension and separator for the template files and also I have a variable (set in the config of course) to allow the application to always create template files or not. In a production environment that variable (stat) will be set to false since templates do not change.

        /**
         * Initializes the model behaviors
         *
         * @param array $options
         */
        protected function initBehaviors($options = array())
        {
            $session = $this->_di->getShared('session');
    
            // Timestamp
            $this->_di->set(
                'Timestamp',
                function() use ($session)
                {
                    $timestamp = new Models\Behaviors\Timestamp($session);
                    return $timestamp;
                }
            );
        }
    

    The above is my implementation of behaviors. Of course it is far from perfect but it works the way I want to. A better implementation of this has been written by Wojtek Gancarczyk and is available in the incubator. All I do here is go through the behaviors I have (Timestamp only for now) and register them in the DI container so that I can reuse them later on with any model that needs them.

    Models

    Every model I have that interacts with my database tables extends the NDN\Model.

    class Model extends \Phalcon\Mvc\Model
    {
        protected $behaviors = array();
    
        /**
         * Adds a behavior in the model
         *
         * @param $behavior
         */
        public function addBehavior($behavior)
        {
            $this->behaviors[$behavior] = true;
        }
    
        public function beforeSave()
        {
            $di   = Di::getDefault();
    
            foreach ($this->behaviors as $behavior => $active)
            {
                if ($active && $di->has($behavior))
                {
                    $di->get($behavior)->beforeSave($this);
                }
            }
        }
    
        /**
         * @param array $parameters
         *
         * @static
         * @return Phalcon_Model_Resultset Model[]
         */
        static public function find($parameters = array())
        {
            return parent::find($parameters);
        }
    
        /**
         * @param array $parameters
         *
         * @static
         * @return  Phalcon_Model_Base   Models
         */
        static public function findFirst($parameters = array())
        {
            return parent::findFirst($parameters);
        }
    }
    

    The class itself is pretty simple, offering find and findFirst to the class that extends this. The interesting thing is that it also registers behaviors and calls the relevant validator function. So for instance the beforeSave validator checks the registered behaviors ($behaviors array), checks if they are active, checks if they exist in the DI container and gets them from there and then calls the beforeSave in the behavior class.

    The behavior class is equally simple:

    class Timestamp
    {
        protected $session;
    
        public function __construct($session)
        {
            $this->session= $session;
        }
    
        /**
         * beforeSave hook - called prior to any Save (insert/update)
         */
        public function beforeSave($record)
        {
            $auth     = $this->session->get('auth');
            $userId   = (isset($auth['id'])) ? (int) $auth['id'] : 0;
            $datetime = date('Y-m-d H:i:s');
            if (empty($record->created_at_user_id)) {
                $record->created_at         = $datetime;
                $record->created_at_user_id = $userId;
            }
            $record->last_update         = $datetime;
            $record->last_update_user_id = $userId;
        }
    }
    

    So effectively every time I call the save() function on a model, this piece of code will be executed, populating my fields with the date time and the user that created the record and/or updated it.

    In order to get this functionality to work, all I have to do in my model is to register the behavior like so:

    class Episodes extends \NDN\Model
    {
        /**
         * Initializes the class and sets any relationships with other models
         */
        public function initialize()
        {
            $this->addBehavior('Timestamp');
            $this->hasMany('id', 'Awards', 'episode_id');
        }
    }
    

    Controllers

    Very little has changed in the controller logic, so that was the easiest part of the upgrade. Of course I tweaked a few things but the code works as is. I still extended my custom NDN\Controller class which takes care of my breadcrumbs (NDN\Breadcrumbs) as well as the construction of the top menu. The biggest difference with the previous version is that I stopped using AngularJS to populate the menu (so I am no longer sending a JSON array in the view) and used Volt instead. It was a matter of preference and nothing more.

    Views

    Quite a bit of work had to be done in the views to switch everything to use Volt. Of course every view extension had to be changed to .volt but that was not the only change. I split the layout to use partials so that the header, navigation and footer are different sections (organizing things a bit better) and kept the master layout index.volt.

    I started using the built in Volt functions to generate content as well as tags and it was a nice surprise to see that everything was easy to use and it worked!

    <!DOCTYPE html>
    <html ng-app='HHF'>
        {{ partial('partials/header') }} 
        <body>
            <div id="spinner" style="display: none;">
                {{ image('img/ajax-loader.gif') }} Loading ...
            </div>
    
            {{ partial('partials/navbar') }}
    
            <div class='container-fluid'>
                <div class='row-fluid'>
                    <ul class='breadcrumb'>
                        <li>
                            {% for bc in breadcrumbs %}
                            {% if (bc['active']) %}
                            {{ bc['text'] }}
                            {% else %}
                            <a href='{{ bc['link'] }}'>{{ bc['text'] }}</a> 
                            <span class='divider'>/</span>
                            {% endif %}
                            {% endfor %}
                        </li>
                    </ul>
                </div>
    
                <?php echo $this->flash->output() ?>
    
                <div class="row-fluid">
                    <?php echo $this->getContent() ?>
                </div> <!-- row -->
    
                {{ partial('partials/footer') }}
            </div>
    
            {{ javascript_include(config.app.js.jquery, config.app.js.local) }}
            {{ javascript_include(config.app.js.jquery_ui, config.app.js.local) }}
            {{ javascript_include(config.app.js.bootstrap, config.app.js.local) }}
            {{ javascript_include(config.app.js.angular, config.app.js.local) }}
            {{ javascript_include(config.app.js.angular_resource, config.app.js.local) }}
            {{ javascript_include(config.app.js.angular_ui, config.app.js.local) }}
            {{ javascript_include('js/utils.js') }}
    
        </body>
    </html>
    

    The above is the index.volt. As you can see I call on the partials/header.volt, then the partials/navbar.volt (where the menu is generated) and then I construct the breadcrumbs (note the {% for bc in breadcrumbs %} block). After that the flash messenger comes into play, the main content displayed, the footer and finally the javascript includes that I need.

    I am still using AngularJS to make the necessary AJAX calls so that the relevant controller to retrieve the data but also to display this data on screen (which is cached to avoid unnecessary database hits).

    The Episodes view became

    {{ content() }}
    
    <div>
        <ul class='nav nav-tabs'>
            <li class='pull-right'>
                {{ addButton }}
            </li>
        </ul>
    </div>
    
    <div ng-controller='MainCtrl'>
        <table class='table table-bordered table-striped ng-cloak' ng-cloak>
            <thead>
            <tr>
                <th><a href='' ng-click="predicate='number'; reverse=!reverse">#</a></th>
                <th><a href='' ng-click="predicate='air_date'; reverse=!reverse">Date</a></th>
                <th><a href='' ng-click="predicate='outcome'; reverse=!reverse">W/L</a></th>
                <th><a href='' ng-click="predicate='summary'; reverse=!reverse">Summary</a></th>
            </tr>
            </thead>
            <tbody>
                <tr ng-repeat="episode in data.results | orderBy:predicate:reverse">
                    <td>[[episode.number]]</td>
                    <td width='7%'>[[episode.air_date]]</td>
                    <td>[[episode.outcome]]</td>
                    <td>[[episode.summary]]</td>
                    {% if (addButton) %}
                    <td width='1%'><a href='/episodes/edit/[[episode.id]]'><i class='icon-pencil'></i></a></td>
                    <td width='1%'><a href='/episodes/delete/[[episode.id]]'><i class='icon-remove'></i></a></td>
                    {% endif %}
                </tr>
            </tbody>
        </table>
    </div>
    

    The beauty of AngularJS! I only have to pass a JSON array with my results. ng-repeat with the orderBy filter allows me to present the data to the user and offer sorting capabilities per column. This is all done at the browser level without any database hits! Pretty awesome feature!

    For those that have used AngularJS in the past, you will note that I had to change the interpolate provider (i.e. the characters that wrap a string or a piece of code that AngularJS understands). Usually these characters are the curly brackets {{ }} but I changed them to [[ ]] to avoid collisions with Volt.

    This was done with a couple of lines of code in my definition of my AngularJS model:

    var ngModule = angular.module(
            'HHF', 
            ['ngResource', 'ui']
        )
        .config(
            function ($interpolateProvider) {
                $interpolateProvider.startSymbol('[[');
                $interpolateProvider.endSymbol(']]');
            }
        )
    

    Conclusion

    I spent at most a day working on this mostly because I wanted to try various things and see how it works. The actual time to convert the application (because let's face it, it is a small application) was a couple of hours inclusive of the time it took me to rename certain fields, restructure the folder structure, compile the new extension on my server and upload the data upstream.

    I am very satisfied with both AngularJS, which helps tremendously in my presentation layer, as well as with Phalcon. Phalcon's new design makes implementation a breeze, while AngularJS offers a lot of flexibility on the view layer.

    As written before, you are more than welcome to download the source code of this application here and use it for your own needs. Some resources are:

    References

  • 2012-07-12 12:00:00

    Building a web app with PhalconPHP and AngularJS Part II

    This is Part II of a series of posts on building an application using Phalcon and AngularJS. Part I is located here.

    Preface

    I have recently discovered Phalcon and I was impressed with its speed and ease of use. At the time of this writing, PhalconPHP is at version 0.4.2, with some serious redesign coming down the line on 0.5.x.

    Phalcon takes a different approach than any other PHP framework (see Zend, Symfony, CakePHP etc.). It is written in C and compiled as a module which is then loaded on your web server. Effectively the whole framework is in memory for you to use, without needing to access the file system so that you can include a file here or a file there.

    Advantages

    The core advantage of this approach is speed. The framework is in memory, ready to deliver its functionality, so your application is now only concerned about its files and not the framework itself. Once a framework is mature enough for usage, its files don't change that much. Yet for any of the traditional frameworks, PHP needs to scan the files, load them and the interpret them. This in effect has a serious impact on performance, especially for large projects.

    Another advantage is that since the framework is a module on your web server, you don't need to upload library files to each and every application you install on your host.

    Finally, you can mix and match whatever you need, using any of the components as 'glue' components rather than the whole framework. Most of the major frameworks also use this methodology for most of their components, however performance still is an issue. Additionally, in the case of any other framework, one might need to upload a very complicated and deep file structure on their web server so as to take advantage of one component to be used in an application.

    Disadvantages

    Support and bug tracing are the two weaknesses of Phalcon. By support I do not mean support from the developers. On the contrary, the developers are doing a great job listening to the relatively young community, and issuing fixes. However, as with any framework, if you find a bug, you will try to trace the code back to each component in an effort to find a solution to your problem. When developing an application and have access to the source files (the library PHP files like Zend Framework has), not only you can learn from those implementations, but you can quickly fix something that might be broken and continue working. With Phalcon you will need to wait until the next version is released, unless you are fluent in C and play around with the source code. For most PHP programmers (like myself), the process will be report the bug and wait for the fix.

    Since the framework is a module on your web server, you will need to be careful on upgrades. If your applications do not take advantage of the latest functionality the framework offers, you might fix something in one application, while breaking something in another. You cannot mix and match versions of Phalcon per application.

    Consideration

    Phalcon is very young as a framework. It does have a lot of power, but there are a lot of things still missing (for instance relationships between models and a query builder). In time these pieces will be implemented and the framework will grow stronger :)

    Implementation

    I downloaded the INVO sample application and set it up on my web browser. Using that as a starting point, I started modifying it to fit my needs. I also set up the PhalconPHP developer tools and PHPStorm support.

    For this application, I needed a table to store information about every podcast episode, a table to store all players and a table to store the users (namely Aaron, Josh and John). The Awards table would be the one that would store all the information regarding the game balls and kick in the balls awards.

    Models

    Once those were in place I started building my models and relevant controllers/views. Setting a model up was really easy. I would create the table in my database and then run

    phalcon create-model --table-name episodes
    

    and my model would be ready for me to use (example below for Episodes).

    class Episodes extends Phalcon_Model_Base 
    {
        public $id;
        public $number;
        public $summary;
        public $airDate;
        public $outcome;
        public $createdAt;
        public $createdAtUserId;
        public $lastUpdate;
        public $lastUpdateUserId;
    }
    

    After a while I decided I wanted to keep a track on who created a record and when, and who last updated a record and when for certain tables. After some refactoring I created my own model class that would give me the functionality I needed, and extended that class in relevant models.

    My custom class (that would take care of the createdAt, createdAtUserId, lastUpdated, lastUpdatedUserId fields) also took advantage of the beforeSave hook to ensure that these fields were transparently updated. The find and findFirst static functions are used throughout the models and there is no reason to repeat them in each model, so they end up in this custom class. (Comments removed to preserve space)

    use NDN_Session as Session;
    
    class NDN_Model extends Phalcon_Model_Base
    {
        public $createdAt;
        public $createdAtUserId;
        public $lastUpdate;
        public $lastUpdateUserId;
    
        public function beforeSave()
        {
            if (empty($this->createdAtUserId)) {
                $auth     = Session::get('auth');
                $datetime = date('Y-m-d H:i:s');
    
                $this->createdAt        = $datetime;
                $this->createdAtUserId  = (int) $auth['id'];
            }
        }
    
        static public function find($parameters = array())
        {
            return parent::find($parameters);
        }
    
        static public function findFirst($parameters = array())
        {
            return parent::findFirst($parameters);
        }
    }
    
    Session

    Although Phalcon provides a flash messenger utility, I had an issue with using the _forward function on a controller, after an action (say Add or Edit) was completed. Effectively the data would not refresh on screen. To combat that I used _redirect. However, all the messages that I had in the flash messenger (Phalcon_Flash) would disappear. An easy solution was to extend the Phalcon_Session and create two new functions setFlash and getFlash. The setFlash is called whenever I want to set a message for the user to see. The function stores the message in a session variable. Before the controller is dispatched, the getFlash is called to return any messages waiting to be displayed, and after that the messages are cleared from the session and displayed on screen.

    class NDN_Session extends Phalcon_Session
    {
        public static function setFlash($class, $message, $css)
        {
            $data = array(
                'class'   => $class,
                'message' => $message,
                'css'     => $css,
            );
            self::set('flash', $data);
        }
    
        public static function getFlash()
        {
            $data = self::get('flash');
            if (is_array($data)) {
                self::remove('flash');
                return $data;
            } else {
                return null;
            }
        }
    }
    
    Breadcrumbs

    I wanted to show breadcrumbs to the user, as a way to easily navigate throughout the application. To do so, I created my own Breadcrumbs class which holds an array of areas that the user is in. The class has a generate function, which returns back a JSON string. This is to be parsed by AngularJS so as to display the breadcrumbs.

    Controllers

    I created my controllers using the Phalcon Developer Tools. Whether you use the webtools or the command line makes no difference. The skeleton of the controller is generated for you to use.

    Based on the flash messenger and _redirect that I mentioned in the previous section, I had to extend the base controller, so as to add functionality that would allow me to show messages on screen after a redirect. Other reasons for this new class were to allow for a prefix on each page title, generate breadcrumbs and menus.

    use Phalcon_Tag as Tag;
    
    use Phalcon_Flash as Flash;
    use NDN_Session as Session;
    
    class NDN_Controller extends Phalcon_Controller 
    {
        protected $_bc = null;
        public function initialize()
        {
            Tag::prependTitle('HHF G&KB Awards | ');
            $this->_bc = new NDN_Breadcrumbs();
        }
    
        public function beforeDispatch()
        {
            $message = Session::getFlash();
            if (is_array($message)) {
                Flash::$message['class'](
                    $message['message'], $message['css']
                );
            }
            $this->view->setVar('breadcrumbs', $this->_bc->generate());
        }
    
        protected function _constructMenu($controller)
        {
            $commonMenu = array(
                'index'      => 'Home', 
                'awards'     => 'Awards', 
                'players'    => 'Players', 
                'episodes'   => 'Episodes', 
                'about'      => 'About', 
                'contact'    => 'Contact Us', 
            ); 
            $auth = Session::get('auth'); 
    
            $class  = get_class($controller); 
            $class  = str_replace('Controller', '', $class); 
            $active         = strtolower($class); 
            $sessionCaption = ($auth) ? 'Log Out'         : 'Log In'; 
            $sessionAction  = ($auth) ? '/session/logout' : '/session/index'; 
    
            $leftMenu = array(); 
            foreach ($commonMenu as $link => $text) { 
                $isActive   = (bool) ($active == $link); 
                $newLink  = ('index' == $link) ? '/' : '/' . $link; 
                $leftMenu[] = array( 
                    'active' => $isActive, 
                    'link'   => $newLink, 
                    'text'   => $text, 
                ); 
            } 
    
            $menu = new StdClass(); 
            $menu->current = $active; 
            $menu->left    = $leftMenu; 
    
            if ($auth != false) { 
                $sessionCaption .= ' ' . $auth['name']; 
            } 
    
            $menu->rightLink = $sessionAction; 
            $menu->rightText = $sessionCaption; 
    
            return json_encode($menu); 
        } 
    }
    

    Each controller would extend my base controller. In the initialize function:

    • the page title is set,
    • the breadcrumbs are added (and generated later on in the beforeDispatch of the base controller),
    • the menu is generated and passed to the view for AngularJS to process,
    • additional variables would be generated for displaying elements based on whether a user is logged in or not.
    Views

    Creating the views was really easy. I already had the structure ready from the sample application (INVO) and with the help of Bootstrap CSS, I was done in no time. The views inherit from a base view (index.phtml) located at the root of the views folder. That view holds the skeleton of the web page and content is injected accordingly based on each controller (and its view).

    In that file I added the relevant variables that will be used by AngularJS as well as variables that hold conditional elements (i.e. elements that appear when a user is logged in).

    More on the views in the next installment of these series.

    Conclusion

    With all that the application was ready as far as the main structure was concerned. Tying everything with AngularJS was the next step, which will be covered in part III of this How-To.

    The whole application, from start to finish, took less than 4 hours to develop. This included breaks, reading the manual and making design decisions based on my ever changing requirements.

    References

  • 2012-07-10 12:00:00

    Building a web app with PhalconPHP and AngularJS Part I

    There are ample frameworks on the Internet, most free, that a programmer can use to build a web application. Two of these frameworks are PhalconPHP and AngularJS.

    I decided to use those two frameworks and build a simple application which will keep track of the Game Balls and Kick in the Balls awards of Harry Hog Football.

    Harry Hog Football is a podcast that has been going strong since 2005, created by Redskins fans for Redskins Fans. (for those that do not know, Washington Redskins is a team on the National Football League in the USA).

    Every week during the regular season, Aaron, Josh and John create a podcast, where they discuss the recent game, the injuries, the cuts, the new signings and they offer their Game Balls to the best players of the week as well as the Kick in the Balls awards for the ones that (according to the podcasters) 'suck'.

    I therefore created an application to record all those game balls and kick in the balls awards, so that we can all see, who is the most valuable player and who is the least valuable player for the Redskins throughout the years (the term valuable is used loosely here).

    As a starting point I used the INVO application that PhalconPHP showcases as an easy application to get you started. I modified it significantly to address my needs, refactoring classes as much as possible to get the least amount of code with maximum usability.

    After building the application, I listened to all the episodes I could find, and entered the game balls and kick in the balls in the database. The models use the PhalconModelBase class to handle data, while the rest of the application is handled by the Phalcon_Controller (and view of course).

    The data transfer between the application and the relevant sections is primarily handled by AngularJS, which is dominant in the view layer. AngularJS controllers handle menu creation, breadcrumbx as well as displaying results on screen.

    Twitter's Bootstrap CSS> is used to put the final touches for the application.

    In subsequent posts I will explain each layer in turn, starting with PhalconPHP and continuing with AngularJS.

    This of course is by no means the perfect implementation. It has been a fun project for me, working on it on my own free time. You are more than welcome to fork the project and make any modifications you need. For those that are interested in getting straight to the code, it is available on Github here.

    NOTE: The Github repository contains code that works with nginx. If you are having problems with Apache, check the public/index.php - there is a note there for nginx (probably will need to remove it)

    References