Professional Documents
Culture Documents
XXX - Super Awesome CakePHP
XXX - Super Awesome CakePHP
By Matt urry pseudocoder.com twitter.com/mcurry matt!pseudocoder.com "ith ontributions #rom Mark $tory mark-story.com twitter.com/mark%story
$uper &wesome &dvanced ake'(' Tips by Matt urry is licensed under a reative &ttribution-)oncommercial-$hare &like *.+ ,nited $tates -icense. ake'(' is a re.istered trademark of the ake $oftware #oundation /http://cakefoundation.or.0
ommons
Contents
Who Should Read This Book..........................................................................................................................6
How to Read This Book.............................................................................................................................................6
'odels...............................................................................................................................................................(
Re)ursion....................................................................................................................................................................( Containa&le Beha"ior................................................................................................................................................( Why *ou Should +se ,t.............................................................................................................................................( This Will Cat)h #"eryone At Least %n)e..............................................................................................................-.
Custo/ Find Ty0es........................................................................................................................................-The +no i)ial Cake Way........................................................................................................................................-'y Way....................................................................................................................................................................-1 Co/0arison..............................................................................................................................................................-2 A00 'odel................................................................................................................................................................-3
Routin5............................................................................................................................................................11
Case ,nsensiti"e........................................................................................................................................................11
+nit Testin5....................................................................................................................................................19
8iews.........................................................................................................................................................................19
Setting Up The Files..................................................................................................................................23 Setting Up The Test Class.........................................................................................................................23 Standard Index View.................................................................................................................................24 Creating The View Test.............................................................................................................................25 Testing the Rendered View.......................................................................................................................26 Controllers.................................................................................................................................................28 oing Things the !ard "a#......................................................................................................................28 Testing $ Controller %ethod....................................................................................................................3& %a'ing assertions......................................................................................................................................3(
'o)k %&:e)ts...........................................................................................................................................................99
"hat is a %o)' *+,e)t..............................................................................................................................33 "here )an I get one o- these -a+.lo.s de/i)es0........................................................................................33 %a'ings expe)tations with %o)' *+,e)ts................................................................................................34
'odels......................................................................................................................................................................96
Ca)he your slow Eueries$we& ser"i)e reEuests$whate"er......................................................................................33 8iew Ca)hin5...........................................................................................................................................................33 HT'L Ca)hin5........................................................................................................................................................36 APC ?or so/e other o0)ode )a)he@.........................................................................................................................36 Persistent 'odels.....................................................................................................................................................36 Store The Persistent Ca)he in APC........................................................................................................................37 S0eed +0 Re"erse Routin5......................................................................................................................................37 +n)hain *our 'odels..............................................................................................................................................37
e+.g........................................................................................................................................................6( Ca)he.........................................................................................................................................................6(
Alternate 'ethods...................................................................................................................................................6-
<
$tep-by-step introduction to rapid web development usin. the open-source MV ake'(' by &hsanul Bari and &nupom $yam !aila"le at #a$on.%o# &or $3'.((
by @ai
hapter 1 hapter *
hapter 1+.1
.odels
Re%ursion
The recursive property in ake'(' models is used when determinin. how much information is returned with your Fueries. The problem is that it .ives you only limited control. 8ou can select the level of information you want7 but if you have many related tables you will often retrieve more information than you need7 which will result in wasted Fueries. (ere are some si.ns you have recursion set too hi.h in your app:
: : :
"hen you4re lookin. at the outputted Fueries with debu. on you4ve reached the cap /1++0 that will be displayed. 8our pa.e takes so lon. to load that you have enou.h time to benchmark all the ma;or '(' frameworks and post about it on your blo.. 8ou4re usin. a recursive level that isn4t -1.
)ow you4re confused7 ri.htE The first thin. 5 do with any new ake'(' app is create an &ppModel and set var $recursive = -1; (ow do you retrieve related tablesE Cead on...
Containa"le Beha!ior
The containable behavior .ives you precise control over what data is returned. This means you won4t waste Fueries loadin. related tables that you may not care about. The ake'(' Book e9plains it pretty well7 so 5 won4t waste space .oin. over the same material. 5nstead 54ll emphasi?e ;ust how important it is to use.
,.h. 5t has that u.ly call to set recursive to +. $o you delete it7 but you still have to load the ,ser model to show who created each post. $o you make the code look like this: function index() { $this->Post->contain('!ser'); $this->set('posts', $this->paginate());
"hen you refresh the pa.e it4s broken. The 'ost data loaded7 but the associated ,ser model wasn4t. -ookin. closer you see that the count Fuery was fine: "#$#%& %'!(&()) *" +count+ ,-'. +posts+ *" +Post+ $#,& /'0( +users+ *" +!ser+ '( (+Post+1+2odified3user3id+ = +!ser+1+id+) 45#-# 1 = 1 But the second Fuery7 the one that loads the actual data7 is messed up - there is no ;oin to the users table. "#$#%& +Post+1+id+, +Post+1+tit6e+, +Post+1+7od8+ ,-'. +posts+ *" +Post+ 45#-# 1 = 1 $0.0& 90 The issue is that even thou.h you are containin. the ,ser model7 it is bein. reset after the first Fuery7 the count Fuery. $o when the second Fuery runs7 the one to .et the actual data7 it doesn4t know anythin. about the bindin.. There is a simple fi9 for this7 and it4s mentioned in the manual7 but it4s .oin. to come up if you force recursive to always be -17 so it4s worth statin. a.ain. 8ou can set the controller:s pa.inate options to contain any related models you want before you call $this->paginate(). function index() { $this->paginate = arra8('contain' => '!ser'); $this->set('posts', $this->paginate());
1+
)ow you have to create the method that will be called. The method name must match the pattern 3find%usto2&8pe. 5n the e9ample about it would be 3find$atest. The tricky part is that this method will be called twice. Bnce before the database Fuery is made and once after. function 3find$atest($state, $:uer8, $resu6ts = arra8()) { if ($state == '7efore') { $:uer8<'6i2it'= = 10; $:uer8<'order'= = 'created >#"%'; return $:uer8; e6seif ($state == 'after') { return $resu6ts;
11
5n the before state you will have access to the $:uer8 array7 where you can set conditions7 limit7 order...all the usual find options. 5n the after state the $resu6ts array is passed. 8ou can then alter $resu6ts to fit whatever you4re tryin. to do or ;ust return it directly.
.y Way
My way reFuires a bit more initial setup7 but really it4s ;ust a matter of copyin. and pastin. some code to your &ppModel and you don4t have to add anythin. for each new find method. 54ll step throu.h what it does7 then include the entire block at the end. #irst in your &ppModel you4ll need to override the find function. function find($t8pe, $options = arra8()) {
5n the actual model class /not &ppModel0 we will create methods for each find type that uses the Jtype parameter as a piece of the method name. $2ethod = sprintf('33find?s', 0nf6ector@@ca2e6iAe($t8pe)); #or e9ample callin. $Post->find('6atest') will look for the method 6%%find-atest6 in the 'ost model class. 5f you can4t come up with snappy names for your find types and end up with $Post>find('6atest3;ith3co22ents') the private method will be 6%%find-atest"ith omments6. Then it is simply a matter of checkin. if the method e9ists and callin. it. Br if it doesn4t e9ist7 callin. the parent. if(2ethod3exists($this, $2ethod)) { return $this->{$2ethod ($options); e6se { return parent@@find($t8pe, $options);
There is one catch. ake will sometimes call find internally and pass an array as the Jtype parameter. To catch that simply check if Jtype is a strin. before settin. Jmethod.
11
The whole thin34 /also available as a plu.in on >it(ub0 function find($t8pe, $options = arra8()) { $2ethod = nu66; if(is3string($t8pe)) { $2ethod = sprintf('33find?s', 0nf6ector@@ca2e6iAe($t8pe)); if($2ethod BB 2ethod3exists($this, $2ethod)) { return $this->{$2ethod ($options); e6se { $args = func3get3args(); return ca663user3func3arra8(arra8('parent', 'find'), $args);
)ow you can create a method in your 'ost model like: function 33find$atest($options) { $options = a2(arra8('conditions' => arra8('pu76ished' => true), 'order' => arra8('created' => 'desc'), '6i2it' => 10 ), $options ); return parent@@find('a66', $options);
1*
Co#2arison
There are advanta.es and disadvanta.es to each method. The ,nofficial ake "ay: 5 -ess initial code 5 &ccess to the before and after states 6 (ave to handle before and after states My "ay: 5 =on4t have to override the Model %%constructor and setup each custom find type. Aust write the method. 6 $ome initial setup. 5 =on4t have to handle before and after states. 54ll e9plain that last one. "ith My "ay you have access to both states7 but don4t have to e9plicitly handle them. #or e9ample what if your custom find type was really ;ust a wrapper to one of the default find types. -et4s say you wanted to .et a count of comments on your blo. and double it so you looked popular. "ith My "ay you4d simply do: function 33find%o22ent%ount($options) { return $this->find('count')) ) 9;
=oesn4t .et much simpler than that. (ere4s the same thin. with the ,noffical
ake "ay:
function 3find%o22ent%ount($state, $:uer8, $resu6ts = arra8()) { if ($state == '7efore') { return $this->3find%ount($state, $:uer8, $resu6ts); e6se { return $resu6ts ) 9;
12
22 .odel
8ou can pull to.ether all of the above to create a base &ppModel suitable for any new app you start. CDphp c6ass *pp.ode6 extends .ode6 { var $acts*s = arra8('%ontaina76e'); var $recursive = -1; function find($t8pe, $options = arra8()) { $2ethod = nu66; if(is3string($t8pe)) { $2ethod = sprintf('33find?s', 0nf6ector@@ca2e6iAe($t8pe)); if($2ethod BB 2ethod3exists($this, $2ethod)) { return $this->{$2ethod ($options); e6se { $args = func3get3args(); return ca663user3func3arra8(arra8('parent', 'find'), $args);
D>
13
This method will be used internally to store and retrieve the user4s information. =on:t worry if you don:t understand what:s .oin. on here. Aust trust that it works.
1<
Before you can access the user info you need to store the user in the static instance. The method 6set6 is already taken by ake4s base Model class so we4ll use 6store6. The store method is very simple: function store($user) { !ser@@get0nstance($user);
1n The 22Controller
The ,ser::store method is used in the &pp ontroller Before#ilter. *pp@@i2port('.ode6', '!ser'); !ser@@store($this->*uth->user()); The lo..ed in user is pulled from the &uth omponent and passed as a parameter to the store method. 5f you4re not usin. the &uth omponent you can substitute whatever half-assed7 cobbledto.ether authentication system you4re usin.. 8ou ;ust need to pass in the user information as an array.
1D
0sa3e
To .et the currently lo..ed in user:s id: !ser@@get('id'); To .et the currently lo..ed in user:s username /assumin. you have a field HusernameI in your users table0: !ser@@get('userna2e'); &ny other fields in your user table can be retrieved in the same manner. &lso if you store related model data in your user session it can be retrieved: !ser@@get('.ode61fie6dna2e');
5t4s not awful7 but the synta9 isn4t as appealin.. But7 wait. "hy not ;ust use the same setup for ,ser7 but have it wrap calls to onfi.ure. Then ,ser doesn4t have to bother .ettin. the static instance of itself and you .et the cooler !ser@@get() synta9. &.ain7 certainly an option. 5 wouldn4t hold it a.ainst you if you did it that way. (owever stickin. the lo..ed in user in the onfi.ure class is a bit misplaced. 5t4s not Hend of the worldI bad to do it this way. More like Hshit7 5 ;ust dropped my i'od in the toiletI bad. Ceally it4s a personal decision that each developer will need to search deep within. their pro.rammer souls to find the solution that fits them best.
1K
)ull Sour%e
(ere is the full source7 which is also available on >it(ub. function Bget0nstance($user=nu66) { static $instance = arra8(); if ($user) { $instance<0= =B $user; if (E$instance) { trigger3error(33(F!ser not set1F, true), #3!"#-34*-(0(G); return fa6se; return $instance<0=; function store($user) { if (e2pt8($user)) { return fa6se; !ser@@get0nstance($user); function get($path) { $3user =B !ser@@get0nstance(); if (strpos($path, '!ser') E== 0) { $path = sprintf('!serH?s', $path); if (strpos($path, 'H') E== 0) { $path = sprintf('H?s', $path); $va6ue = "et@@extract($path, $3user); if (E$va6ue) { return fa6se; return $va6ue<0=;
1L
9ata"ase
#irst off7 add the fields 6created%user%id6 and 6modified%user%id67 both ints7 to any database table you want to track chan.es on.
.odel Relations
)e9t you need to set up a belon.sTo &ssociation for each of the fields. var $7e6ongs&o = arra8('%reated!ser' => arra8('c6ass(a2e' => '!ser'), '.odified!ser' => arra8('c6ass(a2e' => '!ser'));
1+
The lo.ic here is pretty simple. "e check if the 6id6 field is set to determine if this is a new record or an update. "e4re makin. a bit of a leap here7 in believin. that if the id is set it will be an update. 5t is certainly possible that an invalid id is passed. ake handles this by checkin. if the id e9ists first. 8ou4ll usually see a Fuery like this before an insert or update: "#$#%& %'!(&()) *" +count+ ,-'. +posts+ *" +Post+ 45#-# +Post+1+id+ = 1 $o there is the outside possibility that the created%user%id field may not .et set this way. 5f you wanted to be dili.ent you could move the lo.ic to an after$ave7 which is passed a Jcreated boolean. To set the modified%user%id you use basically the same code7 less the check to see if this is a new record. $2ode6->data<$2ode6->a6ias=<'2odified3user3id'= = !ser@@get('id');
11
Routin3
Case 1nsensiti!e
>enerally 5nternet ,C-s are case insensitive. 8ou can put the address http://en.wikipedia.or./wiki/ ake'(' or http://en.wikipedia.or./wiki/cakephp in your browser and you4ll end up at the same place. Coutes in ake'(' are not case insensitive by default. #ortunately the routin. system uses re.ular e9pressions7 so it is easy to make a specific route work no matter how it is typed in. 5f you want to have an HaboutI pa.e that is linked to by H/aboutI7 H/&boutI7 or H/aBo,tI you can use: -outer@@connect('H(Di)a7out', arra8('contro66er' => 'pages', 'action' =>'disp6a8', 'a7out')); The 6/Ei06 part tells the re.ular e9pression en.ine to i.nore the case for all the te9t that follows. There is no way to apply this rule universally to all your routes7 so you4ll have to handle instances individually.
11
0nit Testin3
:iews
The ake'(' ookbook describes a way to test views usin. web testin.. This is different than unit testin. in that you actually make a reFuest to the web pa.e and check the html response. By doin. this there is no way to specify that the test database should be used7 therefore any data that is saved will .o in the HliveI database. This may not be a bi. deal if the HliveI database is ;ust your development environment. 5n addition7 since you are testin. the view by makin. a reFuest7 you aren:t isolatin. the view. The ake framework7 the controller and any models that are normally used in .eneratin. the output for the view will be used. This makes it hard to tell if an error is due to an issue with the view or from somewhere else. The method described isolated testin. to ;ust the view. )o models or controllers are needed as the view class is called directly. Because this method bypasses the controller7 any helpers will need to be included manually and any data needed by the view will need to be faked. Settin3 02 The )iles To setup a view test you:ll first need to create a directory HappHtestsHcasesHvie;s1 5n there put a file for each controller. The namin. convention Ccontro66er>3vie;1test1php works well. 5n this e9ample 5:ll be testin. the views for the 'ost controller/model7 so 5 created a file HappHtestsHcasesHvie;sHpost3vie;1test1php. The file itself is similar to the model or controller tests7 in that it will e9tend c6ass PostIie;&est%ase extends %aJe&est%ase { akeTest ase.
Settin3 02 The Test Class The first thin. needed is an instance of the View class. & .ood place to do this is in the startTest method. function start&est() { $%ontro66er = ne; %ontro66er(); $this->Iie; = ne; Iie;($%ontro66er); $this->Iie;->6a8out = nu66; $this->Iie;->vie;Path = 'posts';
The View class e9pects a controller to be passed as a parameter to the constructor7 so a base controller is created for this. The layout is set to null7 which allows the view to be rendered without the default layout. &lso the view'ath needs to be set to the folder in views that will be tested.
1*
To be safe we:ll be sure that the View was created successfully: function testPost0nstance() { $this->assert&rue(is3a($this->Iie;, 'Iie;'));
Standard 1nde; :iew 5f you use the ake'(' console to bake an inde9 pa.e it will look somethin. like this:
The pa.e consists of a title /'osts07 information about the number of records /pa.ination07 a table with a header row and the records themselves7 some more pa.ination and finally a new post link. To render this view ake used two helpers7 'a.ination and (TM-7 plus the data for the records.
12
Creatin3 The :iew Test )ow we can create the test for the inde9 pa.e. function testPost0ndex() {
Because a .eneric controller class was used7 the view has no knowled.e of which helpers are needed. They will need to be set manually. $this->Iie;->he6pers = arra8('5t26', 'Paginator'); The view e9pects certain pa.ination information to be set by the controller. #or this test that information will need to be set e9plicitly. $this->Iie;->para2s<'paging'= = arra8( 'Posts' => arra8 ( 'count' => 1, 'page%ount' => 1, 'page' => 1, 'current' => 1, 'prevPage' => fa6se, 'nextPage' => fa6se, 'options' => arra8('6i2it' => 90), 'defau6ts' => arra8() ) ); The records for 'ost also need be set so that the view has somethin. to display. $this->Iie;->vie;Iars<'posts'= = arra8 ( arra8 ( 'Post' => arra8 ( 'id' => 1, 'tit6e' => '$ore2 ipsu2 do6or sit a2et', '7od8' => '$ore2 ipsu2 do6or sit a2et111', 'created' => '900L-01-9M 1N@O1@P0', 'updated' => '900L-01-9M 1N@O1@P0' ) ) ); (ere we are ;ust settin. one record7 but you could certainly set as many as you:d like.
13
&ll that is left is to render the view and .et the output. $ht26 = $this->Iie;->render('index'); The strin. Oinde9: is passed7 specifyin. which view file to use. The rendered view7 not wrapped in the layout7 is returned and stored in $ht26. Testin3 the Rendered :iew &t this point the view has been setup and Iie;@@render() has been called7 returnin. some (TM-. The (TM- is a strin.7 so to run tests a.ainst it you can either use strin. parsin. or convert it to another format. 5f you wanted to verify some te9t appears in the view you could simply use '(':s strpos function. $this->assert(ot#:ua6(fa6se, strpos($ht26, 'Page 1 of 1, sho;ing 1 records out of 1 tota6, starting on record 1, ending on 1')); ¬her option is to use ake:s PM- lib to parse the data. 5 like to further convert it to an array7 which allows me to use the $et lib to test various parts of the view. *pp@@i2port('%ore', 'Q26'); $Q26 = ne; Q26($ht26); $page = $Q26->first()->to*rra8(); 5t is now possible to check the number of rows in the table by doin.: $this->assert#:ua6(count("et@@extract('H&a76eH&r', $page)), 9); 5n this case there should be 1Q the header row and the one record. To test that the table headers are correct: $this->assert#:ua6("et@@extract('H&a76eH&rH&hHaHva6ue', $page), arra8('0d', '&it6e', 'Kod8', '%reated', '!pdated'));
1<
To check a specific field in a record: $this->assert#:ua6( tri2(arra83shift("et@@extract('H&a76eH&r<9=H&dHM', $page))), '900L-01-9M 1N@O1@P0' ); There:s a lot .oin. on in that one line7 so let:s work from the inside out. "et@@extract('H&a76eH&r<9=H&dHM', $page) This returns the *rd field from the 1nd row. $ince the 1st row is the header7 this is actually the 1st data record. tri2(arra83shift("et@@extract('H&a76eH&r<9=H&dHM', $page))) $et::e9tract actually returns an array7 but we ;ust want the first element7 which should be the only one. '(':s arra83shift pulls the one record for us. #inally7 the data needs to be trimmed because the view probably looks like this: Ctd> CDphp echo $post<'Post'=<'created'=; D> CHtd> )otice how the (TM- is nicely formatted. "e don:t care about all that spacin.7 so it is trimmed off to leave ;ust the value we want to test. &fter all that the final output should match the e9pected value7 41++L-+1-1* 1<:31:2+4 in this case. The full view test is available at http://.ithub.com/mcurry/cakephp/blob/master/test%sample/tests/cases/views/post%view.test.php
1D
Controllers This section contributed by Mark $tory. Bri.inally posted at http://markstory.com/posts/view/testin.-cakephp-controllers-the-hard-way By now you already know or should know about akeTest ase::test&ction/0 and the wondrous thin.s it can do. (owever7 test&ction has a few shortcomin.s. 5t can4t handle redirects7 it doesn4t let you use the power of Mocks7 and it4s impossible to make assertions on ob;ect state chan.es. $ometimes you need to do thin.s the hard way7 stick your fin.ers in the mud and work it out. (owever7 knowin. how to test a controller the old fashioned way takes a .ood knowled.e of ake'('. 9oin3 Thin3s the Hard Way ontrollers reFuire a number of callbacks to be called before they are workable ob;ects. $o let:s .o7 we4ll start with the venerable 'osts ontroller7 and make a nice test for it. *pp@@i2port('%ontro66er', 'Posts'); c6ass &estPosts%ontro66er extends Posts%ontro66er { var $na2e = 'Posts'; var $auto-ender = fa6se; function redirect($ur6, $status = nu66, $exit = true) { $this->redirect!r6 = $ur6; function render($action = nu66, $6a8out = nu66, $fi6e = nu66) { $this->rendered*ction = $action; function 3stop($status = 0) { $this->stopped = $status;
c6ass Posts%ontro66er&est%ase extends %aJe&est%ase { var $fixtures = arra8('app1post', 'app1co22ent', 'app1posts3tag', 'app1tag'); function start&est() {
function end&est() {
1K
$o we start off with a basic test class. 5mportant thin.s to notice are the fi9tures array and the test class. 54ve included all the fi9tures that are related to the models my controller is .oin. to use. This is important7 as you will .et tons of table errors until they are all setup. 8ou may have noticed that 5 created a subclass of the test sub;ectQ this lets me do a few thin.s. #irst 5 can test functions that call redirect()7 as they no lon.er redirect. 5 can also call methods that use $this->3stop() as they no lon.er halt script e9ecution. #urthermore7 5 override %ontro66er@@render() so 5 can test actions that use render() without havin. to deal with piles of (TM-. 5 personally don4t do many tests that assert the (TM- of my views because 5 find it takes too much time and is tedious. -astly7 5 set $auto-ender to false ;ust in case. function start&est() { $this->Posts = ne; &estPosts%ontro66er(); $this->Posts->construct%6asses(); $this->Posts->%o2ponent->initia6iAe($this->Posts); HHtests are going to go here1 function end&est() { unset($this->Posts); %6ass-egistr8@@f6ush();
"e then build the instance and call some basic callbacks7 much like =aniel blo..ed about. &t this point we have a controller instance and all the components and models built. "e are now ready to start doin. some testin..
1L
Testin3
Controller .ethod
Testin. a controller method is ;ust like testin. any other method. Bften there is a bit more setup involved as controllers reFuire more inputs by nature. (owever7 it is all achievable in the test suite. $o we are .oin. to do a test of our ad2in3edit method. This ad2in3edit is strai.ht out of bake7 so you should know what it looks like. #urthermore7 5 can show you how you can test methods. function test*d2in#dit() { $this->Posts->"ession->;rite('*uth1!ser', arra8( 'id' => 1, 'userna2e' => '2arJstor8', )); $this->Posts->data = arra8( 'Post' => arra8( 'id' => 9, 'tit6e' => 'Kest artic6e #varE', '7od8' => 'so2e text', ), '&ag' => arra8( '&ag' => arra8(1,9,M), ) );
&t this point 54ve created the inputs 5 need for my controller action. 54ve .ot a session and some test data. 54ve provided enou.h information in the session that &uth omponent will let me by and edit my records. (owever7 many would say that you should bypass &uth entirely in your unit testin. and ;ust focus on the sub;ect method. But bein. thorou.h never hurt. function test*d2in#dit() { $this->Posts->"ession->;rite('*uth1!ser', arra8( 'id' => 1, 'userna2e' => '2arJstor8', )); $this->Posts->data = arra8( 'Post' => arra8( 'id' => 9, 'tit6e' => 'Kest artic6e #varE', '7od8' => 'so2e text', ), '&ag' => arra8( '&ag' => arra8(1,9,M), ) ); $this->Posts->7efore,i6ter(); $this->Posts->%o2ponent->startup($this->Posts); $this->Posts->ad2in3edit();
*+
54ve now simulated most of a reFuest in ake'('. 5t is important to fire the callbacks in the correct order. Aust remember that before#ilter happens before Component::startup()7 and %o2ponent@@7efore-ender() happens after you call your controller action. .akin3 assertions "hen 5 test controllers 5 usually make assertions on the viewVars that are set and any records that are modified / deleted. 5 don4t like makin. assertions on the contents of $this->"ession>set,6ash() as 5 find these messa.es chan.e often which can lead to broken tests7 which leads to frowns. ontinuin. from before: function test*d2in#dit() { $this->Posts->"ession->;rite('*uth1!ser', arra8( 'id' => 1, 'userna2e' => '2arJstor8', )); $this->Posts->data = arra8( 'Post' => arra8( 'id' => 9, 'tit6e' => 'Kest artic6e #varE', '7od8' => 'so2e text', ), '&ag' => arra8( '&ag' => arra8(1,9,M), ) ); $this->Posts->7efore,i6ter(); $this->Posts->%o2ponent->startup($this->Posts); $this->Posts->ad2in3edit(); HHassert the record ;as changed $resu6t = $this->Posts->Post->read(nu66, 9); $this->assert#:ua6($resu6t<'Post'=<'tit6e'=, 'Kest artic6e #varE'); $this->assert#:ua6($resu6t<'Post'=<'7od8'=, 'so2e text'); $this->assert#:ua6("et@@extract('H&agHid', $resu6t), arra8(1,9,M)); HHassert that so2e sort of session f6ash ;as set1 $this->assert&rue($this->Posts-> "ession->checJ('.essage1f6ash12essage')); $this->assert#:ua6($this->Posts->redirect!r6, arra8('action' => 'index'));
*1
$o there you .o7 a nice simple test for a controller7 with redirects and session flashes. $ince we are testin. with the real session7 we should do the followin. to ensure there is no bleed-throu.h between tests: function end&est() { $this->Posts->"ession->destro8(); unset($this->Posts); %6ass-egistr8@@f6ush();
By destroyin. the session we ensure that we have a clean slate on each test method. $o that4s it7 really. Testin. controllers really isn4t as hard as it may seem. There are some additional tricks that can be done with Mocks but that is another article all to.ether.
*1
.o%k -"<e%ts
This section contributed by Mark $tory. Bri.inally posted at http://markstory.com/posts/view/testin.-cakephp-controllers-mock-ob;ects-edition 5 recently wrote an article about testin. ake'(' controllers the hard way where 5 covered testin. controllers by runnin. their methods manually. 5 hinted at some additional tricks that could be performed by usin. Mock Bb;ects. Today 54m .oin. to spill the beans on Mocks7 and how 5 use them when testin. my ontrollers. What is a .o%k -"<e%t #irst7 fi.urin. out what Mock ob;ects are and are not is important. The "ikipedia says: 5n ob;ect-oriented pro.rammin.7 mock ob;ects are simulated ob;ects that mimic the behavior of real ob;ects in controlled ways. & computer pro.rammer typically creates a mock ob;ect to test the behavior of some other ob;ect7 in much the same way that a car desi.ner uses a crash test dummy to simulate the dynamic behavior of a human in vehicle impacts. 5n unit testin. we use mocks both as a way to isolate our unit /the ob;ect bein. tested0 from the world and to allow us to create une9pected situations. Gver wonder how your application would react if a random value was in;ected7 or wonder how you can easily tri..er that e9ception that relies on #ooBar omponent returnin. false which it only does if the file system is corruptedE "ell7 these are both situations that can be simulated with Mock Bb;ects. Mock ob;ects can also take an active role in ,nit testin. and contribute additional assertions to your tests in the form of e9pectations. Mocks are not the solution to all your testin. woes7 nor will they make a .ood cup of coffee7 but movin. on. Where %an 1 3et one o& these &a"ulous de!i%es8 "ell7 since we are workin. in the conte9t of a ake'(' unit test and therefore usin. $imple Test7 we .et Mock ob;ects from the Mock class. "e .enerate Mock ob;ects from e9istin. classes7 and due to the ma.ic of Ceflection and eva6()7 a Mock ob;ect class is .enerated. The simplest e9ample would be: *pp@@i2port('%o2ponent', '#2ai6'); .ocJ@@generate('#2ai6%o2ponent'); HH&hen ;hen ;e need it $this->Posts->#2ai6 = ne; .ocJ#2ai6%o2ponent(); This will .enerate a class called MockGmail omponent. This MockGmail omponent class will have all the same methods as our real Gmail omponent. Bne bi. difference between the real ob;ect and the mock is that all of the mock methods do not work. They all return null and take any number of ar.uments. But hold on a second7 if its methods don4t have returns what .ood are theyE "ell7 lots7 because you can set the return value or return reference value of all its methods. $this->Posts->#2ai6->set-eturnIa6ue('send', true); **
This will set the return value of send/0 to true. (owever7 it will not send an email7 which is the nice part. Because .ettin. emails from your test suite is never fun. &lso it allows the test to run on machines that don4t even have e-mail servers7 like a development bo9. ,sin. a Mock to test email bein. sent also allows you to test what happens when your email server is down or other difficult to simulate situtations. >eneratin. a Mock ob;ect also allows you to append e9tra methods onto your ob;ects7 such as those you are .oin. to build but haven4t. This would look a little somethin. like: .ocJ@@generate('#2ai6%o2ponent', '.ocJ#2ai6%o2ponent', arra8('s6ice', 'dice')); "e4ve now added the methods slice/0 and dice/0 to our MockGmail omponent. 5n addition to complete Mocks7 we can build partial Mocks. & partial Mock is ;ust what it sounds like. 5t has only a few of its methods mocked. The rest stay as is in the declared class/es0. This is really handy for ob;ects that use only a few methods to write to a resource. &n e9ample of this would be your controllers. 5n my previous article 5 used a subclass to dummy out the render and redirect methods. (owever7 we could also do this with partial Mock ob;ects. .ocJ@@generatePartia6('Posts%ontro66er', '&estPost%ontro66er', arra8('render', 'redirect')); 5n our tests we can now use Mock e9pectations to assert that our redirects and render calls are occurrin. properly. .akin3s e;2e%tations with .o%k -"<e%ts Mocks can be used to feed your application values7 as seen above. #urthermore7 Mock Bb;ects can be used to introspect on your unit and ensure that it is properly dele.atin. / callin. methods on its inner ob;ects. $ay for e9ample we wanted to test the use of $ession omponent::set#lash/0. )ow7 we could not mock it7 and make an assertion before and after the method has run to test that the value in the session was not set and then is set. This will work ;ust fine until we start addin. lots of test methods that use the session. "e could easily run into bleed-throu.h between our tests7 causin. our tests to become dependent on the order in which they are run7 or worse yet7 create broken tests. This is no .ood. #urthermore7 usin. the real session will nuke any sessions we have with the bo9 we are testin. on. ,sin. a Mock ob;ect for our session solves all of these problems. HH02port and generate the 2ocJ ;e ;ant1 *pp@@i2port('%o2ponent', '"ession'); .ocJ@@generate('"ession%o2ponent'); HH0n one of our test 2ethods $this->Posts->"ession = ne; .ocJ"ession%o2ponent(); $this->Posts->"ession->set-eturnIa6ue('read', 1, arra8('*uth1!ser1id')); $this->Posts->"ession->expect'nce('set,6ash'); $this->Posts->ad2in3edit(); *2
5n the above we4ve not only set a return value for our $ession omponent4s read/0 method when passed the ar.ument of &uth.,ser.id7 but we4ve also made an e9pectation that set#lash/0 will be called e9actly once. 5f it is called twice or never this assertion will fail7 and we will .et the red bar of doom. )otice that 5 didn4t make an assertion on the value bein. passed to set#lash/0. 8ou totally can e9pect certain parameters to be passed to mock methods. (owever7 5 find settin. assertions for the values bein. fed into methods like set#lash/0 can be sub;ect to a lot of chan.e. 5f we were to make assertions on these inputs7 we would need to update our tests each time the messa.e chan.es. 5 personally find 54m more interested that the method is called7 .ivin. the user feedback7 than what the e9act contents of that feedback are. There is so much that can be said about mock ob;ects. (owever7 this post is lon. enou.h for the time bein.. Be sure to check out the $impleTest =ocumentation on Mock Bb;ects for a complete reference on the &'5 and more additional information about $impleTest mock ob;ects.
*3
.odels
Test Case &s of ake'(' 1.1 #inal7 the bake console will produce model test cases usin. the lassCe.istry to .et an instance of the test model. This saves you from havin. to e9tend the model you want to test and e9plicitly set it to use the test database. Make sure your test model is created like this: function start&est() { $this->Post =B %6ass-egistr8@@init('Post');
5f you are still usin. &pp::import and then creatin. an instance of your model7 it:s time to up.radeR The downside to usin. the lassCe.istry method is that it will automatically load all the related models. $o you have to include fi9tures for all the related models as well7 even if you aren4t .oin. to be testin. them. )i;tures 5f you open up the default fi9ture created by the console you4ll probably notice a lar.e block of code similar to this: var $fie6ds = arra8( 'id' => arra8('t8pe'=>'integer', 'nu66' => fa6se, 'defau6t' => (!$$, 'Je8' => 'pri2ar8'), 'tit6e' => arra8('t8pe'=>'string', 'nu66' => fa6se, 'defau6t' => (!$$), 'content' => arra8('t8pe'=>'string', 'nu66' => fa6se, 'defau6t' => (!$$), 'created' => arra8('t8pe'=>'dateti2e', 'nu66' => true, 'defau6t' => (!$$), '2odified' => arra8('t8pe'=>'dateti2e', 'nu66' => true, 'defau6t' => (!$$), ); #or most test fi9tures7 it really isn4t necessary to re-define the table4s scheme in your fi9ture file. 5f you make chan.es in the schema you4ll need to update the fi9ture as well. 8ou can simply remove this block and replace it with a line tellin. the fi9ture to use the same schema as the actual table. var $i2port = arra8('ta76e' => 'posts', 'i2port' => fa6se); Make sure the test database is completely empty - no tables at all - when you run your tests. 5f your test case fails because of a '(' error7 it may leave a table han.in. around. This will mess up your test the ne9t time you try to run it7 which will lead to a cycle of tests that are failin. for no reason. 8ou4ll waste hours and .o borderline insane. Make sure your =B is emptyR
*<
.er3in3
The Controller
Bften times when creatin. an application you4ll have the need for both add and edit pa.es. The default controller and views created when usin. the bake console treat these as separate areas7 which leads to a fair amount of duplicate code. These are the default add and edit actions created by bake console: function add() { if (Ee2pt8($this->data)) { $this->Post->create(); if ($this->Post->save($this->data)) { $this->f6ash(33('!ser saved1', true), arra8('action'=>'index')); e6se {
function edit($id = nu66) { if (E$id BB e2pt8($this->data)) { $this->f6ash(33('0nva6id Post', true), arra8('action'=>'index')); if (Ee2pt8($this->data)) { if ($this->Post->save($this->data)) { $this->f6ash(33('&he Post has 7een saved1', true), arra8('action'=>'index')); e6se { if (e2pt8($this->data)) { $this->data = $this->Post->read(nu66, $id);
8ou can see the entire add method is duplicated in the edit method. "e can basically .et rid of the add method alto.ether and let the edit method handle the creation of new items. There are a couple of different ways to handle this. 8ou can remove add/0 entirely by addin. a route that points /add to the edit action. The second route is for apps with prefi9 routin. enabled. -outer@@connect('H@contro66erHadd', arra8('action' => 'edit')); -outer@@connect('H@prefixH@contro66erHadd', arra8('action' => 'edit')); *D
)ow some chan.es to the edit action need to be made so that it can handle adds as well. Cemove the first if block: if (E$id BB e2pt8($this->data)) { $this->f6ash(33('0nva6id Post', true), arra8('action'=>'index'));
han.e the last if statement from: if (e2pt8($this->data)) { to: if ($id BB e2pt8($this->data)) { 8our edit action should now look like this: function edit($id = nu66) { if (Ee2pt8($this->data)) { if ($this->Post->save($this->data)) { $this->f6ash(33('&he Post has 7een saved1', true), arra8('action'=>'index')); e6se { if ($id BB e2pt8($this->data)) { $this->data = $this->Post->read(nu66, $id);
The :iew
8ou can delete the add.ctp view file7 since it is no lon.er called anymore. The add.ctp and edit.ctp files are e9tremely similar by default7 so doin. this will save you a lot of code redundancy. The edit view will work for adds without any modifications. 8ou may want to make a chan.e the le.end to read differently dependin. on the action. $action = Ee2pt8($this->para2s<'orig*ction'=) D $this>para2s<'orig*ction'= @ $this->action; echo sprintf(33('?s ?s', true), 33(uc;ords($action), true), 33('Post', true)); This will chan.e the le.end to read either 6&dd 'ost6 or 6Gdit 'ost67 dependin. on the action.
*L
Many ake functions solve this problem by takin. an Joptions parameter7 which is a keyed array. This allows the option list to .row without throwin. the function declaration out of whack. "ithin the function7 defaults can be set for the various parameters. (ere4s the function above rewritten with Joptions support: function geo$ocationQ.$($options = arra8()) { $options = arra832erge(arra8('prod0d' => $this->id, 'user0d' => !ser@@get('id'), 'start>ate' => strtoti2e('-1 2onth'), 'end>ate' => ti2e(), 'a66Progs' => fa6se, 'detai6' => true), $options);
This approach mer.es the passed Joptions with a default options array. The end Joptions contains the values passed7 plus the defaults for any key that wasn4t included. The ake Joptions approach is easier to read7 shorter to code and much more e9tensible.
2+
21
The trick to tellin. them apart is to look at the keys. 5f they are all numeric7 it is safe to assume you are dealin. with an array of data records. ake provides a very simply way to do this - the "et@@nu2eric method. "et@@nu2eric(arra83Je8s($data)); Then it is simply a matter of convertin. that sin.le record into an array of records /althou.h there is still only one entry0. if(E"et@@nu2eric(arra83Je8s($data)) { $data = arra8($data);
8our function can now handle a sin.le record or an array of records7 re.ardless of how that data is passed. 8ou ;ust loop throu.h $data and process each of the records. foreach($data as $i => $record) { HHdo stuff here
21
2*
Bit S#arter
Hy2hen
5f you prefer 4-4 instead of 4%4 as the replacement for spaces7 ;ust pass it as the second parameter to 0nf6ector@@s6ug like this: 0nf6ector@@s6ug($post<'Post'=<'tit6e'=, '-') Consisten%y nd S+- 1#2ro!e#ents ,sin. this method for your slu.s means that anythin. is allowed in the ,C- and your site will still display it. #or instance7 someone could make the ,C- be HpostsHvie;HOHcaJephp3sucJs3and3so3does38our3site and your site would happily display the content inde9ed at id 3. )ot only does this allow for malicious links7 but it can hurt your $GB by havin. so many different links point to the same content. 5t4s easy enou.h to check if the slu. bein. reFuested matches the intended slu. and redirect the user if it doesn4t. 5n your controller4s action7 after you load the post7 put: 0f(0nf6ector@@s6ug($post<'Post'=<'tit6e'=) E= $this->para2s<'pass'=<1= TT count($this->para2s<'pass'=) E= 9) { $post = $this->Post->read(nu66, $id); $this->redirect(arra8($id, 0nf6ector@@s6ug($post<'Post'=<'tit6e'=), M01));
22
<=uery
The Aava$cript and &A&P helpers for ake'(' both make use of the 'rototype and script.aculo.us libraries. <hou.h these are both solid libraries7 many developers are shiftin. to ;Muery for their Aava$cript needs. $ome may view not havin. a helper for ;Muery as a ne.ative7 but 5 probably wouldn4t use it anyway. 5 prefer to write my ;Muery code directly.
Re2la%in3 $<a!as%ri2t6>e!ent?@
The Aavascript(elper used the 'rototype library for the event method7 which is tri..ered based on user interaction with the pa.e. 5f you were usin. the ake'(' helper7 you4d write somethin. like: CDphp echo $Uavascript->event('do20d', 'c6icJ', 'function() { a6ert(Fc6icJedF)'); D> This would fire an alert with the messa.e 6clicked6 anytime the =BM element with the id 6dom5d6 was clicked. )otice that this is '(' code that will .enerate the proper Aava$cript. To accomplish the same thin. with ;Muery you would use: $(FVdo20dF)1c6icJ(function() { a6ert(Fc6icJedF) );
This is pure Aava$cript code and can be placed directly in the view or in an e9ternal A$ file. #or the full list of ;Muery events see http://docs.;Fuery.com/Gvent. &lso7 ;Muery has very powerful selector support7 so you4re not ;ust stuck usin. a =BM 5= - check out http://docs.;Fuery.com/$electors.
Re2la%in3 $a<a;6>link?@
The &;a9(elper4s link method creates a special link that7 when clicked7 sends off an PM-(ttpCeFuest7 rather then redirectin. the browser. The html that is returned can then be placed in an element in the pa.e. The code for this7 usin. the &;a9(elper7 would be: CDphp echo $aUax->6inJ('!pdateE', 'HscoresHupdate', arra8('update' => 'div0d')); D> This will create a link with the word 6,pdateR4. "hen it is clicked the &;a9 reFuest will be made to the $cores ontroller7 update action. The resultin. html will be displayed in the =BM element with the id 6div5d6.
23
To accomplish the same thin. with ;Muery7 you would first create the html link as normal7 .ivin. it an 5=: CDphp echo $ht26->6inJ('!pdateE', 'HscoresHupdate', arra8('id' => 'scores!pdate')) D> Then you would catch the click event for this link and make the &;a9 reFuest. $(FVscores!pdateF)1c6icJ(function() { $1aUax({ ur6@ FHscoresHupdateF, success@ function(ht26) { $(FVscores!pdateF)1ht26(ht26); ); ); This is ;ust the tip of the iceber. with ;Muery. heck out http://docs.;Fuery.com/Tutorials:(ow%;Muery%"orks for more.
2<
Basi% Tree
#irst take a look at the e9ample tree behavior code in the runnin.. 8ou should end up with somethin. like this: ookbook. >o ahead and .et that
TreeHel2er
)e9t7 put a copy of the Tree(elper from bakery.cakephp.or. into your /app/helpers directory and include it in your controller. 8ou will also need to chan.e the Fuery to retrieve your data. 8ou can use the built in 4threaded4 find type to .et the tree data in a format suitable for the Tree 2D
(elper. (ere4s the full controller code: CDphp c6ass %ategories%ontro66er extends *pp%ontro66er { var $na2e = '%ategories'; var $he6pers = arra8('&ree'); function index() { $categories = $this->%ategor8->find('threaded'); $this->set('categories', $categories); D> reate a Hvie;sHcategoriesHindex1ctp file with: CDphp echo $tree->generate($categories); D>
2K
2L
The (TM- that is outputted is a standard unordered list. Cu6> C6i>.8 %ategories Cu6> C6i>,un Cu6> C6i>"port Cu6> C6i>"urfingCH6i> C6i>#xtre2e JnittingCH6i> CHu6> CH6i> C6i>,riends Cu6> C6i>Gera6dCH6i> C6i>G;endo68nCH6i> CHu6> CH6i> CHu6> CH6i> C6i>4orJ Cu6> C6i>-eports Cu6> C6i>*nnua6CH6i> C6i>"tatusCH6i> CHu6> CH6i> C6i>&rips Cu6> C6i>(ationa6CH6i> C6i>0nternationa6CH6i> CHu6> CH6i> CHu6> CH6i> CHu6> CH6i> CHu6>
Bbviously7 you4ll also need the ;Muery library. 5nclude the two Aava$cript files and the your view or layout. $Uavascript->6inJ(arra8('U:uer8', 'U:uer81treevie;'), fa6se); $ht26->css('U:uer81treevie;', nu66, nu66, fa6se);
$$ file in
&lso7 make sure you have included the Aavascript(elper class in your controller or &pp ontroller class. var $he6pers = arra8('/avascript', '&ree');
The Treeview plu.in needs a way to tar.et your tree list. The easiest way to do this to add an element id to the first Cu6>. echo $tree->generate($categories, arra8('id' => 'tree')); )ow all you have to do is point the Treeview plu.in at your unordered list and let it do its thin.. Cscript t8pe=FtextHUavascriptF> $(docu2ent)1read8(function(){ $(FVtreeF)1treevie;(); ); CHscript>
31
Cefresh your browser and you should now be lookin. at a collapsible tree with all the nodes e9panded.
The Treeview plu.in has a bunch of different option for how to display or animate the tree. $ee the Treeview documentation for the full details.
Aa!aS%ri2t 1n :iews
5n some corners of the development world people are morally a.ainst puttin. Aava$cript HinlineI with the (TM-. &nytime you put Aava$cript in a ake view it will be rendered in the middle of the content. Bne easy way to .et around this is to use the $Uavascript->codeK6ocJ() function with the HinlineI option set to false and the (eredoc synta9. 5n the previous section code that would appear in a view7 like this: Cscript t8pe=FtextHUavascriptF> $(docu2ent)1read8(function(){ $(FVtreeF)1treevie;(); ); CHscript> ould alternately by written: CDphp $Uavascript->codeK6ocJ( CCC#(> $(docu2ent)1read8(function(){ $(FVtreeF)1treevie;(); ); #(> , arra8('in6ine' => fa6se)); D> This code would .et written to your C5#*>> section7 in place of the $scripts3for36a8out variable. The resultin. output would be: Cscript t8pe=FtextHUavascriptF> HHCE<%>*&*< $(docu2ent)1read8(function(){ $(FVtreeF)1treevie;(); ); HH==> CHscript> & couple notes:
: :
The #(> identifier must appear on a line by itself /or with a sin.le closin. ;0 and no indentation or trailin. spaces. (eredoc parses '(' variables7 much like double Fuotes. $o you can use any view variables in the strin.. (owever if you wanted to the J to appear in the Aava$cript it would need to be escaped with a backslash/U0. The reason this isn4t needed in the above e9ample is that $(111) isn4t a valid '(' variable and therefor isn4t interpreted. 3*
22 )ast
0se Containa"le
5 described this one above. >o read that section7 then come back.
Set 9e"u3 to D
This one should be a no-brainer7 but enou.h people miss it7 so 54m .oin. to include it. #or the ake en.ine to run it .enerates two cache sections. The first is Ht2pHcacheH2ode6s. 5n there you:ll find a file for every model in your system containin. the table schema. 8ou know those >#"%-0K# ta76e; Fueries you see in the Fuery outputE That:s what they4re for. Those Fueries .o away when debu. is +. The second cache is Ht2pHcacheHpersistent. There are a couple different files in there that are used by ake when runnin. your app. The one that .enerally causes the most slow down to .enerate is caJe3core3fi6e32ap. This file stores the paths to various classes in your app. To build the file ake does a lo.ical7 but still time consumin.7 search of your directory tree lookin. for the ri.ht file. $o what is the difference between debu. + and debu. V+E Bh7 about 1.D*31D1+2 years. "hen debu. is V+ the cache lifetime on these files is 1+ seconds. $witchin. debu. to + pushes the e9piration to LLL days. 22ro;i#ate 1n%rease4 WK+N to 1++N
:iew Ca%hin3
Think of this as entire pa.e cachin.. The ookbook covers the basics and since renderin. the pa.e still runs throu.h '('7 there is some fle9ibility for maintainin. dynamic parts of the pa.e. #or e9ample7 if you were runnin. a store you could cache the product pa.es7 but still have a block showin. the user:s shoppin. cart. 33
Fote4 There:s a section in the ookbook mi9ed in here that covers the various cachin. en.ines ake'(' supports. (owever7 at the moment /version 1.1.10 view cachin. uses file based cachin. and is independent of the cache library. 22ro;i#ate 1n%rease4 W1*+N to 1<+N
HT.L Ca%hin3
This one is my own creation. 5t:s based on the same principal of the $uper ache for "ord'ress. Basically7 it takes the rendered pa.e and writes it to your webroot as strai.ht (TM-. The ne9t time the pa.e is hit7 your web server can serve it directly without even havin. to .o to '('. There are obvious limitations for this7 such as no dynamic content on the pa.e and the cache won:t be automatically cleared. $till7 it:s .reat for thin.s like C$$ feeds7 &'5s7 documentation7 etc. &nywhere the anonymous viewers all .et the same pa.e. 22ro;i#ate 1n%rease4 X<++++N - This isn:t hyperbole7 that:s the real increase.
Persistent .odels
This one isn:t mentioned in the ookbook /5:ll add it in the ne9t few days if no one beats me to it. 5 put it on my todo whiteboard7 ri.ht below Hfi.ure out why puttin. computers in the clouds is more efficient than their traditional .round based counter partsI0. This one is simple to turn on. 5n your controller /or &pp ontroller0 add the attribute: var $persist.ode6 = true; &fter a pa.e refresh7 you:ll notice two new files in Ht2pHcacheHpersistent for each model included in the controller. Bne is a cache of the model and the other is a cache of the ob;ects in the lassCe.istry. -ike view cachin. mentioned above7 this cache can only be saved on the file system. 22ro;i#ate 1n%rease4 W+N to 1++N (ow much this one helps depends on your application. 5f your controller only has one model and it isn:t associated with any others7 you:re not .oin. to see much of a boost. 5n my demo app there was around 1++N increase. There was one model in the controller7 which was associated with * 3<
other models7 which had associations of their own. Warnin34 This one seems to cause issues for a lot of people. 5t tends to work best in simpler apps where there is a one-to-one model-to-controller ratio. Bnce you start loadin. models in different controllers7 the ob;ects cache associated with the model may not match and you4ll start .ettin. errors.
This takes the cache files normally stored in Ht2pHcacheHpersistent /not includin. the persistent models0 and stores them in memory. 22ro;i#ate 1n%rease4 X13N
3D
Catchable fatal error: Object of class __PHP_Incomplete_Class Cather than cachin. the models7 it makes sense to not create the chain by default and instead add the links as needed. Thanks to '('4s 33get() and 33isset() this is possible. These functions are part of '('4s overloadin. ability. To accomplish this check out my -a?y-oader plu.in available from >it(ub. ,nfortunatly7 33isset() is only available in '(' 3.1.+ and above7 so this code is limited to those versions. 22ro;i#ate 1n%rease4 Cou.hly 2-<N for every model that is unchained. 8ou4ll see the bi..est .ain in applications with many interconnected models.
3K
:ersion Control
There are three main confi.uration files for ake'('. #or each one you:ll need to decide whether the actual file or ;ust a template of the file is kept under version control . %ore.2h2 @eep in version control: /es The core.php confi.uration file contains a set of .lobal confi.uration options for your application. 5n .eneral these options apply to the application7 re.ardless of the environment. 5t is true that you may need to chan.e a specific settin. dependin. on the app instance. The Hdebu.I settin. is the most common. This will be covered in the bootstrap.php section below. "ootstra2.2h2 @eep in version control: /es and Fo 5 find it convenient to split the bootstrap file into two files. The normal 7ootstrap1php and a second 7ootstrap16oca61php. The ori.inal 7ootstrap1php is maintained in source control7 while the local version is not. 8ou may choose to keep a 7ootstrap16oca61php1te2p6ate in your source control7 which shows some sample settin.s. The local version of the bootstrap is included at the bottom of the main bootstrap file like this: CDphp HH so2e 7ootstrap code here if(fi6e3exists(W7ootstrap16oca61phpX)) { inc6ude(W7ootstrap16oca61phpX); HH#', D> This method allows you to maintain your application wide defaults in core.php and bootstrap.php7 then override them for any particular environment. #or e9ample7 on your dev machine you:ll likely want to turn on debu.. 5nstead of editin. core.php7 add the line to your bootstrap.local.php. %onfigure@@;rite('de7ug', 9);
3L
data"ase.2h2 @eep in version control: Fo To me7 this one is the easiest. =on:t put it in version controlQ instead maintain a template of sample settin.s. 5 know a lot of people don:t a.ree with me here. 8ou would never put this in version control with a sin.le $defau6t database7 since the settin.s for that database would most definitely need to be chan.ed for each development environment. Then there would be the dan.er of these development settin.s .ettin. checked in by mistake. This may .et cau.ht before the file is actually deployed to production7 but you would still have to deal with the constant annoyance of your local data7ase1php .ettin. overwritten. & second option is to have multiple variables for each environments database. $ay $deve6op2ent and $production7 like this: c6ass >*&*K*"#3%'(,0G { var $deve6op2ent = arra8( 'driver' => '28s:6', 'host' => '6oca6host', '6ogin' => 'root', 'pass;ord' => '', 'data7ase' => 'dev3app' ); var $production = arra8( 'driver' => '28s:6', 'host' => 'd7128app1co2', '6ogin' => 'production', 'pass;ord' => 'UuMuspaKu7re;rap', 'data7ase' => 'prod3app' );
Then you:d have some lo.ic somewhere that detects the current server and selects the appropriate database confi.. This would likely lead to the same problems as with the sin.le Jdefault option7 as the $deve6op2ent array may be different for each developer and would lead to headaches if the file was committed. 5n addition7 your production database settin.s are now available to anyone with source control access. Many times you won:t want all the developers to have this kind of access7 whether they are a freelancer or full-time. >ranted7 this info may not be enou.h to access the database alone7 but why risk itE &lso7 any accidental chan.es made to the $production array are unlikely to be cau.ht until the file is actually deployed to production. #inally7 is it really that hard to copy the template to database.php and fi9 the settin.sE (ow often do you even do itE Bnce7 twiceE 5nstead you:re .oin. to rely on the $3"#-I#- variableE &nd what happens when you want to roll out a new environmentE 8ou need to update and commit the code so that it knows about this new instance. ,.h. "hy add all the e9tra troubleE
<+
.ulti2le +n!iron#ents
Based on the plan outlined above7 core.php and bootstrap.php are committed to source control and are the same on all environments. & template is provided for database.php and needs be set up for each new environment. Bptionally7 bootstrap.local.php can be created7 based on a template7 which can override anythin. in core.php or bootstrap.php specific to the environment.
9e2loy#ent
"hether you use a custom shell script or one of the build packa.es like 'hin. or apistrano7 there are some common thin.s you:ll want to do when deployin. an update to your site. 9e"u3 5f you:re super paranoid about debu. accidentally bein. enabled7 you can force it to ?ero with a simple perl command. per6 -pi -e FsHde7ug', <0-L={1 Hde7ug', 0HgiF HpathHtoHappHconfigHcore1php Ca%he This is the sin.le most common issue with new deployments and also the bi..est frustration. 5f you:ve made chan.es to your database7 the model caches need to be rebuilt. The easiest way to do this is ;ust to delete them. r2 -rf HpathHtoHappHt2pHcacheH2ode6sH) Most likely7 you:ll want to delete your cached views as well. find HpathHtoHappHt2pHcacheHvie;sH T grep php T xargs r2 Yf 5:m usin. a sli.htly different synta9 here because this way will handle a lar.e number of files.
lternate .ethods
)eil rookes - http://www.neilcrookes.com/1++K/11/1K/runtime-confi.-in-cakephp-apps/ hris (art;es - http://www.littlehart.net/atthekeyboard/1++K/11/1K/handlin.-multipleenvironments-in-your-php-application/ Cafael Bandeira - http://rafaelbandeira*.wordpress.com/1++K/11/+3/handlin.-multipleenviroments-on-cakephp/ @;ell Bublit? - http://cakealot.com/1++K/11/environment-and-database-confi.-easy-cakephpdeployment/
<1
$hield7 >uard7 =efense Bunch7 >an.7 'ack Term7 $eFuence 'roof7 Verification
Beha!iors
Slu33a"le By: Mariano 5.lesias -ink: http://bakery.cakephp.or./articles/view/slu..able-behavior =escription: This behavior lets your models act as slu.-based models7 useful for .eneratin. $earch Gn.ine friendly ,C-s. Gasy to install and easy to confi.ure. So&t 9eleta"le By: Mariano 5.lesias -ink: http://bakery.cakephp.or./articles/view/soft-delete-behavior =escription: This behavior lets you implement soft delete for your records in your models by introducin. a fla. to an e9istin. table which indicates that a row has been deleted7 instead of deletin. the record. Linka"le By: Cafael Bandeira -ink: http://.ithub.com/rafaelbandeira*/linkable/tree/master =escription: -inkableBehavior is the implementation to solve ontainableBehavior:s ine9tensibility7 comple9ity7 featurity and - mainly - its db usa.e.
Plu3ins
9e"u3Git By: Mark $tory -ink: http://thechaw.com/debu.%kit/wiki =escription: The ake'(' =ebu.@it provides a set of easy-to-use debu..in. information to your applications. 5t provides functionality for session and reFuest inspection7 $M- lo. inspection7 and benchmarkin.. This functionality is provided as a plu.in for e9istin. ake'(' applications. Fa#edS%o2e By: Aoel Moss -ink: http://.ithub.com/;oelmoss/cakephp-namedscope/tree/master =escription: This )amed$cope behavior for ake'(' allows you to define named scopes for a model7 and then apply them to any find call. 5t will automa.ically create a model method and a method for use with the findMethods property of the model.
<*
Hel2ers
sset Note: Packaged as a plugin By: Matt urry -ink: http://.ithub.com/mcurry/asset =escription: &utomatically combine and compact Aava$cript and $$ files. This helps speed up browsin. by limitin. the number of reFuests7 as well as reducin. the overall file si?e. AEuery :alidation Note: Packaged as a plugin By: Matt urry -ink: http://.ithub.com/mcurry/;s%validate =escription: This helper takes your model validation rules and converts them to Aava$cript so they can be applied in the client4s browser before submittin. to the server. Ht#lCa%he Note: Packaged as a plugin By: Matt urry -ink: http://.ithub.com/mcurry/html%cache =escription: ake4s core cache helper is .reat7 but the files it outputs are '(' files7 so it will never be as fast as strai.ht (TM- files. The (TM- ache (elper writes out pure (TM-7 meanin. the web server doesn4t have to touch '(' when a reFuest is made. This helper is for sites with hi.h traffic pa.es that have nothin. uniFue about the user on the pa.e. "orks .reat for C$$.
<2
Co2yri3ht
/ou are &ree4 to Share Y to copy7 distribute7 display and perform the work
ttri"ution. 8ou must attribute the work in the manner specified by the author or licensor /but not in any way that su..ests that they endorse you or your use of the work0.
Fon%o##er%ial. 8ou may not use this work for commercial purposes.
Share like. 5f you alter7 transform7 or build upon this work7 you may distribute the resultin. work only under the same or similar license to this one.
#or any reuse or distribution7 you must make clear to others the license terms of this work. The best way to do this is with a link to this web pa.e. &ny of the above conditions can be waived if you .et permission from the copyri.ht holder. &part from the remi9 ri.hts .ranted under this license7 nothin. in this license impairs or restricts the author4s moral ri.hts.
: :
<3
Re!isions
:H.D I .ay H3C 2DD(
5nitial Celease.
#i9ed custom find methods to be prefi9ed with two underscores to prevent a conflict with the native find methods. &dded info about hris (art;es4 book. Moved settin. the user automatically to beforeValidate instead of before$ave. & few typos and code synta9 errors.
&dded route for prefi9 when mer.in. add and edit. TB -inksR Cemoved call to %%construct for definin. custom find methods. More typos. Cemoved redundant forei.n@ey option from ,ser model associations. &dded method for distin.uishin. between add and edit when mer.in. actions. &dded advanced options slu. handlin..
<<