| 
									
										
										
										
											2016-01-31 14:24:53 +00:00
										 |  |  | <!doctype html> | 
					
						
							|  |  |  | <html> | 
					
						
							|  |  |  | <head> | 
					
						
							| 
									
										
										
										
											2016-02-02 20:30:52 +00:00
										 |  |  |     {{ partial "head.html" . }} | 
					
						
							| 
									
										
										
										
											2016-01-31 14:24:53 +00:00
										 |  |  | </head> | 
					
						
							|  |  |  | <body> | 
					
						
							|  |  |  | <div class="page-container container-fluid"> | 
					
						
							|  |  |  |     <div class="col-md-3 menu"> | 
					
						
							|  |  |  |         <nav class="col-md-3"> | 
					
						
							|  |  |  |             <div id="last-posts" class="open"> | 
					
						
							|  |  |  |                 <h3 data-open="last-posts">Aubm.net - Les derniers articles</h3> | 
					
						
							|  |  |  |                 <ul> | 
					
						
							|  |  |  |                     <li><a href="#">Astuce le serveur de developpement embarque de-php</a></li> | 
					
						
							|  |  |  |                     <li><a href="#">Astuce phpstorm partager ses lives templates</a></li> | 
					
						
							|  |  |  |                     <li><a href="#">Contrainte dunicite dans symfony 2 avec doctrine</a></li> | 
					
						
							|  |  |  |                     <li><a href="#">Du neuf sous le capot</a></li> | 
					
						
							|  |  |  |                     <li><a href="#">Gardez le code explicite court et modulaire</a></li> | 
					
						
							|  |  |  |                     <li><a href="#">Installation et utilisation de sentry</a></li> | 
					
						
							|  |  |  |                     <li><a href="#">La pagination avec doctrine la bonne methode</a></li> | 
					
						
							|  |  |  |                     <li><a href="#">Mes premiers pas avec cakephp 3</a></li> | 
					
						
							|  |  |  |                     <li><a href="#">Retour dexperience sur joomla point de vue dun developpeur</a></li> | 
					
						
							|  |  |  |                     <li><a href="#">Utiliser les event subscriber avec fosrestbundle et jmsserializerbundle</a></li> | 
					
						
							|  |  |  |                     <li><a href="#">Webservice restful avec symfony2 gerer les champs de type datetime</a></li> | 
					
						
							|  |  |  |                 </ul> | 
					
						
							|  |  |  |             </div> | 
					
						
							| 
									
										
										
										
											2016-02-01 22:02:33 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |             <div id="tags" class="open"> | 
					
						
							|  |  |  |                 <h3 data-open="tags">Tags</h3> | 
					
						
							|  |  |  |                 <ul class="tags"> | 
					
						
							|  |  |  |                     <li><a href="#">PHP</a></li> | 
					
						
							|  |  |  |                     <li><a href="#">Golang</a></li> | 
					
						
							|  |  |  |                     <li><a href="#">Javascript</a></li> | 
					
						
							|  |  |  |                     <li><a href="#">HTML</a></li> | 
					
						
							|  |  |  |                 </ul> | 
					
						
							|  |  |  |             </div> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <div id="categories" class="open"> | 
					
						
							|  |  |  |                 <h3 data-open="categories">Categories</h3> | 
					
						
							|  |  |  |                 <ul class="categories"> | 
					
						
							|  |  |  |                     <li><a href="#">Archives Décembre 2015</a></li> | 
					
						
							|  |  |  |                     <li><a href="#">Archives Novembre 2015</a></li> | 
					
						
							|  |  |  |                     <li><a href="#">Archives Octobre 2015</a></li> | 
					
						
							|  |  |  |                     <li><a href="#">Archives Septembre 2015</a></li> | 
					
						
							|  |  |  |                 </ul> | 
					
						
							|  |  |  |             </div> | 
					
						
							| 
									
										
										
										
											2016-01-31 14:24:53 +00:00
										 |  |  |         </nav> | 
					
						
							|  |  |  |     </div> | 
					
						
							|  |  |  |     <div class="col-md-9 article-content"> | 
					
						
							|  |  |  |         <article> | 
					
						
							| 
									
										
										
										
											2016-02-01 21:44:21 +00:00
										 |  |  |             <h1>Mes premiers pas avec cakephp 3</h1> | 
					
						
							|  |  |  |             <h2 id="avant-propos:cef4ef39fb39a8da0d9e48695b83d954">Avant propos</h2> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Profitant d’un peu de temps libre j’ai décidé de m’essayer à la dernière version en | 
					
						
							|  |  |  |                 date de | 
					
						
							|  |  |  |                 CakePHP, à savoir CakePHP 3. Je suis donc parti dans l’idée de pondre un Twitter-like en version | 
					
						
							|  |  |  |                 allégée (très | 
					
						
							|  |  |  |                 allégée).</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Après avoir posé le contexte en présentant mon Twitter fait maison, je développerai cet article en | 
					
						
							|  |  |  |                 apportant des | 
					
						
							|  |  |  |                 précisions sur le code et les différentes fonctionnalités de CakePHP 3 que j’ai utilisées pour | 
					
						
							|  |  |  |                 construire le | 
					
						
							|  |  |  |                 site. A noter que l’objectif premier est de se concentrer sur les spécificités de CakePHP 3. Je | 
					
						
							|  |  |  |                 suggère - afin | 
					
						
							|  |  |  |                 de tirer meilleur parti de cette lecture - de bénéficier en amont d’une certaine expérience autour | 
					
						
							|  |  |  |                 d’outils | 
					
						
							|  |  |  |                 comme Composer, d’être à l’aise avec le modèle MVC, ou encore de savoir ce qu’est un | 
					
						
							|  |  |  |                 ORM.</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Au départ, mes objectifs étaient de comprendre comment un projet CakePHP 3 est structuré et de découvrir | 
					
						
							|  |  |  |                 les | 
					
						
							|  |  |  |                 fonctionnalités offertes par le framework. C’est dans cet esprit que je vais écrire, tâchant de | 
					
						
							|  |  |  |                 rester dans | 
					
						
							|  |  |  |                 une simple description. Le but n’est donc pas de comparer CakePHP 3 à d’autres frameworks, | 
					
						
							|  |  |  |                 ni de | 
					
						
							|  |  |  |                 répondre directement à des questions comme “Cake est-il adapté pour tel type d’application ?”. | 
					
						
							|  |  |  |                 D’autant | 
					
						
							|  |  |  |                 que la forme ne s’y prête pas dans la mesure où un projet comme celui-ci ne permet pas de couvrir | 
					
						
							|  |  |  |                 tous ses | 
					
						
							|  |  |  |                 aspects.</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Cet article est un bilan sur les quelques journées que j’ai passées à jongler entre mon IDE et la | 
					
						
							|  |  |  |                 documentation | 
					
						
							|  |  |  |                 officielle de CakePHP 3. Je l’écris avant tout pour moi, afin qu’il puisse éventuellement me | 
					
						
							|  |  |  |                 servir de | 
					
						
							|  |  |  |                 point de départ si j’ai un jour besoin de travailler avec ce framework. Ceci étant dit, comme il | 
					
						
							|  |  |  |                 semble qu’il | 
					
						
							|  |  |  |                 n’existe encore (du moins à l’heure où j’écris) que relativement peu de ressources sur | 
					
						
							|  |  |  |                 le sujet | 
					
						
							|  |  |  |                 (en dehors de la documentation officielle et en français du moins), je serais content d’apprendre | 
					
						
							|  |  |  |                 qu’il | 
					
						
							|  |  |  |                 a pu servir à d’autres développeurs.</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <h2 id="contexte:cef4ef39fb39a8da0d9e48695b83d954">Contexte</h2> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>L’application que j’ai réalisée s’inspire ouvertement du fonctionnement de Twitter. | 
					
						
							|  |  |  |                 Voilà le | 
					
						
							|  |  |  |                 contenu de ma check-list en début de projet :</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <ul> | 
					
						
							|  |  |  |                 <li>Les utilisateurs enregistrés peuvent poster des messages (des tweets) de moins de 140 caractères | 
					
						
							|  |  |  |                 </li> | 
					
						
							|  |  |  |                 <li>Tous les tweets apparaissent en page d’accueil dans l’ordre du plus récent au plus | 
					
						
							|  |  |  |                     ancien | 
					
						
							|  |  |  |                 </li> | 
					
						
							|  |  |  |                 <li>La page d’un utilisateur affiche les détails de son profil et la liste de ses tweets</li> | 
					
						
							|  |  |  |                 <li>Possibilité d’ajouter des #hashtags dans les tweets, cliquer sur un hashtag affiche la liste | 
					
						
							|  |  |  |                     de tous les | 
					
						
							|  |  |  |                     tweets qui le mentionnent | 
					
						
							|  |  |  |                 </li> | 
					
						
							|  |  |  |                 <li>Pas de pagination pour les tweets, charger les tweets suivants au défilement de la page</li> | 
					
						
							|  |  |  |                 <li>Les utilisateurs peuvent modifier les détails de leur profil et télécharger une image pour | 
					
						
							|  |  |  |                     personnaliser leur | 
					
						
							|  |  |  |                     avatar | 
					
						
							|  |  |  |                 </li> | 
					
						
							|  |  |  |                 <li>Afficher un bloc listant les hashtags les plus populaires</li> | 
					
						
							|  |  |  |             </ul> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>N’ayant pas souhaité déployer sur un serveur, j’ai pris la peine de réaliser cette vidéo de | 
					
						
							|  |  |  |                 présentation | 
					
						
							|  |  |  |                 au cas où vous souhaiteriez voir l’application tourner.</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <div class="video-wrapper"> | 
					
						
							|  |  |  |                 <iframe width="1280" height="750" src="https://www.youtube.com/embed/_-UvsRoXZeM" frameborder="0" | 
					
						
							|  |  |  |                         allowfullscreen></iframe> | 
					
						
							|  |  |  |             </div> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Les sources sont disponibles sur <a href="https://github.com/aubm/Twitthome-CakePHP3" target="_blank">Github</a>. | 
					
						
							|  |  |  |                 Je | 
					
						
							|  |  |  |                 suggère de conserver l’onglet Github ouvert pendant la lecture afin de pouvoir facilement faire | 
					
						
							|  |  |  |                 des parallèles | 
					
						
							|  |  |  |                 entre les notions abordées et le code de l’application.</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Le fichier <a href="https://github.com/aubm/Twitthome-CakePHP3/blob/master/database.sql" | 
					
						
							|  |  |  |                              target="_blank">database.sql</a> contient les requêtes à exécuter pour ajouter les tables | 
					
						
							|  |  |  |                 dans une base | 
					
						
							|  |  |  |                 de données MySQL.</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <figure> | 
					
						
							|  |  |  |                 <img src="/img/twitthome_schema.png" alt="Schéma base de données Twitthome" | 
					
						
							|  |  |  |                      class="img-responsive"/> | 
					
						
							|  |  |  |                 <figcaption>Une représentation graphique du schéma de la base de données.</figcaption> | 
					
						
							|  |  |  |             </figure> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <h2 id="généralités-et-organisation-du-code:cef4ef39fb39a8da0d9e48695b83d954">Généralités et organisation du | 
					
						
							|  |  |  |                 code</h2> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>A supposer que vous souhaitiez démarrer un projet CakePHP 3, la seule chose à faire après avoir installé | 
					
						
							|  |  |  |                 les | 
					
						
							|  |  |  |                 pré-requis nécessaires (PHP 5.3 et Composer) est de lancer cette commande :</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <pre><code class="language-bash">composer create-project --prefer-dist -s dev cakephp/app my_app_name | 
					
						
							|  |  |  | </code></pre> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Composer téléchargera CakePHP 3 et ses dépendances dans un nouveau dossier <code>my_app_name</code>. Le | 
					
						
							|  |  |  |                 script d’installation | 
					
						
							|  |  |  |                 est lancé automatiquement et propose de configurer les droits des répertoires pour vous. Vous pourrez | 
					
						
							|  |  |  |                 alors | 
					
						
							|  |  |  |                 commencer à travailler sur le site en utilisant le serveur HTTP embarqué :</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <pre><code class="language-bash">bin/cake server | 
					
						
							|  |  |  | </code></pre> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Comme pour beaucoup de frameworks web, CakePHP 3 propose une implémentation du pattern composite MVC pour | 
					
						
							|  |  |  |                 la gestion | 
					
						
							|  |  |  |                 du cycle de vie des requêtes HTTP. Le code de l’application va donc être segmenté en trois | 
					
						
							|  |  |  |                 couches, chacune | 
					
						
							|  |  |  |                 pouvant tirer parti d’un certain nombre d’éléments : composants, comportements, helpers, | 
					
						
							|  |  |  |                 etc … Il | 
					
						
							|  |  |  |                 s’agit là de termes propres à CakePHP que je développerai plus tard.</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Le fichier d’entrée de l’application est <code>/webroot/index.php</code>. Son rôle est de | 
					
						
							|  |  |  |                 déclencher le | 
					
						
							|  |  |  |                 processus de démarrage de l’application, puis d’instancier le <strong>dispatcher</strong> | 
					
						
							|  |  |  |                 qui se | 
					
						
							|  |  |  |                 chargera de déléguer la requête au bon contrôleur. <code>/webroot/</code> est le répertoire auquel doit | 
					
						
							|  |  |  |                 être | 
					
						
							|  |  |  |                 configuré le <strong>document root</strong>.<code>index.php</code> devrait y être le seul fichier PHP | 
					
						
							|  |  |  |                 aux côtés d’autres | 
					
						
							|  |  |  |                 ressources web comme des images, des fichiers CSS ou Javascript.</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Si vous avez besoin d’intervenir sur des étapes du démarrage de l’application, vous aurez | 
					
						
							|  |  |  |                 alors besoin d’éditer | 
					
						
							|  |  |  |                 un peu de code dans <code>/config/</code>. Dans <code>/config/app.php</code> sont notamment définis les | 
					
						
							|  |  |  |                 paramètres | 
					
						
							|  |  |  |                 de connexion à la base de données, le niveau de debug ou encore la gestion des sessions. Les routes sont | 
					
						
							|  |  |  |                 définies | 
					
						
							|  |  |  |                 programmatiquement dans <code>/config/routes.php</code>.</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Pour le reste, le répertoire <code>/src/</code> se chargera d’héberger les sources de l’application. | 
					
						
							|  |  |  |                 Les | 
					
						
							|  |  |  |                 contrôleurs, les modèles ou encore les templates sont situés dans des sous-répertoires de | 
					
						
							|  |  |  |                 <code>/src/</code>. Cette | 
					
						
							|  |  |  |                 même structure est reprise au travers <a href="http://book.cakephp.org/3.0/fr/plugins.html" | 
					
						
							|  |  |  |                                                          target="_blank">des | 
					
						
							|  |  |  |                     plugins</a>. Pratique pour packager une application dans le but de la réutiliser dans une autre | 
					
						
							|  |  |  |                 (conceptuellement proche des bundles de Symfony 2).</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <h2 id="les-routes:cef4ef39fb39a8da0d9e48695b83d954">Les routes</h2> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <h3 id="déclarer-des-routes:cef4ef39fb39a8da0d9e48695b83d954">Déclarer des routes</h3> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Les routes sont définies dans <code>/config/routes.php</code> à l’intérieur de | 
					
						
							|  |  |  |                 <strong>scopes</strong>. Un | 
					
						
							|  |  |  |                 scope permet - entre autres - de factoriser plusieurs routes afin de leur attribuer un préfix.</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <pre><code class="language-php">Router::scope('/api/', function ($routes) { | 
					
						
							|  |  |  |     $routes->connect('/tweets', [ | 
					
						
							|  |  |  |     'controller' => 'Tweets', | 
					
						
							|  |  |  |     'action' => 'index' | 
					
						
							|  |  |  |     ], [ | 
					
						
							|  |  |  |     '_name' => 'tweets_index' | 
					
						
							|  |  |  |     ]); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | </code></pre> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Le code ci-dessus connecte la route <code>/api/tweets</code> au dispatcher. Le dispatcher se chargera de | 
					
						
							|  |  |  |                 passer la | 
					
						
							|  |  |  |                 requête à la méthode <code>TweetsController::index()</code>. Le tableau d’options en troisième | 
					
						
							|  |  |  |                 paramètre de la | 
					
						
							|  |  |  |                 méthode <code>connect()</code> est facultatif. Définir l’option <code>_name</code> permet de | 
					
						
							|  |  |  |                 générer les urls | 
					
						
							|  |  |  |                 plus facilement depuis les templates (vu plus tard).</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <h3 id="déclarer-des-ressources-restful:cef4ef39fb39a8da0d9e48695b83d954">Déclarer des ressources | 
					
						
							|  |  |  |                 restful</h3> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Supposons maintenant qu’il s’agisse de mettre en place une API restful. CakePHP 3 offre la | 
					
						
							|  |  |  |                 possibilité de | 
					
						
							|  |  |  |                 s’affranchir de portions de code répétitives en tirant parti de quelques conventions sur | 
					
						
							|  |  |  |                 lesquelles reposent | 
					
						
							|  |  |  |                 des comportements par défaut du framework. Cette philosophie - sans doute héritée de Ruby on Rails - est | 
					
						
							|  |  |  |                 omniprésente. Qu’il s’agisse de travailler avec les routes ou encore avec l’ORM, elle | 
					
						
							|  |  |  |                 peut faire | 
					
						
							|  |  |  |                 gagner un temps précieux.</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Pour l’exemple, jetons un oeil sur ce tableau :</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <table class="table table-condensed"> | 
					
						
							|  |  |  |                 <tr> | 
					
						
							|  |  |  |                     <td>GET</td> | 
					
						
							|  |  |  |                     <td>/api/tweets.:format</td> | 
					
						
							|  |  |  |                     <td>TweetsController::index()</td> | 
					
						
							|  |  |  |                 </tr> | 
					
						
							|  |  |  |                 <tr> | 
					
						
							|  |  |  |                     <td>GET</td> | 
					
						
							|  |  |  |                     <td>/api/tweets/:tweet_id.:format</td> | 
					
						
							|  |  |  |                     <td>TweetsController::view($tweet_id)</td> | 
					
						
							|  |  |  |                 </tr> | 
					
						
							|  |  |  |                 <tr> | 
					
						
							|  |  |  |                     <td>POST</td> | 
					
						
							|  |  |  |                     <td>/api/tweets.:format</td> | 
					
						
							|  |  |  |                     <td>TweetsController::add()</td> | 
					
						
							|  |  |  |                 </tr> | 
					
						
							|  |  |  |                 <tr> | 
					
						
							|  |  |  |                     <td>PUT</td> | 
					
						
							|  |  |  |                     <td>/api/tweets/:tweet_id.:format</td> | 
					
						
							|  |  |  |                     <td>TweetsController::edit($tweet_id)</td> | 
					
						
							|  |  |  |                 </tr> | 
					
						
							|  |  |  |                 <tr> | 
					
						
							|  |  |  |                     <td>PATCH</td> | 
					
						
							|  |  |  |                     <td>/api/tweets/:tweet_id.:format</td> | 
					
						
							|  |  |  |                     <td>TweetsController::edit($tweet_id)</td> | 
					
						
							|  |  |  |                 </tr> | 
					
						
							|  |  |  |                 <tr> | 
					
						
							|  |  |  |                     <td>DELETE</td> | 
					
						
							|  |  |  |                     <td>/api/tweets/:tweet_id.:format</td> | 
					
						
							|  |  |  |                     <td>TweetsController::delete($tweet_id)</td> | 
					
						
							|  |  |  |                 </tr> | 
					
						
							|  |  |  |             </table> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Ces routes peuvent être configurées automatiquement avec ce seul extrait de code :</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <pre><code>Router::scope('/api/', function ($routes) { | 
					
						
							|  |  |  |     $routes->extensions(['xml', 'json']); | 
					
						
							|  |  |  |     $routes->resources('tweets'); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | </code></pre> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <h3 id="les-routes-auto-déclarées:cef4ef39fb39a8da0d9e48695b83d954">Les routes auto-déclarées</h3> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Une chose à savoir à propos de CakePHP 3 est qu’il connecte automatiquement une route au dispatcher | 
					
						
							|  |  |  |                 pour chaque | 
					
						
							|  |  |  |                 nouvelle action de contrôleur. Le nom de ces routes est défini en fonction du nom du contrôleur et de la | 
					
						
							|  |  |  |                 méthode. | 
					
						
							|  |  |  |                 Si bien que le code si dessous :</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <pre><code class="language-php">class TweetsController | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |     function index() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |     ... | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     function add() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |     ... | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     function load() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |     ... | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | </code></pre> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Connectera automatiquement les routes <code>/tweets</code>, <code>/tweets/add</code> et <code>/tweets/load</code>. | 
					
						
							|  |  |  |                 Ce | 
					
						
							|  |  |  |                 comportement est induit par cette instruction du fichier <code>/config/routes.php</code> :</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <pre><code class="language-php">$routes->fallbacks('InflectedRoute'); | 
					
						
							|  |  |  | </code></pre> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Naturellement, supprimer cette instruction supprimera ce comportement.</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <h2 id="la-couche-controller:cef4ef39fb39a8da0d9e48695b83d954">La couche Controller</h2> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <h3 id="les-classes-de-contrôleur:cef4ef39fb39a8da0d9e48695b83d954">Les classes de contrôleur</h3> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Les classes de contrôleur sont situées dans <code>/src/Controller/</code>. Elles doivent étendre la | 
					
						
							|  |  |  |                 classe <code>\Cake\Controller\Controller</code> | 
					
						
							|  |  |  |                 et leur nom doit - par convention - se terminer par le suffixe <code>Controller</code>.</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>L’application Twitthome utilise quatre classes de contrôleur : <code>TweetsController</code>, | 
					
						
							|  |  |  |                 <code>HashtagsController</code>, | 
					
						
							|  |  |  |                 <code>UsersController</code> et <code>AccountParametersController</code>. Comme suggéré dans la | 
					
						
							|  |  |  |                 documentation | 
					
						
							|  |  |  |                 officielle, ces classes étendent <code>AppController</code>. Cette pratique est un moyen simple de | 
					
						
							|  |  |  |                 définir des | 
					
						
							|  |  |  |                 comportements globaux pour l’application, comme par exemple des règles liées à l’authentification. | 
					
						
							|  |  |  |                 L’instruction ci-dessous extraite de la classe <code>AppController</code> autorise les accès | 
					
						
							|  |  |  |                 non-authentifiés | 
					
						
							|  |  |  |                 aux actions (i.e. aux méthodes) <code>index</code>, <code>view</code> et <code>display</code> pour tous | 
					
						
							|  |  |  |                 les | 
					
						
							|  |  |  |                 contrôleurs.</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <pre><code class="language-php">$this->Auth->allow(['index', 'view', 'display']); | 
					
						
							|  |  |  | </code></pre> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Le router mis à part, le contrôleur est le point d’entrée de l’application. Depuis le | 
					
						
							|  |  |  |                 contrôleur, CakePHP | 
					
						
							|  |  |  |                 3 permet de manipuler la requête et la réponse HTTP au moyen des attributs <a | 
					
						
							|  |  |  |                         href="http://api.cakephp.org/3.0/class-Cake.Network.Request.html" | 
					
						
							|  |  |  |                         target="_blank"><code>request</code></a> | 
					
						
							|  |  |  |                 et <a href="http://api.cakephp.org/3.0/class-Cake.Network.Response.html" | 
					
						
							|  |  |  |                       target="_blank"><code>response</code></a>. | 
					
						
							|  |  |  |                 Les paramètres des routes sont quant à eux injectés en tant que paramètres des méthodes (des actions). | 
					
						
							| 
									
										
										
										
											2016-01-31 14:24:53 +00:00
										 |  |  |             </p> | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-01 21:44:21 +00:00
										 |  |  |             <p>Le contrôleur délègue la génération du contenu de la réponse à une vue. La méthode <code>\Cake\Controller\Controller::render()</code> | 
					
						
							|  |  |  |                 est automatiquement appelée et se charge d’invoquer le template correspondant à l’action (vu | 
					
						
							|  |  |  |                 plus tard). | 
					
						
							|  |  |  |                 Le contrôleur peut passer des données au template au moyen de | 
					
						
							|  |  |  |                 <code>\Cake\View\ViewVarsTrait::set()</code>.</p> | 
					
						
							| 
									
										
										
										
											2016-01-31 14:24:53 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-01 21:44:21 +00:00
										 |  |  | <pre><code class="language-php">$this->set([ | 
					
						
							|  |  |  |     'tweets' => $tweets, | 
					
						
							|  |  |  |     'hashtag_name' => $name | 
					
						
							|  |  |  |     ]); | 
					
						
							|  |  |  | </code></pre> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <h3 id="les-composants-components:cef4ef39fb39a8da0d9e48695b83d954">Les composants (components)</h3> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Les composants sont des objets qui peuvent être invoqués par un contrôleur dans le but de remplir une | 
					
						
							|  |  |  |                 tâche | 
					
						
							|  |  |  |                 spécifique. Le core de CakePHP 3 embarque des composants pour l’authentification, la manipulation | 
					
						
							|  |  |  |                 des cookies | 
					
						
							|  |  |  |                 ou encore l’utilisation de messages flash.</p> | 
					
						
							| 
									
										
										
										
											2016-01-31 14:24:53 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-01 21:44:21 +00:00
										 |  |  |             <p>Charger des composants dans un contrôleur peut se faire à l’intérieur du hook | 
					
						
							|  |  |  |                 <code>initialize()</code> du | 
					
						
							|  |  |  |                 contrôleur.</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <pre><code class="language-php">public function initialize() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |     $this->loadComponent('Flash'); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2016-01-31 14:24:53 +00:00
										 |  |  | </code></pre> | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-01 21:44:21 +00:00
										 |  |  |             <p>Une fois fait, le composant est accessible en tant que variable d’instance du contrôleur :</p> | 
					
						
							| 
									
										
										
										
											2016-01-31 14:24:53 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-01 21:44:21 +00:00
										 |  |  | <pre><code class="language-php">class UsersController extends Controller | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |     public function add() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |     ... | 
					
						
							|  |  |  |     $this->Flash->success(__('Your account has been created.')); | 
					
						
							|  |  |  |     ... | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2016-01-31 14:24:53 +00:00
										 |  |  | </code></pre> | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-01 21:44:21 +00:00
										 |  |  |             <p>Créer ses propres composants est une solution simple et ludique permettant d’isoler de la logique | 
					
						
							|  |  |  |                 dans des | 
					
						
							|  |  |  |                 classes utilisables à l’intérieur d’un ou plusieurs contrôleurs. “Où placer la logique | 
					
						
							|  |  |  |                 ?” | 
					
						
							|  |  |  |                 est une des premières questions que je me suis posées. Un cas pratique d’utilisation était la | 
					
						
							|  |  |  |                 possibilité de | 
					
						
							|  |  |  |                 télécharger une photo de profil pour les utilisateurs. Le téléchargement d’une image représente | 
					
						
							|  |  |  |                 une portion de | 
					
						
							|  |  |  |                 code susceptible de vouloir être ré-utilisée à différents emplacements de l’application. | 
					
						
							|  |  |  |                 Comme CakePHP 3 ne semble pas embarquer de composant d’injection de dépendances qui permettrait de | 
					
						
							|  |  |  |                 travailler | 
					
						
							|  |  |  |                 avec des classes de service (à l’instar de Symfony 2 par exemple) et qu’avoir recours à l’héritage | 
					
						
							|  |  |  |                 n’est pas toujours approprié, je me suis lancé de la construction de <a | 
					
						
							|  |  |  |                         href="https://github.com/aubm/Twitthome-CakePHP3/blob/master/src/Controller/Component/ImageUploadComponent.php" | 
					
						
							|  |  |  |                         target="_blank">mon propre composant d’upload</a>.</p> | 
					
						
							| 
									
										
										
										
											2016-01-31 14:24:53 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-01 21:44:21 +00:00
										 |  |  |             <p>Le composant est une classe résident dans <code>/src/Controller/Component/</code> dont le nom doit se | 
					
						
							|  |  |  |                 terminer par le | 
					
						
							|  |  |  |                 suffixe <code>Component</code>. Si la méthode <code>initialize()</code> du composant attend des | 
					
						
							|  |  |  |                 paramètres (comme c’est | 
					
						
							|  |  |  |                 le cas pour mon <code>FileUploadComponent</code> dont hérite <code>ImageUploadComponent</code>) :</p> | 
					
						
							| 
									
										
										
										
											2016-01-31 14:24:53 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-01 21:44:21 +00:00
										 |  |  | <pre><code class="language-php">class FileUploadComponent extends Component | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |     public function initialize(array $config) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |     $this->upload_dir = $this->_getSystemPath($config['upload_dir']); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2016-01-31 14:24:53 +00:00
										 |  |  | </code></pre> | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-01 21:44:21 +00:00
										 |  |  |             <p>Les contrôleurs utilisant le composant fourniront ces paramètres lors du chargement de ce dernier. | 
					
						
							|  |  |  |                 Exemple dans mon | 
					
						
							|  |  |  |                 <code>AccountParametersController</code> :</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <pre><code class="language-php">class AccountParametersController extends AppController | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |     public function initialize() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |     parent::initialize(); | 
					
						
							|  |  |  |     $this->loadComponent('ImageUpload', [ | 
					
						
							|  |  |  |     'upload_dir' => 'webroot/img/avatars' | 
					
						
							|  |  |  |     ]); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | </code></pre> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <h2 id="la-couche-model:cef4ef39fb39a8da0d9e48695b83d954">La couche Model</h2> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <h3 id="les-tables-repositories:cef4ef39fb39a8da0d9e48695b83d954">Les tables (repositories)</h3> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <h4 id="extraire-des-données:cef4ef39fb39a8da0d9e48695b83d954">Extraire des données</h4> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Utiliser l’ORM pour extraire les informations de la base de données est facile et ne requiert la | 
					
						
							|  |  |  |                 création d’aucune | 
					
						
							|  |  |  |                 classe personnalisée. | 
					
						
							|  |  |  |                 A l’intérieur de <code>TweetsController</code>, l’instruction ci-dessous permet d’extraire | 
					
						
							|  |  |  |                 l’ensemble | 
					
						
							|  |  |  |                 des lignes de la table <code>tweets</code>.</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <pre><code class="language-php">$tweets = $this->Tweets->find('all')->toArray(); | 
					
						
							|  |  |  | </code></pre> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Encore une fois, CakePHP 3 repose sur des conventions pour faire fonctionner cette instruction :</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <ul> | 
					
						
							|  |  |  |                 <li>Par soucis de performance (je suppose), les données des tweets ne sont chargées automatiquement que | 
					
						
							|  |  |  |                     dans le | 
					
						
							|  |  |  |                     <code>TweetsController</code>. Le chargement de ce modèle de données devra <a | 
					
						
							|  |  |  |                             href="http://api.cakephp.org/3.0/class-Cake.Datasource.ModelAwareTrait.html#_loadModel" | 
					
						
							|  |  |  |                             target="_blank">être | 
					
						
							|  |  |  |                         fait manuellement</a> s’il s’agit d’un autre contrôleur. | 
					
						
							|  |  |  |                 </li> | 
					
						
							|  |  |  |                 <li>Le nom de la table dans la base de données doit correspondre au nom du contrôleur transformé en | 
					
						
							|  |  |  |                     lower-case + | 
					
						
							|  |  |  |                     underscores - soit pour cet exemple : <code>tweets</code>. | 
					
						
							|  |  |  |                 </li> | 
					
						
							|  |  |  |             </ul> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>CakePHP 3 matérialise l’interface entre l’application et une table de la base de données par | 
					
						
							|  |  |  |                 la création | 
					
						
							|  |  |  |                 d’un objet de type <code>\Cake\ORM\Table</code>. Sorti des conventions listées plus haut, pour | 
					
						
							|  |  |  |                 créer des | 
					
						
							|  |  |  |                 règles de validation ou encore pour exploiter des relations avec d’autres tables, vous aurez | 
					
						
							|  |  |  |                 besoin de créer | 
					
						
							|  |  |  |                 une classe spécialisée pour matérialiser cette interface.</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>La classe <code>TweetsTable</code> qui étend <code>\Cake\ORM\Table</code> dans le fichier <code>/src/Model/Table/TweetsTable.php</code> | 
					
						
							|  |  |  |                 sert justement ce rôle. Le hook <code>initialize()</code> est utilisé pour définir les relations avec | 
					
						
							|  |  |  |                 les autres | 
					
						
							|  |  |  |                 tables.</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <pre><code class="language-php">public function initialize(array $config) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |     $this->belongsTo('Users'); | 
					
						
							|  |  |  |     $this->belongsToMany('Hashtags'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | </code></pre> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>La documentation officielle fournit les informations nécessaires pour <a | 
					
						
							|  |  |  |                     href="http://book.cakephp.org/3.0/fr/orm/associations.html" target="_blank">utiliser les relations | 
					
						
							|  |  |  |                 entre les | 
					
						
							|  |  |  |                 tables</a>.</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Il est intéressant de noter que cet appel : <code>$this->Tweets->find('all');</code> va - de | 
					
						
							|  |  |  |                 manière | 
					
						
							|  |  |  |                 transparente - exécuter la méthode <code>\Cake\ORM\Table::findAll()</code>. Il est donc possible de | 
					
						
							|  |  |  |                 modifier le | 
					
						
							|  |  |  |                 comportement de cette méthode en la redéfinissant à l’intérieur de <code>TweetsTable</code>. Voici | 
					
						
							|  |  |  |                 comment | 
					
						
							|  |  |  |                 demander à l’ORM de charger les données des modèles associés, et de trier les résultats du plus | 
					
						
							|  |  |  |                 récent au plus | 
					
						
							|  |  |  |                 ancien :</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <pre><code class="language-php">public function findAll(Query $query, array $options) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |     $query->contain(['Users', 'Users.AccountParameters']); | 
					
						
							|  |  |  |     $query->order(['Tweets.created' => 'DESC']); | 
					
						
							|  |  |  |     return $query; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | </code></pre> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Cette technique permet de garder les classes de contrôleur DRY tout en continuant d’exploiter toute | 
					
						
							|  |  |  |                 la | 
					
						
							|  |  |  |                 puissance de l’ORM. De la même manière il est possible de définir d’autres | 
					
						
							|  |  |  |                 <strong>finders</strong>. | 
					
						
							|  |  |  |                 Cette méthode est utilisée afin d’extraire les tweets pour un hashtag donné :</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <pre><code class="language-php">// Dans la classe TweetsTable | 
					
						
							|  |  |  |     public function findTagged(Query $query, array $options) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |     $query->contain(['Users', 'Users.AccountParameters', 'Hashtags']); | 
					
						
							|  |  |  |     $query->matching('Hashtags', function ($q) use ($options) { | 
					
						
							|  |  |  |     return $q->where(['Hashtags.name' => $options['tag_name']]); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     $query->order(['Tweets.created' => 'DESC']); | 
					
						
							|  |  |  |     return $query; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | </code></pre> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <pre><code class="language-php">// Dans la classe HashtagsController | 
					
						
							|  |  |  |     $this->Tweets->find('tagged', [ | 
					
						
							|  |  |  |     'tag_name' => $tag_name | 
					
						
							|  |  |  |     ]); | 
					
						
							|  |  |  | </code></pre> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <h4 id="insérer-de-nouvelles-lignes:cef4ef39fb39a8da0d9e48695b83d954">Insérer de nouvelles lignes</h4> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Insérer de nouvelles lignes dans la base de données ne pose pas de problème particulier. | 
					
						
							|  |  |  |                 Pour l’exemple, mon application requiert de pouvoir enregistrer de nouveaux utilisateurs. A chaque | 
					
						
							|  |  |  |                 nouvel | 
					
						
							|  |  |  |                 utilisateur, une nouvelle entrée dans la table <code>account_parameters</code> doit également être | 
					
						
							|  |  |  |                 ajoutée. | 
					
						
							|  |  |  |                 Le code ci-dessous permet d’accomplir cette tâche avec très peu de code :</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <pre><code class="language-php">class UsersController extends AppController | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |     public function add() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |     ... | 
					
						
							|  |  |  |     $user = $this->Users->newEntity($user_data); | 
					
						
							|  |  |  |     $user->set('account_parameter', $this->AccountParameters->newEntity()); | 
					
						
							|  |  |  |     $this->Users->save($user); | 
					
						
							|  |  |  |     ... | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | </code></pre> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <h4 id="valider-des-données:cef4ef39fb39a8da0d9e48695b83d954">Valider des données</h4> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>CakePHP 3 propose une double approche pour permettre de valider les données d’une entité. | 
					
						
							|  |  |  |                 De lors que des données de requête sont converties en entité, CakePHP 3 effectue automatiquement une | 
					
						
							|  |  |  |                 validation | 
					
						
							|  |  |  |                 basée sur les règles configurées dans le hook <code>validationDefault()</code>. Il est possible à ce | 
					
						
							|  |  |  |                 niveau de s’assurer | 
					
						
							|  |  |  |                 qu’une chaine de caractères respecte un format pré-défini ou encore de vérifier qu’un | 
					
						
							|  |  |  |                 attribut reçoit | 
					
						
							|  |  |  |                 bien une valeur en s’inspirant de ce code :</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <pre><code class="language-php">class UsersTable extends Table | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |     public function validationDefault(Validator $validator) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |     return $validator | 
					
						
							|  |  |  |     ->notEmpty('username', __('Username must not be empty')) | 
					
						
							|  |  |  |     ->notEmpty('password', __('Password must not be empty')) | 
					
						
							|  |  |  |     ->notEmpty('email', __('E-mail must not be empty')) | 
					
						
							|  |  |  |     ->add('email', 'validFormat', [ | 
					
						
							|  |  |  |     'rule' => 'email', | 
					
						
							|  |  |  |     'message' => __('E-mail must be valid') | 
					
						
							|  |  |  |     ]) | 
					
						
							|  |  |  |     ->notEmpty('first_name', __('First name must not be empty')) | 
					
						
							|  |  |  |     ->notEmpty('last_name', __('Last name must not be empty')); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | </code></pre> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>D’autre part, lorsqu’une entité s’apprête à être persistée en base de données, CakePHP | 
					
						
							|  |  |  |                 3 s’assure | 
					
						
							|  |  |  |                 que les données respectent les contraintes définies dans le hook <code>buildRules()</code>. Il s’agit | 
					
						
							|  |  |  |                 là de <a | 
					
						
							|  |  |  |                         href="http://book.cakephp.org/3.0/fr/orm/saving-data.html#appliquer-des-regles-pour-l-application" | 
					
						
							|  |  |  |                         target="_blank"><strong>règles de domaine</strong></a>, elles sont relatives à un besoin métier | 
					
						
							|  |  |  |                 de l’application. | 
					
						
							|  |  |  |                 Vous pourriez par exemple vous assurer que le statut de ce ticket l’autorise à recevoir un | 
					
						
							|  |  |  |                 commentaire, ou | 
					
						
							|  |  |  |                 bien que ce produit est toujours disponible avant de l’ajouter au panier. L’exemple | 
					
						
							|  |  |  |                 ci-dessous est | 
					
						
							|  |  |  |                 extrait de Twitthome et montre comment s’assurer de l’unicité des champs | 
					
						
							|  |  |  |                 <code>username</code> et <code>email</code> | 
					
						
							|  |  |  |                 de la table <code>users</code> :</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <pre><code class="language-php">class UsersTable extends Table | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |     public function buildRules(RulesChecker $rules) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |     $rules->add($rules->isUnique(['username'])); | 
					
						
							|  |  |  |     $rules->add($rules->isUnique(['email'])); | 
					
						
							|  |  |  |     return $rules; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | </code></pre> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <h3 id="les-comportements-behaviors:cef4ef39fb39a8da0d9e48695b83d954">Les comportements (behaviors)</h3> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Tout comme les composants permettent de factoriser de la logique des contrôleurs, les comportements | 
					
						
							|  |  |  |                 permettent de | 
					
						
							|  |  |  |                 réutiliser de la logique de la couche Model. La <a | 
					
						
							|  |  |  |                         href="http://book.cakephp.org/3.0/fr/orm/behaviors.html" | 
					
						
							|  |  |  |                         target="_blank">documentation officielle de CakePHP 3</a> les | 
					
						
							|  |  |  |                 présente comme étant “conceptuellement similaires aux traits”. Bien que n’ayant pas eu | 
					
						
							|  |  |  |                 besoin de | 
					
						
							|  |  |  |                 créer mes propres comportements, j’ai pu tirer parti de l’utilisation du <a | 
					
						
							|  |  |  |                         href="http://api.cakephp.org/3.0/class-Cake.ORM.Behavior.TimestampBehavior.html" | 
					
						
							|  |  |  |                         target="_blank"><code>TimestampBehavior</code></a> | 
					
						
							|  |  |  |                 (défini dans le core du framework) pour mettre à jour automatiquement les champs <code>created</code> et | 
					
						
							|  |  |  |                 <code>modified</code> | 
					
						
							|  |  |  |                 des tables <code>tweets</code> et <code>users</code>. Voici comment utiliser un comportement dans une | 
					
						
							|  |  |  |                 table :</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <pre><code class="language-php">class UsersTable extends Table | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |     public function initialize(array $config) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |     $this->addBehavior('Timestamp'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | </code></pre> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <h3 id="les-entités:cef4ef39fb39a8da0d9e48695b83d954">Les entités</h3> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Les objets table manipulent des objets de type <code>\Cake\ORM\Entity</code>. Chaque instance représente | 
					
						
							|  |  |  |                 une ligne d’une | 
					
						
							|  |  |  |                 table de la base de données. Comme pour les tables, il est possible de créer des classes spécialisées | 
					
						
							|  |  |  |                 qui seront | 
					
						
							|  |  |  |                 utilisées par l’ORM pour représenter les entités de l’application. Ces classes sont définies | 
					
						
							|  |  |  |                 dans des | 
					
						
							|  |  |  |                 fichiers à l’intérieur de <code>/src/Model/Entity/</code> et leur nom (par convention) correspond | 
					
						
							|  |  |  |                 au nom de la | 
					
						
							|  |  |  |                 table ramené au singulier.</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Un intérêt d’utiliser des classes spécialisées réside dans la possibilité de surcharger les | 
					
						
							|  |  |  |                 accesseurs et les | 
					
						
							|  |  |  |                 mutateurs des différents attributs. Pratique notamment dans le cas de l’entité <code>User</code> | 
					
						
							|  |  |  |                 pour crypter | 
					
						
							|  |  |  |                 le mot de passe de manière transparente :</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <pre><code class="language-php">class User extends Entity | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |     protected function _setPassword($password) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |     return (new DefaultPasswordHasher)->hash($password); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | </code></pre> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>J’ai utilisé cette même technique afin <a | 
					
						
							|  |  |  |                     href="https://github.com/aubm/Twitthome-CakePHP3/blob/master/src/Model/Entity/Tweet.php" | 
					
						
							|  |  |  |                     target="_blank">d’extraire | 
					
						
							|  |  |  |                 des informations du contenu d’un tweet</a>, comme les hashtags ou les liens externes.</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <h2 id="la-couche-view:cef4ef39fb39a8da0d9e48695b83d954">La couche View</h2> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <h3 id="les-templates:cef4ef39fb39a8da0d9e48695b83d954">Les templates</h3> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Les templates sont des fichiers contenant essentiellement du code HTML. Ils sont situés dans | 
					
						
							|  |  |  |                 <code>/src/Templates/</code> et portent l’extension <code>.ctp</code>. Le répertoire contient les | 
					
						
							|  |  |  |                 templates | 
					
						
							|  |  |  |                 responsables du rendu d’une action spécifique d’un contrôleur, mais également des fichiers | 
					
						
							|  |  |  |                 responsables | 
					
						
							|  |  |  |                 du rendu des <a href="http://book.cakephp.org/3.0/fr/views.html#elements" target="_blank">éléments</a>, | 
					
						
							|  |  |  |                 des <strong>cellules</strong> | 
					
						
							|  |  |  |                 (vu un peu après), ou encore des <strong>layouts</strong>.</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Par défaut, le rendu des actions des contrôleurs est encapsulé à l’intérieur du fichier <code>/src/Template/Layout/default.ctp</code>. | 
					
						
							|  |  |  |                 C’est dans ce fichier que doit être inséré le code commun à tous les templates. Pour mieux | 
					
						
							|  |  |  |                 comprendre, partons | 
					
						
							|  |  |  |                 du principe que le layout par défaut devrait contenir au minimum le code suivant :</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <pre><code class="language-php"><!DOCTYPE html> | 
					
						
							|  |  |  |     <html> | 
					
						
							|  |  |  |     <head> | 
					
						
							|  |  |  |     <title><?= $this->fetch('title') ?></title> | 
					
						
							|  |  |  |     </head> | 
					
						
							|  |  |  |     <body> | 
					
						
							|  |  |  |     <?= $this->fetch('content') ?> | 
					
						
							|  |  |  |     </body> | 
					
						
							|  |  |  |     </html> | 
					
						
							|  |  |  | </code></pre> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>L’affichage généré par le contrôleur sera rendu à l’emplacement de <code><?= $this->fetch('content') | 
					
						
							|  |  |  |                 ?></code>. Pour fonctionner, les templates doivent être nommés en corrélation avec le nom des | 
					
						
							|  |  |  |                 méthodes des | 
					
						
							|  |  |  |                 contrôleurs. Ainsi la méthode <code>TweetsController::index()</code> cherchera par default le fichier | 
					
						
							|  |  |  |                 <code>/src/Template/Tweets/index.ctp</code>. | 
					
						
							|  |  |  |             </p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Le fonctionnement des layouts est basé sur la possibilité de travailler avec des <a | 
					
						
							|  |  |  |                     href="http://book.cakephp.org/3.0/fr/views.html#utiliser-les-blocks-de-vues" target="_blank">blocks | 
					
						
							|  |  |  |                 de vue</a> à | 
					
						
							|  |  |  |                 l’intérieur de vues étendues. Comme vu précédemment, le rendu de l’action sera positionné | 
					
						
							|  |  |  |                 dans le block | 
					
						
							|  |  |  |                 <code>content</code>, mais il est possible de définir d’autres blocks de façon arbitraire.</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Pour l’application Twitthome, je m’étais donné à faire une sidebar dont le contenu serait | 
					
						
							|  |  |  |                 susceptible de | 
					
						
							|  |  |  |                 changer d’une page à l’autre. Un cas typique d’utilisation des blocks de vue. J’ai | 
					
						
							|  |  |  |                 donc | 
					
						
							|  |  |  |                 modifié mon layout <code>default.ctp</code> afin qu’il se rapproche de quelque chose comme ça : | 
					
						
							|  |  |  |             </p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <pre><code class="language-php">... | 
					
						
							|  |  |  |     <body> | 
					
						
							|  |  |  |     <div class="row"> | 
					
						
							|  |  |  |     <aside class="col-md-4"> | 
					
						
							|  |  |  |     <?= $this->fetch('sidebar') ?> | 
					
						
							|  |  |  |     </aside> | 
					
						
							|  |  |  |     <div class="col-md-8"> | 
					
						
							|  |  |  |     <?= $this->fetch('content') ?> | 
					
						
							|  |  |  |     </div> | 
					
						
							|  |  |  |     </div> | 
					
						
							|  |  |  |     </body> | 
					
						
							|  |  |  |     ... | 
					
						
							|  |  |  | </code></pre> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Le contenu du block <code>sidebar</code> peut maintenant être défini dans un autre template, dans <code>/src/Template/Tweets/index.ctp</code> | 
					
						
							|  |  |  |                 par exemple :</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <pre><code class="language-php"><?php $this->start('sidebar'); ?> | 
					
						
							|  |  |  |     <p>Contenu de la sidebar !</p> | 
					
						
							|  |  |  |     <?php $this->end(); ?> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     <?php foreach($tweets as $tweet): ?> | 
					
						
							|  |  |  |     ... | 
					
						
							|  |  |  |     <?php endforeach; ?> | 
					
						
							|  |  |  | </code></pre> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Dans cet exemple, la variable <code>$tweets</code> est issue de l’appel à la méthode <code>\Cake\View\ViewVarsTrait::set()</code> | 
					
						
							|  |  |  |                 dans le contrôleur (cf. partie sur les classes de contrôleur).</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <h3 id="les-helpers:cef4ef39fb39a8da0d9e48695b83d954">Les helpers</h3> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Les helpers sont ce qui facilite la création des templates et ce qui la rend plus ludique. A l’image | 
					
						
							|  |  |  |                 des | 
					
						
							|  |  |  |                 composants pour les contrôleurs ou des comportements pour les tables, les helpers permettent de | 
					
						
							|  |  |  |                 ré-utiliser de la | 
					
						
							|  |  |  |                 logique de vue. Le core de CakePHP 3 embarque une dizaine de classes helpers chargées par défaut dans | 
					
						
							|  |  |  |                 les vues et | 
					
						
							|  |  |  |                 qui permettent entre autres :</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>De générer des urls :</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <pre><code class="language-php"><a href="<?= $this->Url->build(['_name' => 'login']) ?>"><?= | 
					
						
							|  |  |  |     __('Sign in') ?></a> | 
					
						
							|  |  |  | </code></pre> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>D’afficher des formulaires :</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <pre><code class="language-php"><?= $this->Form->create(new Tweet()); ?> | 
					
						
							|  |  |  |     <?= $this->Form->input('content', [ | 
					
						
							|  |  |  |     'label' => false, | 
					
						
							|  |  |  |     'class' => 'form-control', | 
					
						
							|  |  |  |     'placeholder' => __('What\'s up ?') | 
					
						
							|  |  |  |     ]); ?> | 
					
						
							|  |  |  |     <?= $this->Form->button(__('Tweeter')); ?> | 
					
						
							|  |  |  |     <?= $this->Form->end(); ?> | 
					
						
							|  |  |  | </code></pre> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Ou encore d’insérer une feuille de style :</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <pre><code class="language-php"><?= $this->Html->css('app.min.css') ?> | 
					
						
							|  |  |  | </code></pre> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Des classes helpers personnalisées peuvent être ajoutées dans <code>/src/View/Helper</code>, leur nom | 
					
						
							|  |  |  |                 doit se | 
					
						
							|  |  |  |                 terminer par le suffixe <code>Helper</code>. L’exemple ci-dessous est utilisé dans l’application | 
					
						
							|  |  |  |                 Twitthome pour générer le code HTML correspondant à l’avatar d’un utilisateur.</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <pre><code class="language-php">class AvatarHelper extends Helper | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |     public $helpers = ['Html']; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public function render($avatar_file_name) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |     $avatar_path = $avatar_file_name ? | 
					
						
							|  |  |  |     'avatars/' . h($avatar_file_name) : 'no-avatar.jpg'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return $this->Html->image($avatar_path, [ | 
					
						
							|  |  |  |     'alt' => 'Avatar', | 
					
						
							|  |  |  |     'class' => 'img-responsive thumbnail' | 
					
						
							|  |  |  |     ]); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Dans un template ... | 
					
						
							|  |  |  |     ... | 
					
						
							|  |  |  |     <?= $this->Avatar->render($avatar_file_name) ?> | 
					
						
							|  |  |  |     ... | 
					
						
							|  |  |  | </code></pre> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Comme le montre cet exemple, un helper peut dépendre d’autres helpers. Les classes d’helper | 
					
						
							|  |  |  |                 correspondant | 
					
						
							|  |  |  |                 aux éléments du tableau <code>public $helpers</code> seront automatiquement instanciées et ajoutées | 
					
						
							|  |  |  |                 comme attributs. | 
					
						
							|  |  |  |             </p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Si vous souhaitez charger vos helpers pour les rendre utilisables à l’échelle de votre application, | 
					
						
							|  |  |  |                 vous pouvez | 
					
						
							|  |  |  |                 demander à CakePHP 3 de les instancier dans <code>AppView</code> via le hook | 
					
						
							|  |  |  |                 <code>\Cake\View\View::initialize()</code>.</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <pre><code class="language-php">class AppView extends View | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |     public function initialize() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |     $this->loadHelper('Avatar'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | </code></pre> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <h3 id="les-cellules-cells:cef4ef39fb39a8da0d9e48695b83d954">Les cellules (cells)</h3> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Il arrive que des fragments de page HTML dépendent de données qui n’ont pas de lien direct avec le | 
					
						
							|  |  |  |                 contenu | 
					
						
							|  |  |  |                 principale de la page. Par exemple : un nuage de tags, un feed Instagram ou une remontée des posts les | 
					
						
							|  |  |  |                 plus récents | 
					
						
							|  |  |  |                 d’un blog. Si ces fragments apparaissent dans plusieurs templates, cela implique que les données | 
					
						
							|  |  |  |                 doivent être | 
					
						
							|  |  |  |                 rassemblées et passées à la vue dans chaque action de contrôleur correspondant. En adoptant cette | 
					
						
							|  |  |  |                 approche, le code | 
					
						
							|  |  |  |                 des contrôleurs risque d’être rapidement pollué. Utiliser des cellules est une solution plus | 
					
						
							|  |  |  |                 pratique pour | 
					
						
							|  |  |  |                 répondre à ce genre de problématiques.</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>La <a href="http://book.cakephp.org/3.0/fr/views/cells.html" target="_blank">documentation officielle du | 
					
						
							|  |  |  |                 framework</a> définit les cellules comme “des mini-controllers qui peuvent invoquer de la logique | 
					
						
							|  |  |  |                 de vue et | 
					
						
							|  |  |  |                 afficher les templates”. Dans le cadre de Twitthome, j’ai utilisé une cellule pour afficher | 
					
						
							|  |  |  |                 le | 
					
						
							|  |  |  |                 bloc “Tendances”. La cellule existe au travers de deux fichiers. Le premier est une classe | 
					
						
							|  |  |  |                 définie dans | 
					
						
							|  |  |  |                 <code>/src/View/Cell/PopularHashtagsCell.php</code> :</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <pre><code class="language-php">class PopularHashtagsCell extends Cell | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |     public function display() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |     $this->loadModel('Hashtags'); | 
					
						
							|  |  |  |     $hashtags = $this->Hashtags->find('popular')->toArray(); | 
					
						
							|  |  |  |     $this->set('hashtags', $hashtags); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | </code></pre> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Le comportement de cette classe est similaire à celui d’un contrôleur. Celle-ci est capable de | 
					
						
							|  |  |  |                 charger un | 
					
						
							|  |  |  |                 modèle, dans le but d’extraire les informations nécessaires de la base de données. Le second | 
					
						
							|  |  |  |                 fichier est le | 
					
						
							|  |  |  |                 template responsable du rendu de la cellule. Ce template est définit dans <a | 
					
						
							|  |  |  |                         href="https://github.com/aubm/Twitthome-CakePHP3/blob/master/src/Template/Cell/PopularHashtags/display.ctp" | 
					
						
							|  |  |  |                         target="_blank"><code>/src/Template/Cell/PopularHashtags/display.ctp</code></a>.</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Enfin la dernière étape consiste à afficher la cellule à l’intérieur d’un template. Une <a | 
					
						
							|  |  |  |                     href="http://api.cakephp.org/3.0/class-Cake.View.CellTrait.html#_cell" target="_blank">méthode</a> | 
					
						
							|  |  |  |                 est justement | 
					
						
							|  |  |  |                 prévue pour tenir ce rôle.</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <pre><code class="language-php"><?= $this->cell('PopularHashtags'); ?> | 
					
						
							|  |  |  | </code></pre> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <h2 id="le-mot-de-la-fin:cef4ef39fb39a8da0d9e48695b83d954">Le mot de la fin</h2> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Il reste évidemment de nombreux points à aborder. Certains sur lesquels je me suis penchés sont | 
					
						
							|  |  |  |                 volontairement passés | 
					
						
							|  |  |  |                 sous silence (comme notamment la partie sur l’internationalisation) afin de ne pas trop alourdir | 
					
						
							|  |  |  |                 la lecture de | 
					
						
							|  |  |  |                 cet article. D’autres sujets mériteraient une attention particulière, comme l’outil en ligne | 
					
						
							|  |  |  |                 de | 
					
						
							|  |  |  |                 commande, la gestion du cache, les logs ou encore l’intégration des tests.</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Ceci étant dit, si cet article ne peut pas prétendre couvrir (même de loin) tous les aspects de CakePHP | 
					
						
							|  |  |  |                 3, j’ai | 
					
						
							|  |  |  |                 bon espoir qu’il aide à se forger un premier avis sur le framework et puisse éventuellement servir | 
					
						
							|  |  |  |                 de support | 
					
						
							|  |  |  |                 pour le démarrage d’un projet. | 
					
						
							|  |  |  |                 Pour aller plus loin, la <a href="http://book.cakephp.org/3.0/fr/contents.html" target="_blank">documentation | 
					
						
							|  |  |  |                     officielle</a> est plutôt bien fournie. Elle contient des exemples d’applications, un cookbook | 
					
						
							|  |  |  |                 complet et | 
					
						
							|  |  |  |                 une documentation soignée de l’API.</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <p>Si le coeur vous en dit, je vous encourage à commenter si vous pensez pouvoir souligner certains axes d’amélioration, | 
					
						
							|  |  |  |                 autant sur le support (Twitthome) que sur la forme. | 
					
						
							|  |  |  |                 Je vous remercie pour la lecture et happy coding à tous !</p> | 
					
						
							| 
									
										
										
										
											2016-01-31 14:24:53 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-01 21:44:21 +00:00
										 |  |  |             </section> | 
					
						
							| 
									
										
										
										
											2016-01-31 14:24:53 +00:00
										 |  |  |         </article> | 
					
						
							|  |  |  |     </div> | 
					
						
							|  |  |  | </div> | 
					
						
							|  |  |  | </body> | 
					
						
							|  |  |  | <script src="js/theme.js" type="text/javascript"></script> | 
					
						
							|  |  |  | </html> |