Posts tagged with “cakephp”

Learning CakePHP: Foreign Key Constraint Violation

In the course of building forms with CakePHP’s form helper, I kept running into problems with the foreign key constraints in my database. I’d get an error if I didn’t enter a value in the form even though a NULL value was allowed at the database level. A little debugging indicated that the cause was CakePHP trying to insert an empty string into the foreign key field. Since there was no record in the parent table with an empty string id value, the database had no choice but to reject the insert request:

SQL Error: 1452: Cannot add or update a child row: a foreign key constraint fails (`mydatabase/events`, CONSTRAINT `events_ibfk_1` FOREIGN KEY (`location_id`) REFERENCES `locations` (`id`) ON DELETE NO ACTION ON UPDATE CASCADE)

I will confess right now that I didn’t research this extensively and I don’t know whether this is something that’s easily avoidable using existing techniques or resources offered by CakePHP. It could also be that I’ve coded something incorrectly. What I saw, though, was an opportunity to learn. I saw an opportunity to create a behavior – something I hadn’t done yet – that would detect fields where a NULL value was acceptable and replace empty string values accordingly.

My project has events and events have locations. The relevant components of my database schema look like this:

CREATE TABLE locations (
   id            VARCHAR(255)   NOT NULL,
   name          VARCHAR(255)   NOT NULL,
   description   VARCHAR(255)   NULL,
   PRIMARY KEY ( id )
);
CREATE TABLE events (
   id            CHAR(36)       NOT NULL,
   location_id   VARCHAR(255)   NULL,
   name          VARCHAR(255)   NOT NULL,
   start_time    DATETIME       NOT NULL,
   end_time      DATETIME       NULL,
   PRIMARY KEY ( id ),
   FOREIGN KEY ( location_id )
      REFERENCES locations ( id )
         ON UPDATE CASCADE
         ON DELETE SET NULL
);

Using those tables are my Event and Location models:

class Event extends AppModel {
   public $name      = ‘Event’;
   public $belongsTo = array ( ‘Location’ );

/** content */ } class Location extends AppModel { public $name = ‘Location’; public $hasMany = ‘Event’; /** content */ }

In my event form, I have a dropdown list by which a user can select a location where the event will occur. Since not all events have a specific location, I’ve set the empty option to true so that the user can choose no association:

echo $form->input (
     'location_id',
     array (
        'div'      => 'input select',
        'selected' => $this->data['Location']['id'],
        'empty'    => true
     )
);

Unfortunately, my foreign key constraint didn’t like that empty option. My solution was to create a new Nullable behavior. All the behavior does is inspect a model’s schema and the data being saved. If a field is nullable and the data for that field is empty, the data values empty string is replaced with a null value. It does this in the beforeSave() callback function.

class NullableBehavior extends ModelBehavior {
   /**
    * function beforeSave
    * Looks for nullable fields in the schema and replaces empty string values for those fields
    * with NULL values.
    */
   function beforeSave ( $model ) {
      $schema = $model->schema();

foreach ( $schema as $field => $metadata ) { if ( $metadata[‘null’] ) { if ( isset ( $model->data[$model->name][$field] ) && $model->data[$model->name][$field] === ‘’ ) { $model->data[$model->name][$field] = null; } } } } }

Because I really like the technical accuracy that results from this solution even when constraint errors aren’t being thrown, I wanted to apply it to all of my models. Because I’m lazy, I didn’t want to write the code to add the behavior to all of those models. What I did instead was add it to my applications AppModel in the app/ directory.

class AppModel extends Model{
   public $actsAs = array ( 'Nullable' );
}

It solved my problem, but it may not be perfect or even necessary. Any feedback on my approach or the behavior itself would be appreciated.

CakePHP: Recursive Finds

(a.k.a It is not a boolean)

Being so new to CakePHP, I’ve spent a lot of time in the documentation. I mean a lot of time. Contrary to popular belief, the documentation is extensive, quite good and covers most, if not all, of the topics I’ve needed help with. One thing I’ve found, though, is that the documentation has three very specific deficiencies in many cases:

  1. Often, only simple cases are covered.
  2. There are a lot of ambiguities that I’d like to see clarified.
  3. It sometimes feels fragmented. The answer I’m looking for often isn’t found in (or even linked from) one place.

Today I ran into the latter two issues when I needed to pull data from an extended model association. I have an Attraction model that has a many-to-many (hasAndBelongsToMany) relationship with an Event model. Each event takes place at a location, so my Event model belongsTo my Location model.

When doing a find ( ‘all’ ) on my Attraction model, each result included, in addition to the Attraction itself, an Event – the model directly associated with with my attraction. I wanted to be able to display Location information as well, so I looked at the recursive parameter in an effort to have find() retrieve data from the extended association.

The find() documentation really provides very little information about the recursive parameter, but there is a syntax example:

'recursive' => 1, //int

When I looked at that, I read it to be a boolean value. To me, the parameter name itself sounds boolean and the only examples I found had a value of 1, a value commonly used (though not explicitly enough for my tastes) to represent a boolean “true”. When I set the parameter to 1, though, I got exactly the same result with the same lack of Location information.

As you may have guessed, I read it wrong. The recursive parameter is really a recursionLevels parameter. Once I set the value to 2, I got information related to the models that were directly associated with the Event model which included my Location information.

This is one of those cases where the documentation isn’t wrong, but it is fragmented and it’s not always easy for new developers to find what they need easily. As I get more familiar with CakePHP, more confident that I know what I’m doing and that I’m doing things the right (read: Cake) way, I’ll do my part to help clarify the documentation where I believe it necessary, but until then I’ll make my notes here in case they can help someone else or someone else can help me.

CakePHP: Watch Out for the compact() Function

Okay, so maybe this caveat isn’t specific to CakePHP since the function is a native PHP function, but I just spent the past hour trying to figure out why variables that I clearly set in my controller weren’t available to my view. I’ll never get that hour back, but maybe I can keep someone else from losing it. As far as I’ve been able to tell, this particular behavior isn’t documented, but I haven’t done any kind of exhaustive search.

The compact() function exists as a shorthand function to create an array of variables. The PHP documentation does a nice job of detailing the function and providing a few simple examples, so I won’t belabor that point. Within CakePHP, it’s a handy shortcut for passing variables from a controller to a view. In my case, I have a vendor application form with a few variances that I need to manage. Without using compact(), a snippet from the action method of my VendorsController would look like this:

public function index() {
   …
   $vendor_type      = ‘Food’;
   $application_type = ‘Commercial’;
   $states           = $this->Vendor->Address->State->find ( 
      ‘list’, 
      array ( ‘order’ => ‘State.title’ ) 
   );

$this->set ( ‘vendor_type’, $vendor_type ); $this->set ( ‘application_type’, $application_type ); $this->set ( ‘states’, $states ); … }

That’s not an intolerably verbose example, but it’s easy to see how it could become so. Being the laconic kind of guy that I am, though, I decided to use the compact() function to cut down on the verbiage. Now my action code looks like this:

public function index() {
   …
   $vendor_type      = ‘Food’;
   $application_type = ‘Commercial’;
   $states           = $this->Vendor->Address->State->find ( 
      ‘list’, 
      array ( ‘order’ => ‘State.title’ ) 
   );

$this->set ( compact ( ‘vendor_type’, ‘application_type’, ‘states’ ) ); … }

That’s a little better, but there’s a caveat.

Using this example, I went to my view code and tried to access my $vendor_type variable only to get an undefined variable error. After working my way through various debugging techniques, I pulled out the big gun. In my view, I dumped the output of PHP’s get_defined_vars() function and found that my $vendor_type variable had been renamed to $vendorType. I don’t know if the change was made by CakePHP or by PHP itself, but I didn’t expect it (nor do I have any reason to believe that I should have expected it) and the difference is as fatal to an application as it is obvious.

CakePHP: Adding Dynamic Content to Layouts

For the past few months, I’ve had this lingering item on my TODO list to Learn CakePHP. When that item was added to the list, we had decided to use it as our default framework for building new apps at work and I wanted to at least be aware of its basic capabilities, techniques and toolkits. Unfortunately, I don’t get as much time as I’d like to put my head down and write code at work, so this item has been taunting me until this week when some time presented itself.

I come into this brand new to CakePHP, but as an experienced web and OOP developer. I have certain expectations of encapsulation, elegance and maintainability that I want to bring to my CakePHP project. As I run across scenarios where I don’t think that such elegance is obvious, I’ll document the solution I used here. Maybe someone else can benefit from my experience and maybe I can benefit from that of others.

The Project

Since I learn most effectively by doing, I needed a project. The project I chose was a rebuild of a site I did about a year ago; I was familiar with the needs as well as the things I would have liked to do differently, had there been enough time.

For the site I’m building, I have not only a header and a footer, but also a sidebar. The site has navigation menus in each of these areas of the template that I wanted to be data driven and several menu items are shared across menu locations. Not an uncommon need, to be sure, but I wasn’t sure how to handle it with CakePHP because layouts don’t have models – or controllers – of their own to access the data and this layout would be shared by every page in the site – I didn’t want to add the logic to retrieve and manipulate menu data in more than one place.

The Scenario

The scenario, then, is the need to place dynamic content within a layout. Without going into excruciating detail, a CakePHP layout is analogous to a template in a CMS. It’s the part of the page that is fixed and usually shared across multiple pages of a site. On robwilkerson.org, for example, the layout would include the header, the footer and the sidebar on the right. Everything else is the page content or, in CakePHP’s MVC-speak, the view template.

The Data

The backbone of a data driven solution, of course, is the database. For my menus problem, I created three tables: nav_menus, nav_menu_items and nav_menu_items_nav_menus following all of the conventions of CakePHP so that everything would work without any more effort than necessary. The last table, of course, is a linking table to identify which items exist on which menus. The many-to-many relationship it enables is what allows a single menu item to appear on more than one menu.

The Models & Controllers

With the database prepared, I needed to provide access from my application and this is where CakePHP and most other frameworks I’m familiar with really shine. Using CakePHP, I just “baked” models and controllers using the command line utility:

$ cd path/to/cake/console
$ ./cake bake model nav_menu
$ ./cake bake controller nav_menus
$ ./cake bake model nav_menu_items
$ ./cake bake controller nav_menu_items

That’s it. A few simple command line requests and I had all the files I needed. All that was left to get this scaffolded up was to define the associations between the models I’d just created. To do so, I opened each model file (located in app/models/) and added a single line of code to each. To app/models/nav_menu.php, I added the following:

public $hasAndBelongsToMany = array ( 'NavMenuItem' );

Similarly, I added the following to app/models/nav_menu_item.php:

public $hasAndBelongsToMany = array ( 'NavMenu' );

That’s all I needed to do to be able to create new menus and menu items. By requesting http://localhost/nav_menus/add, I was able to use CakePHP’s scaffolded interface to create new menus. Likewise, by accessing http://localhost/nav_menu_items/add, I could create new menu items and assign them to the appropriate menus.

Sharing the Logic

The non-obvious question in my mind was how do I get this dynamic data displayed on a layout? A layout doesn’t have its own controller and I sure has hell don’t want to have to tell every controller how to render my navigation. Inheritance to the rescue.

CakePHP’s controllers all extend the AppController class, so I dropped my menu logic there:

class AppController extends Controller {
   public $uses = array ( ‘NavMenu’ );

public function beforeRender() { /** * Populate and expose menu data */ $raw_menus = $this->NavMenu->find ( ‘all’ ); $menus = $this->NavMenu->unobfuscateMenus ( $raw_menus ); $this->set ( ‘nav_menus’, $menus ); } }

In my mind, this is perfectly reasonable since this logic really will be used on every single page request; there are no CPU cycles being wasted.

An Associative Recordset

If anyone’s wondering about the unobfuscateMenus() method, I created a custom method in my NavMenus model that would allow me to access and key on my menus as an associative array rather than as an indexed array. I have three menus: Primary, Secondary and (surprise!) Tertiary. By default, CakePHP exposes these as $nav_menus[ 0 ][‘Primary’], $nav_menus[ 1 ][‘Secondary’], etc. I wanted to be able to key on the menu name, though, so that in my layout, I had the ability to access the precise menu I wanted when I wanted it.

The unobfuscateMenus() method reorganizes the array slightly so that I can access $nav_menus[‘Primary’] when I want to display the primary menu without looping over all menus and testing for the menu name. This should become more clear in the code that renders the menu below.

With the code added to the AppController class, all of my controllers and, by extension, all of my layouts will have access to my menu data. Since I have several menus on my layout, I not only wanted to share the logic, but also the output.

Sharing the Output

Duplicating presentation code is almost as distasteful to me as duplicating business logic, so I created a navigation element that would accept my $nav_menus array and iterate over each item to create an unordered list. The menu array whose items will be displayed is passed to the element as the $menu variable.

<ul>
   <?php $i_item = 0; ?>
   <?php foreach ( $menu['items'] as $item ): ?>
   <?php
      $i_item++;
      $item_class = '';
      if ( $i_item  1 ) {
         $item_class = ' class="first"';
      }
      else if ( $i_item  count ( $menu['items'] ) ) {
         $item_class = ' class="last"';
      }
   ?>
   <li<?php echo $item_class; ?>>
      <a href="<?php echo $item['target_uri']; ?>" 
         title="<?php echo $item['description']; ?>"
         ><?php echo $item['title']; ?></a>
   </li>
   <?php endforeach; ?>
</ul>

Wiring It All Together

My logic, data and presentation code are all in place now, so the only thing left is to apply the context – the layout. In my layout markup, I included the following code to display my primary menu:

<?php echo $this->element (
      'navigation',
      array (
         'id'   => 'primary',
         'menu' => $nav_menus['PRIMARY']
      )
   );
?>

And for my secondary menu:

<?php echo
      $this->element (
      'navigation',
      array (
         'id'   => 'secondary',
         'menu' => $nav_menus['SECONDARY']
      )
   );
?>

Key Points

Here’s what I like about this solution, in no particular order:

  1. One database query. I have three menus and I certainly could have retrieved each menu individually. Whenever it’s feasible, though, I like to avoid querying the database. In my experience, the database is almost always the bottleneck for an application; the less I have to communicate with it, the better. With a little application logic (the unobfuscateMenus() method), I get all the benefits of three queries at the performance cost of just one.
  2. It will scale. If I need six navigation menus, all that’s required is a little more data. The code can handle the additional menus without any changes.
  3. There’s no waste. I’m very wary of dropping code in a super class, but in this case it works. The AppController class is loaded on every request and the logic it includes is used on every request. Symmetry.
  4. Encapsulation of presentation. Within the element, there’s no logic that isn’t used exclusively for presentation. Because I unobfuscated the menus array, I’m able to pass in only the menu that needs to be rendered by the element at the time it’s called.

CakePHP Workshop Review

I spent last weekend in Raleigh, NC attending a CakePHP workshop. At work we’re beginning to standardize on the CakePHP framework in order to minimize our development cycle while still ensuring reasonably strong application architecture in the presence of time constraints. I haven’t had much time to play in the weeds of this effort, so the workshop looked like it could be a decent way to get introduced to CakePHP.

It was. And it wasn’t.

First, though, many kudos are due to the workshop organizers. The CakePHP contributors (Garrett, Nate, Felix & Tim) who ran the workshop are typical open source developers – they have day jobs. The fact that they were willing to give even more back to the community by giving up their own weekend is commendable and, at the risk of speaking for the other attendees, I think everyone present appreciated that sacrifice. Judging by the amount of content presented, this was not a trivial undertaking. Not to mention making their own travel arrangements and all of the other logistical challenges. The workshop was very well done.

I never understood how we ended up in Raleigh (none of the framework developers are based there, as far as I know), but the training was held in two rooms of an office in one of Raleigh’s many corporate office parks. The first room was a reasonably impressive and well-equipped training room with a number of computers that no one used because we all brought our own laptops. Nonetheless, it was a nice facility that was conducive to learning. This room was the headquarters for “structured” learning – presentations, mostly. The second room, similarly equipped, was dedicated to unstructured discussion with the contributors that weren’t actively engaged in the first room. In this room, attendees were afforded the opportunity to guide the discussion and the developers here were equally responsive to beginner and advanced questions alike.

My only issue with the workshop was that it wasn’t really a workshop. At least not by my definition (so take that for what it’s worth). I went in expecting – from nothing more than the use of the word “workshop”, frankly – far more hands-on activity. I expected to be building a simple application with the framework and, in the process, getting a broad (not deep) introduction to the tools offered – the console, tests, fixtures, etc. – and how to use them. Instead, I got what I would consider to be more of a conference.

I understand that a true workshop (again, by my definition) would have been challenging to the folks leading the conference. This was never billed as a workshop for beginners so there was a wide range of experience levels among the participants. The small majority, I think, were, like me, newbs to CakePHP. Others were far more experienced. Tailoring a one-conference-fits-all curriculum would have been impossible. Instead we got some introductory topics and some advanced topics. Unfortunately, the advanced topics were all but lost on the beginners. I had no trouble with the concepts – I’ve been doing this for a long time – but I had no practical context in which to assimilate these concepts. Conversely, I imagine that the more advanced Cake users were bored by the introductory material.

My suggestion for any future workshops would be to offer two tracks (assuming a similarly diverse audience). There were two rooms available, so it would’ve been great to offer one track to introduce beginners, in an interactive manner, by way of building an application – essentially the blog tutorial on ‘roids coupled with the ability to ask questions and establish a dialog during the process – and another to introduce and talk through more advanced topics that experienced Cake developers can grasp and visualize within the context of their own applications.

That’s not to suggest that I got nothing out of the weekend. I did. I got a lot out of it and it was extremely helpful to have access to several of the key contributors. What I didn’t get out of it was the ability to recognize everything I got out of it. At least not yet. That requires a context I don’t have at this point.

← Earlier Posts Page 1 of 2