Guide de Démarrage Rapide

Le meilleur moyen de tester et d’apprendre CakePHP est de s’assoir et de construire une application simple de bookmarks.

Tutoriel de Bookmarker

Ce tutoriel va vous montrer la création d’une application simple de bookmarking (bookmarker). Pour commencer, nous allons installer CakePHP, créer notre base de données et utiliser les outils que CakePHP nous fournit pour créer une application rapidement.

Voici ce dont vous allez avoir besoin:

  1. Un serveur de base de données. Nous allons utiliser un serveur MySQL dans ce tutoriel. Vous devrez en savoir assez sur SQL pour créer une base de données: CakePHP prendra les rênes à partir de là. Puisque nous utilisons MySQL, assurez-vous aussi d’avoir pdo_mysql activé dans PHP.
  2. Des connaissances de base en PHP.

Avant de commencer, vous devez vous assurer que votre version de PHP est à jour:

php -v

Vous devez avoir installé au moins la version 5.6.0 (CLI) de PHP ou supérieure. La version de PHP de votre serveur web doit aussi être 5.6.0 ou supérieure, et doit être préférablement la même version que celle de votre interface en ligne de commande (CLI). Si vous souhaitez voir ce que donne l’application au final, regardez cakephp/bookmarker. C’est parti !

Récupérer CakePHP

La façon la plus simple pour installer CakePHP est d’utiliser Composer. Composer est un moyen simple d’installer CakePHP depuis votre terminal ou votre prompteur de ligne de commandes. D’abord, vous aurez besoin de télécharger et d’installer Composer si vous ne l’avez pas déjà fait. Si vous avez cURL installé, c’est aussi facile que de lancer ce qui suit:

curl -s https://getcomposer.org/installer | php

Ou alors vous pouvez télécharger composer.phar depuis le site de Composer.

Ensuite tapez simplement la ligne suivante dans votre terminal à partir du répertoire d’installation pour installer le squelette d’application CakePHP dans le répertoire bookmarker:

php composer.phar create-project --prefer-dist cakephp/app bookmarker

Si vous avez téléchargé et exécuté l’installeur Windows de Composer, tapez la ligne suivante dans votre terminal à partir de votre répertoire d’installation. (par exemple C:\wamp\www\dev\cakephp3):

composer self-update && composer create-project --prefer-dist cakephp/app bookmarker

L’avantage d’utiliser Composer est qu’il va automatiquement faire des tâches de configuration importantes, comme de définir les bonnes permissions de fichier et créer votre fichier config/app.php pour vous.

Il y a d’autres façons d’installer CakePHP. Si vous ne pouvez ou ne voulez pas utiliser Composer, consultez la section Installation.

Peu importe la façon dont vous avez téléchargé et installé CakePHP, une fois que votre configuration est faite, votre répertoire devrait ressembler à ce qui suit:

/bookmarker
    /bin
    /config
    /logs
    /plugins
    /src
    /tests
    /tmp
    /vendor
    /webroot
    .editorconfig
    .gitignore
    .htaccess
    .travis.yml
    composer.json
    index.php
    phpunit.xml.dist
    README.md

C’est le bon moment pour en apprendre un peu plus sur la façon dont la structure du répertoire de CakePHP fonctionne. Consultez la section Structure du dossier de CakePHP.

Vérifions notre Installation

Nous pouvons rapidement vérifier que notre installation fonctionne, en vérifiant la page d’accueil par défaut. Avant de faire ceci, vous devrez démarrer le serveur de développement:

bin/cake server

Note

Sur Windows, cette commande doit être bin\cake server (notez l’antislash).

Ceci va lancer le serveur web intégré de PHP sur le port 8765. Ouvrez http://localhost:8765 dans votre navigateur web pour voir la page d’accueil. Tous les points devront être cochés sauf pour CakePHP qui n’est pas encore capable de se connecter à votre base de données. Si ce n’est pas le cas, vous devrez installer des extensions PHP supplémentaires ou définir des permissions de répertoire.

Créer la Base de Données

Ensuite, configurons la base de données pour notre application de bookmarking. Si vous ne l’avez pas déjà fait, créez une base de données vide que nous allons utiliser dans ce tutoriel, avec un nom de votre choix, par exemple cake_bookmarks. Vous pouvez exécuter le SQL suivant pour créer les tables nécessaires:

CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    email VARCHAR(255) NOT NULL,
    password VARCHAR(255) NOT NULL,
    created DATETIME,
    modified DATETIME
);

CREATE TABLE bookmarks (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT NOT NULL,
    title VARCHAR(50),
    description TEXT,
    url TEXT,
    created DATETIME,
    modified DATETIME,
    FOREIGN KEY user_key (user_id) REFERENCES users(id)
);

CREATE TABLE tags (
    id INT AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(255),
    created DATETIME,
    modified DATETIME,
    UNIQUE KEY (title)
);

CREATE TABLE bookmarks_tags (
    bookmark_id INT NOT NULL,
    tag_id INT NOT NULL,
    PRIMARY KEY (bookmark_id, tag_id),
    FOREIGN KEY tag_key(tag_id) REFERENCES tags(id),
    FOREIGN KEY bookmark_key(bookmark_id) REFERENCES bookmarks(id)
);

Vous avez peut-être remarqué que la table bookmarks_tags utilisait une clé primaire composite. CakePHP accepte les clés primaires composites presque partout, facilitant la construction des applications à tenant multiples.

La table et les noms de colonnes que nous avons utilisés n’étaient pas arbitraires. En utilisant les conventions de nommage de CakePHP, nous pouvons mieux contrôler CakePHP et éviter d’avoir à configurer le framework. CakePHP est assez flexible pour s’accommoder de tout schéma de base de données, mais suivre les conventions va vous faire gagner du temps.

Configuration de Base de Données

Ensuite, indiquons à CakePHP où se trouve notre base de données et comment s’y connecter. Pour la plupart d’entre vous, ce sera la première et la dernière fois que vous devrez configurer quelque chose.

La configuration est assez simple: remplacez juste les valeurs dans le tableau Datasources.default dans le fichier config/app.php avec ceux qui correspondent à votre configuration. Un exemple simple de tableau de configuration pourrait ressembler à ce qui suit:

return [
    // Plus de configuration au-dessus.
    'Datasources' => [
        'default' => [
            'className' => 'Cake\Database\Connection',
            'driver' => 'Cake\Database\Driver\Mysql',
            'persistent' => false,
            'host' => 'localhost',
            'username' => 'cakephp',
            'password' => 'AngelF00dC4k3~',
            'database' => 'cake_bookmarks',
            'encoding' => 'utf8',
            'timezone' => 'UTC',
            'cacheMetadata' => true,
        ],
    ],
    // Plus de configuration en dessous.
];

Une fois que vous avez sauvegardé votre fichier config/app.php, vous devriez voir la section ‘CakePHP est capable de se connecter à la base de données’ cochée.

Note

Une copie du fichier de configuration par défaut de CakePHP se trouve dans config/app.default.php.

Génération de Code Scaffold

Comme notre base de données suit les conventions de CakePHP, nous pouvons utiliser l’application de console bake pour générer rapidement une application basique. Dans votre terminal, lancez les commandes suivantes:

// Sur Windows vous devez utiliser bin\cake à la place.
bin/cake bake all users
bin/cake bake all bookmarks
bin/cake bake all tags

Ceci va générer les controllers, models, views, leurs cas de tests correspondants et les fixtures pour nos ressources users, bookmarks et tags. Si vous avez stoppé votre serveur, relancez-le et allez sur http://localhost:8765/bookmarks.

Vous devriez voir une application basique mais fonctionnelle fournissant des accès aux données vers les tables de la base de données de votre application. Une fois que vous avez la liste des bookmarks, ajoutez quelques users, bookmarks, et tags.

Note

Si vous avez une page Not Found (404), vérifiez que le module mod_rewrite d’Apache est chargé.

Ajouter un Hashage de Mot de Passe

Quand vous avez créé vos users, (en visitant http://localhost:8765/users) vous avez probablement remarqué que les mots de passe sont stockés en clair. C’est très mauvais d’un point du vue sécurité, donc réglons ceci.

C’est aussi un bon moment pour parler de la couche model dans CakePHP. Dans CakePHP, nous séparons les méthodes qui agissent sur une collection d’objets, et celles qui agissent sur un objet unique, dans des classes différentes. Les méthodes qui agissent sur la collection des entities sont mises dans la classe Table, alors que les fonctionnalités correspondant à un enregistrement unique sont mises dans la classe Entity.

Par exemple, le hashage des mots de passe se fait pour un enregistrement individuel, donc nous allons intégrer ce comportement sur l’objet entity. Comme nous voulons hasher le mot de passe à chaque fois qu’il est défini nous allons utiliser une méthode mutateur/setter. CakePHP va appeler les méthodes setter basées sur les conventions à chaque fois qu’une propriété est définie dans une de vos entities. Ajoutons un setter pour le mot de passe. Dans src/Model/Entity/User.php, ajoutez ce qui suit:

namespace App\Model\Entity;

use Cake\Auth\DefaultPasswordHasher;
use Cake\ORM\Entity;

class User extends Entity
{

    // Code from bake.

    protected function _setPassword($value)
    {
        $hasher = new DefaultPasswordHasher();
        return $hasher->hash($value);
    }
}

Maintenant mettez à jour un des users que vous avez créé précédemment, si vous changez son mot de passe, vous devriez voir un mot de passe hashé à la place de la valeur originale sur la liste ou les pages de vue. CakePHP hashe les mots de passe avec bcrypt par défaut. Vous pouvez aussi utiliser sha1 ou md5 si vous travaillez avec une base de données existante.

Note

Si le mot de passe n’est pas haché, assurez-vous que vous avez suivi le même cas pour le mot de passe membre de la classe tout en nommant la fonction mutateur/setter

Récupérer les Bookmarks avec un Tag Spécifique

Maintenant que vous avez stocké les mots de passe de façon sécurisé, nous pouvons construire quelques fonctionnalités intéressantes dans notre application. Une fois que vous avez une collection de bookmarks, il peut être pratique de pouvoir les chercher par tag. Ensuite nous allons intégrer une route, une action de controller, et une méthode finder pour chercher les bookmarks par tag.

Idéalement, nous aurions une URL qui ressemble à http://localhost:8765/bookmarks/tagged/funny/cat/gifs Cela nous aide à trouver tous les bookmarks qui ont les tags ‘funny’, ‘cat’ ou ‘gifs’. Avant de pouvoir intégrer ceci, nous allons ajouter une nouvelle route. Votre fichier config/routes.php doit ressembler à ceci:

<?php
use Cake\Routing\Route\DashedRoute;
use Cake\Routing\Router;

Router::defaultRouteClass(DashedRoute::class);

// Nouvelle route ajoutée pour notre action "tagged".
// Le caractère `*` en fin de chaîne indique à CakePHP que cette action a
// des paramètres passés
Router::scope(
    '/bookmarks',
    ['controller' => 'Bookmarks'],
    function ($routes) {
        $routes->connect('/tagged/*', ['action' => 'tags']);
    }
);

Router::scope('/', function ($routes) {
    // Connecte la page d'accueil par défaut et les routes /pages/*.
    $routes->connect('/', [
        'controller' => 'Pages',
        'action' => 'display', 'home'
    ]);
    $routes->connect('/pages/*', [
        'controller' => 'Pages',
        'action' => 'display'
    ]);

    // Connecte les routes basées sur les conventions par défaut.
    $routes->fallbacks();
});

Ce qui est au-dessus définit une nouvelle ‘route’ qui connecte le chemin /bookmarks/tagged/*, vers BookmarksController::tags(). En définissant les routes, vous pouvez isoler la définition de vos URLs, de la façon dont elles sont intégrées. Si nous visitions http://localhost:8765/bookmarks/tagged, nous verrions une page d’erreur de CakePHP. Intégrons maintenant la méthode manquante. Dans src/Controller/BookmarksController.php, ajoutez ce qui suit:

public function tags()
{
    // La clé 'pass' est fournie par CakePHP et contient tous les segments
    // d'URL de la "request" (instance de \Cake\Network\Request)
    $tags = $this->request->getParam('pass');

    // On utilise l'objet "Bookmarks" (une instance de
    // \App\Model\Table\BookmarksTable) pour récupérer les bookmarks avec
    // ces tags
    $bookmarks = $this->Bookmarks->find('tagged', [
        'tags' => $tags
    ]);

    // Passe les variables au template de vue (view).
    $this->set([
        'bookmarks' => $bookmarks,
        'tags' => $tags
    ]);
}

Pour accéder aux autres parties des données de la “request”, référez-vous à la section ServerRequest.

Créer la Méthode Finder

Dans CakePHP, nous aimons garder les actions de notre controller légères, et mettre la plupart de la logique de notre application dans les models. Si vous visitez l’URL /bookmarks/tagged maintenant, vous verrez une erreur comme quoi la méthode findTagged() n’a pas été encore intégrée, donc faisons-le. Dans src/Model/Table/BookmarksTable.php ajoutez ce qui suit:

// L'argument $query est une instance de \Cake\ORM\Query.
// Le tableau $options contiendra les tags que nous avons passé à find('tagged')
// dans l'action de notre Controller
public function findTagged(Query $query, array $options)
{
    $bookmarks = $this->find()
        ->select(['id', 'url', 'title', 'description']);

    if (empty($options['tags'])) {
        $bookmarks
            ->leftJoinWith('Tags')
            ->where(['Tags.title IS' => null]);
    } else {
        $bookmarks
            ->innerJoinWith('Tags')
            ->where(['Tags.title IN ' => $options['tags']]);
    }

    return $bookmarks->group(['Bookmarks.id']);
}

Nous intégrons juste des finders personnalisés. C’est un concept très puissant dans CakePHP qui vous permet de faire un package réutilisable de vos requêtes. Les finders attendent toujours un objet Query Builder et un tableau d’options en paramètre. Les finders peuvent manipuler les requêtes et ajouter n’importe quels conditions ou critères. Une fois qu’ils ont terminé, les finders doivent retourner l’objet Query modifié. Dans notre finder nous avons amené les méthodes innerJoinWith(), where() et group() qui nous permet de trouver les bookmarks distinct qui ont un tag correspondante. Lorsque aucune tag n’est fournie, nous utilisons un leftJoinWith() et modifions la condition ‘where’, en trouvant des bookmarks sans tags.

Créer la Vue

Maintenant si vous vous rendez à l’url /bookmarks/tagged, CakePHP va afficher une erreur vous disant que vous n’avez pas de fichier de vue. Construisons donc le fichier de vue pour notre action tags(). Dans src/Template/Bookmarks/tags.ctp mettez le contenu suivant:

<h1>
    Bookmarks tagged with
    <?= $this->Text->toList(h($tags)) ?>
</h1>

<section>
<?php foreach ($bookmarks as $bookmark): ?>
    <article>
        <!-- Utilise le HtmlHelper pour créer un lien -->
        <h4><?= $this->Html->link($bookmark->title, $bookmark->url) ?></h4>
        <small><?= h($bookmark->url) ?></small>

        <!-- Utilise le TextHelper pour formater le texte -->
        <?= $this->Text->autoParagraph(h($bookmark->description)) ?>
    </article>
<?php endforeach; ?>
</section>

Dans le code ci-dessus, nous utilisons le Helper HTML et le Helper Text pour aider à la génération du contenu de notre vue. Nous utilisons également la fonction h pour encoder la sortie en HTML. Vous devez vous rappeler de toujours utiliser h() lorsque vous affichez des données provenant des utilisateurs pour éviter les problèmes d’injection HTML.

Le fichier tags.ctp que nous venons de créer suit la convention de nommage de CakePHP pour un ficher de template de vue. La convention d’avoir le nom de template en minuscule et en underscore du nom de l’action du controller.

Vous avez peut-être remarqué que nous pouvions utiliser les variables $tags et $bookmarks dans notre vue. Quand nous utilisons la méthode set() dans notre controller, nous définissons les variables spécifiques à envoyer à la vue. La vue va rendre disponible toutes les variables passées dans les templates en variables locales.

Vous devriez maintenant pouvoir visiter l’URL /bookmarks/tagged/funny et voir tous les bookmarks taggés avec ‘funny’.

Ainsi nous avons créé une application basique pour gérer des bookmarks, des tags et des users. Cependant, tout le monde peut voir tous les tags de tout le monde. Dans le prochain chapitre, nous allons intégrer une authentification et restreindre la visibilité des bookmarks à ceux qui appartiennent à l’utilisateur courant.

Maintenant continuons avec Tutoriel de Bookmarker Part 2 pour construire votre application ou plongez dans la documentation pour en apprendre plus sur ce que CakePHP peut faire pour vous.

Tutoriel de Bookmarker Part 2

Après avoir fini la première partie de ce tutoriel vous devriez avoir une application basique de bookmarking. Dans ce chapitre, nous ajouterons l’authentification et nous allons restreindre les bookmarks pour que chaque utilisateur puisse voir/modifier seulement ceux qui lui appartiennent.

Ajouter la Connexion

Dans CakePHP, l’authentification est gérée par les Components (Composants). Les components peuvent être imaginés comme des façons de créer des parties réutilisables de code du controller pour une fonctionnalité spécifique ou un concept. Les components peuvent aussi se lancer dans le cycle de vie de l’event du controller et interagir avec votre application de cette façon. Pour commencer, nous ajouterons AuthComponent à notre application. Nous voulons que chaque méthode nécessite l’authentification, donc nous allons ajouter AuthComponent dans notre AppController:

// Dans src/Controller/AppController.php
namespace App\Controller;

use Cake\Controller\Controller;

class AppController extends Controller
{
    public function initialize()
    {
        $this->loadComponent('Flash');
        $this->loadComponent('Auth', [
            'authenticate' => [
                'Form' => [
                    'fields' => [
                        'username' => 'email',
                        'password' => 'password'
                    ]
                ]
            ],
            'loginAction' => [
                'controller' => 'Users',
                'action' => 'login'
            ],
            // Si l'utilisateur arrive sur une page non-autorisée, on le
            // redirige sur la page précédente.
            'unauthorizedRedirect' => $this->referer()
        ]);

        // Autorise l'action display pour que notre controller de pages
        // continue de fonctionner.
        $this->Auth->allow(['display']);
    }
}

Nous avons seulement indiqué à CakePHP que nous souhaitions charger les components Flash et Auth. En plus, nous avons personnalisé la configuration de AuthComponent, puisque notre table users utilise email comme username. Maintenant, si vous tapez n’importe quelle URL, vous serez renvoyé vers /users/login, qui vous montrera une page d’erreur puisque nous n’avons pas encore écrit ce code. Créons donc l’action login:

// Dans src/Controller/UsersController.php

public function login()
{
    if ($this->request->is('post')) {
        $user = $this->Auth->identify();
        if ($user) {
            $this->Auth->setUser($user);
            return $this->redirect($this->Auth->redirectUrl());
        }
        $this->Flash->error('Votre username ou mot de passe est incorrect.');
    }
}

Et dans src/Template/Users/login.ctp, ajoutez ce qui suit:

<h1>Connexion</h1>
<?= $this->Form->create() ?>
<?= $this->Form->control('email') ?>
<?= $this->Form->control('password') ?>
<?= $this->Form->button('Login') ?>
<?= $this->Form->end() ?>

Note

La méthode control() est disponible depuis 3.4. Si vous utilisez une version précédente, utilisez la méthode input().

Maintenant que nous avons un formulaire simple de connexion, nous devrions pouvoir nous connecter avec un de nos utilisateurs qui a un mot de passe hashé.

Note

Si aucun de vos utilisateurs n’a de mot de passe hashé, commentez la ligne loadComponent('Auth'). Puis allez modifier l’utilisateur, créez- lui un nouveau mot de passe.

Vous devriez maintenant pouvoir vous connecter. Si ce n’est pas le cas, assurez-vous que vous utilisez un utilisateur qui a un mot de passe hashé.

Ajouter la Déconnexion

Maintenant que les personnes peuvent se connecter, vous voudrez aussi probablement fournir un moyen de se déconnecter. Encore une fois, dans UsersController, ajoutez le code suivant:

public function initialize()
{
    parent::initialize();
    $this->Auth->allow(['logout']);
}

public function logout()
{
    $this->Flash->success('Vous êtes maintenant déconnecté.');
    return $this->redirect($this->Auth->logout());
}

Ce code autorise l’action logout en tant qu’action publique, et implémente la méthode logout. Vous pouvez maintenant visiter la page /users/logout pour vous déconnecter. Vous devriez alors être renvoyé vers la page de connexion.

Permettre de s’Enregistrer

Si vous n’êtes pas connecté et que vous essayez de visiter /users/add vous serez renvoyés vers la page de connexion. Nous devrions régler cela puisque nous voulons que les utilisateurs s’inscrivent à notre application. Dans UsersController, ajoutez ce qui suit:

public function initialize()
{
    parent::initialize();
    // Ajoute l'action 'add' à la liste des actions autorisées.
    $this->Auth->allow(['logout', 'add']);
}

Ce qui est au-dessus indique à AuthComponent que l’action add() ne nécessite pas d’authentification ou d’autorisation. Vous pouvez prendre le temps de nettoyer Users/add.ctp et de retirer les liens, ou continuez vers la prochaine section. Nous ne ferons pas de fichier d’édition (edit) ou de vue d’un utilisateur (view), ni de liste d’utilisateurs (index) dans ce tutoriel donc ils ne fonctionneront pas puisque AuthComponent va vous refuser l’accès pour ces actions de controller.

Restreindre l’Accès aux Bookmarks

Maintenant que les utilisateurs peuvent se connecter, nous voulons limiter les bookmarks qu’ils peuvent voir à ceux qu’ils ont créés. Nous allons le faire en utilisant un adaptateur ‘authorization’. Puisque nos besoins sont assez simples, nous pouvons écrire quelques lignes de code simple dans notre BookmarksController. Mais avant de le faire, nous voulons dire à AuthComponent comment notre application va autoriser les actions. Dans notre AppController, ajoutez ce qui suit:

public function isAuthorized($user)
{
    return false;
}

Ajoutez aussi ce qui suit dans la configuration de Auth dans AppController:

'authorize' => 'Controller',

Votre méthode initialize() doit maintenant ressembler à ceci:

public function initialize()
{
    $this->loadComponent('Flash');
    $this->loadComponent('Auth', [
        'authorize'=> 'Controller',//added this line
        'authenticate' => [
            'Form' => [
                'fields' => [
                    'username' => 'email',
                    'password' => 'password'
                ]
            ]
        ],
        'loginAction' => [
            'controller' => 'Users',
            'action' => 'login'
        ],
        'unauthorizedRedirect' => $this->referer()
    ]);

    // Allow the display action so our pages controller
    // continues to work.
    $this->Auth->allow(['display']);
}

Nous allons par défaut refuser l’accès, et permettre un accès incrémental où cela est utile. D’abord, nous allons ajouter la logique d’autorisation pour les bookmarks. Dans notre BookmarksController, ajoutez ce qui suit:

public function isAuthorized($user)
{
    $action = $this->request->params['action'];

    // Add et index sont toujours permises.
    if (in_array($action, ['index', 'add', 'tags'])) {
        return true;
    }
    // Tout autre action nécessite un id.
    if (!$this->request->getParam('pass.0')) {
        return false;
    }

    // Vérifie que le bookmark appartient à l'utilisateur courant.
    $id = $this->request->getParam('pass.0');
    $bookmark = $this->Bookmarks->get($id);
    if ($bookmark->user_id == $user['id']) {
        return true;
    }
    return parent::isAuthorized($user);
}

Maintenant, si vous essayez de voir, de modifier ou de supprimer un bookmark qui ne vous appartient pas, vous devriez être redirigé vers la page d’où vous venez. Si aucun message ne s’affiche, ajoutez la ligne suivante dans votre layout:

// Dans src/Template/Layout/default.ctp
<?= $this->Flash->render() ?>

Vous devriez maintenant voir les messages d’erreur d’autorisation.

Régler la Vue de Liste et les Formulaires

Alors que view et delete fonctionnent, edit, add et index ont quelques problèmes:

  1. Lors de l’ajout d’un bookmark, vous pouvez choisir l’utilisateur.
  2. Lors de l’édition d’un bookmark vous pouvez choisir l’utilisateur.
  3. La page de liste montre les bookmarks des autres utilisateurs.

Attaquons nous d’abord à add. Pour commencer, retirez control('user_id') de src/Template/Bookmarks/add.ctp. Une fois retiré, nous allons aussi mettre à jour l’action add() dans src/Controller/BookmarksController.php pour ressembler à ceci:

public function add()
{
    $bookmark = $this->Bookmarks->newEntity();
    if ($this->request->is('post')) {
        $bookmark = $this->Bookmarks->patchEntity($bookmark, $this->request->getData());
        $bookmark->user_id = $this->Auth->user('id');
        if ($this->Bookmarks->save($bookmark)) {
            $this->Flash->success('Le bookmark a été sauvegardé.');
            return $this->redirect(['action' => 'index']);
        }
        $this->Flash->error('Le bookmark ne peut être sauvegardé. Merci de réessayer.');
    }
    $tags = $this->Bookmarks->Tags->find('list');
    $this->set(compact('bookmark', 'tags'));
    $this->set('_serialize', ['bookmark']);
}

En définissant la propriété entity avec les données de session, nous retirons la possibilité que l’utilisateur puisse modifier l’auteur d’un bookmark. Nous ferons la même chose pour le formulaire et l’action edit. Votre action edit() dans src/Controller/BookmarksController.php devrait ressembler à ceci:

public function edit($id = null)
{
    $bookmark = $this->Bookmarks->get($id, [
        'contain' => ['Tags']
    ]);
    if ($this->request->is(['patch', 'post', 'put'])) {
        $bookmark = $this->Bookmarks->patchEntity($bookmark, $this->request->getData());
        $bookmark->user_id = $this->Auth->user('id');
        if ($this->Bookmarks->save($bookmark)) {
            $this->Flash->success('Le bookmark a été sauvegardé.');
            return $this->redirect(['action' => 'index']);
        } else {
            $this->Flash->error('Le bookmark ne peut être sauvegardé. Merci de réessayer.');
        }
    }
    $tags = $this->Bookmarks->Tags->find('list');
    $this->set(compact('bookmark', 'tags'));
    $this->set('_serialize', ['bookmark']);
}

Vue de Liste

Maintenant nous devons afficher les bookmarks pour l’utilisateur actuellement connecté. Nous pouvons le faire en mettant à jour l’appel à paginate(). Faites en sorte que votre action index() dans src/Controller/BookmarksController.php ressemble à ceci:

public function index()
{
    $this->paginate = [
        'conditions' => [
            'Bookmarks.user_id' => $this->Auth->user('id'),
        ]
    ];
    $this->set('bookmarks', $this->paginate($this->Bookmarks));
    $this->set('_serialize', ['bookmarks']);
}

Nous devrions aussi mettre à jour l’action tags() et la méthode finder liée, mais nous vous laisserons ceci en exercice que vous pouvez faire vous-même.

Améliorer l’Experience de Tag

Actuellement, ajouter des nouveaux tags est un processus difficile, puisque TagsController interdit tous les accès. Plutôt que de permettre l’accès, nous pouvons améliorer l’UI de sélection de tag en utilisant un champ de texte séparé par des virgules. Cela donnera une meilleure expérience à nos utilisateurs, et utilisera quelques unes des super fonctionnalités de l’ORM.

Ajouter un Champ Computed

Comme nous voulons un accès simple vers les tags formatés pour une entity, nous pouvons ajouter un champ virtuel/calculé à l’entity. Dans src/Model/Entity/Bookmark.php ajoutez ce qui suit:

use Cake\Collection\Collection;

protected function _getTagString()
{
    if (isset($this->_properties['tag_string'])) {
        return $this->_properties['tag_string'];
    }
    if (empty($this->tags)) {
        return '';
    }
    $tags = new Collection($this->tags);
    $str = $tags->reduce(function ($string, $tag) {
        return $string . $tag->title . ', ';
    }, '');
    return trim($str, ', ');
}

Cela nous laissera l’accès à la propriété calculée $bookmark->tag_string. Nous utiliserons cette propriété dans controls plus tard. Rappelez-vous d’ajouter la propriété tag_string dans la liste _accessible de votre entity, puisque nous voulons la ‘sauvegarder’ plus tard.

Dans le fichier src/Model/Entity/Bookmark.php, ajoutez tag_string à la propriété _accessible comme ceci:

protected $_accessible = [
    'user_id' => true,
    'title' => true,
    'description' => true,
    'url' => true,
    'user' => true,
    'tags' => true,
    'tag_string' => true,
];

Mettre à Jour les Vues

Avec l’entity mise à jour, nous pouvons ajouter un nouveau control pour nos tags. Dans src/Template/Bookmarks/add.ctp et src/Template/Bookmarks/edit.ctp, remplacez l’input tags._ids existant avec ce qui suit:

echo $this->Form->control('tag_string', ['type' => 'text']);

Persister la Chaîne Tag

Maintenant que nous pouvons voir les tags existants en chaîne, nous voudrions aussi sauvegarder les données. Comme nous marquons les tag_string accessibles, l’ORM va copier ces données à partir de la requête dans notre entity. Nous pouvons utiliser une méthode hook beforeSave() pour parser la chaîne de tag et trouver/construire les entities liées. Ajoutez ce qui suit dans src/Model/Table/BookmarksTable.php:

public function beforeSave($event, $entity, $options)
{
    if ($entity->tag_string) {
        $entity->tags = $this->_buildTags($entity->tag_string);
    }
}

protected function _buildTags($tagString)
{
    // Trim tags
    $newTags = array_map('trim', explode(',', $tagString));
    // Retire tous les tags vides
    $newTags = array_filter($newTags);
    // Réduit les tags dupliqués
    $newTags = array_unique($newTags);

    $out = [];
    $query = $this->Tags->find()
        ->where(['Tags.title IN' => $newTags]);

    // Retire les tags existants de la liste des tags nouveaux.
    foreach ($query->extract('title') as $existing) {
        $index = array_search($existing, $newTags);
        if ($index !== false) {
            unset($newTags[$index]);
        }
    }
    // Ajoute les tags existants.
    foreach ($query as $tag) {
        $out[] = $tag;
    }
    // Ajoute les nouveaux tags.
    foreach ($newTags as $tag) {
        $out[] = $this->Tags->newEntity(['title' => $tag]);
    }
    return $out;
}

Alors que ce code est un peu plus compliqué que ce que nous avons déjà fait, il permet de montrer la puissance de l’ORM de CakePHP. Vous pouvez facilement manipuler les résultats de requête en utilisant les méthodes des Collections, et gérer les scenarios où vous créez les entities à la volée avec facilité.

Récapitulatif

Nous avons élargi notre application de bookmarking pour gérer les scenarios de contrôle d’authentification et d’autorisation/d’accès basique. Nous avons aussi ajouté quelques améliorations UX en tirant parti du FormHelper et des capacités de l’ORM.

Merci d’avoir pris le temps d’explorer CakePHP. Ensuite, vous pouvez finir le tutoriel du Tutoriel d’un Blog, en apprendre plus sur l’ORM ou vous pouvez lire attentivement Utiliser CakePHP.