Blog and News

Funding Campaign Update

Good morning everyone!

We would like to update you on our funding campaign. As most know, we started a funding campaign a few months back, in an effort to raise capital which will help us offer a better, faster and more feature rich framework to our community.

The funds would be used to hire specialists in C, sponsor bug fixes and features, cover expenses for potential conferences etc.

In order to increase transparency and so that everyone knows how much money we have, who the backers are and where we spent it, it was suggested that we switch our funding vendor to Open Collective.

Our Patreon campain therefore has been suspended, and replaced with Open Collective. You can find our new funding page here:

https://opencollective.com/phalcon

With Open Collective, any expense has to be approved by the team and everyone can see who spent how much and on what.

If you have been supporting us through Patreon, we have already sent you an email asking you to cancel your patronage there and continue (if you wish) in Open Collective.

If you are considering in supporting us, feel free to visit our OpenCollective page and support us!

A big thank you to our supporters, backers as well as users!!

<3 Phalcon Team

01010000011010000110000101101100011000110110111101101110010100000100100001010000

Phalcon 3.1.1 released!

Hey everyone.

We are releasing a hotfix today 3.1.1 that addresses some urgent issues with the framework. We strongly recommend that you upgrade your Phalcon version to the latest release 3.1.1.

As with any software, we have the you broke it scenario here. Thanks to the quick reporting from the community, we managed to fix the issues that came up, and therefore issue the hotfix today.

The release tag can be found here: 3.1.1

Undefined indexes in models

After the upgrade to 3.1.0, all models were issuing warnings:

Undefined index: keepSnapshots in Users.php on line 61
Undefined index: keepSnapshots in Groups.php on line 57

The issue would be rectified after clearing the query cache but still not a perfect solution. We have fixed it with PR-12737.

doLowUpdate() - First argument is not array

After the upgrade we have experienced the following issue:

$robot = Robots::findFirst();

$robot2 = new Robot($robot->toArray(), $di, $modelsManager);
$robot2->setNewValueForField(100);

try {
    $robot2->setDirtyState($robot2::DIRTY_STATE_PERSISTENT); 
    $robot2->save();
} catch (\Exception $exception) {
   echo "ERROR: " . $exception->getMessage();
}
Results in:
ERROR: First argument is not an array

It was reported in #12742 and it was fixed with PR-12739.

Expanding on 3.1.0 Version highlights

  • Added Phalcon\Validation\Validator\Callback, Phalcon\Validation::getData [NEW]

    We added new validator Phalcon\Validation\Validator\Callback where you can execute any logic you want. It should return true, false or new Validator which should be used to validate your field.

<?php

use Phalcon\Validation;
use Phalcon\Validation\Validator\Callback;
use Phalcon\Validation\Validator\PresenceOf;

$validation = new Validation();
$validation->add(
  "amount",
  new Callback(
      [
          'callback' => function($data) {
              return $data['amount'] % 2 == 0;
          },
          'message'  => 'Only even number of products are accepted',
      ]
  )
);

$messages = $validation->validate(['amount' => 1]); // will return a message from first validator

For more information read our documentation

  • Added Phalcon\Mvc\Model\Binder, class used for binding models to parameters in dispatcher, micro

  • Added Phalcon\Dispatcher::getBoundModels and Phalcon\Mvc\Micro::getBoundModels to getting bound models

  • Added Phalcon\Mvc\Micro\Collection\LazyLoader::callMethod

    In Phalcon 3 we introduced binding model instances in controller actions. In Phalcon 3.1 we decided to move the code controlling this to a separated class, optimize it a bit and offer a way to use the same functionality in Phalcon\Mvc\Micro as well. Since it's using Reflection API we also added way to cache it. In addition to this Phalcon\Dispatcher::setModelBinding() is now deprecated and will be removed in Phalcon 4. From Phalcon 3.2 usage of this method will trigger E_DEPRECATED

<?php

use Phalcon\Di\FactoryDefault;
use Phalcon\Mvc\Dispatcher;
use Phalcon\Mvc\Model\Binder;

$di = new FactoryDefault();
$di->set(
    'dispatcher', 
    function() {
        $dispatcher = new Dispatcher();
        $dispatcher->setModelBinder(new Binder(), 'cache');

        return $dispatcher;
    }
);

And you can type-hint your action parameters with class names. For more information read docs: using in micro, using in dispatcher

  • Added afterBinding event to Phalcon\Dispatcher and Phalcon\Mvc\Micro, added Phalcon\Mvc\Micro::afterBinding [NEW]

    We added new event to the dispatcher and micro application. afterBinding event (or middleware in micro) will trigger after binding model instances done by the Phalcon\Mvc\Model\Binder component but before executing an action.

  • Added the ability to set custom Resultset class returned by find() #12166 [NEW]

    By using this feature you can have your own custom Resultset classes with your own logic.

<?php

use Phalcon\Mvc\Model;
use Phalcon\Mvc\Model\Resultset\Simple;

class AgeStats extends Model
{
  public function getSource()
  {
      return 'stats';
  }

  public function getResultsetClass()
  {
      return 'MyResultset';
  }
}

class MyResultset extends Simple 
{
 // implement your custom logic here
}

$stats = AgeStats::find(); // it will return MyResultset instance
  • Clear appended and prepended title elements (Phalcon\Tag::appendTitle, Phalcon\Tag::prependTitle) [NEW]

    You can now clear and add multiple elements to appendTitle and prependTitle on the Phalcon\Tag component.

<?php

\Phalcon\Tag::setTitleSeparator(' - ');
\Phalcon\Tag::setTitle('Title');

// Somewhere in controller
\Phalcon\Tag::prependTitle('Category');
\Phalcon\Tag::prependTitle('Article');

// Same situation - clear and put just one prepend element
// (will be faster than clear all values)
\Phalcon\Tag::prependTitle(['Just article']);

// Or other - clear and put a few elements
\Phalcon\Tag::prependTitle(['Other category', 'Other article']);
  • Added the ability to specify what empty means in the 'allowEmpty' option of the validators. It now accepts also an array specifying what is considered empty, i.e. ['', false] [NEW]

    Previously allowEmpty option in validators would accept only true value, meaning it allows empty values. Right now you can provide an array with the values that are considered as empty for your validator.

<?php

use Phalcon\Validation;
use Phalcon\Validation\Validator\PresenceOf;

$validation = new Validation();
$validation->add(
    'description', 
    new PresenceOf(
        [
            'message'    => 'Description is required',
            'allowEmpty' => [null],
        ]
    )
);

$messages = $validation->validate(['description' => null]); // empty messages
$messages = $validation->validate(['description' => '']);   // will return message from validator
  • Added the ability to use Phalcon\Validation with Phalcon\Mvc\Collection, deprecated Phalcon\Mvc\Model\Validator classes [NEW]

    In Phalcon 3 we made changes to model validators so as to use the same classes as form validators. The same functionality was missing from the Phalcon\Mvc\Collection. We have addressed that issue and you can now use the Phalcon\Validation component for Mongo Collections. The required changes were also made in phlacon/incubator and PHP7 related classes. We encourage you to switch to the new validation as soon as you can, since in Phalcon 4 we will remove old Phalcon\Mvc\Model\Validator namespace. From Phalcon 3.2, usage of old classes will trigger E_DEPRECATED

<?php

use Phalcon\Mvc\Collection;
use Phalcon\Validation;
use Phalcon\Validation\Validator\PresenceOf;

class Robots extends Collection
{
  public function validation()
  {
      $validation = new Validation();
      $validation->add('name', new PresenceOf());

      return $this->validate($validation);
  }
}

$robots = new Robots();
$robots->create(); // returns false
$robots->name = 'Some Robot';
$robots->create(); // returns true

More information can be found in our documentation: validating collections, validation

Please note that Phalcon 3.1 is not compatible with PHP 7.1. If you want to use PHP 7, you will need to compile it with PHP 7.0. Full support for PHP 7.1+ will be introduced in our next version

Community

Again big kudos to our community for finding the bugs addressed in this hotfix and @jurigag for the help with this blog post.

Update/Upgrade

Phalcon 3.1.1 can be installed from the master branch, if you don't have Zephir installed follow these instructions:

git clone http://github.com/phalcon/cphalcon
cd cphalcon/build
sudo ./install

Note that running the installation script will replace any version of Phalcon installed before.

PackageCloud.io has been updated to allow your package manager (for Linux machines) to upgrade to the new version seamlessly.

Windows DLLs are available in the download page.

We encourage existing Phalcon 3 users to update to this version.

<3 Phalcon Team

01010000011010000110000101101100011000110110111101101110010100000100100001010000

Phalcon 3.1.0 released!

We are really excited to announce Phalcon's latest release: 3.1.0!

This release addresses several bug fixes and also introduces additional functionality to the framework

The release tag can be found here: 3.1.0

Highlights

  • Added Phalcon\Validation\Validator\Callback, Phalcon\Validation::getData
  • Added the ability to truncate database tables
  • Added Phalcon\Mvc\Model\Binder, class used for binding models to parameters in dispatcher, micro, added Phalcon\Dispatcher::getBoundModels and Phalcon\Mvc\Micro::getBoundModels to getting bound models, added Phalcon\Mvc\Micro\Collection\LazyLoader::callMethod
  • Added afterBinding event to Phalcon\Dispatcher and Phalcon\Mvc\Micro, added Phalcon\Mvc\Micro::afterBinding
  • Added the ability to set custom Resultset class returned by find() #12166
  • Added the ability to clear appended and prepended title elements (Phalcon\Tag::appendTitle, Phalcon\Tag::prependTitle). Now you can use array to add multiple titles. For more details check #12238.
  • Added the ability to specify what empty means in the 'allowEmpty' option of the validators. Now it accepts as well an array specifying what's empty, for example ['', false]
  • Added the ability to use Phalcon\Validation with Phalcon\Mvc\Collection, deprecated Phalcon\Mvc\Model\Validator classes
  • Added the value of the object intanceof Interface to Phalcon\Acl\Adapter\Memory
  • Added the ability to get original values from Phalcon\Mvc\Model\Binder, added Phalcon\Mvc\Micro::getModelBinder, Phalcon\Dispatcher::getModelBinder
  • Added prepend parameter to Phalcon\Loader::register to specify autoloader's loading order to top most
  • Fixes internal cache saving in Phalcon\Mvc\Model\Binder when no cache backend is used
  • Fixed Phalcon\Session\Bag::remove to initialize the bag before removing a value #12647
  • Fixed Phalcon\Mvc\Model::getChangedFields to correct detect changes from NULL to Zero #12628
  • Fixed Phalcon\Mvc\Model to create/refresh snapshot after create/update/refresh operation #11007, #11818, #11424
  • Fixed Phalcon\Mvc\Model::validate to correctly set code message #12645
  • Fixed Phalcon\Mvc\Model to correctly add error when try to save empty string value to not null and not default column #12688
  • Fixed Phalcon\Validation\Validator\Uniqueness collection persistent condition
  • Fixed Phalcon\Loader::autoLoad to prevent PHP warning #12684
  • Fixed Phalcon\Mvc\Model\Query::_executeSelect to correctly get the column map #12715
  • Fixed params view scope for PHP 5 #12648
Please note that Phalcon 3.1 is not compatible with PHP 7.1. If you want to use PHP 7, you will need to compile it with PHP 7.0. Full support for PHP 7.1+ will be introduced in our next version

Community

Big kudos to our community as always for reporting, suggesting and applying fixes and making our framework better with every release! A big thank you to all our backers and supporters that help us by joining our funding campaign. https://phalcon.link/fund

Team

We are making some changes to our team, bringing more people in to help with the organization, management as well as structure of the project. Our end goals are to produce timely releases with zero or minimal bugs, and to implement new features regularly. This is still work in progress, so once we have everything settled, we will explain everything with a relevant blog post.

Update/Upgrade

Phalcon 3.1.0 can be installed from the master branch, if you don't have Zephir installed follow these instructions:

git clone http://github.com/phalcon/cphalcon
cd cphalcon/build
sudo ./install

Note that running the installation script will replace any version of Phalcon installed before.

PackageCloud.io has been updated to allow your package manager (for Linux machines) to upgrade to the new version seamlessly.

Windows DLLs are available in the download page.

Linux packages will be available in a couple of hours after the posting of this blog post

We encourage existing Phalcon 3 users to update to this version.

<3 Phalcon Team

01010000011010000110000101101100011000110110111101101110010100000100100001010000

Q1 Goals Update

Hello everyone!

We would like to give you a quick update on the work done so far, trying to meet our Q1 goals.

Github

We have converted the Phalcon Github account to an organization. This move offers much better flexibility and organization for the project. Several contributors/members of the core team have been added to it, and more will follow soon.

We also started creating different groups in the organization, that will allow for a much better and streamlined process of assigning bugs and issues, merging pull requests, maintaining documentation etc. We are also investigating viable options for NFRs, where we can get a feel of which NFRs are mostly requested so that we can address them first.

We believe that this move will allow us to maintain a proper workflow, something that Phalcon and our community deserve.

We have also started moving some of the satellite repositories into the organization (such as link, phalcon-compose etc.) to help with resources and the organization of the project.

PHP 7.1

Work is continuing on fixing some PHP 7.1 blocking bugs with Phalcon and Zephir. We have managed so far to compile Phalcon with PHP 7.1 but it is not ready yet for release.

Additionally we reached out to some very talented members of the community with advanced C knowledge to help with this process. As things stand, we are still on track to have PHP 7.1 support by the end of Q1, most likely earlier.

Documentation

Admittedly our documentation needs a big revamp, adding more examples, tutorials, proper explanations for each component and keeping consistency throughout.

We have already started the effort with rewriting some of the documentation, which will be in the form of .md files using Markdown for the formatting. Everyone is welcome to join our effort in our Slack channel, where we discuss and collaborate on the content.

Sites

We started refactoring all the sites with different implementations. In recent blog posts, we explained how we refactored our website using a Micro application and middleware. The blog posts (links below) will become part of our documentation in an effort to bring real life examples to the community.

The next site to be revamped will be the builtwith.

Survey

We recently asked the community to take a short survey, so that we can assess why developers use Phalcon, if it is used in production and also asked for any additional input they want to give us.

If you have not taken the survey yet, feel free to visit this form

We will post the results of the survey in a week or so, when we get more replies. So far we have over 100 replies. Thank you all for your valuable input!!!

Thank you

Once more a big thank you to our amazing community! You guys rock!

<3 Phalcon Team

References

01010000011010000110000101101100011000110110111101101110010100000100100001010000

Building the new Phalcon Website - Middleware - Part 3

This post is part of a series. Part 1 - Part 2 - Part 3

In the final part of our series, we are going to investigate Middleware and how it helps our application.

Middleware

The core of the application is its Middleware. We discussed how the middleware is set up in Part 2 in the initRoutes() method of our AbstractBootstrap class. Note that this only applies to our main application and not the CLI.

Setup

Middleware needs to be attached to specific events in our events manager. These events are:

  • before: This attaches the middleware to the event that fires before the handler has been executed.
  • after: This attaches the middleware to the event that fires after the handler has been executed.
  • finish: This attaches the middleware to the event that fires after the response has been sent to the caller.

You can attach as many middleware classes in each of these events. They will be processed in a sequential manner, i.e. the first one registered gets processed first, then the second one etc.

Execution

Each middleware class has specific events in it that get executed (if present). These are methods/events available in every middleware class. The main method that gets executed is call

public function call(Micro $application)
{
    return true;
}

The events present in each middleware class are:

  • beforeHandleRoute - Called before any routes are matched
  • beforeExecuteRoute - Called when a route is matched and a valid handler exists but has not been executed
  • afterExecuteRoute - Called after executing a handler
  • beforeNotFound - Called when a route has not been matched
  • afterHandleRoute - Called after the handler has been executed successfully

For instance for the NotFoundMiddleware we have:

public function beforeNotFound()
{
    $language = $this->registry->language;
    $redirect= sprintf('/%s/404', $language);

    $this->response->redirect($redirect);
    $this->response->send();

    return false;
}

Returning false stops the execution of the application. As we can see above, since the beforeNotFound event has been triggerred, we need to first pick up the $language from the registry, set up the 404 route and then redirect the caller to the relevant handler/action.

EnvironmentMiddleware

This middleware has been attached to the before event, so it will be executed before a handler starts processing

public function call(Micro $application)
{
    /**
     * This is where we calculate what language we need to work with
     * and what slug has been requested
     */
    $params   = $application->router->getParams();
    $language = $this->getLang($application, 'en');
    $slug     = $application->utils->fetch($params, 'slug', 'index');
    $image    = $application->utils->fetch(
        $this->getImageMap($application),
        $language,
        'en'
    );

    /**
     * These are needed for all pages
     */
    $application->registry->language      = $language;
    $application->registry->slug          = $slug;
    $application->registry->imageLanguage = $image;
    $application->registry->menuLanguages = $this->getMenuLanguages($application, $language);
    $application->registry->version       = $application->config->get('app')->get('version');

    switch ($slug) {
        /**
         * Contributors are needed only in the front page or 'team'
         */
        case 'team':
        case 'index':
        case '':
            $application->registry->contributors = $this->getContributors();
            break;
        /**
         * Releases are needed in 'windows'
         */
        case 'windows':
            $application->registry->releases = $this->getReleases();
            break;
    }

    return true;
}

First we get a few parameters that have been passed to our application. One of those is the language. We use the getLang() method to check if the passed language exists. If not, we try to detect the browser language and if that fails it defaults to English. The getLang() method is located in the LanguageTrait (app/library/Traits).

Then we set some more variables in the registry and as the last step, we set the contributors and releases only in the specific pages that require them. This way we remove unnecessary processing and transfer of data from the application to the view.

NotFoundMiddleware

We discussed briefly the NotFoundMiddleware above. The implementation uses the events within the middleware, in particular the beforeNotFound one.

public function beforeNotFound()
{
    $language = $this->registry->language;
    $redirect= sprintf('/%s/404', $language);

    $this->response->redirect($redirect);
    $this->response->send();

    return false;
}

This method is called within our middleware class, before the call() method is called. As a result, if we are here, that means that we have a 404 and therefore need to route the user to the relevant handler/view.

RedirectMiddleware

This middleware is responsible for redirections. We only have one actual redirection from the /roadmap url/stub to our Github page, which can be easily achieved with a directive in our .htaccess file.

Since this application serves as a showcase or tutorial, we opted to create a middleware class that will handle this.

public function call(Micro $application)
{
    $slug     = $application->registry->slug;
    $uri      = $application->request->getURI();
    $redirect = '';

    if ('roadmap' === $slug) {
        $redirect= 'https://github.com/phalcon/cphalcon/wiki/Roadmap';
    } elseif ('download' === substr($uri, 4) || 'download/' === substr($uri, 4)) {
        $redirect = $uri
                  . ('/' === substr($uri, -1) ? '' : '/')
                  . 'linux';
    }

    if (true !== empty($redirect)) {
        $application->response->redirect($redirect);
        $application->response->send();

        return false;
    }

    return true;
}

If the request is for the roadmap page, then our $slug is indeed roadmap and therefore the user will be redirected to the Github page.

Also if the user just requested the /download page, they will be redirected automatically to the /download/linux page.

There are many implementations that a developer can employ to achieve the above task. This is just one of them using Middleware.

AssetsMiddleware

The assets middleware is invoked to inject specific asset files to specific pages. The front page requires a few more CSS files than the other pages, so this middleware checks where we are and adds the relevant CSS pages in the header_css asset collection if necessary.

public function call(Micro $application)
{
    /**
     * Adds relevant assets to the assets manager
     */
    $slug       = $application->registry->slug;
    $cdnUrl     = $application->utils->getCdnUrl();
    $isCdnLocal = $application->utils->isCdnLocal();

    if (true !== empty($slug) && 'index' !== $slug) {
        $application
            ->assets
            ->collection('header_css')
            ->addCss($cdnUrl . 'css/style.css', $isCdnLocal)
            ->addCss($cdnUrl . 'css/phalconPage.css', $isCdnLocal);
    } else {
        $application
            ->assets
            ->collection('header_css')
            ->addCss($cdnUrl . 'css/flags.css', $isCdnLocal)
            ->addCss($cdnUrl . 'css/highlight.js.css', $isCdnLocal)
            ->addCss($cdnUrl . 'css/phalcon.min.css', $isCdnLocal)
            ->addCss($cdnUrl . 'css/style.css', $isCdnLocal);
    }

    return true;
}

ViewMiddleware

This middleware is responsible for our final response to the client. It utilizes variables set in the registry service passes them to the view service (Phalcon\Mvc\View\Simple) and then sends the response back.

public function call(Micro $application)
{
    $cacheKey = str_replace(
        '/',
        '_',
        $application->router->getRewriteUri()
    ) . '.cache';

    /** @var \Phalcon\Registry $registry */
    $registry     = $application->registry;
    $viewName     = $registry->view;

    if ('production' === $application->config->get('app')->get('env')) {
        $application->viewSimple->cache(['key' => $cacheKey]);
    }

    if (true === $application->viewCache->exists($cacheKey)) {
        $contents = $application->viewCache->get($cacheKey);
    } else {
        $application->viewSimple->setVars(
            [
                'page'          => $registry->slug,
                'language'      => $registry->language,
                'imageLanguage' => $registry->imageLanguage,
                'contributors'  => $registry->contributors,
                'languages'     => $registry->menuLanguages,
                'noindex'       => $registry->noindex,
                'releases'      => $registry->releases,
                'version'       => $registry->version,
            ]
        );

        $contents = $application->viewSimple->render($viewName);
    }
    $application->response->setContent($contents);
    $application->response->send();

    return true;
}

We first check where we are. Using a simple str_replace, we create a unique file name based on the route, so that we can have a cache file name (or key depending on your cache adapter).

We then check if we are in production mode (set in our .env file) and if so, we invoke the cache for the view. If the data has been cached we use that.

If we do not have a cache hit, several variables are being sent to the view, which have originally been set in our EnvironmentMiddleware or other areas of the site. We then render the view, set the response contents and send the response back.

CLI Application

Executable

In our root folder, we have created a file called phalcon which is executable under a Linux based environment. The file resembles the index.php (under public).

#!/usr/bin/env php
<?php

use Website\Bootstrap\Cli;

if (true !== defined('APP_PATH')) {
    define('APP_PATH', dirname(__FILE__));
}

try {
    require_once APP_PATH . '/app/library/Bootstrap/AbstractBootstrap.php';
    require_once APP_PATH . '/app/library/Bootstrap/Cli.php';

    /**
     * We don't want a global scope variable for this
     */
    (new Cli())->run();

} catch (\Exception $e) {
    fwrite(STDERR, PHP_EOL . $e->getMessage() . PHP_EOL);
    fwrite(STDERR, PHP_EOL . $e->getTraceAsString() . PHP_EOL . PHP_EOL);
    exit(1);
}

We once more set up the application path, include the needed files, and run our CLI bootstrap process.

Running this command without any parameters will show a menu of available parameters that our application accepts.

Bootstrap

We discussed the bootstrap process in our previous post. The CLI application uses the same class but removes services that are not needed such as the assets, the view etc. (since this is a CLI application), while it sets up some services differently. We encourage you to check the bootstrap application in the source code of our [github repository]()

Tasks

Our CLI application uses Task classes (Phalcon\CLI\Task) instead of handlers. This routing is set with our CLI dispatcher during the bootstrap process. For our application, we have three task classes which correspond to the commands that our CLI application can accept. The task classes can be found in app/tasks.

MainTask

This task class displays the available commands for our CLI application

ClearCache

This task class checks all the available cache files (in storage/cache/*/) and deletes them when invoked. It is used to clear the cache after each deployment.

FetchContributors

For quite some time now, we have introduced a big image map at the bottom of our site, to thank our contributors. This command is responsible for fetching the contributors from Github and constructing the final JSON file. To execute the HTTP request we are using the excellent Guzzle library.

We first assign some weights in different repositories. The more updates, the "heavier" the repository. We then interrogate the Github API for each of the repositories, and retrieve all the contributors.

Looping through the contributors we create a final array of contributors with their avatar, github profile, name and weight. We sort the results and then save those results in our contributors.json file located in storage/cache/data.

This task is run once a day using a CRON job.

Conclusion

We have looked at the middleware implementation, what is the purpose of each of those classes as well as our CLI implementation.

No implementation is perfect for every application. We have tried to keep this one simple so that the learning curve is much smoother, and also tried to show you different ways of doing things with Phalcon so that we can inspire you to write even better applications!

If you have any comments, suggestions, need to discuss something, please remember that you can find us in our slack channel or our forum.

We would welcome suggestions for this implementation using the above, or even pull requests for something we have missed or a new enhancement, in our github repository.

If Phalcon has helped you with your personal projects, consider supporting us in our Patreon page. Contributions do not necessarily need to be monetary. We always welcome pull requests for improvements to Phalcon or documentation, as well as success stories to be showcased in this blog.

Thank you all

<3 Phalcon Team

References

01010000011010000110000101101100011000110110111101101110010100000100100001010000

Building the new Phalcon Website - Bootstrap - Part 2

This post is part of a series. Part 1 - Part 2 - Part 3

Continuing with our series, we will now discuss the bootstrapping process in depth.

Bootstrap process

The call stack is as follows:

$this->initOptions();
$this->initDi();
$this->initLoader();
$this->initRegistry();
$this->initEnvironment();
$this->initApplication();
$this->initUtils();
$this->initConfig();
$this->initDispatcher();
$this->initCache();
$this->initLogger();
$this->initLocale();
$this->>initErrorHandler();
$this->initRoutes();
$this->initView();
$this->initAssets();

return $this->runApplication();

initOptions()

Our bootstrap process starts by calling the initOptions() method. For the main application, this method is empty. However the CLI application requires some options to be set because we need to process the parameters passed using the command line.

protected function initOptions()
{
    $arguments = [];
    if (true === isset($_SERVER['argv'])) {
        foreach ($_SERVER['argv'] as $index => $argument) {
            switch ($index) {
                case 1:
                    $arguments['task'] = $argument;
                    break;
                case 2:
                    $arguments['action'] = $argument;
                    break;
                case 3:
                    $arguments['params'] = $argument;
                    break;
            }
        }
    }

    $this->options = $arguments;
}

We are using the $_SERVER array to access the passed variables. We could have also used func_get_args() to achieve the same result. The reason we are not using the Phalcon\Http\Request object and the hasServer()/getServer() methods is because the DI container has not been initialized yet.

initDi()

We initialize the DI container and set it as a default. It is also stored in a private variable so that it can be passed in the application later on, but also to access relevant services if necessary.

initLoader()

Our application uses several packages such as Dotenv, CLI Progress Bar and Guzzle. To ensure that those packages are available in our application, we use the composer autoloader.

protected function initLoader()
{
    require_once APP_PATH . '/vendor/autoload.php';
}

In other implementations, we could initialize the Phalcon\Loader to load the files that our application uses. However for this implementation, we decided to use only one loader (the composer autoloader) for the whole application.

To achieve this, we changed the composer.json file so that the composer autoloader understands our namespaces.

"autoload": {
  "psr-4": {
    "Website\\": "app/library/",
    "Website\\Cli\\Tasks\\": "app/tasks/",
    "Website\\Controllers\\": "app/controllers/",
    "Website\\Middleware\\": "app/library/Middleware/"
  }
}

We also issued the following command when installing composer packages to ensure that we can get the most out of our autoloader.

composer install --optimize-autoloader

initRegistry()

We use the Phalcon\Registry as a storage of information that can be used throughout the request process. For instance we store the actual view file name that needs to be rendered. How we render views will be discussed later on when we will discuss Middleware.

protected function initRegistry()
{
    $registry = new PhRegistry();
    $registry->contributors  = [];             // The contributors array (main page/about)
    $registry->executionTime = 0;              // Execution time (profiling)
    $registry->language      = 'en';           // Current language requested
    $registry->imageLanguage = 'en';           // Image on the language selector (dropdown)
    $registry->memory        = 0;              // Memory usage (profiling)
    $registry->menuLanguages = [];             // The available languages menu (dropdown)
    $registry->noindex       = false;          // Whether this page is to be indexed or not
    $registry->slug          = '';             // The slug requested (url)
    $registry->releases      = [];             // The releases array (download windows)
    $registry->version       = '3.0.0';        // The current version
    $registry->view          = 'index/index';  // The view name to be rendered

    $this->diContainer->setShared('registry', $registry);
}

initEnvironment()

We set up some variables that can be used for profiling in the registry. Additionally we call the Dotenv()->load() function to read the .env file which is specific to our installation.

/**
 * Initializes the environment
 */
protected function initEnvironment()
{
    /** @var \Phalcon\Registry $registry */
    $registry                = $this->diContainer->getShared('registry');
    $registry->memory        = memory_get_usage();
    $registry->executionTime = microtime(true);

    (new Dotenv(APP_PATH))->load();
}

Again, similar to our index.php we do not use a variable to instantiate the Dotenv object.

initApplication()

For our main application, the $application variable is set to an object of Phalcon\Mvc\Micro.

protected function initApplication()
{
    $this->application = new PhMicro($this->diContainer);
}

For the CLI application we return a different application object: Phalcon\Cli\Console

protected function initApplication()
{
    $this->application = new PhCliConsole($this->diContainer);
    $this->diContainer->setShared('console', $this->application);
}

initUtils()

The Utils class contains helper methods for our application.

protected function initUtils()
{
    $this->diContainer->setShared('utils', new Utils());
}
  • fetch() Returns the element of an array or an object or the default value if not set
  • getDocsUrl($lang) Returns the docs language
  • getCdnUrl($resource = '') Returns the asset with/without the CDN URL
  • isCdnLocal() If this is a CDN resource or a local one
  • timeToHuman($microseconds, $precision = 3) Profiling - time to human readable time
  • bytesToHuman Profiling - bytes to human readable bytes

initConfig()

We now load the configuration file, which contains elements populated by getenv calls. Those have been set earlier using the Dotenv library.

protected function initConfig()
{
    $fileName = APP_PATH . '/app/config/config.php';
    if (true !== file_exists($fileName)) {
        throw new Exception('Configuration file not found');
    }

    $configArray = require_once($fileName);
    $config = new PhConfig($configArray);

    $this->diContainer->setShared('config', $config);
}

The configuration file contains also information about the routes of our application, middleware class stack, available languages, sitemap generation pages as well as logger, cache and other initialization variables.

It can be found in app/config/config.php and it looks like this:

    ...
    'app'           => [
        'version'         => '3.0.3',
        'timezone'        => getenv('APP_TIMEZONE'),
        'debug'           => getenv('APP_DEBUG'),
        'env'             => getenv('APP_ENV'),
        ...
    ],
    'cache'         => [
        'driver'          => getenv('CACHE_DRIVER'),
        'viewDriver'      => getenv('VIEW_CACHE_DRIVER'),
        'prefix'          => getenv('CACHE_PREFIX'),
        'lifetime'        => getenv('CACHE_LIFETIME'),
    ],
    ...

initDispatcher()

For our main application, the class is empty, since Phalcon\Mvc\Micro applications do not have a dispatcher.

The CLI application though requires a dispatcher, so we set one there:

protected function initDispatcher()
{
    $dispatcher = new PhCliDispatcher();
    $dispatcher->setDefaultNamespace('Website\Cli\Tasks');

    $this->diContainer->setShared('dispatcher', $dispatcher);
}

initCache()

We now initialize the cache for our main application. We initialize two caches. One for data and one for the view. The CLI application overrides this function and is empty, since the CLI application does not require a cache service.

More about the viewCache later on when we explore Middleware.

protected function initCache()
{
    /**
     * viewCache
     */
    /** @var \Phalcon\Config $config */
    $config   = $this->diContainer->getShared('config');
    $lifetime = $config->get('cache')->get('lifetime', 3600);
    $driver   = $config->get('cache')->get('viewDriver', 'file');
    $frontEnd = new PhCacheFrontOutput(['lifetime' => $lifetime]);
    $backEnd  = ['cacheDir' => APP_PATH . '/storage/cache/view/'];
    $class    = sprintf('\Phalcon\Cache\Backend\%s', ucfirst($driver));
    $cache    = new $class($frontEnd, $backEnd);

    $this->diContainer->set('viewCache', $cache);

    /**
     * cacheData
     */
    $driver   = $config->get('cache')->get('driver', 'file');
    $frontEnd = new PhCacheFrontData(['lifetime' => $lifetime]);
    $backEnd  = ['cacheDir' => APP_PATH . '/storage/cache/data/'];
    $class    = sprintf('\Phalcon\Cache\Backend\%s', ucfirst($driver));
    $cache    = new $class($frontEnd, $backEnd);

    $this->diContainer->setShared('cacheData', $cache);
}

initLogger()

We now initialize the logger based on values from the configuration file, which in turn is populated from the .env file.

protected function initLogger()
{
    /** @var \Phalcon\Config $config */
    $config   = $this->diContainer->getShared('config');
    $fileName = $config->get('logger')
                       ->get('defaultFilename', 'application');
    $format   = $config->get('logger')
                       ->get('format', '[%date%][%type%] %message%');

    $logFile   = sprintf(
        '%s/storage/logs/%s-%s.log',
        APP_PATH,
        date('Ymd'),
        $fileName
    );
    $formatter = new PhLoggerFormatter($format);
    $logger    = new PhFileLogger($logFile);
    $logger->setFormatter($formatter);

    $this->diContainer->setShared('logger', $logger);
}

initLocale()

We set the default timezone for the application and initialize the locale object.

The Locale object is responsible for converting the language strings located in the views to the relevant text. Initially it loads the en.json file which contains the strings and text available for the English version of the website. Based on the requested page/language, the new language file is also loaded and merged with the English one. This ensures that English text is always displayed if text is not translated in the requested language.

protected function initLocale()
{
    $config = $this->diContainer->getShared('config');

    date_default_timezone_set($config->get('app')->get('timezone', 'US/Eastern'));

    $this->diContainer->setShared('locale', new Locale());
}

All translations are handled by Transifex.

initErrorHandler()

We override the default PHP error handler with something we can control. Therefore we set our own error handler that logs all the errors using our logger service. Additionally, we set up our own register_shutdown_function so that we can enable the profiler. The profiler is very simple, it utilizes the registry service and calculates the execution time as well as the memory consumption per request. This can be invaluable in your application (only in development mode), allowing you to find areas where your application is not performing at maximum.

protected function initErrorHandler()
{
    $registry = $this->diContainer->getShared('registry');
    $logger   = $this->diContainer->getShared('logger');
    $utils    = $this->diContainer->getShared('utils');
    $mode     = getenv('APP_ENV');
    $mode     = (false !== $mode) ? $mode : 'development';

    ini_set('display_errors', boolval('development' === $mode));
    error_reporting(E_ALL);

    set_error_handler(
        function ($errorNumber, $errorString, $errorFile, $errorLine) use ($logger) {
            if (0 === $errorNumber & 0 === error_reporting()) {
                return;
            }

            $logger->error(
                sprintf(
                    "[%s] [%s] %s - %s",
                    $errorNumber,
                    $errorLine,
                    $errorString,
                    $errorFile
                )
            );
        }
    );

    set_exception_handler(
        function () use ($logger) {
            $logger->error(json_encode(debug_backtrace()));
        }
    );

    register_shutdown_function(
        function () use ($logger, $utils, $registry, $mode) {
            $memory    = memory_get_usage() - $registry->memory;
            $execution = microtime(true) - $registry->executionTime;

            if ('development' === $mode) {
                $logger->info(
                    sprintf(
                        'Shutdown completed [%s] - [%s]',
                        $utils->timeToHuman($execution),
                        $utils->bytesToHuman($memory)
                    )
                );
            }
        }
    );
}

initRoutes()

The routes configuration is located in our config.php file (located at app/config). This method sets up the routes (including 404) as well as the middleware stack.

Note that this method is overriden and replaced by an empty one for our CLI application, since CLI applications do not use routes.

The configuration array looks something like this:

[
    'class'   => Website\Controllers\DownloadController::class,
    'methods' => [
        'get' => [
            '/download'                                               => 'redirectAction',
            "/download/{slug:({$downloadSlugs})}"                     => 'redirectAction',
            '/{language:[a-z]{2}}/download'                           => 'pageAction',
            "/{language:[a-z]{2}}/download/{slug:({$downloadSlugs})}" => 'pageAction',
        ],
    ],
],
[
    'class'   => Website\Controllers\UtilsController::class,
    'methods' => [
        'get' => [
            '/sitemap'      => 'sitemapAction',
        ],
    ],
],

We are registering actual classes as handlers instead of anonymous functions for our Micro application. We use the ::class suffix to return the actual name of the class for our handler, which avoids typing errors and delays in finding them :)

The second element of the array contains sub arrays, whose keys are the names of the request methods that our Micro application needs. For our application we only use the get request method.

That sub array contains the actual route pattern as a key and the method in our class handler that will handle that request.

As you can see with the above example, we are matching a get request of /sitemap to the Website\Controllers\UtilsController::class, method sitemapAction.

The code that makes all this happen is in the initRoutes.

protected function initRoutes()
{
    /** @var PhConfig $config */
    $config     = $this->diContainer->getShared('config');
    $routes     = $config->get('routes')->toArray();
    $middleware = $config->get('middleware')->toArray();

    foreach ($routes as $route) {
        $collection = new PhMicroCollection();
        $collection->setHandler($route['class'], true);
        if (true !== empty($route['prefix'])) {
            $collection->setPrefix($route['prefix']);
        }

        foreach ($route['methods'] as $verb => $methods) {
            foreach ($methods as $endpoint => $action) {
                $collection->$verb($endpoint, $action);
            }
        }
        $this->application->mount($collection);
    }

    $eventsManager = $this->diContainer->getShared('eventsManager');

    foreach ($middleware as $element) {
        $class = $element['class'];
        $event = $element['event'];
        $eventsManager->attach('micro', new $class());
        $this->application->$event(new $class());
    }

    $this->application->setEventsManager($eventsManager);
}

IMPORTANT: One of the reasons for this implementation is lazy loading. Phalcon\Mvc\Micro allows you to lazy load handlers. This minimizes the resources needed for each request, since only the files needed are interpreted per request. In our implementation we kept the handlers very thin so that only a couple of methods are present per handler, so as to reduce even more the execution time.

Lazy loading is achieved by the setHandler()'s second parameter in a Micro collection.

$collection->setHandler($route['class'], true);

initView()

A micro application does not render views automatically, nor does it have a view object. For our views we use the Phalcon\Mvc\View\Simple component. Setting it up is very easy, it resembles any other view setup.

Note that this method is overriden and replaced by an empty one for our CLI application, since CLI applications do not use views.

protected function initView()
{
    /** @var \Phalcon\Config $config */
    $config = $this->diContainer->getShared('config');
    $mode   = $config->get('app')->get('env', 'development');

    $view  = new PhViewSimple();
    $view->setViewsDir(APP_PATH . '/app/views/');
    $view->registerEngines(
        [
            '.volt' => function ($view) use ($mode) {
                $volt  = new PhVolt($view, $this->diContainer);
                $volt->setOptions(
                    [
                        'compiledPath'      => APP_PATH . '/storage/cache/volt/',
                        'compiledSeparator' => '_',
                        'compiledExtension' => '.php',
                        'compileAlways'     => boolval('development' === $mode),
                        'stat'              => true,
                    ]
                );

                /**
                 * Register the PHP extension, to be able to use PHP
                 * functions in Volt
                 */

                $volt->getCompiler()->addExtension(new Php());

                return $volt;
            },
        ]
    );

    $this->diContainer->setShared('viewSimple', $view);
}

We are setting up Volt as our templating engine. We also ensure that all of our templates are recompiled all the time while in development mode. This is accomplished with the following option (in setOptions)

'compileAlways' => boolval('development' === $mode),

Additionally we are registering a Volt extension, which allows us to use any PHP function in our Volt templates.

$volt->getCompiler()->addExtension(new Php());

The code for the extension is located in library/View/Engine/Volt/Extensions/Php.php

initAssets()

Trying to keep ourselves DRY, we initialize our Asset service with the assets that are common to all pages of the application.

Again this method is overriden in our CLI application because CLI tasks do not need assets.

protected function initAssets()
{
    /** @var \Website\Utils $utils */
    $utils = $this->diContainer->getShared('utils');

    $assets = new Manager();

    /**
     * Collections
     */
    $assets->collection("header_js");
    $assets
        ->collection('header_css')
        ->addCss('//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css', false)
        ->addCss('//netdna.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css', false)
        ->addCss('//fonts.googleapis.com/css?family=Open+Sans:700,400', false);

    $assets
        ->collection('footer_js')
        ->addJs('//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js', false)
        ->addJs('//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js', false)
        ->addJs($utils->getCdnUrl() . 'js/plugins/jquery.lazyload.min.js', $utils->isCdnLocal())
        ->addJs($utils->getCdnUrl() . 'js/plugins/jquery.magnific-popup.min.js', $utils->isCdnLocal())
        ->addJs($utils->getCdnUrl() . 'js/plugins/highlight.pack.js', $utils->isCdnLocal())
        ->addJs($utils->getCdnUrl() . 'js/plugins/jquery.ajaxchimp.min.js', $utils->isCdnLocal())
        ->addJs($utils->getCdnUrl() . 'js/plugins/jquery.backstretch.min.js', $utils->isCdnLocal())
        ->addJs($utils->getCdnUrl() . 'js/custom.js');

    $this->diContainer->setShared('assets', $assets);
}

runApplication()

Finally we run our application. You will notice that for the main application we invoke the handle() method with no parameters

protected function runApplication()
{
    return $this->application->handle();
}

and as far as the CLI application is concerned, we invoke it with our options (set in the initOptions method).

protected function runApplication()
{
    return $this->application->handle($this->options);
}

Conclusion

We have looked at the boostrap of the application and each service setup for both the CLI and the main application. In the next part of these series we will discuss the middleware.

References

01010000011010000110000101101100011000110110111101101110010100000100100001010000

Building the new Phalcon Website - Implementation - Part 1

This post is part of a series. Part 1 - Part 2 - Part 3

Our website has undergone a number of iterations in its implementation.

When we released Phalcon 3.0, we also released a fresh look for our website. However, some files were left over from a previous implementation and new text was introduced in several pages. This made that particular text not translatable by Transifex, the excellent service we use to handle translations for our site.

Goals

One our Q1 goals for 2017 is to improve documentation and also offer better implementations of Phalcon in applications to our community. So we cleaned up our website application, and will use it as an implementation sample for our community to inspire them for their own projects :)

Standards

Building the website we used a particular style throughout. Specifically:

  • PSR-2 was used as the coding standard
  • Used for comparisons, yoda conditions were
  • Identical comparisons
  • Single quotes for all strings

Implementation

This implementation of our website showcases the Phalcon\Mvc\Micro application with Middleware. It was built for maximum performance.

We implemented two applications for the website. One to dispatch the site for web users to see and a CLI application that allows for certain tasks that need to be run from the console, such as fetching the contributors from Github or cleaning the cache folders.

Let's look at the implementation:

Skeleton

The skeleton of the application is very simple.

Folder Purpose
app The main application folder
app/config/ Contains the configuration file for our application
app/controllers/ The controllers (or handlers) of the application
app/library Necessary library classes such as the middleware, locale, utils, bootstrap etc.
app/tasks Contains the CLI application tasks
app/views The Volt view files that the application needs
public The `index.php`, images and assets (css, js files)
storage/cache/data Stores the contributors cache file and any additional cache files
storage/cache/view Stores the view cache data
storage/cache/volt Stores the volt compiled templates
storage/files Stores the `releases.json` file which contains metadata for download files
storage/languages Stores the languages files which are pulled from Transifex
storage/logs/ Stores the application logs

index.php

The index.php file is the entry point of our application. Apache has been configured with a virtual host to route all requests to that file as follows:

<VirtualHost *:443>
    ServerName              phalconphp.com
    ServerAlias             www.phalconphp.com
    DocumentRoot            /path/to/installation/public
    ...
    <Directory "/path/to/installation/public">
        Options FollowSymLinks MultiViews
        AllowOverride All
        RewriteEngine On
        Options +FollowSymlinks
        # Phalcon
        RewriteCond %{REQUEST_FILENAME} !-d
        RewriteCond %{REQUEST_FILENAME} !-f
        RewriteRule ^(.*)$ index.php?_url=/$1 [QSA,L]
    </Directory>
</VirtualHost>

The file itself is very small

use Website\Bootstrap\Main;

if (true !== defined('APP_PATH')) {
    define('APP_PATH', dirname(dirname(__FILE__)));
}

try {
    require_once APP_PATH . '/app/library/Bootstrap/AbstractBootstrap.php';
    require_once APP_PATH . '/app/library/Bootstrap/Main.php';

    /**
     * We don't want a global scope variable for this
     */
    (new Main())->run();

} catch (\Exception $e) {
    echo $e->getMessage() . PHP_EOL . $e->getTraceAsString();
}

First of all we set a constant APP_PATH which points to the actual path of our application. We are going to use this constant throughout the application to ensure that the files are included from their correct paths, logs are stored in their proper locations etc.

We then require two files, the AbstractBootstrap.php and Main.php. We do so because the autoloader has not been loaded yet. Main.php is the file that bootstraps our main application and that file extends AbstractBootstrap.php, so we have to require them both.

The next line is invoking the run() command which bootstraps the application, creating the necessary objects and handles each request.

Instead of using this syntax

$ourApp = new Main();
$ourApp->run();

we use this:

(new Main())->run();

The reason for this is because we do not want to have a variable available in the global scope. With the first implementation $ourApp is available in the global scope.

This particular syntax can be very useful in your application, especially if you want to ensure that a particular object cannot be used or overridden by accident.

Bootstrap

We opted using abstraction for our bootstrap process. The main file for bootstrapping is AbstractBootstrap.php located in app/library/Bootstrap and contains our abstract bootstrap class. The files Main.php and Cli.php located in the same folder extend the abstract class, to bootstrap the main application as well as the CLI one.

The run() method makes several calls to protected functions within AbstractBootstrap to initialize objects necessary for our application.

public function run()
{
    $this->initOptions();           // Initializes options - used in CLI
    $this->initDi();                // Initializes the DI container
    $this->initLoader();            // Initializes the autoloader
    $this->initRegistry();          // Initializes the registry
    $this->initEnvironment();       // Initializes the environment
    $this->initApplication();       // Initializes the application (micro/console)
    $this->initUtils();             // Initializes the utilities object
    $this->initConfig();            // Initializes the configuration
    $this->initDispatcher();        // Initializes the dispatcher (only cli)
    $this->initCache();             // Initializes the cache
    $this->initLogger();            // Initializes the logger
    $this->initLocale();            // Initializes the locale (translations)
    $this->initRoutes();            // Initializes the routes
    $this->initView();              // Initializes the view (only main)
    $this->initAssets();            // Initializes the assets (only main)

    return $this->runApplication();
}

Using this implementation we can override relevant methods to serve the purposes of each of our two applications (main or CLI). For instance since we are using the Phalcon\Mvc\Micro application, we do not have a dispatcher. Therefore, in our AbstractBootstrap class the initDispatcher() method is empty. However in the CLI application, the method initDispatcher() sets up the necessary Phalcon\Cli\Dispatcher.

Similarly, the initApplication() returns an instance of Phalcon\Mvc\Micro for our main application, and an instance of Phalcon\Cli\Console for the CLI application.

Conclusion

We have looked at the skeleton of our application and also discussed briefly about the bootstrap process. In the next part of these series we will discuss in depth the bootstrap process both for the main app as well as the CLI.

References

01010000011010000110000101101100011000110110111101101110010100000100100001010000

Phalcon 3.0.4 released!

Hello everyone!

We are really excited to announce Phalcon's latest release: 3.0.4!

This is another maintenance release for the 3.0.x series which addresses several issues.

The release tag can be found here: 3.0.4

Roadmap

  • This is going to be the last release for the 3.0.x series
  • We will continue our focus on v3 as part of our LTS
  • The next release will be 3.1.0

Github Branches

Prior to this release

  • 3.0.x - Only bug fixes
  • 3.1.x - Only for new features that were not breaking backward compatibility
  • 4.0.x - New version features but also certain changes will not be backwards compatible

After 3.0.4 release

  • 3.1.x - Only bug fixes
  • 3.2.x - Only for new features that were not breaking backward compatibility
  • 4.0.x - New version features but also certain changes will not be backwards compatible

The appropriate branches will be prepared shortly.

Highlights

  • Fixed Isnull check is not correct when the model field defaults to an empty string. 12507
  • Fixed Phalcon\Forms\Element::label to accept 0 as label instead of validating it as empty. 12148
  • Fixed Phalcon\Crypt::getAvailableCiphers, Phalcon\Crypt::decrypt, Phalcon\Crypt::encrypt by getting missed aliases for ciphers 12539
  • Fixed Phalcon\Mvc\Model by adding missed use statement for ResultsetInterface 12574
  • Fixed adding role after setting default action 12573
  • Fixed except option in Phalcon\Validation\Validator\Uniquenss to allow using except fields other than unique fields
  • Cleaned Phalcon\Translate\Adapter\Gettext::query and removed ability to pass custom domain 12598, 12606
  • Fixed Phalcon\Validation\Message\Group::offsetUnset to correct unsetting a message by index 12455
  • Fix using Phalcon\Acl\Role and Phalcon\Acl\Resource as parameters for Phalcon\Acl\Adapter\Memory::isAllowed

PHP 7.1 and Zephir

We are still working on identifying all the changes that PHP 7.1 has and how it affects Zephir.

A big thank you to all our backers and supporters that help us by joining our funding campaign. https://phalcon.link/fund

Update/Upgrade

Phalcon 3.0.4 can be installed from the master branch, if you don't have Zephir installed follow these instructions:

git clone http://github.com/phalcon/cphalcon
cd cphalcon/build
sudo ./install

Note that running the installation script will replace any version of Phalcon installed before.

PackageCloud.io has been updated to allow your package manager (for Linux machines) to upgrade to the new version seamlessly.

Windows DLLs are available in the download page.

We encourage existing Phalcon 3 users to update to this maintenance version.

<3 Phalcon Team

01010000011010000110000101101100011000110110111101101110010100000100100001010000

New Website Implementation and Enhancements

Hello everyone

Today we have released a brand new implementation of our website.

Apart from some minor changes to the layout and addition of content, the website looks the same as before.

Implementation

In our previous application serving our website, we had used the full stack application that Phalcon offers.

In order to offer a wider variety of implementation to our community, we opted for using the \Phalcon\Mvc\Micro application, as well as Middleware events in order to showcase the power of Phalcon, even when not using the full stack application.

The implementation of our site can be found in our Github repository, where you can clone and investigate the implementation.

In the very near future, we will create additional blog posts as tutorials for this implementation, in an effort to bolster our documentation but also offer a different implementation methodology.

Translations

We use Transifex to handle the translations to our main website. In our previous website we had some orphaned strings and some text that was not translated at all.

We have reviewed the whole site and ensured that all translation strings are where they are supposed to be.

If you wish to translate our website to any language other than English, you can join our team in Transifex and either join an existing language or request a new one.

All of the translations occur in Transifex and we regularly pull down translated strings in their respective languages so as to update our site.

Please note that we only accept top level languages (2 character slug i.e. /en /es /it etc.).

Funding

Our funding campaign is moving along nicely! A big thank you to all of our sponsors! We are utilizing the currently collected funds to meet our Q1 goals, starting with a thorough investigation of PHP 7.1.x changes and how they affect Zephir and Phalcon.

If you wish to join our backers, feel free to visit https://phalcon.link/fund

Conclusion

A big thank you to our community as well as our generous supporters!

<3 Phalcon Team

01010000011010000110000101101100011000110110111101101110010100000100100001010000

Phalcon Link and Update

Greetings everyone!

We are happy to announce that we have recently registered a new domain (https://phalcon.link/) to help the project as well as our community.

The purpose of the new domain is:

  • Help the community share Phalcon related URLs using a short, easy to remember link
  • Showcase the ease of use of creating a new Phalcon application.

Phalcon Link has been created using one single file, utilizing the \Phalcon\Mvc\Micro application (documentation).

Links

Phalcon Link offers short URLs that are easy to remember and easy to share with social media. For instance:

There are many more links that we have set up. You can check the list of available short links here: https://phalcon.link.

Of course if you have any ideas on adding new links to the list, feel free to issue a pull request. The repository is located here.

Another blog post will follow, discussing the implementation of the Phalcon Link site.

Funding and Store

We are pleased to announce that our funding campaign is moving along nicely. Our generous patrons have pledged so far $600+ which is definitely a step forward towards achieving our goals and offering a much better, faster and more robust framework by dedicating more time solely to the project.

If you wish to become a patron, you can always visit https://phalcon.link/fund and pledge your support on a one time or recurring basis.

Also if you wish to support the project with buying some swag, you can always purchase some stickers from our store for you and your team, or you can purchase T-shirts. Please note that the T-shirt campaign will only be available until the beginning of February.

Goals for Q1

Work is underway to meet our goals for Q1.

We have already started refactoring all of our websites, using different implementation techniques to showcase the power of Phalcon.

Some might already have noticed the changes in the source code of our forum.

Also the main website is being refactored, using the \Phalcon\Mvc\Micro (application) and middleware. This is work in progress, and we will be updating you when we have finished it so that you can have a look and either adapt the implementation to your projects or get some ideas.

Finally, we have started working on the revamp of the documentation. Very soon a brand new introduction page will be published along with instructions on how to install Phalcon using source, packagecloud.io, docker, vagrant etc.

Conclusion

A big thank you to our community as well as our generous supporters!

<3 Phalcon Team

01010000011010000110000101101100011000110110111101101110010100000100100001010000