| CARVIEW |
nuts and bolts of cakephp
Make updateAll() fire behavior callbacks
For a while now updateAll() would not trigger any of the model’s behavior callbacks.
This presents a problem for a couple of reasons:
- The
updateAll()method allows to easily do things likefield_value = field_value + 1, because unlike other similar methods it does not escape fields/values - Secondly we cannot rely on proper Behavior execution, which can lead to some unexpected results
What we have below is a simple override (and a very rough patch at this point) that will trigger afterSave() of the attached behavior as expected.
Take the code below and place into the model, which uses updateAll() at some point.
public function updateAll($fields, $conditions = true) {
$db =& ConnectionManager::getDataSource($this->useDbConfig);
$created = FALSE;
$options = array();
if($db->update($this, $fields, null, $conditions)) {
$created = TRUE;
$this->Behaviors->trigger($this, 'afterSave', array($created, $options));
$this->afterSave($created);
$this->_clearCache();
$this->id = false;
return true;
}
return FALSE;
}
Again, this works for my immediate needs and doesn’t break any tests. However there are a few things that can be improved to make it more consistent with other methods like save(), for example.
Well, either way I am hoping that this will help someone.
Quickly grant Auth access to multiple controllers
Common way to allow Auth access to all actions in a controller is to do something like this:
//In some controller
public function beforeFilter() {
$this->Auth->allow('*');
parent::beforeFilter();
}
However it can get pretty tedious if you’ve got lots of controllers and have to go through a bunch of them to enable (or disable) access.
Instead, try something like this in your AppController:
public function beforeFilter() {
$this->Auth->loginAction = array('controller' => 'users', 'action' => 'login');
$this->Auth->loginRedirect = array('controller' => 'users', 'action' => 'index');
$this->allowAccess();
}
private function allowAccess() {
if(in_array($this->name, array('Pages'))) {
$this->Auth->allow('*');
}
}
The above will let Auth to access everything in the PagesController.
If you need to grant access to additional controllers simply add them to the array of names:
array('Pages', 'Books', 'Customers', 'Etc')
Having to deal with a single file to grant/deny access just makes things easier…
You could even make that “grantable” array a property of the AppController.
Bye-bye $cakeDebug…
The infamous $cakeDebug variable, which has been around for a long time and managed to annoy a few people in the process, has been replaced in the recent builds of CakePHP 1.3 with an element.
See the ticket here:
https://cakephp.lighthouseapp.com/projects/42648/tickets/35-the-implementation-of-dbo_sourcephp-logging-needs-changed
The two points to take away are:
- Automatic SQL dumps have been removed from
DboSource - If you wish to display SQL dumps, as before… replace any occurrence of
$cakeDebugin your layouts withecho $this->element('sql_dump');
One of the obvious benefits, is that you no longer have to tweak the core to take control of the SQL debug. As always, simply place sql_dump.ctp in your own app (i.e. app/views/elements) and do what you wish with the output.
Just to test things out let’s throw the SQL debug into the Firebug console, rather than directly into the view.
We’ll need to modify the sql_dump.ctp (which now has been copied into our own app), just a little bit.
Around line 35, let’s replace the default output for SQL dump, for something like this:
//unchanged part of the sql_dump.ctp above this line
//here we are outputting the queries into the Firebug console
foreach($logs as $source => $logInfo) {
foreach($logInfo['log'] as $key => $query) {
echo $this->Html->scriptBlock('console.log("' . $query['query'] . '")');
}
}
For the real world example, this may not be a reasonable thing to do by any means… but it does show how easy it is now to output the SQL debug wherever you need it (logging, debugging, stylizing, parsing, sending to some remote destination… up to your imagination really).
Overriding default URL’s (aka persistent routes)
CakePHP’s routes allow for an easy way to serve content by using URL aliases.
Let’s take an example from the manual:
Router::connect(
'/cooks/:action/*', array('controller' => 'users', 'action' => 'index')
);
By having the user access the URL such as example.com/cooks/index simply shows the default view for example.com/users/index (as was our intention).
However, if you have a bunch of links on that page, which point to other actions in the Users controller, the URL’s will remain as example.com/users/some_other_action. In some cases it may not be desired, i.e. if a visitor entered the section of your site by using the “cooks” URL, you don’t want to suddenly confuse them by presenting a completely different link to other actions. Otherwise, you may simply wish to be consistent (or sneaky).
A simple override of the Helper::url() method allows us to solve this problem.
(Place the following in your app_helper.php)
public function url($url = NULL, $full = FALSE) {
if(strstr($this->params['url']['url'], 'cooks') && $url['controller'] == 'users') {
$url['controller'] = 'cooks';
}
return parent::url($url, $full);
}
That’s it, now any link that would otherwise point to example.com/users/some_other_action will now point to example.com/cooks/some_other_action.
p.s. You might want to add an additional check: isset($url['controller'])
Improved form handling in CakePHP 1.3
Here is a typical, simple form done with cake’s form helpers:
echo $this->Form->create('Article', array('action' => 'test'));
echo $this->Form->input('Article.title');
echo $this->Form->input('Article.body');
echo $this->Form->input('Article.user_id', array('type' => 'hidden'));
echo $form->end('Add Aricle with Tags and Comment');
Which outputs the following HTML:
<form id="ArticleTestForm" method="post" action="/articles/test" accept-charset="utf-8">
<fieldset style="display:none;">
<input type="hidden" name="_method" value="POST" />
</fieldset>
<div class="input text">
<label for="ArticleTitle">
Title
</label>
<input name="data[Article][title]" type="text" maxlength="50" value="" id="ArticleTitle" />
</div>
<div class="input textarea">
<label for="ArticleBody">
Body
</label>
<textarea name="data[Article][body]" cols="30" rows="6" id="ArticleBody">
</textarea>
</div>
<input type="hidden" name="data[Article][user_id]" value="" id="ArticleUserId" />
<div class="submit">
<input type="submit" value="Add Aricle with Tags and Comment" />
</div>
</form>
This is fine and all, but one thing worth noting is that default behavior is wrapping each element in a div, and in some cases this may not be desirable. Either a legacy CSS or a front-end developer preference, might require the form to be structured (wrapped) differently.
Let’s see how we can set some defaults for our form with CakePHP 1.3., while simplifying the code a little at the same time by using the inputs() method…
echo $this->Form->create('Article', array('action' => 'test',
'inputDefaults' => array(
'div' => array('tag' => 'p'),
'before' => '-- goes before the label --',
'after' => '-- goes after the input --')));
echo $this->Form->inputs(array('Article.title',
'Article.body',
'Article.user_id' => array('type' => 'hidden')),
array('legend' => 'Article submit'));
echo $this->Form->end('Add Aricle');
Which produces:
<form id="ArticleTestForm" method="post" action="/articles/test" accept-charset="utf-8">
<fieldset style="display:none;">
<input type="hidden" name="_method" value="POST" />
</fieldset>
<fieldset>
<legend>
New Article
</legend>
<p class="input text">
-- goes before the label --
<label for="ArticleTitle">
Title
</label>
<input name="data[Article][title]" type="text" maxlength="50" value="" id="ArticleTitle" />
-- goes after the input --
</p>
<p class="input textarea">
-- goes before the label --
<label for="ArticleBody">
Body
</label>
<textarea name="data[Article][body]" cols="30" rows="6" id="ArticleBody">
</textarea>
-- goes after the input --
</p>
<input type="hidden" name="data[Article][user_id]" value="" id="ArticleUserId" />
</fieldset>
<div class="submit">
<input type="submit" value="Add Aricle" />
</div>
</form>
Of course for a simple form this may not be a very significant improvement, but having the ability to set defaults for the entire form and relying on some automagic for the rest, certainly makes one’s life easier.
Click the buttons, I tell ya…
Easy CakePHP API search with Firefox
Some of you might know this, but…
- Go here: https://mycroft.mozdev.org/search-engines.html?name=cakephp
- Click the “CakePHP API” link (or any other you might think is useful)
- Add “CakePHP API” to the list of engines available in the search bar?… Most definitely “Add”
- (Sure, let’s try it right away)
- Ctrl + K (windows) or similar shortcut for your OS
- Type search string, hit “Enter”
- See results
Nice.
CakePHP 1.3 helps with team-based development workflow…
I do have to say that in 1.2 some of the features described here are available as well, however CakePHP 1.3 takes it slightly to the next level to make team-based development even easier… With SVN or Git it is already easy enough to make team-based development readily accessible, that being said DB (schema and data) management during development process has always been a little bit painful.
Without too much dwelling let’s consider the following scenario. We have our favorite developers Joe and Bob.
(By the way, we are not going to cover any specific version control system here).
Bob starts up the project.
1. He creates the relevant Database with tables, adds a few models, controllers, actions, views.
2. He writes some code and populates the DB in the process with some dummy data.
So far, this should be familiar to most. Generally speaking, once Bob is done with the initial logical point of setup, he would commit all his work into some version control system.
Here’s where the pain of the past came into play…
While Joe can easily grab the application files, which are needed for the project, he is missing the DB structure and some data to continue working on the project.
Since early versions of CakePHP we’ve had a few built-in and third-party tools to help with that process, but they were lacking something… in one way or another. Not to say they didn’t get the job done, just the overall package wasn’t quite there.
Let’s see what to do in cake 1.3..
Bob fires up the command line and…
#cake schema generate
… which creates a php-based, cake-like schema.php file, which holds the information about the table structure.
Next, Bob types…
#cake bake fixture all -records -count 15
(Thanks Mark Story!)
This will bake all fixtures based on the current DB structure… and populate them with maximum of 15 records, which are currently in the database tables.
Fixtures are “nice little things”, but as far as we are concerned here, they simply hold our test data, so that Joe can have easy access to it.
OK, now that Bob was nice enough to do all of the above. He can happily commit all of the application code (including our newly baked schema and fixtures).
Moving on, at this point Joe is ready to get started on the project. He checks out the initial code from the version control repository.
Once the code is on his local development machine, Joe runs:
#cake schema create
Which, as you might have guessed, crates all the tables based on the schema developed and committed by Bob.
(It goes without saying that appropriate CakePHP and ultimately database configuration should already be in place on Joe’s development box).
Now that the tables are created Joe can begin his work, and while it is important to remember the “empty state” of an application, more often than not, some data is needed to get things rolling.
Well, all Joe needs to do at this point is:
#cake fixtures
Which, loads all the data from the fixtures into the newly created tables in Joe’s local database. Nice, eh?
At this point both development environments are fully in-sync and the progress can continue.
The above feature is currently in development and will not be available (or might change) depending on when you are reading this post.
A few things to note…
- I rarely find it useful to attempt to alter my schema. If the latest check-in has the required data and updated schema, I can safely drop all the tables, recreate them and insert new data
- To address the point above, all of my schema files are in the version control system, so if for whatever reason I need to go back, I simply check out the required revision and rebuild the schema (and add data if needed).
- This process can be followed back and forth for as many iterations as necessary.
- Human connection is necessary. If there is conflict in the code or schema… check with your fellow developer Bob or Joe and come to an agreement over a beer.
An intro look at jQuery Tools, enhancement for setFlash() and CakePHP 1.3
Actually the main focus of this post will be how to creatively use JavaScript and Session::setFlash() to make your app a little more sexy.
(Slightly tweaked to use CakePHP 1.3, although there is nothing special here that you couldn’t do with other versions of cake).
First, I wanted to point out an awesome little library, called jQuery Tools, which comes with a set of really nice widgets, a whopping 5KB in size and its own CDN for easy loading.
To proceed further let’s consider an Articles Controller, where we are adding a new article.
This is something that you should’ve seen at least a few hundred times…
<?php
class ArticlesController extends AppController {
public function add() {
if (!empty($this->data)) {
if($this->Article->save($this->data)) {
$this->Session->setFlash('Article saved!', 'modal', array('class' => 'modal success'));
} else {
$this->Session->setFlash('Article was not saved!', 'modal', array('class' => 'modal problem'));
}
}
}
}
?>
Here we are using a pretty standard approach to write a message to the session by using $this->Session->setFlash().
What usually happens then, is a redirect to some other place where the message is displayed.
In our case the message will be displayed in the same view, using a nice-looking modal/dialog.
Another point to note, is that once the message is displayed to the user, we’ll automatically hide it (let’s say after three seconds).
To further extend the app you might consider disabling (or converting to plain-text) the form fields, and possibly displaying an “Edit” link… though these features are brewing in my app, I thought it would be too much to cover in one example.
Alright, now that we are looking at our familiar add() action, let’s see what tweaks we should do achieve the desired goals listed above.
In our layout, we’ll need to load both the jQuery and jQuery Tools libraries.
This is a snippet to do so using the existing CDN’s (and in CakePHP 1.3 style):
<?php echo $this->Html->script(array('https://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js',
'https://cdn.jquerytools.org/1.1.2/jquery.tools.min.js')); ?>
$this->Html->script() replaced CakePHP 1.2 approach of using $javascript->link(), the rest should be pretty obvious.
Next, let’s consider the view for our add() action:
<?php $this->Session->flash(); ?>
<?php
echo $this->Form->create();
echo $this->Form->input('Article.title');
echo $this->Form->input('Article.body');
echo $this->Form->end('Add Aricle');
?>
One thing you’ll notice is the way helpers are referenced in CakePHP 1.3… while $form->input() still works, the approach above is “safer” and more consistent. (Take a look here for more details).
Let’s get back to this line of code for a second…
$this->Session->setFlash('Article saved!', 'modal', array('class' => 'modal success'));
The main difference in CakePHP 1.3, is that modal, the second param, represents an element (unlike a layout in previous versions).
Therefore for this setup to work properly you’ll need to create app/views/elements/modal.ctp
Let’s see what it looks like:
<div id="flash-message" class="<?php echo $class; ?>"> <?php echo $message; ?> </div>
$message is replaced by whatever we place in the first argument to the setFlash() method (which happens to be “Article saved!”, in our case).
As you’ve probably guessed $class gets replaced with the appropriate value from the array of passed params.
Now, if we have successfully saved the article the add.ctp view gets re-displayed with our message showing up and the element rendered. (This all happens automagically by using $this->Session->flash()).
This could be already fine in itself, but it’s just not sexy enough… so we add a little jQuery and jQuery Tools magic to make things a little more fun.
$(document).ready(function() {
//with this approach you can assign the jQuery Tools API to be used later
//otherwise you could use a standard one-liner:
//$('#flash-message').expose().load();
var flashMessage = $('#flash-message').expose({api: true});
//now that we have a direct access to the API through the "flashMessage" object
//we can call the jQuery Tools methods
//such as load(), which is required to show/expose our modal
flashMessage.load();
//now that the modal is displayed we can create a simple way to automatically
//remove it after 3 seconds
flashMessage.onLoad(function() {
setTimeout(function() {
flashMessage.close();
$('#flash-message').slideUp(500);
}, 3000);
});
});
By including this script we will achieve exactly what our goals are. Display the message in a nice modal/overlay fashion, wait 3 seconds and then auto-hide the message from the user and make our page act as before again.
If you care, please take a look at the comments in the code, to see some nice features of jQuery Tools, i.e. the ability to access any tool’s API. (Please refer to jQuery Tools site for more details).
Overall, we are just relying on the jQuery goodness and overriding our boring flash() behavior, with something quite a bit more interesting.
Well, there you have it, aimed with these tools you are ready to bake sexy CakePHP 1.3 apps ;)
P.S.
Just a little CSS for the modal:
.modal {
width: 350px;
padding: 60px;
margin: 50px 50px;
}
.success {
border: 1px solid #004000;
}
.problem {
border: 1px solid #800000;
}
Top 10 things to look forward to in CakePHP 1.3
With CakePHP 1.3 release is just about on the horizon, there are a few developments that I am particularly excited about.
There are some great summaries and previews at code.cakephp.org, but still a few things are somewhat scattered, so here’s my attempt to better highlight some goodies in 1.3. Forgive shameless copy/paste in some parts, but really it goes without saying that all the credit goes to the developers for baking yet another awesome cake.
Maybe, the proper title should be “Top 10 things I’m looking forward to”… but either way, I hope you’ll find something useful and if there are some interesting features/updates that I’ve not included, please do share them in the comments.
So, without “further dudes”…
10. Missing behaviors trigger an error
Using behaviors that do not exist, now triggers a cakeError making missing behaviors easier to find and fix.
9. GROUP BY support in the Containable behavior
Finally, we should be able to do something along the lines of:
'contain' => array(
'Answer' => array(
'fields' => array('Answer.value', 'COUNT(*) AS count'),
'group' => array('Answer.value')
)
)
Which really makes reporting across multiple models much easier and any previously required hackery is no longer needed.
8. “Missing” model methods will trigger a warning
In the past, when Model::someMethod() was not found, the method itself was attempted to be executed as a query. Now, we should see a warning that such method isn’t there. This does affect some existing code, but is a sound improvement overall.
7. Support for engine type in MySQL DB
The new schema shell will properly recognize the engine type used by your tables (for example MyISAM vs InnoDB).
This was a bit of a pain in the “old days” when running a new schema would always default to MyISAM.
6. Helpers are now referenced by $this->HelperName
This is actually a very nice improvement because it ensures that your helper object will not conflict with some random variable from your view.
For example, if you have an ImageHelper and you also had an $image variable, in the old days the collision between the two would create major problems, especially because the conflict can happen completely unknowingly to the user (consider an app with lots and lots of views). Such an error was too easy to make.
5. Updates to the Form helper
What could be better than the lovely input(s) methods of the Form helper?
How about the suggestions outlined in this ticket…
To summarize, we should have greater control over the form element placement therefore having much more flexibility in the way the forms are laid out.
4. Error logging with debug at zero
This has been a long-requested feature and it is finally making it’s way into 1.3
You can use Configure::write(‘log’, $val), to control which errors are logged when debug is off. By default all errors are logged.
The $val is a standard PHP constant such as E_WARNING.
3. New fixture task
Baking fixtures is now another tasty piece of cake. One of the lovely feature is that it properly handles UUID’s.
2. Sweet prefix routing
The old admin routing is gone in the way of proper prefix routing, so if you wish to have:
www.example.com/user/profiles/edit
www.example.com/admin/users/delete
www.example.com/editor/articles/publish
It is now easily accomplished with a simple setup in the core.php:
Configure::write('Routing.prefixes', array('admin', 'editor', 'user));
Of course, you’ll need appropriate “prefixed” action such as editor_publish() in your relevant controllers.
1. Brand new way to handle Javascript and Ajax
This is probably the most anticipated and welcomed change in 1.3. One of the great things about CakePHP has always been the fact that it did not depend on any other libraries or what have you … (well other than PHP). In the old days that paradigm was somewhat “broken” because Ajax helper required the use of the Prototype JS library.
It wasn’t necessarily a huge problem, but for people who preferred to work with other JS frameworks… the helpers were of little help and mostly dead weight in the framework.
Not only that, the the new JS helper and improved HTML helper solve a lot of other little problems, which make CakePHP 1.3 something to really look forward to.
Well… it’s no worth repeating what’s already said at 1.3 wiki regarding the new helpers.
Go cake!
Copy/Paste
It’s been done
- January 2010 (2)
- December 2009 (2)
- November 2009 (2)
- October 2009 (4)
- September 2009 (4)
- August 2009 (3)
- July 2009 (2)
- June 2009 (1)
- May 2009 (3)
- April 2009 (5)
- March 2009 (5)
- February 2009 (2)
- January 2009 (9)
- December 2008 (4)
- November 2008 (7)
- October 2008 (9)
- September 2008 (12)
- August 2008 (9)
- July 2008 (7)
- June 2008 (9)
- May 2008 (25)
Blogroll
- good and necessary cakephp drama (plus awesome advice) :)
- nice stuff at debuggable
- see them all
- smash, stomp, kick!
- The official place to learn
- the one and only
- the proper way to write about cakephp
Pages
Blog at WordPress.com. Theme: The Journalist by Lucian Marin
2 comments