diff --git a/README.md b/README.md new file mode 100644 index 0000000..1a26ec4 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# What is it ? + +A theme for [Hugo](https://gohugo.io/), inspired by Atom editor. + +# TODOs + +This repo is a work in progress, there is still work to do, contributions are welcome ! + +- Support for code highlighting +- Support for bootstrap tables +- Support for tags and categories +- Support Twitter and Facebook share buttons +- Support pagination in list layout +- Support "dynamic" titles and descriptions +- Uglify CSS and Javascript +- Add integration with Disqus +- Add integration with Google Analytics +- Add a "Home" link in the top of the menu diff --git a/TOTO.md b/TOTO.md deleted file mode 100644 index f1bb191..0000000 --- a/TOTO.md +++ /dev/null @@ -1 +0,0 @@ -- Add support for code highlighting diff --git a/layouts/_default/list.html b/layouts/_default/list.html index e69de29..2db2582 100644 --- a/layouts/_default/list.html +++ b/layouts/_default/list.html @@ -0,0 +1,10 @@ +{{ partial "top.html" . }} +
+ {{ range .Data.Pages }} +
+

{{ .Title }} - {{ dateFormat "01-02-2006 15:04:05" .Date }}

+ {{ .Summary }} +
+ {{ end }} +
+{{ partial "bottom.html" . }} diff --git a/layouts/_default/single.html b/layouts/_default/single.html index e69de29..beb77f2 100644 --- a/layouts/_default/single.html +++ b/layouts/_default/single.html @@ -0,0 +1,7 @@ +{{ partial "top.html" . }} +

{{ .Title }}

+

Publié le {{ dateFormat "01-02-2006 15:04:05" .Date }}

+
+ {{ .Content }} +
+{{ partial "bottom.html" . }} diff --git a/layouts/index.html b/layouts/index.html deleted file mode 100644 index a4e5708..0000000 --- a/layouts/index.html +++ /dev/null @@ -1,913 +0,0 @@ - - - - {{ partial "head.html" . }} - - -
- -
-
-

Mes premiers pas avec cakephp 3

-

Avant propos

- -

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 :

- -
    -
  • Les utilisateurs enregistrés peuvent poster des messages (des tweets) de moins de 140 caractères -
  • -
  • Tous les tweets apparaissent en page d’accueil dans l’ordre du plus récent au plus - ancien -
  • -
  • La page d’un utilisateur affiche les détails de son profil et la liste de ses tweets
  • -
  • Possibilité d’ajouter des #hashtags dans les tweets, cliquer sur un hashtag affiche la liste - de tous les - tweets qui le mentionnent -
  • -
  • Pas de pagination pour les tweets, charger les tweets suivants au défilement de la page
  • -
  • Les utilisateurs peuvent modifier les détails de leur profil et télécharger une image pour - personnaliser leur - avatar -
  • -
  • Afficher un bloc listant les hashtags les plus populaires
  • -
- -

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

- -

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

- -
$this->set([
-    'tweets' => $tweets,
-    'hashtag_name' => $name
-    ]);
-
- -

Les composants (components)

- -

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

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

- -
class UsersController extends Controller
-    {
-    public function add()
-    {
-    ...
-    $this->Flash->success(__('Your account has been created.'));
-    ...
-    }
-    }
-
- -

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.

- -

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 !

- - -
-
-
- - - \ No newline at end of file diff --git a/layouts/partials/bottom.html b/layouts/partials/bottom.html new file mode 100644 index 0000000..22a4eeb --- /dev/null +++ b/layouts/partials/bottom.html @@ -0,0 +1,5 @@ + + +{{ partial "footer.html" . }} + + diff --git a/layouts/partials/footer.html b/layouts/partials/footer.html index e69de29..000bb1a 100644 --- a/layouts/partials/footer.html +++ b/layouts/partials/footer.html @@ -0,0 +1 @@ + diff --git a/layouts/partials/header.html b/layouts/partials/header.html deleted file mode 100644 index e69de29..0000000 diff --git a/layouts/partials/menu.html b/layouts/partials/menu.html new file mode 100644 index 0000000..b33a6fb --- /dev/null +++ b/layouts/partials/menu.html @@ -0,0 +1,38 @@ + diff --git a/layouts/partials/top.html b/layouts/partials/top.html new file mode 100644 index 0000000..ed9657d --- /dev/null +++ b/layouts/partials/top.html @@ -0,0 +1,11 @@ + + + + {{ partial "head.html" . }} + + +
+ +
diff --git a/static/css/theme.scss b/static/css/theme.scss index 1257f22..9d55c14 100644 --- a/static/css/theme.scss +++ b/static/css/theme.scss @@ -31,13 +31,17 @@ a { } } -.article-content { +.content { background-color: $content-bg-color; min-height: 100%; padding-top: 1px; padding-bottom: 15px; } +.articles-list article { + margin-bottom: 25px; +} + .menu { padding-left: 0px; nav {