CakeDC Blog

TIPS, INSIGHTS AND THE LATEST FROM THE EXPERTS BEHIND CAKEPHP

Upgrading to CakePHP 4

As you know, CakePHP announced the version 4.x last December.I recommend that you consider upgrading your applications to the next version, to keep up to date and get all the benefits. Now, let's see how to bake!  

Step 1: Upgrade PHP

First things first, if you are not running on PHP 7.2 or higher, you will need to upgrade PHP before updating CakePHP. CakePHP 4.0 requires a minimum of PHP 7.2.  

Step 2: Upgrade Templates and Resources

There is an upgrade CLI tool for rename and moving the templates and resources:   Templates and Resources must have been moved and renamed, check the result below: * This project doesn't have Resources files   Now, let's create a new constant for Resources on /config/paths.php: Finally, update the paths on config/app.php:  

Step 3: Upgrade CakePHP

The next step is optional (and the Migration Guide included this) - run the rector command to automatically fix many deprecated method calls: The rector applied on codebase some return type declarations: https://github.com/rafaelqueiroz/cakephp-upgrade-sample/commit/d7e5c2ecc5dc28045700a270721f07098a8e189c?branch=d7e5c2ecc5dc28045700a270721f07098a8e189c&diff=split Pay attention: It is important to apply rector before you upgrade your dependencies.   Upgrade CakePHP and PHPUnit: PHPUnit can be upgraded easily. Most of the time, the --update-with-dependencies doesn’t work with me for CakePHP: The root of the issue is the packages using Caret Version Range, so let’s update debug_kit, migrations and bake using editor:   Here we go:   Now, let see how the project looks: Here, we have few deprecations and warnings. Do you remember I mentioned the rector is optional? So, the question is the rector and it's not always able to handle these issues.   I will use the PHPStan to fix this - we will install with composer: Now, we can run the phpstan analyse and fix the issues:   It's up to you how much effort you will put in with PHPStan issues. I recommend fixing everything. For this post, I did fix only what was needed to run the project after the update, you can check the fixes on this commit.   After the last fixes, the project is running well:  That’s all? No. But we upgraded CakePHP? Yes. Real applications probably use many plugins, and if these plugins don't have a version for CakePHP 4, you will need to update. Depending on the size and level of complexity of the project, the upgrade could be hard, but never impossible.    If you do not feel confident or your company would like to outsource support for this, don't hesitate to contact us at Cake Development Corporation. Our team is offering a full upgrade from CakePHP 2/3 to CakePHP 4. This will be a migration of your current application code to make it compatible with CakePHP 4 features, plugins, security settings, etc. We will be doing these migration services for a special rate - something we have never done before! Learn more about our Upgrade Services You can check the codebase of the examples on this repository. The branch upgrade has all steps by commit.  With every release CakePHP gets better, and version 4.x is no exception. There are many benefits that come with upgrading, and it makes baking a lot easier.

Using Postgres as default database- hi...

SQL language for different databases has some differences, which could cause problems after migrations between these databases. Here, we collected some hints, problems could appear during migration from MySQL to PostgreSQL. We focus on the CakePHP model layer and query generation layer.  

Tables join in where expression

Often we want to join two tables using a condition like $query->where([‘Author.id = Article.author_id’]) which works fine till we dont need field alias quotes. In the case of migration to postgres, we might want to enable autoQuotes. In this case, we can use $query->newExpr()->equalFields(‘Author.idArticle.author_id’).   

Case sensitivity in like expressions

By default mysql does case insensitive search. Switching to postgres, you can note that some functionality works differently. Hopefully you have tests, which covers your code, and this will be detected during migration. Postgres uses custom syntax for such queries named ILIKE.The case of old style conditions where method arrays straight forward,  you’d just go with ILIKE instead of LIKE. But what if we want to use ILIKE in builder methods... Here is that example:     return $query->where(         function (QueryExpression $exp) use ($field, $value): QueryExpression {             return $exp->add(new \Cake\Database\Expression\Comparison($field, $value, 'string', 'ILIKE'));         });   

Type casing

As postgres is much more strict with types, type casing is not a rare operation and may be needed. Here is an example of how to perform it using FunctionExpression$expr = (new FunctionExpression('CAST'))->setConjunction(' AS ')->add([$id, 'varchar' => 'literal']); which generates expression like :id AS varchar there :id is the placeholder for variable $id. This trick, used with literal, allows you to cast to any postgres type.  

 Quotes of tables and fields

Sometimes it is critical to inform CakePHP that the field should be quoted. One recommendation is to avoid using plain strings in case of table joins, or using IS NULL as string. So if array syntax is used, all CakePHP conventions must be followed. However,  sometimes we should help the ORM and obviously wrap a field name with IdentifiedExpression. Let's take a look back to the previous example, but now we want to type cast not value, but a field. The only solution to quote field name correctly is using this code:     $id = new IdentifierExpression($this->aliasField($field));     $expr = (new FunctionExpression('CAST'))->setConjunction(' AS ')->add([$id, 'varchar' => 'literal']);   

Building complex arithmetic expressions

In case we want to generate expressions in query fields, and we don’t want to overcomplicate logic, we could use these next tricks. Here, I have created ListExpression, which could be used as a collection of expressions. Each of these are corrected, and generates a query with correct handling of each element. See: https://gist.github.com/skie/f6e4f1a1b61e0f902a507f7907c3bbf2 So, say we want to generate expressions like this: “Events”.”time_to” - “Events”.”time_from”... With ListExpression, it can be done quite easy:  $diff = new ListExpression([new IdentifierExpression('Events.time_to'), '-', new IdentifierExpression('Events.time_from')]);   Hopefully these tricks will be as useful for your baking as they have been for mine! 

CakePHP Common Errors: Saving HasMany ...

The Cake Development Corporation team performs many code reviews. In fact, that is our starting point with every new client, as we offer free quick reviews. This is a good way to see where code stands, and how much work will need to be done to get it functioning properly.  One of the common errors we have found while doing Code Reviews of existing applications or just working with inherited code, it’s the way HasMany relations data is saved.  We have noticed that to save HasMany relations, some developers save the target relation, and then when the ID is retrieved after saving, they save one per one each item of the ‘many’ relation. There is no need to do this, as CakePHP can do all this in one single ‘save’! You won’t have any issue related to inconsistent data, because everything will be stored in one single transaction and your code will look much more clean. Let’s see a quick and easy to follow example - We will have the following relations: ‘Users’ and one User could have many ‘Addresses’.  We wish to save one user and this user will have two addresses.  First, you need to build the form in the proper way, the request data should follow the structure of your entities. The key in the form is the fieldName for the hasMany inputs. They must follow this format: {entityname}.{position}.{property}, for example: adddress.0.street_1, adddress.0.street_2, etc for the first item so store, for the second one: : adddress.1.street_1, adddress.1.street_2, and so on. More examples can be found here:  https://book.cakephp.org/4/en/views/helpers/form.html#creating-inputs-for-associated-data.
<?= $this->Form->create($user) ?> <fieldset>    <legend><?= __('Add User') ?></legend>    <?php        echo $this->Form->control('first_name');        echo $this->Form->control('last_name');        echo $this->Form->control('phone');    ?>    <legend><?= __('Address 1') ?></legend>    <?php    echo $this->Form->control('addresses.0.street_1');    echo $this->Form->control('addresses.0.street_2');    echo $this->Form->control('addresses.0.zip');    echo $this->Form->control('addresses.0.city');    echo $this->Form->control('addresses.0.state');    ?>    <legend><?= __('Address 2') ?></legend>    <?php    echo $this->Form->control('addresses.1.street_1');    echo $this->Form->control('addresses.1.street_2');    echo $this->Form->control('addresses.1.zip');    echo $this->Form->control('addresses.1.city');    echo $this->Form->control('addresses.1.state');    ?> </fieldset> <?= $this->Form->button(__('Submit')) ?> <?= $this->Form->end() ?>
Now that we have the form, we need to convert the request data. The Table class provides an easy and efficient way to convert one or many entities from request data. It’s needed to define which associations should be marshalled, using associated
public function add() {    $user = $this->Users->newEmptyEntity();    if ($this->request->is('post')) {        $user = $this->Users->patchEntity($user, $this->request->getData(), ['associated' => ['Addresses']]);        if ($this->Users->save($user)) {            $this->Flash->success(__('The user has been saved.'));              return $this->redirect(['action' => 'index']);        }        $this->Flash->error(__('The user could not be saved. Please, try again.'));    }    $this->set(compact('user')); } In this example we are saving one user and two addresses for the given user.  Associated data is validated by default, If you wish to bypass data validation, pass the validate => false option, for example: $this->Users->patchEntity($user, $this->request->getData(), ['associated' => ['Addresses' => [‘validate’ => false]]]).    We are all about working smarter and in less time, so I hope this information will be useful! Take a look here for more information: https://book.cakephp.org/4/en/orm/saving-data.html#converting-request-data-into-entities  We will be posting more Common Errors while using CakePHP.  Keep checking back!

Virtual CakeFest 2020

There we were… just 8 months ago, bright eyed and planning our annual CakeFest event for 2020. The community picked Los Angeles for a host city, and who doesn’t love LA? Our team was excited! Now, fast forward, and we are planning our virtual event. After sitting in on a few well executed virtual events this year, I was pleasantly surprised and impressed. There are still ways to keep these conferences interactive, but theres no denying that it is hard to beat the face to face communication within our CakePHP community, like we have experienced in the past.  Wondering what our (and many others) event will look like this year? Let’s see….  

Virtual

Duh. We will be hosting for attendees all over the world. This is probably the biggest pro about doing digital conferences. Attendees are able to join in, no matter where they live. With international travel, there are many things at play, schedule, finances, family, work. And it is not always an option to attend… The fact that ANYONE can tune in from ANYWHERE is so refreshing. We usually host a 4 day event, but this year we have condensed all of the fun into 2 days, to make things easier on those who don’t want to miss anything. I have had many people reach out to me saying that this will be the first CakeFest that they get to attend live (because we do always post the event after the fact or stream).  Basically, come as you are, dress comfy, and grab the coffee. You get to watch and learn from the comfort of… anywhere you want.
 

Speakers

The same thing goes for speakers - except that they will actually have to wear pants because they will be seen on camera. Our speakers will not have to worry about travel or accommodations, and can fit us in their busy schedules. We have some pretty great talks lined up this year, from building plugins, to mental health in tech, theres something that all attendees can benefit from.  As always, we will have our workshops as well, one FULL day of basic to advanced CakePHP workshops, which is the highlight of the event for most developers. The schedule is posted on our site, but there could be unexpected changes or additions moving forward.  Times are ever changing, am I right? We will allow for quick breaks throughout the events to allow potty time, refills, walking the dog etc.   

Interactive

We will have the opportunity for Q&A during talks, as well as a “convos with speakers” chat available for attendees to interact with the spealers during the event. On RingCentral, there is a fully interactive chat rolling through the presentations. The speakers are on a pretty strict time schedule, but will get to any questions they can. For this reason, we are creating chat rooms for post talk Q&A as well.  Basically you can have a chat with any available speaker for more insight on their presentations, to ask them questions, or just say hello!  This access will be sent to attendees via email before the live event. As I mentioned, our schedule is posted, and we are even taking questions ahead of the event. If there’s something you’d like to request the speaker includes, or get ahead of the question game, you can send us an email HERE.  

Giveaways

Need I say more? This year we have decided to give away some pretty cool digital prizes during the event. This may be in the form of trivia, or a game of “who can tweet us the fastest”. Either way, everyone attending will have the chance to win! If you don’t follow us on social media, you may want to start, because there MAY or may not be some pre event giveaways as well.   

Tickets

Tickets are available now. They are limited, so make sure you purchase & register ASAP. Get them here: CakeFest.orgWe hope to “see” you at CakeFest 2020! October 8-9.   

Why Use CakePHP

CakePHP  is a web development framework running on PHP. CakePHP provides tools to help build websites and web apps faster, stable and very easy to maintain. We will outline some of the interesting features of the CakePHP framework below:

Authorization

The authorization layer is in charge of determining if a user is able to access a feature or not. Authorization in CakePHP may be as complex as you want. It is powerful because you can define permissions per role, ownership, or anything else by just writing a policy.  In CakePHP 4, the Authorization layer is part of another package, which means that it can be used in a non-CakePHP app.

MVC Support

The layers in CakePHP are very explicit. Firstly, you will see that the application has specific folders for each layer (Model, Controller, Template). Secondly, you are encouraged to not access layers incorrectly, because the right way is simple enough. After using multiple frameworks out of there, I can say that CakePHP implements MVC and it implements it well.

Bake

Bake is not something new in CakePHP 4. It has been included with Cake since version 0.1.0 and even now when it is released as a plugin, it is a required tool for any developer. Bake makes generating an application, controllers, models and everything else easier... just running a command and within minutes. Other frameworks may have some tools, but in my opinion, there is nothing like bake.

Database Migration

Database migrations with CakePHP are simple, quick and safe. Those are probably the only three things you look for when versioning your database. MigrateShell can be used to generate migrations and you are able to alter database structure in any way, as well as running queries/commands. The CakePHP team is also responsible for Phinx plugin development.

Multi Language Support

This is another old feature that has been in CakePHP for years and has been recently improved in CakePHP 4. I must say that we have not found any other framework with the same set of features for internationalization and localization. You only need to use the translation function for any strings [__()] and define a way to set the language in your app. After doing that, just extract PO files using the shell and start translating your strings.

Powerful ORM

ORM in CakePHP 4 is able to do anything you can imagine. Simple and complex queries have an easy way to be executed using ORM functions. Joins, grouping, subqueries can be done in just minutes. Even after working in complex reporting for several clients, we have not found something that cannot be done through CakePHP ORM.

PHP Standards 

Going back to previous CakePHP versions (1.x and <2.x) we found that they implemented their own standard, which was good if you only worked with CakePHP... but it made integrating external libraries more difficult. In CakePHP 4, as well as 3.x, it is not a problem anymore because the wonderful CakePHP team is aware of the latest standards for the language and they are implemented inside the core... as well as the libraries released around it. It allows you to use any PHP library or package inside your Cake application.  I could go on all day about features that I like about the CakePHP framework, but there's more blogs to write in the future. I hope this gives you some incentive to use it.  

Managing A Company During A Global Pan...

A worldwide pandemic is not something a company, a manager, or a team ever plans for. This time 6 months ago we were bringing in more clients than anticipated, and planning for an international conference. Fast forward to now, just like most companies we have been hit, our conference has gone virtual, and many employees are still worried about what is to come.  Here are 5 things I have learned during these uncertain times:  

1. Don’t panic. 

Easier said than done, right? Being responsible for a team of great people and their financial, as well as professional, well being can be trying at times. I have learned it is best to stay calm, push forward and still do the best we can, even when our best isn’t always enough. Luckily, I am not a worrier by nature, and I hope that I can be a solid backbone for my team and clients, while letting them know that they (and our company) are my top priority … now more than ever.   

2. Be transparent

It is best to have sure answers and knowledge of what is expected, and to be open and honest about this with the team. If we are going to be working longer hours one week, maybe shorter the next, I want to be upfront so that no one is caught off guard. If policies or procedures are changing, they are notified immediately   Same thing goes for our clients, we have always prided ourselves on being honest and transparent about the behind the scene scenarios. It may not always be good news that’s delivered, but it will be honest. I have set goals to make my expectations clear, and reasonable.   

3. Be available:

Someone like myself, I am always going in a million directions. I have made it a point to make myself available for help, support, or whatever it is that someone may need. This goes for  clients, team members, even friends.  A pandemic like this really makes you step back and think about what is important, and things that you may not have made priorities in the past that needed to change. Our team is used to working remotely, but we communicate daily, and we always have open lines of communication (sometimes in the middle of the night as we all work in different time zones.  

4. Be Understanding

If 2020 has taught us anything, it’s to expect the unexpected. It is vital that each individual understands that not everyone is the same. We do not handle stress the same, we go through trials and tribulations differently, so it is important to be empathetic. We need to provide tools for success... and sometimes that means paid time off, new communication platforms, team building exercises, or just listening and being compassionate. I think a mistake a lot of people made early on was expecting everyone to adjust to the new way of life, with no clear direction. This resulted in a lot of confusion, and negativity, instead of learning together and changing the course of action. 
 

5. Surround yourself with a great team. 

Luckily for me, our team is fully functional without me, maybe that should scare me a little, huh? We have structure, we built up trust with each other, and everyone works towards the same goal - being successful, delivering to our clients, and growing together. While it may be my job to keep everyone moving forward and be motivating, it’s no secret that they motivate me, too. Despite working in different countries, our team has built great relationships with each other, and everyone is ready to step in and help their colleagues whenever necessary. 
 

Final thoughts

Has 2020 been different than I imagined? Absolutely. We do not know the answers to every question. We also do not know where this year may take our team, the company, or the world! One thing I do know is we will adapt, adjust and keep pushing forward. We will keep providing the best service to our clients as we always have, and we will not panic…. Not yet at least. 
 

A Quick CakePHP Local Environment With...

CakePHP and Docker

We all know that while developing a CakePHP software, we need to have a local environment with PHP, HTTP Server (nginx, apache) and a database (MySql, Postgres, Mongodb, etc). Installing those tools directly to your system is the basic way, but it can become a bit tricky when we have multiple projects using different versions of those tools... that’s where Docker will help us. In this article, we will show a quick docker setup to improve our CakePHP local environment. If you don’t have docker installed go to: https://docs.docker.com/get-docker/. It is available for Linux, MacOS and Windows. For our setup we are going to use PHP, Nginx, and Mysql. All of the information required will be added to a new file named docker-compose.yml. In our environment we will need two docker images [https://docs.docker.com/engine/reference/commandline/images/], one image for php + nginx and one for mysql.   

Setup Nginx + PHP service

Create the file  docker-compose.yml inside your application with this:    version: "3.1" services:   php-fpm:     image: webdevops/php-nginx:7.4     container_name: myapp-webserver     working_dir: /app     volumes:       - ./:/app     environment:       - WEB_DOCUMENT_ROOT=/app/webroot     ports:       - "80:80"   Now,we have a service named php-fpm, which is able to run php 7.4 and nginx at port 80 pointing to our webroot dir. Important note: the container_name must be unique in your system.   

Setup MySql service

Our MySql service requires a username, password and database name. For this, we are going to create the file mysql.env (don’t use a weak password in production, you could share a mysql.env.default file with your team) with this content:   MYSQL_ROOT_PASSWORD=password MYSQL_DATABASE=my_app MYSQL_USER=my_user MYSQL_PASSWORD=password   Now, at the end of docker-compose.yml , add this:      mysql:     image: mysql:5.6     container_name: myapp-mysql     working_dir: /app     volumes:       - .:/app       - ./tmp/data/mysql_db:/var/lib/mysql     env_file:       - mysql.env     command: mysqld --character-set-server=utf8 --init-connect='SET NAMES UTF8;'     ports:       - "3306:3306"   Before we start this service, lets add the service for our database, include this at the end of the file:  docker-compose.yml . You’ll see that we have - ./tmp/data/mysql_db:/var/lib/mysql, this allows us to persist mysql data. Now we also have a service named mysql with one empty database named my_app and a user name my_user.
 

Starting the services and app configuration

Before we continue, make sure that you don’t have any other http server or mysql server running. Now that we have finished our docker-compose.yml  we can execute docker-compose up to start the services and access the app at http://localhost. The next thing you need to do is update your database configuration with the correct credentials - the host is the service name, in our case it is “mysql”:   'host' => ‘mysql’,             'username' => 'my_user',             'password' => ‘password’,             'database' => 'my_app',   That’s it! Now we have a working local environment for our CakePHP app. We can now access the services using docker-compose exec php-fpm bash  and docker-compose exec mysql bash.  The files mentioned here (docker-compose.yml and mysql.env) can be found at  https://gist.github.com/CakeDCTeam/263a65336a85baab2667e08c907bfff6.  

The icing on the cake

Going one step further, we could add some alias (with linux) to make it even easier. Let’s add these lines at the end of your ~/.bashrc file:   alias cake="docker-compose exec -u $(id -u ${USER}):$(id -g ${USER}) php-fpm bin/cake" alias fpm="docker-compose exec -u $(id -u ${USER}):$(id -g ${USER}) php-fpm" alias composer="docker-compose exec -u $(id -u ${USER}):$(id -g ${USER}) php-fpm composer"   With those entries, instead of typing docker-compose exec php-fpm bin/cake, we can just type cake. The other two aliases are for composer and bash. Notice that we have ${USER}? This will ensure that we are using the same user inside the services.  

Additional information

Normally docker images allow us to customize the service, for webdevops/php-nginx:7.4 - you can check more information at: https://dockerfile.readthedocs.io/en/latest/content/DockerImages/dockerfiles/php-nginx.html and for mysql check: https://hub.docker.com/_/mysql . You can find more images at: https://hub.docker.com/. If you are not familiar with docker, take a look at: https://docs.docker.com/get-started/overview/, as this documentation provides good information.   Hope you have enjoyed this article and will take advantage of docker while working in your CakePHP application.  

Debug & Run Gitlab Pipelines In Your L...

Some time ago, we established https://git.cakedc.com/ as our company workflow. Along with it we created automated tools to support a continuous integration environment, with automated deployments based on develop, qa, stage, master branches and some useful tools to run on specific branches. We used jenkins for a long time, then switched to gitlab around version 6 (more than 5 years ago!) and we've been using it since. Gitlab provides a very powerful way to configure your pipelines and define specific docker images to be used as your runners. So we defined our own runner image and configured it to provide the typical dependencies needed to run static analysis tools, unit tests and other utilities as part of our build process. For example, one typical build file for a simple CakePHP project could be: # https://hub.docker.com/r/jorgegonzalezcakedc/cakephp-runner image: jorgegonzalezcakedc/cakephp-runner:yarn   before_script:   # install ssh-agent   - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'     # run ssh-agent   - eval $(ssh-agent -s)     # add ssh key stored in SSH_PRIVATE_KEY variable to the agent store   - ssh-add <(echo "$SSH_CI_PRIVATE_KEY")   - echo "$SSH_CI_PRIVATE_KEY" > /root/.ssh/id_rsa   - chmod 600 /root/.ssh/id_rsa     # replace git oauth key for composer   - sed -i "s/__TOKEN__/$GIT_OAUTH_TOKEN/g" ~/.composer/auth.json   variables:   # Configure mysql service (https://hub.docker.com/_/mysql/)   MYSQL_DATABASE: app_test   MYSQL_ROOT_PASSWORD: root   stages:   - test   - deploy   test:mysql:   services:   - mysql:5.7.22   script:   - echo $MYSQL_PORT_3306_TCP_ADDR   - composer install --verbose --prefer-dist --optimize-autoloader --no-progress --no-interaction   - ( [ -f vendor/bin/phpunit ] && echo "phpunit already installed";) || composer require phpunit/phpunit   - mysql -uroot -p$MYSQL_ROOT_PASSWORD -h $MYSQL_PORT_3306_TCP_ADDR -e 'CREATE DATABASE test_myapp_template;';   - DATABASE_TEST_TEMPLATE_URL="mysql://root:$MYSQL_ROOT_PASSWORD@$MYSQL_PORT_3306_TCP_ADDR/test_myapp_template" bin/cake db_test -i   - DATABASE_TEST_URL="mysql://root:$MYSQL_ROOT_PASSWORD@$MYSQL_PORT_3306_TCP_ADDR/app_test" DATABASE_TEST_TEMPLATE_URL="mysql://root:$MYSQL_ROOT_PASSWORD@$MYSQL_PORT_3306_TCP_ADDR/test_myapp_template" QUEUE_DEFAULT_URL='null:///?queue=default&timeout=1' vendor/bin/phpunit --verbose --colors=never   except:   - tags   deploy_develop:   stage: deploy   environment:     name: develop     url: https://www.cakedc.com   script:     - cd deploy && php deployer.phar deploy develop -vvv   only:     - develop   except:     - tags   In this case, on every push to the "develop" branch, we'll run unit tests of the project, then call the specific deploy script to push the project to our CI environment. This process is usually smooth and clean,  if it's not,  then you need to debug why the runner is failing at some step.   One possible answer to this situation would be to dockerize the project and ensure the local docker version matches 100% the runner being used, so you don't have surprises while running your pipelines.  This process is actually done in some projects to ensure we match specific versions and dependencies. But for legacy projects, it's useful to have something more or less generic that just works™ and does not require the effort to dockerize. In this case, and going back to the topic of the article, how can we debug the issues locally without waiting for the pipelines to run? (Note I'm using Ubuntu 16.04 as my dev environment, and possibly aiming to switch to 20.04 LTS soon…)

  • Install docker in your local machine see https://docs.docker.com/get-docker/ 
  • Ensure docker is up and running sudo service docker start
  • Install the gitlab apt repositories curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | sudo bash
  • Install the gitlab-runner package sudo apt update && sudo apt install -y gitlab-runner
  • Go to your local project, where the .gitlab-ci.yml file is located
  • Run your pipeline locally, note you can pass environment variables via --env and you can name the target you want to build, in this case test:mysql gitlab-runner exec docker test:mysql --env SSH_CI_PRIVATE_KEY="`cat ~/.ssh/id_rsa`" --env GIT_OAUTH_TOKEN="XXX"
  • If there's a problem with the pipeline, add a long sleep time in your .gitlab-ci.yml file to keep the pipeline up and running while you connect to it, for example after the like to run your unit tests, add a new line sleep 1337
  • Run your pipeline again, you'll notice it won't stop…
  • Open a new terminal and check the id of the docker instance using docker ps
  • You'll see a list of the active docker container IDs
  • Finally connect to the container using docker exec -it CONTAINER_ID bash
  • If bash is not available in the container, you'll need another way to connect to it (or another container)
  Once you get access to the container, you can manually execute commands, check permissions, run shells, and debug db and code contents to hunt down the bug you should have already in a unit test… This method saved me some time trying to understand a specific issue in a pipeline, I hope it'll save some of your time too!  

CakePHP 4 - First Look

Last december, the CakePHP team announced the immediate availability of 4.0.0. This release begins a new chapter for CakePHP, as 4.0 is now API stable. With this release, Cake 3.x moves into maintenance mode, while 2.x moves into security release mode. The promise of the version is: cleaner, faster and still tasty as usual. I had the opportunity to bake a new application from scratch and I will give my feedback about my process.  

Skeleton Design

The new version refreshes the skeleton design of the application. Now we have 2 new folders on root:
  • Templates

The templates folder has presentational files placed here: elements, error pages, layouts, and view template files. Pay attention for subfolders: 
  • Core templates are lowercase: cell, element, email, layout
  • App templates still uppercase: Error, Pages
  • Resources

The resources folder has subfolders for various types of resource files.  The locales* sub folder stores string files for internationalization.   If you are familiar with i18n, you will see the difference:
  • src/Locale/pt_BR/default.po (3.x)
  • resources/locales/pt_BR/default.po (4.x)
  Another important change was the .ctp files. They are moved for .php. CakePHP template files have a default extension of .php now. We have a new config/app_local.php file, which contains the configuration data that varies between environments and should be managed by configuration management, or your deployment tooling.  

PHP Strict Type Mode

In PHP the declare (strict_types = 1); directive enables strict mode. In strict mode, only a variable of exact type of the “type declaration” will be accepted, or a TypeError will be thrown. The only exception to this rule is that an integer may be given to a function expecting a float. This is a feature from PHP 7 - which we strongly recommended. All codebase from the skeleton and files generated by bake will include the function.  

Entities

The preferred way of getting new entities is using the newEmptyEntity() method: $product = $this->Products->newEmptyEntity();  

Authentication

After 10 years baking, that's a really big change for me. I don't usually use plugins for authentication, I really like the Auth Component. I think many bakers would agree, as I remember on the first international meetup, the co-host shared the same opinion.   The Auth Component is deprecated, so it's better move on and save the good memories. The new way for implementing Authentication is more verbose. It requires a few steps, I won't go into detail about that,  because you can easily check on book:
  • Install Authentication Plugin
  • Load the Plugin
  • Apply the Middleware
  • Load the Component
  My first look is like I said,  too verbose, for me anyway. We need to write a lot of code. Also it is not included on the skeleton of CakePHP applications, you need include by your own. https://book.cakephp.org/authentication/2/en/index.html  

HTTPS Enforcer Middleware

Contrary to the Authentication, I was really surprised how easy it was to force my Application to use HTTPS. If you are familiar with CakePHP, you will use the Security Component for that: class AppController extends Controller {      public function initialize()    {        parent::initialize();        $this->loadComponent('Security', [            'blackHoleCallback' => 'forceSSL',        ]);    }      public function beforeFilter(Event $event)    {        if (!Configure::read('debug')) {            $this->Security->requireSecure();        }    }      public function forceSSL()    {        return $this->redirect(            'https://' .            env('SERVER_NAME') .            Router::url($this->request->getRequestTarget())        );    }   }
  The implementation on version 4 is less verbose and easy, kudos for the new version:    public function middleware(MiddlewareQueue $middlewareQueue)    {        $middlewareQueue            ->add(new HttpsEnforcerMiddleware([                'redirect' => true,                'statusCode' => 302,                'disableOnDebug' => true,            ]));          return $middlewareQueue;    }   What I know is a drop, what I don’t know is an ocean. The new version is here to stay, and this article it's a just one overview of basic usage of the new version. * Version 4.1.0 is released already with more improvements and features.  

Links 

[1] Book https://book.cakephp.org/4/en/contents.html [2] Migration Guide https://book.cakephp.org/4/en/appendices/migration-guides.html  

CakeDC API plugin - Authentication and...

This article covers new changes for CakePHP 4 version of plugin. So it covers versions starting from 8.x (8.0) and later.  

Permissions system. RBAC

By default, the plugin uses CakeDC Users and CakeDC Auth plugins for authentication. For RBAC it uses the same style as defined in the Auth plugin RBAC system with minor changes required for the API plugin. First, let's consider the case when we want public api without any authorization. In this case the most simple way would be is to define in config/api_permissions.php next rule   return [     'CakeDC/Auth.api_permissions' => [         [             'role' => '*',             'service' => '*',             'action' => '*',             'method' => '*',             'bypassAuth' => true,         ],      ], ];   Now, consider the case we want to use users plugin authentication. Since Api is supposed to be used from another domain, we should allow all requests with OPTIONS type. To do this we should add this rule as first on in config/api_permissions.php       [         'role' => '*',         'service' => '*',         'action' => '*',         'method' => 'OPTIONS',         'bypassAuth' => true,     ],    Here, method define OPTIONS and bypassAuth means that such actions should work for any users, including not authenticated. Now we should allow Auth service methods       [         'role' => '*',         'service' => '*',         'action' => ['login', 'jwt_login', 'register', 'jwt_refresh',],         'method' => ['POST'],         'bypassAuth' => true,     ],    All other services/actions should be declared in api_permissions file to define what user roles are allowed to access them. Imagine we want to allow the admin role to access the add/edit/delete posts and make index and view public. We can do it based on method or based on action names.       [         'role' => 'admin',         'service' => 'posts',         'action' => '*',         'method' => ['POST', 'PUT', 'DELETE'],     ],      [         'role' => 'admin',         'service' => 'posts',         'action' => ['index', 'view'],         'method' => '*',         'bypassAuth' => true,     ],   

 Routers and Middlewares

Starting from the 8.x version, API Plugin uses router middlewares. This gives great abilities to configure the plugin. So now it is possible to have separate authentication and authorization configuration for website and for api. Also, It is possible to have more then one api prefix, and as result provide more then single api for website with different configuration. Let’s take a look on the default configuration for middlewares   'Middleware' => [     'authentication' => [         'class' => AuthenticationMiddleware::class,         'request' => ApiInitializer::class,         'method' => 'getAuthenticationService',     ],     'bodyParser' => [         'class' => BodyParserMiddleware::class,     ],     'apiParser' => [         'class' => ParseApiRequestMiddleware::class,     ],     'apiAuthorize' => [         'class' => AuthorizationMiddleware::class,         'request' => ApiInitializer::class,         'params' => [             'unauthorizedHandler' => 'CakeDC/Api.ApiException',         ],     ],     'apiAuthorizeRequest' => [         'class' => RequestAuthorizationMiddleware::class,     ],     'apiProcessor' => [         'class' => ProcessApiRequestMiddleware::class,     ], ],   First we see the order of middlewares that proceed api request. It passes through AuthenticationMiddleware, AuthorizationMiddleware, and RequestAuthorizationMiddleware to perform generic auth tasks. It passes through BodyParserMiddleware to unpack the json request. And finally ParseApiRequestMiddleware does initial service analysis and ProcessApiRequestMiddleware performs the request. Also we can note CakeDC\Api\ApiInitializer class used to define Authentication and Authorization configuration. It can be redefined in the application layer to provide needed Identifiers and  Authenticators.  

 Jwt authentication - Refreshing tokens

New plugin feature is embedded jwt_login action which allows the user to get access_token and refresh_token included into the login response. Tokens should be passed in the Authorization header with bearer prefix. Access token is supposed to be used as default token and refresh token needed to get a new access token when it's expired. So for refreshing provided additional jwt_refresh action which should be used in this case.  

 Configuration

Configuration should be defined on application level in config/api.php. Need to note that it is important to enable this file to load by the Api plugin. It could be done in config/bootstrap_app.php using global configuration: Configure::write('Api.config', ['api']);       'Api' => [          ...                  'Jwt' => [             'enabled' => true,             'AccessToken' => [                 'lifetime' => 600,                 'secret' => 'accesssecret',             ],             'RefreshToken' => [                 'lifetime' => 2 * WEEK,                 'secret' => 'refreshsecret',             ],         ],    Hopefully, this was helpful. Our team is always working on adding new features and plugins. You can check out more available plugins HERE.

CakePHP Meetup: Unit Test Fixtures, Qu...

Developers are used to living in a virtual world, so adjusting has been easier than expected. Recently, we’ve been holding virtual meetups, and we are so happy with the feedback. Digital training sessions allow bakers from all over the world to come together and enjoy. Our plan is to host one each month, and coordinate time zones so that everyone gets a chance to attend. Our latest one was based around a good time for our Japanese community.  If you missed the meetup, no problem. We always post the recording for playback, and I’ll even give you a quick rundown of the topics covered. Let’s jump in:

CakePHP Fixture Factory Plugin

by Juan Pablo Ramirez CakePHP Fixture Factory Plugin https://github.com/pakacuda/cakephp-fixture-factories  helps to improve the way fixtures are generated, when having a big database writing fixtures can get so complicated. This plugin provides Fixture Factories in replacement of the fixtures found out of the box in CakePHP.
Generating fixtures can be done in a few code lines reducing the effort of writing and maintaining tests. There are some other plugins to manage fixtures: 

CakePHP Queue Plugin

By Mark Scherer @dereuromark CakePHP Queue Plugin https://github.com/dereuromark/cakephp-queue is a simple Queue solution, it can be used for small applications and it’s a good one to get started with Job Queues, having something easy to maintain at the beginning is a good starting point.
Queues are a good option for functionalities like: image processing, email sending, PDF generation; to improve the response-time for heavy-processing tasks. For more robust solutions can be used:
  • CakePHP Queuesadilla  https://github.com/josegonzalez/cakephp-queuesadilla This plugin is a simple wrapper around the Queuesadilla queuing library, providing tighter integration with the CakePHP framework. We have used this plugin in CakeDC in several projects, we also had to build  a Mongo Engine for a specific client.

CakePHP PHP PM Bridge

By Jorge Gonzalez @steinkel CakePHP Bridge https://github.com/CakeDC/cakephp-phppm  to use with PHP-PM project.
PPM is a process manager, supercharger and load balancer for modern PHP applications. PHP PM It's based on ReactPHP, the approach of this is to kill the expensive bootstrap of PHP (declaring symbols, loading/parsing files) and the bootstrap of feature-rich frameworks.
It’s a good option If you want to significantly improve the responsiveness of an application that could have spikes. PM works as PHP FPM, it’s a replacement for it.  Below some benchmark:  50 Concurrent threads in 10 seconds
  • FPM 83 transactions per second, Failed 0,  Concurrency 6.58.
  • PPM 90.30 transactions per second, Failed 0, Concurrency 3.86.
200 Concurrent threads in 10 seconds
  • FPM 116,49 transactions per second, Failed 142,  Concurrency 116.64.
  • PPM 207.35 transactions per second, Failed 0, Concurrency 85.59.
1000 Concurrent threads in 10 seconds
  • FPM 109,88 transactions per second, Failed 1759, Concurrency 187.49.
  • PPM 214.91 transactions per second, Failed 0,  Concurrency 302.39.
PPM is able to handle a lot of concurrency connections coming in spike to the server  in a better way than PHP FPM.
For watching the Meetup visit the following link https://www.youtube.com/watch?v=POI0IwyqULo Stay up to date on all virtual meetups here  https://cakephp.org/pages/meetups      

Working From Home - A Developer's View

Long before COVID-19, I (and my team) started working from home. Even before working at CakeDC, between 2007 and 2010, I worked for a company where you were able to split your time between home and office.    Maybe that's why I actually feel comfortable working remotely and I would never change it for an in office position anymore.     Anyway, I am not going to talk about remote work because these days there are thousands of articles about it (even in  one of our previous posts). I want to focus today on writing about our experience with different communication tools and the pros and cons for each of them.   Back in 2011 when I started working at CakeDC, we had an IRC server in place with a couple of plugins to retain messages for our day-to-day communication. Everyone used different clients like Colloquy on mac or Pidgin on Linux. Additionally, we used Skype for peer/team calls and also for client communication. In my opinion, the linux experience was awful until they improved the client a few years later. This setup was implemented in 2007 when the company was started, and in 2012, we decided to shut it down because it was easier just using Skype for everything, messages and calls.     After several years using - suffering - Skype, with new options in the market, we decided to move away to a more reliable and modern approach. The main reason was the lack of updates for Skype linux client, and the message retention limits. In 2016 we started utilizing the more than popular Slack and its open source alternative, Rocket Chat; always keeping Skype for client communication.    Some months later the team concluded that Rocket Chat was the right choice, mainly because we wanted to have control over the messages and the information transmitted. Since the features we were using were available in both solutions, we installed our own Rocket Chat server. At this point I have to say that we did try the calls solution (Jitsy) inside Rocket Chat, but the experience was not good at all, issues with missing calls, poor call quality, etc, made us keep Skype for calls.   On the other hand CakePHP training was provided using Join.me and even when it worked very well in most situations; our trainer Jorge, always had to use a Windows machine instead of his usual Linux one. And then, Zoom emerged.     The year was 2018, when Zoom became so popular even though it started back in 2011 (yes, Zoom existed before COVID-19 crisis). We started using it and it quickly replaced Skype for our team calls. It allowed multiple people calls, screen share, etc. I must say, however, the Zoom chat is one of the worst things I have ever seen.   Going back to Jorge, as you can imagine he was very happy when he saw Zoom had a good Linux client. Unfortunately, he was quickly disappointed because the client screen crashed randomly, went black, and the computer ran out of memory when he tried to share his screen to more than 10 attendees. Happily he didn't throw his windows machine out of the window yet so he could continue giving the training with that machine.   It's 2020, COVID-19 is around us, and Zoom is probably the most popular telecommuting tool. For both adults and children, it is an essential tool to keep working and studying. However, fame never comes alone and threats, rumors and comments are making us move away (again) from Zoom to Google Meet. Also, it didn't make sense to pay for Zoom if we were already paying for GSuite.    I didn't mention it before, but we have been using GSuite (former Google Apps) since the beginning. Google - late as usual - detected a market niche and decided to pump out its meeting tool. In my opinion, I am a standalone app person. This means that I will always prefer having 20 apps instead of 20 browser tabs. So, I don't like Google Meet a lot because of this, but I must say the call quality is superb.    I am not sure how or when we will move to other tools, but right now we are very happy with our Rocket Chat installation and we are still getting used to Google Meet, but it fits our needs.    As a side note we are still using skype to communicate with clients because it is the app everyone has, and sometimes people simply don't want to install something else or use something they are not used to.     To conclude I must say that each team and person should try all of these different tools before choosing one, because one tool that may fits my needs may not fit yours.     

Links

Rocket Chat - https://rocket.chat/ Google Meet - https://meet.google.com Zoom - https://www.zoom.us Skype - https://www.skype.com Colloquy - https://colloquy.app/ Pidgin - https://pidgin.im/

CakePHP Virtual Meetup

Unless you’ve been on Mars for the past few months, you know that things have changed. We can no longer prance worry-free into a department store, or go grab drinks with friends. Everything is now masks, grocery delivery, and video chats. While this was a hard transition at first, we have surprisingly become well-adjusted.  

Virutal Meetups

Our past 2 CakePHP meetups have been in the form of virtual meetups, and it has been a huge success. Normally, meetups are restricted to a certain city, state or even continent in the world, but not anymore! We are over here like, “why haven’t we done this before?”. The idea that CakePHP lovers from all over the globe came come together, live, at the same moment and bake together… it’s amazing.    The first virtual meetup, was suggested and hosted by Mark Scherer (Dereuromark on Twitter). He came to the CakeDC team and asked about joining forces and using one platform to bring everyone together. While picking a meetup time is difficult, given the differences in zones, we tried to find a happy place in the middle of them all. The community team will let this vary from time to time for those, say in Japan, who still found it difficult to tune in. Luckily, even if the time isn’t ideal for you, we ALWAYS record and post the session for playback.    If you’ve missed these (you can still catch the playback!), and want to know more, it's a simple setup: the organizer can give access to any speakers (panelists), they present their talks/trainings and everyone gets to tune in from the comfort of their couch. No worries about background noise, as only speaker's mics are enabled. There is a Q&A feature that allows attendees to ask/post their questions in real time.  For the first meetup, the main topic was a CakePHP 4.1 preview, and we can spend all day on all of the new stuff in CakePHP 4. Also covered was: IdeHelper, Auth with Authentication plugin and fixture factories. We didn’t really know what to expect, or what would go wrong, but things went great! We had a lot of good feedback, which of course inspired everyone to do it again! We got Jorge on board (steinkelz on Twitter) and even tried out a new platform, RingCentral (shout out, thanks guys!). This meetup explored Baking a new plugin and TDD for it’, Authorization Plugin, Pro-Debug with Xdebug + stepping through in IDE, and Mark Story checked in to talk about common-table-expressions  

Missed the meetups?

PLAYBACK OPTIONS: April Meetup: LISTEN HERE  May Meetup: LISTEN HERE    My favorite part about these virtual meetups is that bakers from all over can get involved, and we get to hear from some people that without the digital world, wouldn’t be possible. The CakePHP community team is always looking for speakers for the next meetup, so if you’re reading this thinking, “i’d really like to talk about ___!”, then we want to hear from you! You can also submit for a lightning talk - the email is community@cakephp.org. We are just so thankful that we get to bring so many Cake lovers together with one common goal in mind, to bake and learn new recipes.   

There will be more!

So, how will you find out about future meetups? Easy! Follow CakePHP (or CakeDC) on social media: Twitter / Facebook. You can also sign up for the CakePHP Newsletter (and stay informed about other fresh news, too!). Training sessions can be found at training.cakephp.org.    Have an idea for the next topic? What do YOU want the speakers to talk about? Let us know. Our next session is scheduled for June 18. See details HERE.

PHP Quality Assurance Tools with CakePHP

In Software Development, we are always looking for resources to improve the products we deliver. In this article we show some awesome php qa tools to help us improve our CakePHP software quality and development process. Let's dive in. Note: The snippets provided in this article can be found at https://github.com/CakeDC/phpqa-app-example
 

Coding Standards

Choosing a coding standard for your project will help the developers team speak the same language by defining a set of rules on how to name variables, methods, class and etc. The coding standard usage can make the integration of community libs and plugins easier. Checking and fixing coding standards are easily done with the awesome tool PHP_CodeSniffer, this tool has two main scripts:
  • phpcs check for coding standard violations
  • phpcbf autofix coding standard violations, keep in mind that not all errors will be autofixed, but will help you a lot.
     
CakePHP offers a customized coding standards at https://github.com/cakephp/cakephp-codesniffer and you should be using it.
  Let’s install the tool and the coding standard: composer require --dev cakephp/cakephp-codesniffer:~4.1.0
Create the file phpcs.xml <?xml version="1.0"?> <ruleset name="App">    <config name="installed_paths" value="../../cakephp/cakephp-codesniffer"/>    <rule ref="CakePHP"/> </ruleset>
Update “scripts” block in composer.json  with: "scripts": {         "cs-check": "phpcs -p ./src ./tests",         "cs-fix": "phpcbf -p ./src ./tests",
  Now we can run composer cs-check and composer cs-fix. In a sample app I got this output saying some errors can be autofixed with composer cs-fix


Static Analysis

How many times have you deployed a project on a production server and found a silly error like calling an undefined method? I hope not often. To avoid this type of issue, a static analysis tool is useful. I recommend you to use PHPStan and PSalm, these tools will help you find errors without having to run the application.

PHStan

PHPStan will rely on phpdoc, so that’s one more reason to use a coding standard tool like PHP_CodeSniffer. Also I recommend that you use the plugin: cakephp-ide-helper, to update annotations (phpdoc) in your app classes. In some cases we need to help PHPStan understand our code better, and for this reason we created a custom extension: https://github.com/cakedc/cakephp-phpstan. Let’s install PHPStan using composer: composer require --dev phpstan/phpstan phpstan/extension-installer cakedc/cakephp-phpstan:^1.0
  We also need to create two config files Includes:     - vendor/cakedc/cakephp-phpstan/extension.neon     - phpstan-baseline.neon parameters:     level: 6     checkMissingIterableValueType: false     checkGenericClassInNonGenericObjectType: false     autoload_files:         - tests/bootstrap.php     ignoreErrors: services: parameters:     ignoreErrors:
  And add two scripts into “scripts” block from composer.json "stan": "phpstan analyse src/",         "stan-rebuild-baseline": "phpstan analyse --configuration phpstan.neon --error-format baselineNeon src/ > phpstan-baseline.neon",
Now we can run composer stan and  composer stan-rebuild-baseline the second one will populate phpstan-baseline.neon to ignore all errors returned in composer stan so only use when all the errors shown should be ignored.  

Psalm 

Psalm is another awesome static analysis tool, it provides a way to check for errors in your code, and can fix some of them, too. In my experience psalm and phpstan work perfect, so you don’t need to pick one or the other, you can use both. Let’s install psalm: composer require --dev  "psalm/phar:~3.11.2"
We also need to create two config files <?xml version="1.0"?> <psalm     allowCoercionFromStringToClassConst="true"     allowStringToStandInForClass="true"     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"     xmlns="https://getpsalm.org/schema/config"     xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"     errorBaseline="psalm-baseline.xml" >
   <projectFiles>         <directory name="src" />     </projectFiles>     <issueHandlers>         <LessSpecificReturnType errorLevel="info" />         <RedundantConditionGivenDocblockType errorLevel="info" />         <TypeCoercion errorLevel="info" />         <DocblockTypeContradiction errorLevel="info" />     </issueHandlers> </psalm>
  <?xml version="1.0" encoding="UTF-8"?> <files psalm-version="3.11.2@d470903722cfcbc1cd04744c5491d3e6d13ec3d9"> </files>
  And add two scripts into “scripts” block from composer.json "psalm": "psalm.phar --show-info=false src/ ",         "psalm-rebuild-baseline": "psalm.phar --show-info=false  --set-baseline=psalm-baseline.xml  src/"

Now we can run composer psalm and  composer psalm-rebuild-baseline the second one will populate psalm-baseline.xml to ignore all errors returned in composer stan, so only use this when all the errors shown should be ignored. When we run composer psalm it may inform you that some errors can be autofixed and which arguments you should use to fix. Normally it will be something like vendor/bin/psalm.phar --alter --issues=MissingReturnType,MissingClosureReturnType --dry-run
Psalm will only change the file If you remove the --dry-run part.  

Testing

Testing is extremely important for any application development. There are many types of software for testing, in this article we are focusing on unit tests. Unit tests help us check if one specific piece of code is working as expected, you can think like ‘Is method ‘A’ returning the correct value when argument has value ‘Z’?’. In CakePHP we have built-in support to PHPUnit, CakePHP integration offers additional features to make it easier to run units tests like custom asserts and methods for integration tests, and fixtures logic for models. We can bake unit tests using the bake plugin. To improve your unit tests try the cakedc/cakephp-db-test with this one you can use database fixtures instead of files. Learn more at: https://www.cakedc.com/rafael_queiroz/2020/04/27/unit-testing-with-cakedc-db-test PHPUnit probably already is installed in your app but if not I recommend you to compare your project files with: https://github.com/cakephp/app. For more information check out: https://book.cakephp.org/4/en/development/testing.html   Hope you enjoyed this article and will start using these awesome tools to make your projects better than ever. Good Baking.  

Working with CakePHP Authorization

As you may know, there are 2 new plugins "recently" (not so recently) added to deal with the concepts of Authentication and Authorization in your CakePHP applications. Over the years, both Authentication and Authorization were managed in the Controller layer, via AuthComponent.  These 2 things usually grow in complexity as your project grows too, making the AuthComponent a complex class dealing with many features at the same time.
One of the original ideas behind these new plugins was to refactor AuthComponent and create specific layers to handle:

  • Authentication: who are you?
  • Authorization: are you allowed?
We are going to explore the Authorization concepts in this article using a specific example: Let's imagine we have some kind of game application where Users are going to manage Tournaments. The Users will be able to create new Tournaments, and join the Tournaments through a TournamentMemberships many to many association. Other users won't have access to the Tournaments unless they are invited to play. Players of a Tournament can invite other Users to play. So, a quick list of the use cases we are going to cover below are:
  • /tournaments/add  any user can create a new Tournament
  • /tournaments/index  browse all joined tournaments
  • /tournaments/invite  only current Members can invite others, and only if the Tournament has not started yet
We are assuming Authorization step is done in our application and we have a logged in user available in our request. At this point we'll also assume you've installed cakephp/authentication and cakephp/authorization and loaded both plugins. Authorization does not impose restrictions  on when the authorization checks will be done, let's quickly examine the workflow and related classes for Authorization:
  • AuthorizationMiddleware is attached to your Application, and will ensure the Authorization will be checked somewhere while processing the request.
     The unauthorizedHandler config will allow you to define what to do if the request was not authorized for some reason.
  • At some point in your code, you'll need to call AuthorizationComponent, either to
    • skipAuthorization when you don't require any specific condition to authorize the operation. Example: // ... somewhere in your beforeFilter...     if ($user->is_superadmin) {         $this->Authentication->skipAuthorization();     } // ...
    • authorize($resource, $action) when you need to check if a given user is allowed to do some action on a given resource. Note the resource must be an Object.


How Authorization checks are done?

  1. We start by checking the resource, it's an Object so we use a Resolver to map every resource with a given Policy. There are some common defaults, for example to map ORM classes.
  2. Once we get to a Policy class, we check the matching method, for example if the action is "invite" we would check the method canInvite(IdentityInterface $user, Tournament $tournament)
Configuration: After the Authentication middleware, in your src/Application.php class, add the Authorization Middleware            $authorizationService = new AuthorizationService(new OrmResolver());             ...             ->add(new AuthorizationMiddleware($authorizationService, [                 'unauthorizedHandler' => [                     'className' => 'Authorization.Redirect',                     'url' => '/users/login',                     'queryParam' => 'redirectUrl',                 ],             ]));   Note the $authorizationService is configured with one resolver to match the CakePHP typical ORM classes, like Entities or Queries. https://book.cakephp.org/authorization/2/en/policy-resolvers.html#using-ormresolver   Once the middleware is added, you'll need to ensure the Authorization is checked, or you'll get an  error?: "The request to / did not apply any authorization checks" . The first step would be to skip authorization for all the controllers and actions, for example in beforeFilter callback that all Users are allowed to access. About the previous Tournaments specific cases, we'll need to create a new Policy class including all the possible actions to be done, for example:
 
  • /tournaments/add
We need to create a new Policy for the Tournament Entity file src/Policy/TournamentPolicy.php to define policies related to specific tournaments class TournamentPolicy {     public function canAdd(IdentityInterface $user, Tournament $tournament)     {         // all users can create tournaments         return true;     } } file src/Controller/TournamentsController.php // ...     public function add()     {         $tournament = $this->Tournaments->newEmptyEntity();         $this->Authorization->authorize($tournament);         if ($this->request->is('post')) { // ... The call to $this->Authorization->authorize($tournament); will map the Tournament entity to the TournamentPolicy, by default the action is taken from the controller action, in this case "add" so we will need to define a canAdd() method. We allowed all Users to create Tournaments.  
  • /tournaments/index
We'll need to create a new policy for the TournamentsTable, and additionally a scope method to filter the Tournaments based on the current User membership. file src/Policy/TournamentsTablePolicy.php to define policies for the TournamentsTable class TournamentsTablePolicy {     public function canIndex(IdentityInterface $user, Query $query)     {         // all users can browse tournaments         return true;     }     public function scopeIndex(IdentityInterface $user, Query $query)     {         // scope to filter tournaments for a logged in user         return $query->matching('TournamentMemberships', function (Query $q) use ($user) {             return $q->where(['TournamentMemberships.user_id' => $user->get('id')]);         });     } } file src/Controller/TournamentsController.php     public function index()     {         $query = $this->Tournaments->find();         $this->Authorization->authorize($query);         $tournaments = $this->paginate($this->Authorization->applyScope($query));           $this->set(compact('tournaments'));     }
 
  • /tournaments/invite
file src/Policy/TournamentPolicy.php to define policies related to specific tournaments // ...     public function canInvite(IdentityInterface $user, Tournament $tournament)     {         return TableRegistry::getTableLocator()->get('TournamentMemberships')             ->exists([                 'user_id' => $user->get('id'),                 'tournament_id' => $tournament->get('id'),             ]);     } // ... file src/Controller/TournamentsController.php // ...     public function invite($tournamentId, $userId)     {         $tournament = $this->Tournaments->get($tournamentId);         $this->Authorization->authorize($tournament); // ...   In this case, we need to check if the logged in User is already a member of the TournamentMemberships group, if so, we are allowed to invite another user. As you can see, Authorization plugin will provide a flexible way to manage your application permissions.   In the previous examples we've covered typical application use cases to handle permissions per resource and action. New classes and interfaces, like policies, resolvers and mappers will allow you to configure the Authorization and ensure all the resources in your application will provide the required permissions. If you're looking for RBAC based on your controller actions, take a look at https://github.com/CakeDC/auth/blob/master/Docs/Documentation/Authorization.md For additional tools and plugins, check https://github.com/FriendsOfCake/awesome-cakephp#authentication-and-authorization  

Benefits of S.E.O

As a marketer that works with web developers daily, I know that content may not be top priority on their long list of to-do’s. However, for the success of a company, it is essential. If your team doesn’t have a designated marketing person or team, and you’re not a seasoned content creator, I have 3 letters for you to learn: S.E.O.   So what is SEO? It stands for search engine optimization. Basically this represents guidelines for gaining traffic to your website (or one you’ve been hired to create), and doing it organically. What is organic traffic? This is the results people see without being targeted, no paid ads, no cold calling - just desired results because your company offers something that they are interested in.    Today’s market is extremely competitive, so it is important to take every step in making sure that your webpage stands out and is easy to find. Think about how you find information daily… how to make fried chicken? Where to get your car fixed? Or even when a new movie is being released? You search for it, online, right? 9 times out of 10, you’re probably going to run a search for it on a site like Google, right? Then, most likely, you’re going to click on one of the first couple results that pop up on your screen, because they include keywords relevant to the search that you performed. This is an example of SEO. You search for a term that is relevant or appears on a company’s website, and Google recognizes that term/phrase and yields the webpage to you as a result. Thus, increasing traffic for the website, and a lot of times, without any cost to them.    Utilizing this idea, or service, is essential for any marketing department. Actually, according to a recent survey done by HubSpot, about 64% of marketers actively invest time in search engine optimization. So if you're not, you're falling behind.     Now that you have a basic understanding of what SEO is, we can talk about some of the benefits.   

1. Better PR

  The better your SEO is, the more people that will see your company name pop up on their search engine results. While most companies, like CakeDC, offer specific services (and input specific keywords into ads), we have very detailed SEO streaming from our website so that we do not miss a potential client. It is the goal to be in the top 3 results of a search engine like Google.   Even if someone is searching for something your company doesn’t offer, they may see you, remember you, and use you in the future. win-win. Start building your reputation! For example if you wanted to search for CakePHP web development, you may see:    

2. Increased Traffic 

  This is a no brainer. The more keywords that trigger your webpage at the top of consumer results, the more people that will click. This generates better quality leads, in my opinion. Things like cold calling, or cold emailing, while still effective, have become outdated. Inbound marketing compared to these avenues has produced better return, without becoming annoying to your potential leads. When you aim your focus on specific consumers looking for services that are related to your business, you can build better relationships.   

3. FREE

  Perhaps the best benefit of SEO? The cost… or lack of cost. SEO gets clicks, and sales (we hope!) for the wonderful price of $0 per month. How? Well, Google’s organic rankings come from their algorithm, which determines which webpage’s information relates closely to the “searcher’s” inquiry. This can result from keywords coming from your web pages that may not be imputed on any sort of ads you may be running (we will talk about paid ads in a future blog).    This doesn’t mean that getting a perfect SEO score (yes, you can test it) comes for free. It is important to have good content, detailed content, in all areas on your webpage. Make sure each page describes services you provide, or products that you sell, in great detail. Have pages for each option, and make it accessible like this:       It’s time to beat out the competition, are you ready? I first recommend seeing where you stand. There are a few tools you can use to test your SEO score. One example is: Woorank. I like this site because it gives you your score, shows the good, the bad, and the ugly. The tool also shows you what you can and should do to improve your SEO. You can try most tools for free, or get extra optimization help by paying a premium.    Once you run a report, it’s time to get to work. Fix the issues, and constantly monitor your information. I do think it’s important to mention - don’t expect results to happen overnight, it can take up to 6 months for SEO strategies to yield return. If you’re like me, and impatient, just chill. Put the work in, and get the reward.  

Unit Testing with CakeDC DB Test

The only way to go fast, is to go well, my Uncle Bob always said. Research has shown that development with TDD evolves 10% faster than work without TDD. [See here] CakePHP comes with comprehensive testing support built-in with integration for PHPUnit. It also offers some additional features to make testing easier. This article will cover how to write Unit Tests with CakePHP and using the CakeDC DbTest plugin.
First, let's bake a new project: composer create-project --prefer-dist cakephp/app:4.* Now, we need to think  about a model so we can create it and test it. I guess everybody has written a Products model before, our model would looks like this:

  • Name (string)
  • Slug (string, unique)
  • Description (text)
  • Price (decimal)
If you are not familiar with Slug, Slug is the part of a URL that identifies a page in a human-readable way, usually for pages with friendly urls. It will be the target of our tests. bin/cake bake migration CreateProducts name:string slug:string:unique price:decimal[5,2] description:text created modified Pay attention, for slug, It was created with a unique index. Meanwhile our goal will be to have urls like: /slug-of-product and this way, the slug needs to be unique.
Let's run the migrations for database: bin/cake migrations migrate At this point, our database is ready with the `products` table and we can start coding and writing the tests. * Note: some points were abstracted, such as installation, project configuration, and shell commands, because that's not the goal of the article. You can find all information on these in the cookbook.
Let's bake the models, controller, and templates for Product: bin/cake bake all Products
Now that we have all the Classes we can start writing the unit tests. Let's start with ProductsController, writing one test for add Product: tests/TestCase/Controller/ProductsControllerTest.php public function testAdd(): void     {         $this->enableCsrfToken();         $this->enableRetainFlashMessages();         $this->post('products/add', [             'name' => 'iPhone 11',             'slug' => 'iphone-11',             'price' => 699,             'description' => 'Lorem ipsum dolor sit amet, aliquet feugiat.',         ]);         $this->assertResponseSuccess();         $this->assertFlashMessage(__('The product has been saved.'));         $this->assertRedirect('products');     } Let's write another test that tries to add a duplicated product. First, we need to update the fixture, then write the test: tests/Fixture/ProductsFixture.php     public function init(): void     {         $this->records = [             [                 'id' => 1,                 'name' => 'iPhone SE',                 'slug' => 'iphone-se',                 'price' => 399,                 'description' => 'Lorem ipsum dolor sit amet, aliquet feugiat.',                 'created' => '2020-04-23 13:12:58',                 'modified' => '2020-04-23 13:12:58',             ],         ];         parent::init();     } tests/TestCase/Controller/ProductsControllerTest.php public function testAddDuplicated(): void     {         $this->enableCsrfToken();         $this->enableRetainFlashMessages();         $this->post('products/add', [             'name' => 'iPhone SE',             'slug' => 'iphone-se',             'price' => 399,             'description' => 'Lorem ipsum dolor sit amet, aliquet feugiat.',         ]);         $this->assertResponseSuccess();         $this->assertFlashMessage(__('The product could not be saved. Please, try again.'));         $this->assertNoRedirect();     }   With these tests, we know the work is complete when the acceptance criteria (the slug of product must be unique) of the tests is passed.
That's all? No, this article it's not only about tests, this article is about the CakeDC DbTest plugin and how it can be advantageous.

CakeDC DB Test

Maintaining fixtures on real applications can be hard, a big headache. Imagine writing +1k products on ProductFixture, and adding a relationship like Product belongs to Category, then having to write new fixtures and keep them in sync. Real applications usually have features like authentication with ACL, where each User has one Role, and each Role can access many features. Administrator has full rights, Manager has many rights, and so on.
Keeping all of this information in our fixtures is painful. Most of the frameworks have plugins to help with that issue. Thanks to the CakeDC team, we can easily let the DbTest to do the "dirty" work for us: Let's install and load the plugin: composer require cakedc/cakephp-db-test:dev-2.next bin/cake plugin load CakeDC/DbTest
Then configure the plugin on project:
  1. Copy/replace the phpunit.xml: https://github.com/CakeDC/cakephp-db-test/blob/2.next/phpunit.xml.dbtest
  2. Configure test_template datasource on config/app.php:
'Datasources' => [     // ...     'test_template' => [         'className' => Connection::class,         'driver' => Mysql::class,         'persistent' => false,         'timezone' => 'UTC',         //'encoding' => 'utf8mb4',         'flags' => [],         'cacheMetadata' => true,         'quoteIdentifiers' => false,         'log' => false,         //'init' => ['SET GLOBAL innodb_stats_on_metadata = 0'],     ],     // ...
Now, we can delete our fixture and generate the dump of our database for using on tests: // migrate the database for template bin/cake migrations migrate -c test_template // import fixtures bin/cake fixture_import dump // generate dump /bin/cake db_test -i   Finally, we can see some advantages of CakeDC DbTest:
  • Speed.
  • Maintain fixtures with your regular database tool.
  • Run migrations to your dbtest db too.
  • Copy data from your live db to reproduce bugs.
     
That's all, bakers. Now we have test_db.sql, and you can see how our fixtures will come from this data. You can check the code source of this article on this repository: https://github.com/rafaelqueiroz/cakephp-db-test-sample    

CakeFest Is Going Digital!

What a strange unprecedented time we are living in, right? Thanks, COVID-19. CakeFest, just like many other conferences and events, has been up in the air. Our team spent weeks discussing the possibility of moving forward as planned, having backup plans, or ways out of contracts should the emergency continue. After negotiations with pending sponsors, venues, vendors, etc., we toyed with the idea of going virtual and following suit of many others. After discussions with the core team, it was decided that this was the way to go.    This virus infused world is uncharted territory for billions of people, but one thing remains the same among us all…. the safety and health of our clients, families, and teams is the number one concern and priority. I’m sure everyone has encountered those who don’t understand these types of decisions, but allow me to put some numbers into perspective: Currently, there are 1,696,588 cases of COVID-19 worldwide, and 105,952 deaths have been reported  (as of April 12, 2020 via World Health Organization). According to hopkinsmedicine.org: “The virus can spread between people interacting in close proximity—for example, speaking, coughing, or sneezing—even if those people are not exhibiting symptoms. In light of this evidence, CDC recommends wearing cloth face coverings in public settings where other social distancing measures are difficult to maintain (e.g., grocery stores and pharmacies) especially in areas of significant community-based transmission.”    So what I am reading is that someone who has it, may not even know they are carrying the virus, and can spread the germ by just being in the same area as a peer. This is even more frightening due to the fact that there’s no research to this new virus, therefore no cure or vaccine to combat its effects on those infected.    With the statistics and facts about this virus, we made the difficult decision to go digital for CakeFest 2020. We understand that our intended dates in October are still far away, but without knowing how regulations and orders will change from government officials, it is hard to plan such an important event. Hopefully after learning more about how we came to this decision, you will understand and support us. We were extremely excited to bring the Cake to Los Angeles, but not at the cost of anyone’s health. We also hope to still deliver the knowledge, information, and possibly some ElePHPants to our virtual attendees this year (while also keeping you safe). The good news is, we can host in LA next year! And look at it this way, you can watch and learn in your pajamas!    So what will CakeFest look like this year? We are still in the planning phases, and allowing for sponsors. After a lot of research we hope to find the best platform for our needs, and the best speakers to share the knowledge. Speaking of which, if you have tips or suggestions for hosting a virtual conference, we’d love to hear from you! You can email us.  Tickets will be sold (for less cost, there's a silver lining) for full access to all videos - before and after the event. We will conduct the conference as normal, but the difference is, we won’t be face to face   To keep up with the planning process or any changes, make sure you follow us on Twitter, or Facebook. You can always stay up to date at CakeFest.org, too.   

Improving your mental health while wor...

We’ve previously covered tips, tricks and resources to improve your working at home productivity - today we chat about how to improve your mental health!  There are currently more and more people switching over to remote working - perhaps your company has changed policies, or maybe you’ve been forced to work at home due to a stay-at-home order - whatever the case, mental health while working at home is vitally important. Some of us are made to work at home - working from an office, may not be your jam, and the peace and serenity that a home office offers completes the mood.  However, there are some of us that enjoy the daily routine, the commute to the office and the morning catch up around the coffee machine!  So have you been stuck lately feeling a little more down than usual? Here are some tips to increase your mental health during this time.

Keep your morning routine

Even though you are working from home, and technically can get away with wearing your pajamas all day - you definitely shouldn’t! A morning routine helps set the mood for the day. Perhaps this includes getting ready, dressed or going for a morning run. 

Exercise and eat well

We aren’t talking about a full gym session - merely taking some time to move around. A walk around the block or a bit of stretching will help get those endorphins going. A good diet also goes a long way to helping you feel better - unfortunately too much sugar and too many carbs can have a negative impact on your mental health.

Focus on the now

Practice mindfulness - the ability to focus in on the here and now helps us to drop the negative or difficult emotions that weigh us down. 

Open up to someone

You are valued by others - knowing that is important. Finding a safe space to chat about things goes a long way to lifting the mood. Not sure where that safe space is? Check out https://osmihelp.org/ for some great resources and information.

Take a break

Working from home can sometimes feel endless - some of us struggle to log off at “home time” because, well, you are at home. But this important step is essential to your mental health. Take moments throughout the day to step away - just a couple of minutes to grab some water and take a break. If you haven’t already, please sign up and donate to OSMI - Open Sourcing Mental Illness. We’ve previously chatted with Ed Finkler, OSMI’s founder. CakeDC fully supports OSMI and we love opening up the conversation around mental health - Do you?

We Bake with CakePHP