diff --git a/.gitignore b/.gitignore index 39e08c2..a6cb17d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea +npm-debug.log public/**/*.css node_modules public/fonts diff --git a/TOTO.md b/TOTO.md index 070a8c4..1da8b75 100644 --- a/TOTO.md +++ b/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 diff --git a/public/css/theme.scss b/public/css/theme.scss index d9b7237..1fab9d8 100644 --- a/public/css/theme.scss +++ b/public/css/theme.scss @@ -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%; + } +} diff --git a/public/img/twitthome_schema.png b/public/img/twitthome_schema.png new file mode 100644 index 0000000..e17dc84 Binary files /dev/null and b/public/img/twitthome_schema.png differ diff --git a/public/index.html b/public/index.html index 6b27c55..2b80c31 100644 --- a/public/index.html +++ b/public/index.html @@ -35,61 +35,864 @@
-

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é.

+

Mes premiers pas avec cakephp 3

+

Avant propos

-

Pour en savoir plus, rendez vous directement sur la documentation - officielle de - PHP. 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 - ! +

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).

+ +

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.

+ +

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.

+ +

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.

+ +

Contexte

+ +

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 :

+ + + +

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.

+ +
+ +
+ +

Les sources sont disponibles sur Github. + 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.

+ +

Le fichier database.sql contient les requêtes à exécuter pour ajouter les tables + dans une base + de données MySQL.

+ +
+ Schéma base de données Twitthome +
Une représentation graphique du schéma de la base de données.
+
+ +

Généralités et organisation du + code

+ +

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 :

+ +
composer create-project --prefer-dist -s dev cakephp/app my_app_name
+
+ +

Composer téléchargera CakePHP 3 et ses dépendances dans un nouveau dossier my_app_name. 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é :

+ +
bin/cake server
+
+ +

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.

+ +

Le fichier d’entrée de l’application est /webroot/index.php. Son rôle est de + déclencher le + processus de démarrage de l’application, puis d’instancier le dispatcher + qui se + chargera de déléguer la requête au bon contrôleur. /webroot/ est le répertoire auquel doit + être + configuré le document root.index.php devrait y être le seul fichier PHP + aux côtés d’autres + ressources web comme des images, des fichiers CSS ou Javascript.

+ +

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 /config/. Dans /config/app.php 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 /config/routes.php.

+ +

Pour le reste, le répertoire /src/ 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 + /src/. Cette + même structure est reprise au travers des + plugins. Pratique pour packager une application dans le but de la réutiliser dans une autre + (conceptuellement proche des bundles de Symfony 2).

+ +

Les routes

+ +

Déclarer des routes

+ +

Les routes sont définies dans /config/routes.php à l’intérieur de + scopes. Un + scope permet - entre autres - de factoriser plusieurs routes afin de leur attribuer un préfix.

+ +
Router::scope('/api/', function ($routes) {
+    $routes->connect('/tweets', [
+    'controller' => 'Tweets',
+    'action' => 'index'
+    ], [
+    '_name' => 'tweets_index'
+    ]);
+    });
+
+ +

Le code ci-dessus connecte la route /api/tweets au dispatcher. Le dispatcher se chargera de + passer la + requête à la méthode TweetsController::index(). Le tableau d’options en troisième + paramètre de la + méthode connect() est facultatif. Définir l’option _name permet de + générer les urls + plus facilement depuis les templates (vu plus tard).

+ +

Déclarer des ressources + restful

+ +

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.

+ +

Pour l’exemple, jetons un oeil sur ce tableau :

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
GET/api/tweets.:formatTweetsController::index()
GET/api/tweets/:tweet_id.:formatTweetsController::view($tweet_id)
POST/api/tweets.:formatTweetsController::add()
PUT/api/tweets/:tweet_id.:formatTweetsController::edit($tweet_id)
PATCH/api/tweets/:tweet_id.:formatTweetsController::edit($tweet_id)
DELETE/api/tweets/:tweet_id.:formatTweetsController::delete($tweet_id)
+ +

Ces routes peuvent être configurées automatiquement avec ce seul extrait de code :

+ +
Router::scope('/api/', function ($routes) {
+    $routes->extensions(['xml', 'json']);
+    $routes->resources('tweets');
+    });
+
+ +

Les routes auto-déclarées

+ +

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 :

+ +
class TweetsController
+    {
+    function index()
+    {
+    ...
+    }
+
+    function add()
+    {
+    ...
+    }
+
+    function load()
+    {
+    ...
+    }
+    }
+
+ +

Connectera automatiquement les routes /tweets, /tweets/add et /tweets/load. + Ce + comportement est induit par cette instruction du fichier /config/routes.php :

+ +
$routes->fallbacks('InflectedRoute');
+
+ +

Naturellement, supprimer cette instruction supprimera ce comportement.

+ +

La couche Controller

+ +

Les classes de contrôleur

+ +

Les classes de contrôleur sont situées dans /src/Controller/. Elles doivent étendre la + classe \Cake\Controller\Controller + et leur nom doit - par convention - se terminer par le suffixe Controller.

+ +

L’application Twitthome utilise quatre classes de contrôleur : TweetsController, + HashtagsController, + UsersController et AccountParametersController. Comme suggéré dans la + documentation + officielle, ces classes étendent AppController. 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 AppController autorise les accès + non-authentifiés + aux actions (i.e. aux méthodes) index, view et display pour tous + les + contrôleurs.

+ +
$this->Auth->allow(['index', 'view', 'display']);
+
+ +

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 request + et response. + Les paramètres des routes sont quant à eux injectés en tant que paramètres des méthodes (des actions).

-

Encore mieux

+

Le contrôleur délègue la génération du contenu de la réponse à une vue. La méthode \Cake\Controller\Controller::render() + 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 + \Cake\View\ViewVarsTrait::set().

-

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 :

- -
php app/console server:run
+
$this->set([
+    'tweets' => $tweets,
+    'hashtag_name' => $name
+    ]);
 
-

La console vous affichera un message comme :

+

Les composants (components)

-
Server running on http://localhost:8000
+            

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.

+ +

Charger des composants dans un contrôleur peut se faire à l’intérieur du hook + initialize() du + contrôleur.

+ +
public function initialize()
+    {
+    $this->loadComponent('Flash');
+    }
 
-

Ouvrez votre navigateur et rendez vous à l’adresse http:/localhost:8000 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 app_dev.php.

+

Une fois fait, le composant est accessible en tant que variable d’instance du contrôleur :

-

Pour finir un petit coup d’oeil sur les informations que nous fournit la commande suivante :

- -
php app/console server:run --help
+
class UsersController extends Controller
+    {
+    public function add()
+    {
+    ...
+    $this->Flash->success(__('Your account has been created.'));
+    ...
+    }
+    }
 
-

Enfin, pour en savoir plus, rendez-vous directement sur la documentation - officelle - de Symfony.

+

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 mon propre composant d’upload.

-

Et voilà, have fun :)

+

Le composant est une classe résident dans /src/Controller/Component/ dont le nom doit se + terminer par le + suffixe Component. Si la méthode initialize() du composant attend des + paramètres (comme c’est + le cas pour mon FileUploadComponent dont hérite ImageUploadComponent) :

+ +
class FileUploadComponent extends Component
+    {
+    public function initialize(array $config)
+    {
+    $this->upload_dir = $this->_getSystemPath($config['upload_dir']);
+    }
+    }
+
+ +

Les contrôleurs utilisant le composant fourniront ces paramètres lors du chargement de ce dernier. + Exemple dans mon + AccountParametersController :

+ +
class AccountParametersController extends AppController
+    {
+    public function initialize()
+    {
+    parent::initialize();
+    $this->loadComponent('ImageUpload', [
+    'upload_dir' => 'webroot/img/avatars'
+    ]);
+    }
+    }
+
+ +

La couche Model

+ +

Les tables (repositories)

+ +

Extraire des données

+ +

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 TweetsController, l’instruction ci-dessous permet d’extraire + l’ensemble + des lignes de la table tweets.

+ +
$tweets = $this->Tweets->find('all')->toArray();
+
+ +

Encore une fois, CakePHP 3 repose sur des conventions pour faire fonctionner cette instruction :

+ +
    +
  • Par soucis de performance (je suppose), les données des tweets ne sont chargées automatiquement que + dans le + TweetsController. Le chargement de ce modèle de données devra être + fait manuellement s’il s’agit d’un autre contrôleur. +
  • +
  • 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 : tweets. +
  • +
+ +

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 \Cake\ORM\Table. 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.

+ +

La classe TweetsTable qui étend \Cake\ORM\Table dans le fichier /src/Model/Table/TweetsTable.php + sert justement ce rôle. Le hook initialize() est utilisé pour définir les relations avec + les autres + tables.

+ +
public function initialize(array $config)
+    {
+    $this->belongsTo('Users');
+    $this->belongsToMany('Hashtags');
+    }
+
+ +

La documentation officielle fournit les informations nécessaires pour utiliser les relations + entre les + tables.

+ +

Il est intéressant de noter que cet appel : $this->Tweets->find('all'); va - de + manière + transparente - exécuter la méthode \Cake\ORM\Table::findAll(). Il est donc possible de + modifier le + comportement de cette méthode en la redéfinissant à l’intérieur de TweetsTable. 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 :

+ +
public function findAll(Query $query, array $options)
+    {
+    $query->contain(['Users', 'Users.AccountParameters']);
+    $query->order(['Tweets.created' => 'DESC']);
+    return $query;
+    }
+
+ +

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 + finders. + Cette méthode est utilisée afin d’extraire les tweets pour un hashtag donné :

+ +
// 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;
+    }
+
+ +
// Dans la classe HashtagsController
+    $this->Tweets->find('tagged', [
+    'tag_name' => $tag_name
+    ]);
+
+ +

Insérer de nouvelles lignes

+ +

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 account_parameters doit également être + ajoutée. + Le code ci-dessous permet d’accomplir cette tâche avec très peu de code :

+ +
class UsersController extends AppController
+    {
+    public function add()
+    {
+    ...
+    $user = $this->Users->newEntity($user_data);
+    $user->set('account_parameter', $this->AccountParameters->newEntity());
+    $this->Users->save($user);
+    ...
+    }
+    }
+
+ +

Valider des données

+ +

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 validationDefault(). 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 :

+ +
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'));
+    }
+    }
+
+ +

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 buildRules(). Il s’agit + là de règles de domaine, 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 + username et email + de la table users :

+ +
class UsersTable extends Table
+    {
+    public function buildRules(RulesChecker $rules)
+    {
+    $rules->add($rules->isUnique(['username']));
+    $rules->add($rules->isUnique(['email']));
+    return $rules;
+    }
+    }
+
+ +

Les comportements (behaviors)

+ +

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 documentation officielle de CakePHP 3 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 TimestampBehavior + (défini dans le core du framework) pour mettre à jour automatiquement les champs created et + modified + des tables tweets et users. Voici comment utiliser un comportement dans une + table :

+ +
class UsersTable extends Table
+    {
+    public function initialize(array $config)
+    {
+    $this->addBehavior('Timestamp');
+    }
+    }
+
+ +

Les entités

+ +

Les objets table manipulent des objets de type \Cake\ORM\Entity. 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 /src/Model/Entity/ et leur nom (par convention) correspond + au nom de la + table ramené au singulier.

+ +

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é User + pour crypter + le mot de passe de manière transparente :

+ +
class User extends Entity
+    {
+    protected function _setPassword($password)
+    {
+    return (new DefaultPasswordHasher)->hash($password);
+    }
+    }
+
+ +

J’ai utilisé cette même technique afin d’extraire + des informations du contenu d’un tweet, comme les hashtags ou les liens externes.

+ +

La couche View

+ +

Les templates

+ +

Les templates sont des fichiers contenant essentiellement du code HTML. Ils sont situés dans + /src/Templates/ et portent l’extension .ctp. 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 éléments, + des cellules + (vu un peu après), ou encore des layouts.

+ +

Par défaut, le rendu des actions des contrôleurs est encapsulé à l’intérieur du fichier /src/Template/Layout/default.ctp. + 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 :

+ +
<!DOCTYPE html>
+    <html>
+    <head>
+    <title><?= $this->fetch('title') ?></title>
+    </head>
+    <body>
+    <?= $this->fetch('content') ?>
+    </body>
+    </html>
+
+ +

L’affichage généré par le contrôleur sera rendu à l’emplacement de <?= $this->fetch('content') + ?>. Pour fonctionner, les templates doivent être nommés en corrélation avec le nom des + méthodes des + contrôleurs. Ainsi la méthode TweetsController::index() cherchera par default le fichier + /src/Template/Tweets/index.ctp. +

+ +

Le fonctionnement des layouts est basé sur la possibilité de travailler avec des blocks + de vue à + l’intérieur de vues étendues. Comme vu précédemment, le rendu de l’action sera positionné + dans le block + content, mais il est possible de définir d’autres blocks de façon arbitraire.

+ +

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 default.ctp afin qu’il se rapproche de quelque chose comme ça : +

+ +
...
+    <body>
+    <div class="row">
+    <aside class="col-md-4">
+    <?= $this->fetch('sidebar') ?>
+    </aside>
+    <div class="col-md-8">
+    <?= $this->fetch('content') ?>
+    </div>
+    </div>
+    </body>
+    ...
+
+ +

Le contenu du block sidebar peut maintenant être défini dans un autre template, dans /src/Template/Tweets/index.ctp + par exemple :

+ +
<?php $this->start('sidebar'); ?>
+    <p>Contenu de la sidebar !</p>
+    <?php $this->end(); ?>
+
+    <?php foreach($tweets as $tweet): ?>
+    ...
+    <?php endforeach; ?>
+
+ +

Dans cet exemple, la variable $tweets est issue de l’appel à la méthode \Cake\View\ViewVarsTrait::set() + dans le contrôleur (cf. partie sur les classes de contrôleur).

+ +

Les helpers

+ +

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 :

+ +

De générer des urls :

+ +
<a href="<?= $this->Url->build(['_name' => 'login']) ?>"><?=
+    __('Sign in') ?></a>
+
+ +

D’afficher des formulaires :

+ +
<?= $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(); ?>
+
+ +

Ou encore d’insérer une feuille de style :

+ +
<?= $this->Html->css('app.min.css') ?>
+
+ +

Des classes helpers personnalisées peuvent être ajoutées dans /src/View/Helper, leur nom + doit se + terminer par le suffixe Helper. L’exemple ci-dessous est utilisé dans l’application + Twitthome pour générer le code HTML correspondant à l’avatar d’un utilisateur.

+ +
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) ?>
+    ...
+
+ +

Comme le montre cet exemple, un helper peut dépendre d’autres helpers. Les classes d’helper + correspondant + aux éléments du tableau public $helpers seront automatiquement instanciées et ajoutées + comme attributs. +

+ +

Si vous souhaitez charger vos helpers pour les rendre utilisables à l’échelle de votre application, + vous pouvez + demander à CakePHP 3 de les instancier dans AppView via le hook + \Cake\View\View::initialize().

+ +
class AppView extends View
+    {
+    public function initialize()
+    {
+    $this->loadHelper('Avatar');
+    }
+    }
+
+ +

Les cellules (cells)

+ +

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.

+ +

La documentation officielle du + framework 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 + /src/View/Cell/PopularHashtagsCell.php :

+ +
class PopularHashtagsCell extends Cell
+    {
+    public function display()
+    {
+    $this->loadModel('Hashtags');
+    $hashtags = $this->Hashtags->find('popular')->toArray();
+    $this->set('hashtags', $hashtags);
+    }
+    }
+
+ +

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 /src/Template/Cell/PopularHashtags/display.ctp.

+ +

Enfin la dernière étape consiste à afficher la cellule à l’intérieur d’un template. Une méthode + est justement + prévue pour tenir ce rôle.

+ +
<?= $this->cell('PopularHashtags'); ?>
+
+ +

Le mot de la fin

+ +

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.

+ +

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 documentation + officielle est plutôt bien fournie. Elle contient des exemples d’applications, un cookbook + complet et + une documentation soignée de l’API.

+ +

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 !

+ +