CakeDC Blog

TIPS, INSIGHTS AND THE LATEST FROM THE EXPERTS BEHIND CAKEPHP

Quick start with Migrations plugin

In a previous post I gave an overview of the CakePHP Migrations plugin, what it does and why you should use it in your applications. This article will explain how to use it in a practical way. We are going to bake a simple blog application recipe application and see how migrations are integrated in the development process.

Since we recently moved all our open source projects on http://cakedc.github.com/, this sample application source code is also available there: Sample Migrations Application - Github (it is a CakePHP 1.3 application). Ready?

Bake a new application and add the migrations plugin

First of all, we need to bake a new CakePHP application. Easy enough to do using cake bake, then configure your database (an empty database is sufficient for now) and check that the home page is all green! If you have not set up your environment to use the CakePHP command line yet, take some time to do so... it worth it!

Adding the migrations plugin might also be a straightforward task. You can either download the archive containing the plugin code and unzip it in the "/plugins/migrations" folder of your application, or  add it as a git submodule with the following command:

git submodule add git://github.com/CakeDC/Migrations.git plugins/migrations

Then check that it is correctly installed by executing the following command from your application root:

cake migration help

If you see a list of available commands you can move on next step.

Create initial tables and bake the MVC

We now need something to migrate! Let's create some tables in the database. The application will have Users who can publish Recipes, each one having several Ingredients (of course Ingredients can be used in many Recipes). Here is a SQL dump of this simple database schema:

CREATE TABLE `ingredients` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(100) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

CREATE TABLE `ingredients_recipes` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `ingredient_id` int(11) NOT NULL,
  `recipe_id` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

CREATE TABLE `recipes` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL,
  `name` varchar(100) NOT NULL,
  `content` text NOT NULL,
  `created` datetime NOT NULL,
  `modified` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(100) NOT NULL,
  `password` varchar(255) NOT NULL,
  `created` datetime NOT NULL,
  `modified` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1;

As our goal here is not to focus on the application code itself, baked MVC from these tables might be sufficient... just run the command cake bake all for User, Recipe and Ingredient to bake'em all!

At this point we must have an application with an initial architecture ready to share. To start from here, one will just have to checkout the related commit... but don't you see a problem with this? How will he create the initial database? Maybe we could send him the SQL dump by email, or better commit it with the application! It is where the Migrations plugin comes in.

Generate the initial migration

"Be kind with your coworkers and include the database schema with your code... along with some sample data."

Let's use the migrations shell to generate an agnostic database schema containing our 4 tables, and an initial admin user account. To do so we just need to run the following command:

cake migration generate

After entering a name for the migration and selected the database dump option, we might have a new "/config/migrations" directory containing two files:

  • map.php representing the different migrations order,
  • name_of_the_migration.php a migration file containing all the necessary information to create your actual database. In the sample application it is named: "001_added_users_recipes_and_ingredients_tables.php". You might have noticed that we added a 001 prefix to the migration name to make it easier to see migrations order, it is a good practice.

We can now open the generated migration file (/config/migrations/001_added_users_recipes_and_ingredients_tables.php) and take a look at it. If you need more information and understand all available migration directives, you can read the plugin documentation.

For now we are just going to focus on the empty "after()" callback. This callback is triggered once the migration has been executed, and allow you to do whatever you want, given the direction of the migration: applied (up) or reverted (down). We are going to use this callback to create an initial admin User. Here is the code of the callback (as you are a CakePHP developer you might understand it quite easily):

function after($direction) {
	if ($direction === 'up') {
		if (!class_exists('Security')) {
			App::import('Core', 'Security');
		}

		$User = $this->generateModel('User');
		$user = array(
			'User' => array(
				'name' => 'admin',
				'password' => Security::hash('unsecurepassword', null, true)));
		$User->save($user);
	}
	return true;
}

Notice the use of the generateModel() method provided by the Migrations plugin. It is a shorthand allowing you to cleanly load a model in the callback to insert new data or update the existing. We could explain the reason of it more deeply but it is not the goal of this article, so just keep in mind that it is the best way to load a Model from callbacks!

Here we are! We can now share the application with anyone. After checked out the application, one will just have to run cake migration all to turn an empty database to a database containing all the needed tables, and an initial admin user to start using the application.

Categorize the recipes!

As the application evolves, we need to sort recipes by categories. This change involves two changes in the current database schema: a new categories table must be created, and a category_id field added to the recipes table.

Note: If you later want to use the migrations diff feature to generate a migration containing a diff between your previous database schema and the current one, you have to generate a Cake Schema of your database at this point. Simply run cake schema generate.

We can now update the recipes table and create a new categories table. Here is a simple SQL script:

CREATE TABLE `categories` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(100) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1;
ALTER TABLE `recipes` ADD `category_id` INT NOT NULL

Bake the MVC for categories and update recipes view pages to display the category so the application reflect these database changes. Before sharing these code changes, we need to generate a second migration describing the above SQL snippet in an agnostic way... and creating initial categories!

Nothing different than what we did previously: run cake migration generate, give a name to the migration, and choose between generating a diff from the schema.php file (if one was generated), generating a dump of the database (we will remove unnecessary instructions later) or generating an empty migration file. Once generated, it is always important to check the generated directives for the migration and fix them if needed. The migration must look like this:

var $migration = array(
	'up' => array(
		'create_table' => array(
			'categories' => array(
				'id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'),
				'name' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 100),
				'indexes' => array(
					'PRIMARY' => array('column' => 'id', 'unique' => 1),
				),
				'tableParameters' => array('charset' => 'latin1', 'collate' => 'latin1_swedish_ci', 'engine' => 'MyISAM'),
			),
		),
		'create_field' => array(
			'recipes' => array(
				'category_id' => array('type' => 'integer', 'null' => false, 'default' => NULL)
			),
		),
	),
	'down' => array(
		'drop_table' => array(
			'categories'
		),
		'drop_field' => array(
			'recipes' => array(
				'category_id'
			),
		),
	),
);

If you understood what we did in the first migration callback to add an initial user you might be able to implement this one. We would like to add initial categories: Starters, Main Dish and Desserts.

For lazy people, the code is here:

function after($direction) {
	if ($direction === 'up') {
		$Category = $this->generateModel('Category');
		$categories = array(
			array('name' => 'Starters'),
			array('name' => 'Main Dish'),
			array('name' => 'Desserts'));
		$Category->saveAll($categories);
	}
	return true;
}

Here we are again! The changes are ready to commit, and the commit will contains both code and database changes. One could update the database after checking out this commit by running: cake migration all.

The end

I hope this very simple use case and the code we built will help you to start using Migrations. As you could see it is very simple to use and will make your life much more easier: you would not have to worry anymore about the state of your database schema.

The source code of this tutorial is available on Github. If you found any bug or have any suggestion about the Migrations plugin, please create a ticket on Github. Comment this article if you have any question, and do not hesitate to share it if you found it useful!

Latest articles

How to take a rest in five minutes

There are a lot of toys these days. Let me show you one very simple and powerful brick for your next revolutionary invention. Let's assume that you're either:

  • A curious person, and you like to play with things you don't know, just for the case
  • A frontend developer with no PHP coding experience, and your javascript components could use some dynamic backend json resource. Maybe you want to do this without playing with all mocking options or Node.js modules out there...and you don't want to set up MySQL database, user, grant permissions and all that stuff
  • building dynamic user interface, and your code needs to be aware of continuously changing structure of databases using unpredictable table names
  • A creator of a wireless network of IoT devices or swarm of flying educational robots. Or, looking for some stable and performant data backend running on your central Raspberry Pi Zero W with tmpfs mounts and ridiculously huge memory card, while sniffing around for bluetooth signatures
  • aware of services like ngrok and wanting to consume payloads of some IFTTT webhooks locally. Or, allowing a friend with Postman to access and/or feed up your database created (or temporarily exposed) just for that reason.
  • Having a rich html application and flirting with the idea of full blown CakePHP backend running in kiosk mode of the DraftCode application right on your iOS device
  • able to pack a PHP application as a single file Phar archive. For example: to simplify all sorts of domain logic/responsibility distribution across bazillions of machines spawned on demand and utilizing jq in your provisioning scripts
  • praying for headless access to any database that your CakePHP application is able connect to, bypassing implemented validation, callbacks, events, and even sneaking through your authentication/authorization backdoor if you want to
  • going to try following example right away, or at least very soon
If your machine meets the CakePHP Requirements, and you have composer in your $PATH then we are good to go for a CakePHP application using SQLite database accessible through REST. This would have obvious api goodies like CRUD, CORS, HATEOAS, versioning, list, describe, relations, sorting and pagination included by default.

Clock is ticking, time to rest with some book(s)

I don't know how much time of the rest limit we lost on the preface (quick learners are quick readers), but don't worry, unless you are on the slow internet connection and packagist is taking a break - we are almost done. We have a clear picture of what we're going to create, so the rest is a piece of cake.
  • create a CakePHP 4.x project named rest with CakeDC/Api plugin unlocked and loaded:
composer create-project \ --prefer-dist --no-interaction \ cakephp/app:~4.0 rest cd $_ composer config \ minimum-stability dev composer require \ cakedc/cakephp-api:~8.0
  • Unlock and load! Auth bypass, "allow all" switch (don't try this at home)
cp vendor/cakedc/cakephp-api/config/api_permissions.php.default \ ./config/api_permissions.php bin/cake plugin load CakeDC/Api
  • manually (meh, we should be resting now) configure Database.default connection in the file config/app_local.php
'database' => TMP . 'rest.sqlite', 'driver' => 'Cake\Database\Driver\Sqlite',
  • create example table books in the database and start local server
bin/cake bake migration create_books \ title:string:unique \ description:text \ price:integer:index \ available:boolean:index \ created \ modified bin/cake migrations migrate bin/cake server Open http://localhost:8765/api/books in your browser to see the (empty) list of your books, then pick some RESTful Route and take a rest from the back end, even without backend coding, scratching who knows where, etc. You maybe have some SQLite relation databases laying around, and that would be also worth a try. I forgot to remind you to stop that 300s timer, sorry. You will get lost in time as well, once you'll realize what all is possible with the CakeDC/Api plugin using little to no configuration, and what dimensions are opening to you. If you combine its powerful multilayer services approach with solid implementation of your custom ORM classes embraced by CakePHP core and conventions, the results are amazing. Oh, and a quick note for CakePHP 3.x developers - we like you, but please take your time to upgrade your toolset. Feel free to ask us for help, or use following warranty-limited commands: composer create-project \ --prefer-dist --no-interaction \ cakephp/app:^3.9 rest composer require \ cakedc/cakephp-api:~7.0 bin/cake plugin load -b -r CakeDC/Api

What's next

CakeFest 2020 of course. I hope you all are as excited as we are, together in these hard times. Be safe and take care, folks! And rest too, even if it's a quick one :-)

CakeFest Insider

In case we haven’t reminded you enough lately, CakeFest 2020 is less than a month away. While we have recently hosted a few virtual meetups, a fully virtual conference is uncharted territory for our team. I like to look on the bright side of things, so I will say how excited I am that bakers from all over the world will be able to join in and participate this year. Obviously, with international travel, this hasn’t always been the case for individuals. So my last CakeFest blog, I went into details of what to expect, and how the conference will (hopefully) run - smoothly. However, we’ve had a lot of emails and interest in hearing about what topics will be covered. Our lineup is excellent this year - so we wanted to share some things to look forward to:  

SPONSORS

First, we have to give a shout out to this year’s sponsors Cake Development Corporation  Passbolt Mark’s Software  RingCentral Companies that support Open Source are essential for our communities to move forward and grow!   

SCHEDULE

Day 1 will feature our popular workshops. These sessions are different than normal conference talks because they are basically training sessions to help grow your recipe book. You can see examples of previous years HERE. There will be basic workshops, as well as advanced during the full day session. We have condensed this year’s duration, so what is normally 2 days of workshops will be done in 2 hour sessions by each baker.  Mark Story, Jorge Gonzalez, and Jose Lorenzo are back. This year, we’ve also recruited another core member, Mark Scherer… you may be familiar with Mark from hosting many of our virtual meetups.  Topics that our core members MIGHT include are (these are being discussed): CakePHP 4.x, as well as project examples -  a repost builder, a headless micro cms, a media server to upload/download cache files from S3. The speaker’s will build through a project with you, answering questions as they progress.  Have an idea you’d like the workshops to include? Email us! Cakefest@cakephp.org.
  Now, day 2. This year, we decided to go ahead and try a few new things. Of course being 100% digital, condensing times, and some topics and speakers that may be unexpected. I personally invited some speakers that I thought could bring something fresh to the tech conference table.  One of these being OSMI (Open Source Mental Illness). I wanted to bring mental health importance to light, and this group is doing amazing things for the development world. Mental health doesn’t have to be a taboo subject. Dr. Jennifer Akullian will be talking about mental health, stress, and burnout in the tech industry. In addition to an overview of the research, distinctive considerations of the industry we work in will be discussed, as well as how to improve the management of common stressors in technology during an incomparable time in the world. We will also hear how some people got their start, like Michael Hoffman, and learn how to build solid architecture with CakePHP Plugins thanks to Ed Barnard. Ever wondered how to release your own plugin? Jose Gonzalez will shed some light. We are also delighted to have one of our involved Japanese community bakers, Junichi Okuyama, joining us as a speaker this year talking about helpful tips that he has learned for baking with CakePHP. Our keynote will be given by our diamond sponsor representative from Passbolt, Remy Bertot.  Other talks will include: well known podcast host Cal Evans talking about all of the cool things that can be done with PHP's built in DateTime math, Mariano Iglesias,  core members Mark Story, Chris Nizzardini and Mark Scherer. Popular contributors Juan Pablo Ramirez and Nicolas Masson will join us and share more details about CakePHP fixture factories.   You can see the full schedule HERE, and plan your attendance accordingly. 
 

SLACK CHATS 

We will also have slack chat rooms for attendees. This will give everyone the opportunity to interact with other watchers as well as speakers. Questions are welcomed before, during, and after the event. We will have team members monitoring the chat and trying to get all questions answered as soon as possible.   Slack channel tag: #cakefest  So basically, have a coffee and a chat with other attendees and presenters.    Have you purchased your ticket yet? WHAT ARE YOU WAITING FOR?    

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.

We Bake with CakePHP