• 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