feat(): add support for responsive iframes
This commit is contained in:
parent
384accc987
commit
721c05de4d
|
@ -1,4 +1,5 @@
|
|||
.idea
|
||||
npm-debug.log
|
||||
public/**/*.css
|
||||
node_modules
|
||||
public/fonts
|
||||
|
|
1
TOTO.md
1
TOTO.md
|
@ -1,4 +1,3 @@
|
|||
- Add support for elements like H*, blocquotes, images, etc ...
|
||||
- Add support for code highlighting
|
||||
- Display a list of categories and tags in the navbar
|
||||
- Change folder structure to be a actual theme for Hugo
|
||||
|
|
|
@ -13,6 +13,7 @@ body {
|
|||
font-family: 'Source Code Pro';
|
||||
background-color: $bg-color;
|
||||
color: $text-color;
|
||||
overflow-x: hidden;
|
||||
&>.container-fluid {
|
||||
@extend .no-padding-left-and-right;
|
||||
}
|
||||
|
@ -107,3 +108,18 @@ a {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.video-wrapper {
|
||||
position: relative;
|
||||
padding-bottom: 56.25%;
|
||||
padding-top: 10px;
|
||||
height: 0;
|
||||
|
||||
iframe {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 84 KiB |
|
@ -35,61 +35,864 @@
|
|||
</div>
|
||||
<div class="col-md-9 article-content">
|
||||
<article>
|
||||
<p>Bonjour, ce petit article pour partager une de mes découvertes récentes. Il s’agit du serveur web
|
||||
interne de
|
||||
PHP, intégré depuis la version 5.4. La lecture de cette article ne vous apprendra surement pas grand
|
||||
chose
|
||||
si vous
|
||||
avez déjà connaissance de cette fonctionnalité.</p>
|
||||
<h1>Mes premiers pas avec cakephp 3</h1>
|
||||
<h2 id="avant-propos:cef4ef39fb39a8da0d9e48695b83d954">Avant propos</h2>
|
||||
|
||||
<p>Pour en savoir plus, rendez vous directement sur la <a
|
||||
href="http://php.net/manual/fr/features.commandline.webserver.php" target="_blank">documentation
|
||||
officielle de
|
||||
PHP</a>. La documentation explique comment utiliser de façon très simple ce service. A noter que celui n’est
|
||||
pas pensé pour être utilisé dans un environnement de production, mais est destiné à servir dans un
|
||||
environnement de
|
||||
développement. Il n’est, par conséquent, plus nécessaire de configurer une nouvelle vhost sur
|
||||
votre
|
||||
serveur
|
||||
http (typiquement apache ou nginx), et d’ajouter une entrée dans le fichier hosts. Démarrez votre
|
||||
application
|
||||
à l’aide d’une simple commande dans votre terminal, et testez directement votre application
|
||||
!
|
||||
<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).
|
||||
</p>
|
||||
|
||||
<h2 id="encore-mieux:b661274995ee589e28a51c629100f44d">Encore mieux</h2>
|
||||
<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>
|
||||
|
||||
<p>De nombreux framework de développement PHP, dont Symfony ou encore Laravel facillitent encore plus l’utilisation
|
||||
de ce service. Pour l’exemple, si vous utilisez Symfony2 pour développer votre application, tapez
|
||||
directement
|
||||
dans votre terminal la commande suivante :</p>
|
||||
|
||||
<pre><code class="language-bash">php app/console server:run
|
||||
<pre><code class="language-php">$this->set([
|
||||
'tweets' => $tweets,
|
||||
'hashtag_name' => $name
|
||||
]);
|
||||
</code></pre>
|
||||
|
||||
<p>La console vous affichera un message comme :</p>
|
||||
<h3 id="les-composants-components:cef4ef39fb39a8da0d9e48695b83d954">Les composants (components)</h3>
|
||||
|
||||
<pre><code class="language-bash">Server running on http://localhost:8000
|
||||
<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>
|
||||
|
||||
<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');
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<p>Ouvrez votre navigateur et rendez vous à l’adresse <code>http:/localhost:8000</code> pour utiliser
|
||||
votre
|
||||
application. Note : la commande lance l’application dans l’environnement de développement,
|
||||
vous
|
||||
n’avez
|
||||
donc pas besoin de faire précéder toutes vos route par <code>app_dev.php</code>.</p>
|
||||
<p>Une fois fait, le composant est accessible en tant que variable d’instance du contrôleur :</p>
|
||||
|
||||
<p>Pour finir un petit coup d’oeil sur les informations que nous fournit la commande suivante :</p>
|
||||
|
||||
<pre><code class="language-bash">php app/console server:run --help
|
||||
<pre><code class="language-php">class UsersController extends Controller
|
||||
{
|
||||
public function add()
|
||||
{
|
||||
...
|
||||
$this->Flash->success(__('Your account has been created.'));
|
||||
...
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<p>Enfin, pour en savoir plus, rendez-vous directement sur la <a
|
||||
href="http://symfony.com/doc/current/cookbook/web_server/built_in.html" target="_blank">documentation
|
||||
officelle
|
||||
de Symfony</a>.</p>
|
||||
<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>
|
||||
|
||||
<p>Et voilà, have fun :)</p>
|
||||
<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>
|
||||
|
||||
<pre><code class="language-php">class FileUploadComponent extends Component
|
||||
{
|
||||
public function initialize(array $config)
|
||||
{
|
||||
$this->upload_dir = $this->_getSystemPath($config['upload_dir']);
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<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>
|
||||
|
||||
</section>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue