Daves dev warblings

For all the things I never remember

Upgrading Session to Flash components in Cake 2.7

Changes in 2.7

So one of the major changes which was made to CakePHP with the 2.7 release was that the flash messages from the Session component have been refactored into the Flash Component, as they were with Cake 3.

You can view the change in the migration log.

How to update?

If, like me you’re using the setFlash method for all your controller feedback, you’ll need to update all that code.

I used this regular expression which captures the various params and re-orders them for replacement in PHP Storm.

1
2
3
4
5
// Regex search
this->Session->setFlash\((.*),\s(.*),\s(.*)\);

// Replace
this->Flash->set($1, ['params' => $3]);

This will convert stuff like this,

1
2
3
$this->Session->setFlash('Provider updated successfully', 'NiceAdmin.alert-box', array('class' => 'alert-success'));
// into
$this->Flash->set('Provider updated successfully', ['params' => array('class' => 'alert-success')]);

So that’s it, you can run this per script if you want to check it, or just across all your controllers.

All done!

Time for a brew!

How to configure flash messages using Crud plugin

Scenario

You’re using the Crud plugin you might find that it will use the default flash message elements, so if, like me, you want to use something like Twitter Boostrap Alerts, you’re out of luck.

Never fear, help is at hand.

Customise flash

1
2
3
4
5
6
7
8
// src/Controller/AppController.php
public function beforeFilter(Event $event)
{
  $this->eventManager()->on('Crud.beforeHandle', function () {
      $this->Crud->action()->config('messages.success', ['params' => ['class' => 'alert alert-success alert-dismissible']]);
      $this->Crud->action()->config('messages.error', ['params' => ['class' => 'alert alert-danger alert-dismissible']]);
  });
}

I tend to put this, as per the example, in my AppController because that way it will only configure the messages for any action which will be handled by the Crud plugin, but across all my controllers.

Success!

That’s it! Go make a brew.

Finding neighbours in CakePHP3

So it seems that in Cake 3 you can no longer use the find('neighbors') to find posts which are next to each other according to a defined series.

As I tend to use this for previous and next blog posts when viewing a blog post, I’ll note down my technique here for future reference.

The method

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// src/Model/Table/ExamplesTable.php
/**
 * Find an item from a table by slug, along with it's two adjacent items
 *
 * @param string $slug
 * @return array
 */
public function neighbours($slug)
{
    $current = $this->find()
        ->where(['slug' => $slug])
        ->first();

    $previous = $this->find()
        ->where(['publish_date <' => $current->publish_date->format('Y-m-d')])
        ->order(['publish_date' => 'DESC'])
        ->first();

    $next = $this->find()
        ->where(['publish_date >' => $current->publish_date->format('Y-m-d')])
        ->order(['publish_date' => 'DESC'])
        ->first();

    return [
        'current' => $current,
        'previous' => $previous,
        'next' => $next
    ];
}

The finder

I’ve not been able to develop a finder for this as a custom find only gets passed a single query object and in order to do a Union between the previous and next you’d need to join two query objects.

There is also the fact that you need to execute the query to return the result of the parent item so that its value can be passed to the query to find the siblings.

If you have any ideas on how to tackle this challenge, please let me know in the comments.

Making a calendar

How to generate a calendar using PHP?

At work I built a new holiday calendar system to replace the old system which was being closed down. The company needed a way for employees to request holiday and also managers to check booked holidays by team.

This is what it looks like once it’s been styled.

Why have I written my own code?

A few reasons really, primarily because it’s a learning experience and secondly because I don’t recall being able to find a decent library with did it. No doubt there are a few, if you know any, please leave a comment and I’ll update this post, thanks!

Calendar code

Just a point to note, this is written in a CakePHP view, and uses a table.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<table summary="calendar" class="table table-bordered" id="holiday-calendar">
  <thead>
      <tr>
          <th><?= __('Monday')?></th>
          <th><?= __('Tuesday')?></th>
          <th><?= __('Wednesday')?></th>
          <th><?= __('Thursday')?></th>
          <th><?= __('Friday')?></th>
          <th><?= __('Saturday')?></th>
          <th><?= __('Sunday')?></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <?php
          // $days is a CakePHP2 data array of days
          
          // Work out when the month starts in the grid
          for ($i = 1; $i <= 7; $i++) {
              if ((int)date('N', strtotime($days[0]['Day']['date'])) === $i) {
                  // This is a CakePHP element to make a table cell for the day
                  echo $this->element('calendar-day', ['day' => $days[0]]);
                  break;
              } else {
                  echo "<td>&nbsp;</td>";
              }
          }

          $numOutput = 1;
          $remainingCells = 7 - $i;

          // Output the remaining cells after the first day
          for ($i = 1; $i <= $remainingCells; $i++) {
              echo $this->element('calendar-day', ['day' => $days[$i]]);
              $numOutput++;
          }
          ?>
      </tr>

      <tr>
          <?php
          $dayOfWeek = 0;
          // Output the remaining rows
          for ($i = $numOutput; $i < count($days); $i++) {
              echo $this->element('calendar-day', ['day' => $days[$i]]);

              // Look for the end of the week to roll a new row
              $dayOfWeek++;
              if ($dayOfWeek === 7) {
                  $dayOfWeek = 0;
                  echo "</tr><tr>";
              }
          }

          // Finish off the table with empty rows
          if ($dayOfWeek != 7) {
              for ($i = $dayOfWeek; $i < 7; $i++) {
                  echo "<td>&nbsp;</td>";
              }
          }
          ?>
      </tr>

  </tbody>
</table>

All done

That’s it, you now have a nice horizontal calendar similar to Google Calendar!

jQuery UI Accordion

Time for some Javascript!

So I’ve implemented the jQueryUI Accordion as a menu system for a CMS that I’m building and I’ve noticed that the active header will not open automatically.

This means that when a user hits a link to visit a new page, the accordion will not display their current menu item as active.

Time for some code!

Markup

I’ve used the same markup as the example, except that because it’s a menu, I needed some bullet points.

1
2
3
4
5
6
7
8
9
10
11
<div id='accordion'>
  <h3>First Heading</h3>
  <div>
      <ul>
          <!-- Add an active class how you want to -->
          <li class='active'>First item</li>
          <li>Second item</li>
      </ul>
  </div>
  <!-- More items repeated -->
</div>

jQuery

So how can we use the markup to tell the javascript which heading to mark active? Turns out it’s remarkably simple.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$(function () {

//  Accordian navigation
    $('#accordion').accordion({
        heightStyle: "content"
    });

    $('#accordion h3').each(function (i,e) {
        if ($(e).next('div').find('li.active').length > 0) {
            $("#accordion").accordion("option", "active", i);
        }
    });

})

The trick here is the use of next() to select the immediately adjacent div without selecting all the other siblings.

Go make a brew!

All done, that’s it!

Paginating multiple types of list in the same controller

What a complicated title

Okay, agreed. Perhaps an example would be better. Imagine you have a ContentsController which you use in your application to render lots of different types of content. You might have ContentTypes such as News, Guides, Articles, Pages, etc. so there is no point creating lots of controllers, when your ContentsController::index() method could just output the same thing with just different data.

This post is about how you deal with those lists in your routing and how to paginate them so that you can preserve your urls.

Routing

So the first thing to do is to setup your routing so that you can keep your content separate.

1
2
3
4
5
6
7
Router::scope('/', function ($routes) {
    $routes->connect('/guides/:slug', ['controller' => 'Contents', 'action' => 'view', 'type' => 'Guides'], ['slug' => '[a-z-]+', 'pass' => ['type', 'slug']]);
    $routes->connect('/guides', ['controller' => 'Contents', 'action' => 'index', 'type' => 'Guides'], ['pass' => ['type']]);

    $routes->connect('/news/:slug', ['controller' => 'Contents', 'action' => 'view', 'type' => 'News'], ['slug' => '[a-z-]+', 'pass' => ['type', 'slug']]);
    $routes->connect('/news', ['controller' => 'Contents', 'action' => 'index', 'type' => 'News'], ['pass' => ['type']]);
});

This now means that we can access all our different types of content on different urls so that it makes sense to the user.

Creating our links

So now we need to point users to our content.

1
2
3
4
5
// Link to the index
echo $this->Html->link('Guides', ['controller' => 'Contents', 'action' => 'index', 'type' => 'Guides']);

// Link to a single item
echo $this->Html->link($guide->title, ['controller' => 'Contents', 'action' => 'view', 'type' => 'Guides', 'slug' => $guide->slug]);

 Paginating the index

As we are using a single view to paginate many different lists we need to tell the Pagination helper how to form the url properly.

1
2
3
$this->Paginator->options([
    'url' => ['controller' => 'Contents', 'action' => 'index', 'type' => $type->name]
]);

This will now pass the type correctly in all our pagination links, which means that it’ll match the routing correctly and you should end up with nicely paginated urls which match your routing such as /guides?page=2.

Getting the list

So you’ll need to process the type when it’s passed to your controller, so be sure to include it in your controllers method signature.

1
2
3
4
5
6
7
class ContentsController extends AppController {
  public function index($type)
  {
      // Lookup the data by type
      // Perhaps with a custom finder
  }
}

Make a brew

It really is that simple!

Working with the Crud plugin in CakePHP

So what is it?

It’s a plugin for CakePHP 2 and CakePHP 3 which saves you time and energy by automating basic tasks which you would usually write or bake youself. These are the CRUD operations (Create, Read, Update and Delete), hence the name of the plugin.

The idea is that instead of writing repeated controller actions the same functionality can be provided for any model automatically using the events system.

This post is about Crud v3 which is for Cake 2.x

Read more about the plugin in the repo.

I’m sold, how do I use it?

Once the plugin is installed all you have to do is map a controller action to a crud action, create a controller and a view and you’re job done. It’s really that simple.

Let’s do an example. I will add comments to the code for various techniques.

Assumptions
Here we’ll assume the plugin is loaded already, the trait is being used and we have a model called Examples. The following example is for Cake 2, as in Cake 3, there is no need to use the prefix_method notation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// I tend to configure Crud in my AppController to save on repitition
class AppController extends Controller {
  use CrudControllerTrait;
  
  public $components = [
      // Here we can configure our action maps, finders and all kinds of things
      'Crud.Crud' => [
          'actions' => [
              'index' => 'Crud.index', // Map any index action to the Crud index action handler
              'view' => [
                  // Configure the options for this action method
                  'className' => 'Crud.view',
                  'validateId' => false
              ],
              'admin_index' => 'Crud.index', // We can even hookup prefix methods
              'admin_add' => 'Crud.add',
              'admin_edit' => 'Crud.edit',
              'admin_delete' => 'Crud.delete'
          ]
      ]
  ];
}

// Create your ExamplesController.php
class ExamplesController extends AppController {
  // We don't need any code here because Crud plugin can handle the methods for us!
  
  // If we want to just change how this action finds it's data we can specify a custom 
  // finder method and still have Crud do the rest of the work for us
    public function view($slug) {
        $this->Crud->action()->findMethod('slug');
      return $this->Crud->execute();
    }

    // What if we want to change the pagination in the index action?
    public function index() {
      // We can hook the beforePaginate event to make changes
      $this->Crud->on('beforePaginate', function (CakeEvent $event) {
          // The event subject here is the controller, and paginator is the 
          // paginator component.
          // So we can change the page limit to 10 to include 10 items per page
          // and we could contain a related model too!
          $event->subject()->paginator->settings = [
              'contain' => [
                  'User'
              ],
              'limit' => 10
          ];
      });
      
      // Let crud handle the rest of the action
      return $this->Crud->execute();
    }
}

// Create our view Examples/index.ctp
<div class='examples index'>
  <?php foreach($examples as $example) {
      echo $example['Example']['title'];
  }?>
</div>

 Woah, we just did loads!

Yep, with the above example, we’ve created all our admin prefixed Example methods, both our index and view methods. It’s amazing how much quicker you can do stuff when you focus on the parts of your system which are not in the basic four CRUD actions.

Make a brew

Why not eh? You’ve got loads of spare time now.

Installing and using pt-visual-explain

Why?

So I attended CakeFest this year and it was brilliant. One of the talks which I took at homework was the Profiling and Optimisation: A practical approach by Mark Story.

One the tools mentioned was pt-visual-explain, and I’ve been playing with it to see how it can help me better understand my queries and what is happening when I’m getting data.

I thought that I would document the process so I don’t forget it.

Install

Go grab your specific platform download of Percona Toolkit. Personally I used the tarball option for my work machine as it’s OS X.

Mac OSX

Here are the instructions for installation on a Mac. I’ve not tried it on my Ubuntu laptop, but when I do, I’ll update this post.

Then I unzipped the tarball into a folder. You’ll notice that there is a Makefile.PL, this is a Perl script for building the various executables you’ll need.

Make sure you have Perl installed, perl --version, which you should be default I think. Then it’s time to build the various executables.

1
2
3
4
5
$ cd percona-toolkit-2.2.10
$ perl Makefile.PL
$ sudo make
$ sudo make test
$ sudo make install

Then you’ll probably find some warnings or errors about a missing Perl package called DBD::MySQL. So let’s install this. I used this guide, but I will summarise it here.

1
$ perl -MCPAN -e 'shell'

If this is the first time you’ve used the Cpan module on your Mac you will be asked about configuring your Cpan set-up.

1
2
3
4
5
6
7
cpan> get DBI
cpan> get DBD::mysql
cpan> exit

$ sudo perl -MCPAN -e 'install DBI'
$ cd ~/.cpan/build/DBD-mysql-x.xxx/
$ perl Makefile.PL --testuser='daz' --testpassword='YOUR_PASSWORD'

Don’t include the --testpassword='' argument if your database doesn’t have a password.

1
2
3
$ make
$ make test
$ sudo make install

So now it’s time to run it and see what our queries look like.

1
$ pt-visual-explain --connect --host=localhost --user=root --database=myDatabase ~/Percona/MyExampleQuery.sql

You should get a nice visual output of what is going on with your query. At this point you can add indexes, change the query and keep running the visual explainer against it to see how things are changing.

Done

That’s it! Pretty simple really, apart from all that crap with Perl ;)

Sorting multi-dimension model data

Scenario

So often you have a large collection of collated data, probably with some calculated fields and you want to sort one of the dimensions by a certain value. Pretty hard using regular array methods, unless you start slicing arrays out of their dimension, sort them and inject them back in. We don’t want to do that because it’s fiddly.

Solution

So the solution I like to use is to use usort(). Which is a handy function allowing you to sort an array using your own function. Perfect if you want to sort a related models data.

Example

So for the example we’ll assume that we have a League model which hasMany LeaguesUser. We want to count the number of points a user has and order the data accordingly.

When returned from a Cake find() we’ll end up with related data in dimensions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php
array (size=2)
  'League' =>
    array (size=9)
      'id' => string '1' (length=1)
      'name' => string 'Examples!' (length=9)
      'slug' => string 'examples' (length=9)
      'description' => string 'The league for people who like examples' (length=45)
      'cover' => string '1000x1000.jpg' (length=13)
      'image_dir' => string '1' (length=1)
      'join_code' => string 'da39a3ee5e6b4b0d3255bfef95601890afd80709' (length=40)
      'created' => string '2014-08-04 10:45:45' (length=19)
      'modified' => string '2014-08-11 14:54:34' (length=19)
  'LeaguesUser' =>
    array (size=2)
      0 =>
        array (size=5)
          'id' => string '1' (length=1)
          'league_id' => string '1' (length=1)
          'user_id' => string '2' (length=1)
          'admin' => boolean true
          'User' =>
            array (size=5)
              'id' => string '2' (length=1)
              'email' => string 'test@example.com' (length=16)
              'username' => string 'testuser' (length=8)
              'predictions' => int 2
              'correct' => int 0
      1 =>
        array (size=5)
          'id' => string '2' (length=1)
          'league_id' => string '1' (length=1)
          'user_id' => string '1' (length=1)
          'admin' => boolean false
          'User' =>
            array (size=5)
              'id' => string '1' (length=1)
              'email' => string 'testuser1@example.com' (length=20)
              'username' => string 'testuser1' (length=8)
              'predictions' => int 2
              'correct' => int 1

So let’s sort that LeaguesUser['User'] dimension by the number of correct predicitons.

Firstly, we’ll want to create a new private function sortByCorrect($a, $b) in our controller. Then we just need to sort using it in our currenct controller method.

The important thing to note is that the callable function passed to usort() is an array containing the current controller as the first item. usort($data, [$this, 'callableFunction']) without including $this you’ll get an error.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php
public function view($slug) {
  $league = $this->League->find('first', [
      'contain' => [
          'LeaguesUser' => [
              'User' => [
                  'fields' => ['id', 'email', 'username', 'correct', 'predicitons']
              ]
          ]
      ],
      'conditions' => [
          'League.slug' => $slug
      ]
  ]);
  
  usort($league, [$this, 'sortByCorrect']);
  
  $this->set('league', $league);
}

private function sortByCorrect($a, $b) {
  if ($a['User']['correct'] < $b['User']['correct']) {
      return 1;
  } elseif ($a['User']['correct'] == $b['User']['correct']) {
      return 0;
  } else {
      return -1;
  }
}

// Rest of controller

Done

Go make a brew! Your work here is done.

Requiring CakePHP 2.x using Composer

Scenario

You have started your project using one of the 2.x downloads. Now you are using Composer to pull in your plugins, but you are still having to commit the framework to your repo.

So it’s time to remove your CakePHP lib and start requiring it as a dependancy instead.

I will assume you are fimiliar with Composer.

Solution

So it’s actually pretty straight forward. For the sake of this example, we will assume that you have a standard layout in your current project.

If you are not familiar with the current layout, take a quick look at the 2.5.3 tag to check the folders.

To see what we are aiming at, check out the Friends of Cake app-template.

If you don’t already have a composer.json file, then you’ll want to create that. composer init inside your project folder.

Adding the framework

Firstly we want to let Composer know that you want to require the framework, so let’s update the composer.json to add the following to your require section.

1
"cakephp/cakephp": "2.5.3"

You can pick your version here if you want to lock in the version, which I’d recommend. Or you could specify another version, such as 2.5.*. Check the Composer docs for more on versions.

When you composer update now it should download the framework for you into vendors/cakephp/cakephp.

Pointing to the correct core

The next task is to tell CakePHP that we have a new place for it to find the core. There are only really a few files you need to update.

  • app/webroot/index.php
  • app/Console/cake.php

app/webroot/index.php
You’ll want to update the ROOT, APP_DIR, TMP and the CAKE_CORE_INCLUDE_PATH constants.

1
2
3
4
define('ROOT', dirname(dirname(__FILE__)));
define('APP_DIR', 'app'); // I define this as a string because why not right?
define('TMP', ROOT . DS . 'tmp' . DS);
define('CAKE_CORE_INCLUDE_PATH', ROOT . DS . 'vendor' . DS . 'cakephp' . DS . 'cakephp' . DS . 'lib');

app/Console/cake.php
So that the console commands can still find the right lib, we need to update the include path here too.

1
ini_set('include_path', '..' . $ds . 'vendor' . $ds . 'cakephp' . $ds . 'cakephp' . $ds . 'lib' . PATH_SEPARATOR . ini_get('include_path'));

As you are running your shell tasks from within app we can use a relative path.

Edit your bootstrap to build the new paths

1
2
3
4
5
6
7
8
// app/config/bootstrap.php
App::build(
  [
      'Plugin' => [ROOT . '/Plugin/', ROOT . '/app/Plugin/'],
      'Vendor' => [ROOT . '/vendor/', ROOT . '/app/Vendor/']
  ],
  App::RESET
);

Testing

To check that the new setup is working you can run a Console/cake to check what the core value is.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ cd app
$ Console/cake

Welcome to CakePHP v2.5.3 Console
---------------------------------------------------------------
App : app
Path: /Users/david/Sites/example/app/
---------------------------------------------------------------
Current Paths:

 -app: app
 -working: /Users/david/Sites/example/app
 -root: /Users/david/Sites/example
 -core: /Users/david/Sites/example/vendor/cakephp/cakephp/lib

Done!

Feel free to delete your old lib folder as you now don’t need it. Have fun updating your applications version of CakePHP using Composer.