CakeDC Blog

TIPS, INSIGHTS AND THE LATEST FROM THE EXPERTS BEHIND CAKEPHP

Updating Model Layer

One reason to migrate from CakePHP 2.x to newer versions, is the very powerful ORM system that was introduced in CakePHP 3.x.

 

Improved ORM Objects

The CakePHP model layer in CakePHP 3.x uses the Data Mapper pattern.

Model classes in CakePHP 3.x ORM are split into two separate objects. Entity represents a single row in the database and it is responsible for keeping record state. Table class provides access to a collection of database records and describe associations, and provides api to work with a database.

One notable change is afterFind callback removal. In CakePHP 3.x, it is possible to use Entity level getters to provide calculated fields on entity level.

 

Association Upgrade 

In CakePHP 2.x associations are defined as arrays properties like this:

    public $belongsTo = [

        'Profile' => [

            'className' => 'Profile',

            'foreignKey' => 'profile_id',

        ]

    ];

 

In CakePHP 3.x and 4.x associations are declared in the initialize method. This gives much more flexibility in association configuration.

    public function initialize(): void

    {

        $this->belongsTo('Profile', [

                'className' => 'Profile',

                'foreignKey' => 'profile_id',

        ]);         

    }

 

Or using setters syntax, it could be done this way:

public function initialize(): void

    {

        $this->belongsTo('Profile')

        ->setForeignKey('profile_id')

    }

 

Behavior Upgrade

In CakePHP 2.x, behaviors are initialized as arrays properties:

    public $actsAs = [

        'Sluggable' => [

            'label' => 'name',

        ],

    ];

 

In CakePHP 3.x and 4.x,  behaviors are configured in the initialize method. This gives much more flexibility in configuration, as in params it's possible to pass anonymous functions.

    public function initialize(): void

    {

        $this->addBehavior('Sluggable', [

            'label' => 'name',

        ]);         

    }

 

Validation Upgrade

In CakePHP 2.x, behaviors are  initialized as arrays properties:

    public $validation = [

        'title' => [

            'notBlank' => [

                'rule' => ['notBlank'],

                'allowEmpty' => false,

                'required' => true,

                'message' => 'Title is required'

            ],

        ],

    ];

 

In CakePHP 3.x and 4.x, validation is defined in validationDefault method which builds validation rules.

    public function validationDefault(Validator $validator): Validator

    {

        $validator

            ->scalar('title')

            ->requirePresence('title', 'create')

            ->notEmptyString('title');

 

        return $validator;

    }

 

Additionally, CakePHP introduced the buildRules method, which is where  described foreign keys constraints, uniqueness, or business level rules.

    public function buildRules(RulesChecker $rules): RulesChecker

    {

        $rules->add($rules->existsIn(['user_id'], 'Users'));

        $rules->add($rules->isUnique(['username'], __('username should be unique'))); 

        

        

        return $rules;

    }

 

Finder Methods

In CakePHP 2.x, the custom finder method is called twice - before and after fetching data from the database, which is defined by the $state parameter. Parameter $query contains current query state, and in $results passed data returned from database.

    protected function _findIndex($state, $query, $results = array()) {

        if ($state == 'before') {

            $query['contain'] = ['User'];

        } else {

            // ...

        }

    }    

 

In CakePHP 3.x, custom finder method accepts query object and some options passed from client code and returns an updated query. This allows for combining multiple finder methods in the same call, and has better grained finder logic.

    public function findIndex(Query $query, array $options): Query

    {

        return $query->contain(['Users']);

    }

 

The afterFind method could be implemented with the Query::formatResults method, which accepts an anonymous function to map each collection item.

Latest articles

Dependency Injection with CakePHP

Let’s talk about Dependency Injection!

SOLID principles

As you know SOLID is an acronym for the  five object-oriented design principles. In this topic, we will focus on Interface segregation principle and Dependency inversion principle. Interface segregation principle states that a client must not be forced to implement an interface that they do not use, or clients shouldn’t be forced to depend on methods they do not use. In other words, having  many client-specific interfaces is better than one general-purpose interface. From the other side, Dependency inversion principle states that objects must depend on abstractions, not on concretions. It states that the high-level module must not depend on the low-level module, but they should depend on abstractions. To follow Dependency inversion principle, we need to construct low-level modules and pass them to constructors, and that might create a lot of manual work for developers. The dependency injection container is created specifically for solving the problem with manual construction of an object, before creating a specific object. If we follow interface segregation principle when developing application modules, it would be easy to configure a container and switch module dependency. This is where the interface shows its incredible power.  

Few words about CakePHP Events System

CakePHP Events System was created to allow injecting some logic using listeners. However, in some cases, it is used to get results from code that will be created by the module user. When an event is dispatched by the listener, it can return the result. Callback injection through the event system has some drawbacks. First of all, parameters passed to the event need to pass as a hash array. So unfortunately, there is no way to check that all params are really passed or to be sure that all passed params have correct types. Is there a way to solve this problem? Yes, and containers could help with that. Instead of passing events, we can get the required object from the container and call it method. But you could say: wait, we don't know what object could be used in client code within the developed plugin. That's fine, and this  is where interface segregation principle can help. In our plugin, we define an interface for each such case, and instead of dispatching an event, we can easily get an object from the container by interface.       $updater = $container->get(AfterLoginInterface::class);     if ($updater !== null) {         $user = $updater->afterLogin($user);     }   In the Application::services method, users link the interface with the specific class.       public function services(ContainerInterface $container): void     {         $container->add(AfterLoginInterface::class, MyAfterLogin::class);     }   In some of default behavior needed we can map service class for container to default implementation using Plugin::services method.       public function services(ContainerInterface $container): void     {         if (!$container->has(AfterLoginInterface::class)) {             $container->add(AfterLoginInterface::class, NullAfterLogin::class);         }     }  

Container propagation

Dependency injection is an experimental feature. Initial implementation limited by Controllers constructors and methods, and Commands constructors. If we want to access the container in other parts of the application, we may want to propagate it from app level. The most logical way would be to implement middleware and store the container inside the request attribute.   <?php declare(strict_types=1);   namespace App\Middleware;   use Cake\Core\ContainerInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use RuntimeException;   /**  * Container Injector Middleware  */ class ContainerInjectorMiddleware implements MiddlewareInterface {     /**      * @var \Cake\Core\ContainerInterface      */     protected $container;       /**      * Constructor      *      * @param \Cake\Core\ContainerInterface $container The container to build controllers with.      */     public function __construct(ContainerInterface $container)     {         $this->container = $container;     }       /**      * Serve assets if the path matches one.      *      * @param \Psr\Http\Message\ServerRequestInterface $request The request.      * @param \Psr\Http\Server\RequestHandlerInterface $handler The request handler.      * @return \Psr\Http\Message\ResponseInterface A response.      */     public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface     {         return $handler->handle($request->withAttribute('container', $this->container));     }   That’s it! I hope that this will help you when you are baking with dependency injections. If you run into any problems, there are many support channels that allow the CakePHP community to help  You can check them out under the community tab at CakePHP.org.

One CakePHP Project Per Day

The whole team here at CakeDC are big supporters and contributors of the CakePHP community. For this month, I decided to do “one CakePHP project per day” to share with the community.  Here are some of my projects so far:

Project 01 - Notes App

A one page note application using CakePHP 4 and Bootstrap 5. This project is  a good starting point to learn the framework. Link: https://github.com/rochamarcelo/one-project-a-day-challenge-01-notes  

Project 02 - Contact List

An application to manage contacts - you are able to list, add, edit and delete contacts, upload contact avatar images or use avatar images from gravatar.com . It was built using CakePHP 4, plugin friendsofcake/search, plugin josegonzalez/cakephp-upload, Gravatar, and Bootstrap 5.  Link: https://github.com/rochamarcelo/one-project-a-day-challenge-02-contact-list  

Project 03 - Recipe Box

An application to manage recipes, using CakePHP 4,  CouchDB and Bootstrap 5. This one is a good starting point to learn to use CouchDB with CakePHP, including how to list, add and edit recipes (documents). Link: https://github.com/rochamarcelo/one-cakephp-project-a-day-challenge-03-recipe-box  

Project 04 - Service Plan with Exchange rate

An application to list services and apply exchange rate using the api https://exchangeratesapi.io/documentation/ and CakePHP 4. In this one you see the custom namespace WebService to handle logic related to api as client. Link: https://github.com/rochamarcelo/one-cakephp-project-a-day-challenge-04-service-plans-ex-rate  

Project 05 - Polls

A fun poll app, using the awesome Bulma CSS Framework and CakePHP 4. A good example of model association and the CounterCache Behavior. Link: https://github.com/rochamarcelo/one-cakephp-project-a-day-challenge-05-polls-emmy  

Project 06 - Movie Theater Schedule

An application to see which movies are in the theaters and which hours by screen each day of the week. A good example of complex queries, model associations and seed data. Link: https://github.com/rochamarcelo/one-cakephp-project-a-day-challenge-06-movie-theater-schedule  

Project 07 - Podcast Finder

An application to help easily find podcasts and download episodes. In the source code you’ll find how to use the itunes api,  a structure to handle Model actions (that I think is a good option to make your models cleaner), and a way to parse podcasts feed (XML); example usage of dependency injection. The application was built with CakePHP 4 and Bulma CSS Framework. Link: https://github.com/rochamarcelo/one-cakephp-project-a-day-challenge-07-podcast-finder  

Project 08 - Url Shortener

An application to create short urls - a good example of how to create custom routes and use custom primary key types for a model. The application was built with CakePHP 4. Link: https://github.com/rochamarcelo/one-cakephp-project-a-day-challenge-08-url-shortener  

Project 09 - Quiz

Users can list quizzes, create quizzes and answer at any time. A good example of how to use MongoDB with CakePHP 4 with a base structure for Collection classes.  Link: https://github.com/rochamarcelo/one-cakephp-project-a-day-challenge-09-quiz  

Project 10 - File Transfer

An application to easily send files to anyone, create an account, upload the file and inform the person email to send to. Built with CakePHP 4, plugin CakeDC/Users,  plugin Josegonzalez/Upload,  plugin friendsofcake/bootstrap-ui, SMTP and Bootstrap. A good example to see the usage of these plugins. Link: https://github.com/rochamarcelo/one-cakephp-project-a-day-challenge-10-file-transfer  

Project 11 - Tasks

A one page application for  users to manage their tasks. The user can create and remove decks, create and complete tasks, and list tasks grouped by decks. Built with CakePHP 4, plugin CakeDC/Users and Bootstrap 5 Link: https://github.com/rochamarcelo/one-cakephp-project-a-day-challenge-11-tasks  

Project 12 - Blog

A blog website with blog posts and tags management, WYSIWYG editor, blog search, tags filtering. Built with CakePHP 4, CakeDC/Users plugin, friendsofcake/bootstrap-ui, Muffin/Slug, friendsofcake/search and Bootstrap 4 . A good example of usage of custom routes, route prefix, finders and multiple plugins. Link: https://github.com/rochamarcelo/one-cakephp-project-a-day-challenge-12-blog  

Project 13 - Olympic Medal Count

Perfect time for this project, right?! An application to display olympic medal count by country and sports. The source code uses CouterCache behavior and aggregated query. Built with CakePHP 4 and Bootstrap 5. Link: https://github.com/rochamarcelo/one-cakephp-project-a-day-challenge-13-olympic-medal-count
 

Project 14 - Smart Home Dashboard

An awesome dashboard to manage smart devices using MQTT Messaging, CakePHP 4, CakeDC/Users plugin, php-mqtt/client (testing with Mosquitto Broker) and Bootstrap 5. The application is able to publish messages to change device status and subscribe for status changes. Link: https://github.com/rochamarcelo/one-cakephp-project-a-day-challenge-14-smart-home-dashboard-mqtt    I hope that this initiative will somehow inspire others to put their Cake skills to work, and share their projects with the community. If you’d like to see my future projects and posts, you can follow me on Twitter, and I will share them all there! https://twitter.com/mrcodex

Logging CakePHP Applications To Team Communication

The log of applications is gold. It's an important part of the software, they represent the health of the application. By default, CakePHP will use the FileLog adapter which will write to /logs/ folder. It's hard to track the live issues, and by hard I mean you will need to connect to the server, open the file on /logs/ and look at the issue which you want to investigate.   What do you think if your application sends the error directly to your team communication (Slack, Teams, RocketChat) application? Will be easier to know about a new error after some deployment? This error is sneaky, and can be in command applications. Often, we only look at the errors when the users report it.   For this sample I will use Slack, but this approach can be implemented for any application.  All we need is to create a Log adapter and configure it. So…let’s bake that:     Now we may get errors like this:   That’s all bakers! I hope this article can be useful and you can improve your logs.  

We Bake with CakePHP