Sauvegarder vos Données

CakePHP rend la sauvegarde des données d’un model très rapide. Les données prêtes à être sauvegardées doivent être passées à la méthode save() du model en utilisant le format basique suivant:

Array
(
    [NomDuModele] => Array
    (
        [nomduchamp1] => 'valeur'
        [nomduchamp2] => 'valeur'
    )
)

La plupart du temps vous n’aurez même pas à vous préoccuper de ce format : le FormHelper et les méthodes de recherche de CakePHP réunissent les données sous cette forme. Si vous utilisez un de ces helpers, les données sont également disponibles dans $this->request->data pour un usage rapide et pratique.

Voici un exemple simple d’une action de controller qui utilise un model CakePHP pour sauvegarder les données dans une table de la base de données:

public function edit($id) {
    //Est-ce que des données de formulaires ont été POSTées ?
    if ($this->request->is('post')) {
       //Si les données du formulaire peuvent être validées et sauvegardées ...
       if($this->Recipe->save($this->request->data)) {
           //On définit une message flash en session et on redirige.
           $this->Session->setFlash("Recipe sauvegardée !");
           return $this->redirect('/recettes');
       }
    }
    //Si aucune données de formulaire, on récupère la recipe à éditer
    //et on la passe à la vue
    $this->set('recipe', $this->Recipe->findById($id));
}

Quand save() est appelée, la donnée qui lui est passée en premier paramètre est validée en utilisant le mécanisme de validation de CakePHP (voir le chapitre Validation des Données pour plus d’informations). Si pour une raison quelconque vos données ne se sauvegardent pas, pensez à regarder si des règles de validation ne sont pas insatisfaites. Vous pouvez débugger cette situation en affichant Model::$validationErrors:

if ($this->Recipe->save($this->request->data)) {
    // Traite le succès.
}
debug($this->Recipe->validationErrors);

Il y a quelques autres méthodes du model liées à la sauvegarde que vous trouverez utiles :

Model::set($one, $two = null)

Model::set() peut être utilisé pour définir un ou plusieurs champs de données du tableau de donnés à l’intérieur d’un Model. C’est utile pour l’utilisation de models avec les fonctionnalités ActiveRecord offertes par le model:

$this->Post->read(null, 1);
$this->Post->set('title', 'Nouveau titre pour l\'article');
$this->Post->save();

C’est un exemple de l’utilisation de set() pour mettre à jour les champs uniques, dans une approche ActiveRecord. Vous pouvez aussi utiliser set() pour assigner de nouvelles valeurs aux champs multiples:

$this->Post->read(null, 1);
$this->Post->set(array(
    'title' => 'Nouveau titre',
    'published' => false
));
$this->Post->save();

Ce qui est au-dessus met à jour les champs title et published et sauvegarde l’enregistrement dans le base de données.

Model::clear()

Cette méthode peut être utilisée pour réinitialiser l’état du model et effacer toutes les données non sauvegardées et les erreurs de validation.

Nouveau dans la version 2.4.

Model::save(array $data = null, boolean $validate = true, array $fieldList = array())

La méthode ci-dessus sauvegarde des données formatées sous forme tabulaire. Le second paramètre vous permet de mettre de côté la validation, et le troisième vous permet de fournir une liste des champs du model devant être sauvegardés. Pour une sécurité accrue, vous pouvez limiter les champs sauvegardés à ceux listés dans $fieldList.

Note

Si $fieldList n’est pas fourni, un utilisateur malicieux peut ajouter des champs supplémentaires dans le formulaire de données (si vous n’utilisez pas SecurityComponent), et ainsi changer la valeur de champs qui n’étaient pas prévus à l’origine.

La méthode save a aussi une syntaxe alternative:

save(array $data = null, array $params = array())

Le tableau $params peut avoir n’importe quelle option disponible suivante en clé:

  • validate Défini à true/false pour activer/désactiver la validation.
  • fieldList Un tableau de champs que vous souhaitez autoriser pour la sauvegarde.
  • callbacks Défini à false permet la désactivation des callbacks. En utilisant “before” ou “after” activera seulement ces callbacks.
  • counterCache (depuis 2.4) Booléen pour contrôler la mise à jour des counter caches (si il y en a).
  • atomic (depuis 2.6) Booléen pour indiquer que vous voulez sauvegarder les enregistrements dans une transaction.

Plus d’informations sur les callbacks du model sont disponibles ici.

Astuce

Si vous ne voulez pas le que champ modified soit mis à jour pendant la sauvegarde de certaines données, ajoutez 'modified' => false à votre tableau de $data.

Une fois qu’une sauvegarde est terminée, l’ID de l’objet peut être trouvé dans l’attribut $id de l’objet Model - quelque chose de spécialement pratique quand on crée de nouveaux objets.

$this->Ingredient->save($nouvellesDonnees);
$nouvelIngredientId = $this->Ingredient->id;

La création ou la mise à jour est contrôlée par le champ id du model. Si $Model->id est défini, l’enregistrement avec cette clé primaire est mis à jour. Sinon, un nouvel enregistrement est créé:

// Création: id n'est pas défini ou est null
$this->Recipe->create();
$this->Recipe->save($this->request->data);

// Mise à jour: id est défini à une valeur numérique
$this->Recipe->id = 2;
$this->Recipe->save($this->request->data);

Astuce

Lors de l’appel à save() dans une boucle, n’oubliez pas d’appeler clear().

Si vous voulez mettre à jour une valeur, plutôt qu’en créer une, assurez-vous que vous avez passé le champ de la clé primaire dans le tableau data:

$data = array('id' => 10, 'title' => 'Mon Nouveau Titre');
// Cela mettra à jour la Recipe avec un id 10
$this->Recipe->save($data);

Model::create(array $data = array())

Cette méthode initialise la classe du model pour sauvegarder de nouvelles informations. Cela ne crée pas réellement un enregistrement dans la base de données mais efface Model::$id et définit Model::$data basé sur les champs par défaut dans votre base de données. Si vous n’avez défini aucun champ par défaut dans votre base de données, Model::$data sera défini comme un tableau vide.

Si le paramètre $data (utilisant le format de tableau souligné ci-dessus) est passé, il sera fusionné avec les champs par défaut de la base de données et l’instance du model sera prête à être sauvegardée avec ces données (accessible dans $this->data).

Si false ou null sont passés pour le paramètre $data, Model::$data sera défini comme un tableau vide.

Astuce

Si vous voulez insérer une nouvelle ligne au lieu de mettre à jour une ligne existante, vous devriez toujours appeler en premier lieu create(). Cela évite les conflits avec d’éventuels appels à save en amont dans les callbacks ou à tout autre endroit.

Model::saveField(string $fieldName, string $fieldValue, $validate = false)

Utilisée pour sauvegarder la valeur d’un seul champ. Fixez l’ID du model ($this->ModelName->id = $id) juste avant d’appeler saveField(). Lors de l’utilisation de cette méthode, $fieldName ne doit contenir que le nom du champ, pas le nom du model et du champ.

Par exemple, pour mettre à jour le titre d’un article de blog, l’appel depuis un controller à saveField ressemblerait à quelque chose comme:

$this->Post->saveField('title', 'Un nouveau titre pour un Nouveau Jour');

Avertissement

Vous ne pouvez pas arrêter la mise à jour du champ modified avec cette méthode, vous devrez utiliser la méthode save().

La méthode saveField a aussi une syntaxe alternative:

saveField(string $fieldName, string $fieldValue, array $params = array())

Le tableau $params peut avoir en clé, les options disponibles suivantes:

  • validate Définie à true/false pour activer/désactiver la validation.
  • callbacks Définie à false pour désactiver les callbacks. Utiliser “before” ou “after” activera seulement ces callbacks.
  • counterCache (depuis 2.4) Booléen pour contrôler la mise à jour des counter caches (si il y en a).

Model::updateAll(array $fields, mixed $conditions)

Met à jour plusieurs enregistrements en un seul appel. Les enregistrements à mettre à jour, ainsi qu’avec leurs valeurs, sont identifiés par le tableau $fields. Les enregistrements à mettre à jour sont identifiés par le tableau $conditions. Si l’argument $conditions n’est pas fourni ou si il n’est pas défini à true, tous les enregistrements seront mis à jour.

Par exemple, si je voulais approuver tous les bakers qui sont membres depuis plus d’un an, l’appel à update devrait ressembler à quelque chose du style:

$thisYear = date('Y-m-d H:i:s', strtotime('-1 year'));

$this->Baker->updateAll(
    array('Baker.approve' => true),
    array('Baker.created <=' => $thisYear)
);

Le tableau $fields accepte des expressions SQL. Les valeurs littérales doivent être manuellement quotées en utilisant DboSource::value(). Par exemple, si une de vos méthodes de model appelait updateAll(), vous feriez ce qui suit:

$db = $this->getDataSource();
$value = $db->value($value, 'string');
$this->updateAll(
    array('Baker.status' => $value),
    array('Baker.status' => 'old')
);

Note

Même si le champ modifié existe pour le model qui vient d’être mis à jour, il ne sera pas mis à jour automatiquement par l’ORM. Ajoutez le seulement manuellement au tableau si vous avez besoin de le mettre à jour.

Par exemple, pour fermer tous les tickets qui appartiennent à un certain client:

$this->Ticket->updateAll(
    array('Ticket.status' => "'closed'"),
    array('Ticket.client_id' => 453)
);

Par défaut, updateAll() joindra automatiquement toute association belongsTo pour les bases de données qui suportent la jointure. Pour éviter cela, délier les associations temporairement.

Model::saveMany(array $data = null, array $options = array())

La méthode utilisée pour sauvegarder les lignes multiples du même model en une fois. Les options suivantes peuvent être utilisées:

  • validate: Définie à false pour désactiver la validation, true pour valider chaque enregistrement avant la sauvegarde, “first” pour valider tous les enregistrements avant qu’un soit sauvegardé (par défaut),
  • atomic: Si true (par défaut), essaiera de sauvegarder tous les enregistrements en une seule transaction. Devrait être définie à false si la base de données/table ne supporte pas les transactions.
  • fieldList: Equivalent au paramètre $fieldList dans Model::save()
  • deep: (since 2.1) Si défini à true, les données associées sont aussi sauvegardées, regardez aussi saveAssociated.
  • callbacks Défini à false pour désactiver les callbacks. En utilisant “before” ou “after” va activer seulement ces callbacks.
  • counterCache (depuis 2.4) Booléen pour contrôler la mise à jour des counter caches (si il y en a).

Pour sauvegarder de multiples enregistrements d’un unique model, $data a besoin d’être un tableau d’enregistrements indexé numériquement comme ceci:

$data = array(
    array('title' => 'titre 1'),
    array('title' => 'titre 2'),
)

Note

Notez que nous passons les indices numériques de la variable habituelle $data contenant le clé Article. Quand vous passez plusieurs enregistrements du même model, les tableaux d’enregistrements doivent être seulement indexés numériquement sans la clé model.

Il est aussi possible d’avoir les données dans le format suivant:

$data = array(
    array('Article' => array('title' => 'title 1')),
    array('Article' => array('title' => 'title 2')),
)

Pour sauvegarder les données associées avec $options['deep'] = true (depuis 2.1), les deux exemples ci-dessus ressembleraient à cela:

$data = array(
    array('title' => 'title 1', 'Assoc' => array('field' => 'value')),
    array('title' => 'title 2'),
);
$data = array(
    array(
        'Article' => array('title' => 'title 1'),
        'Assoc' => array('field' => 'value')
    ),
    array('Article' => array('title' => 'title 2')),
);
$Model->saveMany($data, array('deep' => true));

Gardez à l’esprit que si vous souhaitez mettre à jour un enregistrement au lieu d’en créer un nouveau, vous devez juste ajouter en index la clé primaire à la ligne de donnée:

array(
    // Ceci crée une nouvelle ligne
    array('Article' => array('title' => 'New article')),
    // Ceci met à jour une ligne existante
    array('Article' => array('id' => 2, 'title' => 'title 2')),
)

Model::saveAssociated(array $data = null, array $options = array())

Méthode utilisée pour sauvegarder des associations de model en une seule fois. Les options suivantes peuvent être utilisées:

  • validate: Définie à false pour désactiver la validation, true pour valider chaque enregistrement avant sauvegarde, “first” pour valider tous les enregistrements avant toute sauvegarde (par défaut).
  • atomic: Si à true (par défaut), va tenter de sauvegarder tous les enregistrements en une seule transaction. Devrait être définie à false si la base de données/table ne supporte pas les transactions.
  • fieldList: Equivalent au paramètre $fieldList de Model::save().
  • deep: (depuis 2.1) Si définie à true, les données pas seulement associées directement vont être sauvegardées, mais aussi les données associées imbriquées plus profondément. Par défaut à false.
  • counterCache (depuis 2.4) Booléen pour contrôler la mise à jour des counter caches (si il y en a).

Pour sauvegarder un enregistrement et tous ses enregistrements liés avec une association hasOne ou belongsTo, le tableau de données devra ressembler à cela:

array(
    'User' => array('username' => 'billy'),
    'Profile' => array('sex' => 'Male', 'occupation' => 'Programmer'),
)

Pour sauvegarder un enregistrement et ses enregistrements liés avec une association hasMany, le tableau de données devra ressembler à cela:

$data = array(
    'Article' => array('title' => 'My first article'),
    'Comment' => array(
        array('body' => 'Comment 1', 'user_id' => 1),
        array('body' => 'Comment 2', 'user_id' => 12),
        array('body' => 'Comment 3', 'user_id' => 40),
    ),
);

Et pour sauvegarder un enregistrement avec ses enregistrements liés par hasMany qui ont plus de deux niveaux d’association de profondeur, le tableau de données devra être comme suit:

$data = array(
     'User' => array('email' => 'john-doe@cakephp.org'),
     'Cart' => array(
         array(
             'payment_status_id' => 2,
             'total_cost' => 250,
             'CartItem' => array(
                 array(
                     'cart_product_id' => 3,
                     'quantity' => 1,
                     'cost' => 100,
                 ),
                 array(
                     'cart_product_id' => 5,
                     'quantity' => 1,
                     'cost' => 150,
                 )
             )
         )
     )
 );

Note

Si cela réussit, la clé étrangère du model principal va être stockée dans le champ id du model lié, par ex: $this->RelatedModel->id.

Avertissement

Attention quand vous vérifiez les appels saveAssociated avec l’option atomic définie à false. Elle retourne un tableau au lieu d’un boléen.

Modifié dans la version 2.1: Vous pouvez maintenant aussi sauvegarder les données associées avec la configuration $options['deep'] = true;.

Pour sauvegarder un enregistrement et ses enregistrements liés avec une association hasMany ainsi que les données associées plus profondément de type Comment belongsTo User, le tableau de données devra ressembler à ceci:

$data = array(
    'Article' => array('title' => 'My first article'),
    'Comment' => array(
        array('body' => 'Comment 1', 'user_id' => 1),
        array(
            'body' => 'Save a new user as well',
            'User' => array('first' => 'mad', 'last' => 'coder')
        ),
    ),
);

Et sauvegarder cette donnée avec:

$Article->saveAssociated($data, array('deep' => true));

Modifié dans la version 2.1: Model::saveAll() et ses amis supportent maintenant qu’on leur passe fieldList pour des models multiples.

Exemple d’utilisation de fieldList avec de multiples models:

$this->SomeModel->saveAll($data, array(
    'fieldList' => array(
        'SomeModel' => array('field_1'),
        'AssociatedModel' => array('field_2', 'field_3')
    )
));

La fieldList sera un tableau d’alias de model en clé et de tableaux avec les champs en valeur. Les noms de model ne sont pas imbriqués comme dans les données à sauvegarder.

Model::saveAll(array $data = null, array $options = array())

La fonction saveAll est juste un wrapper autour des méthodes saveMany et saveAssociated. Elle va inspecter les données et déterminer quel type de sauvegarde elle devra effectuer. Si les données sont bien formatées en un tableau indicé numériquement, saveMany sera appelée, sinon saveAssociated sera utilisée.

Cette fonction reçoit les mêmes options que les deux précédentes, et est généralement une fonction rétro-compatible. Il est recommandé d’utiliser soit saveMany soit saveAssociated selon le cas.

Sauvegarder les Données de Models Liés (hasOne, hasMany, belongsTo)

Quand vous travaillez avec des models associés, il est important de réaliser que la sauvegarde de données de model devrait toujours être faite avec le model CakePHP correspondant. Si vous sauvegardez un nouveau Post et ses Comments associés, alors vous devriez utiliser les deux models Post et Comment pendant l’opération de sauvegarde.

Si aucun des enregistrements du model associé n’existe pour l’instant dans le système (par exemple, vous voulez sauvegarder un nouveau User et ses enregistrements du Profile lié en même temps), vous aurez besoin de sauvegarder d’abord le model principal, ou le model parent.

Pour avoir une bonne idée de la façon de faire, imaginons que nous ayons une action dans notre UsersController qui gère la sauvegarde d’un nouveau User et son Profile lié. L’action montrée en exemple ci-dessous supposera que vous avez POSTé assez de données (en utilisant FormHelper) pour créer un User unique et un Profile unique:

public function add() {
    if (!empty($this->request->data)) {
        // Nous pouvons sauvegarder les données de l'User:
        // it should be in $this->request->data['User']

        $user = $this->User->save($this->request->data);

        // Si l\'user a été sauvegardé, maintenant nous ajoutons cette information aux données
        // et sauvegardons le Profile.

        if (!empty($user)) {
            // L'ID de l\'user nouvellement crée a été défini
            // dans $this->User->id.
            $this->request->data['Profile']['user_id'] = $this->User->id;

            // Parce que notre User hasOne Profile, nous pouvons accéder
            // au model Profile à travers le model User:
            $this->User->Profile->save($this->request->data);
        }
    }
}

Comme règle, quand vous travaillez avec des associations hasOne, hasMany, et belongsTo, tout est question de clé. L’idée de base est de récupérer la clé d’un autre model et de la placer dans le champ clé étrangère sur l’autre. Parfois, cela pourra gêner l’utilisation de l’attribut $id de la classe model après un save(), mais d’autres fois, cela impliquera juste la collecte de l’ID provenant d’un champ caché d’un formulaire qui vient d’être POSTé d’une action d’un controller.

Pour compléter l’approche fondamentale utilisée ci-dessus, CakePHP offre également une méthode très pratique saveAssociated(), qui vous permet de valider et de sauvegarder de multiples models en une fois. De plus, saveAssociated() fournit un support transactionnel pour s’assurer de l’intégrité des données dans votre base de données (par ex: si un model échoue dans la sauvegarde, les autres models ne seront également pas sauvegardés).

Note

Pour que les transactions fonctionnent correctement dans MySQL, vos tables doivent utiliser le moteur InnoDB. Souvenez-vous que les tables MyISAM ne supportent pas les transactions.

Voyons comment nous pouvons utiliser saveAssociated() pour sauvegarder les models Company et Account en même temps.

Tout d’abord, vous avez besoin de construire votre formulaire pour les deux models Company et Account (nous supposerons que Company hasMany Account):

echo $this->Form->create('Company', array('action' => 'add'));
echo $this->Form->input('Company.name', array('label' => 'Company name'));
echo $this->Form->input('Company.description');
echo $this->Form->input('Company.location');

echo $this->Form->input('Account.0.name', array('label' => 'Account name'));
echo $this->Form->input('Account.0.username');
echo $this->Form->input('Account.0.email');

echo $this->Form->end('Add');

Regardez comment nous avons nommé les champs de formulaire pour le model Account. Si Company est notre model principal, saveAssociated() va s’attendre à ce que les données du model lié (Account) arrivent dans un format spécifique. Et avoir Account.0.fieldName est exactement ce dont nous avons besoin.

Note

Le champ ci-dessus est nécessaire pour une association hasMany. Si l’association entre les models est hasOne, vous devrez utiliser la notation ModelName.fieldName pour le model associé.

Maintenant, dans notre CompaniesController nous pouvons créer une action add():

public function add() {
    if (!empty($this->request->data)) {
        // Utilisez ce qui suit pour éviter les erreurs de validation:
        unset($this->Company->Account->validate['company_id']);
        $this->Company->saveAssociated($this->request->data);
    }
}

C’est tout pour le moment. Maintenant nos models Company et Account seront validés et sauvegardés en même temps. Par défaut saveAssociated validera toutes les valeurs passées et ensuite essaiera d’effectuer une sauvegarde pour chacun.

Sauvegarder hasMany through data

Regardons comment les données stockées dans une table jointe pour deux models sont sauvegardées. Comme montré dans la section hasMany through (Le Model Join), la table jointe est associée pour chaque model en utilisant un type de relation hasMany. Notre exemple est une problématique lancée par la Tête de l’Ecole CakePHP qui nous demande d’écrire une application qui lui permette de connecter la présence d’un étudiant à un cours avec les journées assistées et validées. Jettez un œil au code suivant.

// Controller/CourseMembershipController.php
class CourseMembershipsController extends AppController {
    public $uses = array('CourseMembership');

    public function index() {
        $this->set(
             'courseMembershipsList',
             $this->CourseMembership->find('all')
         );
    }

    public function add() {
        if ($this->request->is('post')) {
            if ($this->CourseMembership->saveAssociated($this->request->data)) {
                return $this->redirect(array('action' => 'index'));
            }
        }
    }
}

// View/CourseMemberships/add.ctp

<?php echo $this->Form->create('CourseMembership'); ?>
    <?php echo $this->Form->input('Student.first_name'); ?>
    <?php echo $this->Form->input('Student.last_name'); ?>
    <?php echo $this->Form->input('Course.name'); ?>
    <?php echo $this->Form->input('CourseMembership.days_attended'); ?>
    <?php echo $this->Form->input('CourseMembership.grade'); ?>
    <button type="submit">Save</button>
<?php echo  $this->Form->end(); ?>

Le tableau de données ressemblera à ceci quand il sera soumis.

Array
(
    [Student] => Array
    (
        [first_name] => Joe
        [last_name] => Bloggs
    )

    [Course] => Array
    (
        [name] => Cake
    )

    [CourseMembership] => Array
    (
        [days_attended] => 5
        [grade] => A
    )

)

CakePHP va heureusement être capable de sauvegarder le lot ensemble et d’assigner les clés étrangères de Student et de Course dans CourseMembership avec un appel saveAssociated avec cette structure de données. Si nous lançons l’action index de notre CourseMembershipsController, la structure de données reçue maintenant par un find(“all”) est:

Array
(
    [0] => Array
    (
        [CourseMembership] => Array
        (
            [id] => 1
            [student_id] => 1
            [course_id] => 1
            [days_attended] => 5
            [grade] => A
        )

        [Student] => Array
        (
            [id] => 1
            [first_name] => Joe
            [last_name] => Bloggs
        )

        [Course] => Array
        (
            [id] => 1
            [name] => Cake
        )
    )
)

Il y a bien sûr beaucoup de façons de travailler avec un model joint. La version ci-dessus suppose que vous voulez sauvegarder tout en une fois. Il y aura des cas où vous voudrez créer les Student et Course indépendamment et associer les deux ensemble avec CourseMemberShip plus tard. Donc, vous aurez peut-être un formulaire qui permet la sélection de students et de courses existants à partir d’une liste de choix ou d’une entrée d’un ID et ensuite les deux meta-champs pour CourseMembership, par ex.

// View/CourseMemberships/add.ctp

<?php echo $this->Form->create('CourseMembership'); ?>
    <?php
        echo $this->Form->input(
            'Student.id',
            array(
                'type' => 'text',
                'label' => 'Student ID',
                'default' => 1
            )
        );
    ?>
    <?php
        echo $this->Form->input(
            'Course.id',
            array(
                'type' => 'text',
                'label' => 'Course ID',
                'default' => 1
            )
        );
    ?>
    <?php echo $this->Form->input('CourseMembership.days_attended'); ?>
    <?php echo $this->Form->input('CourseMembership.grade'); ?>
    <button type="submit">Save</button>
<?php echo $this->Form->end(); ?>

Et le POST résultant:

Array
(
    [Student] => Array
    (
        [id] => 1
    )

    [Course] => Array
    (
        [id] => 1
    )

    [CourseMembership] => Array
    (
        [days_attended] => 10
        [grade] => 5
    )
)

Encore une fois, CakePHP est bon pour nous et envoie les id de Student et de Course dans CourseMembership avec saveAssociated.

Sauvegarder les Données de Model Lié (HABTM=HasAndBelongsToMany)

Sauvegarder les models qui sont associés avec hasOne, belongsTo, et hasMany est assez simple: vous venez de remplir le champ de la clé étrangère avec l’ID du model associé. Une fois que c’est fait, vous appelez juste la méthode save() sur un model, et tout se relie correctement. Un exemple du format requis pour le tableau de données passé à save() pour le model Tag model est montré ci-dessous:

Array
(
    [Recipe] => Array
        (
            [id] => 42
        )
    [Tag] => Array
        (
            [name] => Italian
        )
)

Vous pouvez aussi utiliser ce format pour sauvegarder plusieurs enregistrements et leurs associations HABTM avec saveAll(), en utilisant un tableau comme celui qui suit:

Array
(
    [0] => Array
        (
            [Recipe] => Array
                (
                    [id] => 42
                )
            [Tag] => Array
                (
                    [name] => Italian
                )
        )
    [1] => Array
        (
            [Recipe] => Array
                (
                    [id] => 43
                )
            [Tag] => Array
                (
                    [name] => Pasta
                )
        )
    [2] => Array
        (
            [Recipe] => Array
                (
                    [id] => 51
                )
            [Tag] => Array
                (
                    [name] => Mexican
                )
        )
    [3] => Array
        (
            [Recipe] => Array
                (
                    [id] => 17
                )
            [Tag] => Array
                (
                    [name] => American (new)
                )
        )
)

Passer le tableau ci-dessus à saveAll() va créer les tags contenus, chacun associé avec leur recipies respectives.

Un autre exemple utile est lorsque quand vous souhaitez sauver de nombreusex Tags dans un Post. Vous devez transmettre les données HABTM associeés dans le format de tableau HABTM suivant. Notez que vous devez passer uniquement l’id du modèle HABTM associé mais il doit être imbriquées à nouveau:

Array
(
    [0] => Array
        (
            [Post] => Array
                (
                    [title] => 'Saving HABTM arrays'
                )
            [Tag] => Array
                (
                    [Tag] => Array(1, 2, 5, 9)
                )
        )
    [1] => Array
        (
            [Post] => Array
                (
                    [title] => 'Dr Who's Name is Revealed'
                )
            [Tag] => Array
                (
                    [Tag] => Array(7, 9, 15, 19)
                )
        )
    [2] => Array
        (
            [Post] => Array
                (
                    [title] => 'I Came, I Saw and I Conquered'
                )
            [Tag] => Array
                (
                    [Tag] => Array(11, 12, 15, 19)
                )
        )
    [3] => Array
        (
            [Post] => Array
                (
                    [title] => 'Simplicity is the Ultimate Sophistication'
                )
            [Tag] => Array
                (
                    [Tag] => Array(12, 22, 25, 29)
                )
        )
)

Passer le tableau ci-dessus à la fonction saveAll($data, array('deep' => true)) remplira la table jointe posts_tags avec l’association Tag vers Post.

Par exemple, nous allons construire un formulaire qui crée un nouveau tag et génèrerons le tableau de données approprié pour l’associer à la volée avec certaines recipies.

Le formulaire le plus simple ressemblerait à ceci (nous supposerons que $recipe_id est déjà définie à une valeur):

<?php echo $this->Form->create('Tag');?>
    <?php echo $this->Form->input(
        'Recipe.id',
        array('type' => 'hidden', 'value' => $recipe_id)); ?>
    <?php echo $this->Form->input('Tag.name'); ?>
<?php echo $this->Form->end('Add Tag'); ?>

Dans cet exemple, vous pouvez voir le champ caché Recipe.id dont la valeur est définie selon l’ID de la recette que nous voulons lier au tag.

Quand la méthode save() est appelée dans le controller, elle va automatiquement sauvegarder les données HABTM dans la base de données:

public function add() {
    // Sauvegarder l'association
    if ($this->Tag->save($this->request->data)) {
        // faire quelque chose en cas de succès
    }
}

Avec le code précédent, notre Tag nouveau est crée et associé avec un Recipe, dont l’ID a été défini dans $this->request->data['Recipe']['id'].

Les autres façons possibles pour présenter nos données associées peuvent inclure une liste déroulante. Les données peuvent être envoyées d’un model en utilisant la méthode find('list') et assignées à une variable de vue du nom du model. Une entrée avec le même nom va automatiquement envoyer ces données dans un <select>:

// dans le controller:
$this->set('tags', $this->Recipe->Tag->find('list'));

// dans la vue:
$form->input('tags');

Un scénario plus probable avec une relation HABTM incluerait un <select> défini pour permettre des sélections multiples. Par exemple, un Recipe peut avoir plusieurs Tags lui étant assignés. Dans ce cas, les données du model sont triées de la même façon, mais l’entrée du formulaire est déclarée légèrement différemment. Le nom du Tag est défini en utilisant la convention ModelName:

// dans le controller:
$this->set('tags', $this->Recipe->Tag->find('list'));

// dans la vue:
$this->Form->input('Tag');

En utilisant le code précédent, un liste déroulante est créée, permettant aux multiples choix d’être automatiquement sauvegardés au Recipe existant en étant ajouté à la base de données.

Self HABTM

Normalement HABTM est utilisé pour lier 2 models ensemble mais il peut aussi être utilisé avec seulement 1 model, mais il nécéssite une attention plus grande encore.

La clé est dans la configuration du model className. En ajoutant simplement une relation Project HABTM Project entraine des problèmes lors des enregistrements de données. En configurant le className au nom de models et en utilisant l’alias en clé, nous évitons ces problèmes.

class Project extends AppModel {
    public $hasAndBelongsToMany = array(
        'RelatedProject' => array(
            'className'              => 'Project',
            'foreignKey'             => 'projects_a_id',
            'associationForeignKey'  => 'projects_b_id',
        ),
    );
}

Créer des éléments de form et sauvegarder les données fonctionne de la même façon qu’avant mais vous utilisez l’alias à la place. Ceci:

$this->set('projects', $this->Project->find('list'));
$this->Form->input('Project');

Devient ceci:

$this->set('relatedProjects', $this->Project->find('list'));
$this->Form->input('RelatedProject');

Que faire quand HABTM devient compliqué?

Par défaut, quand vous sauvegardez une relation HasAndBelongsToMany, CakePHP supprime toutes les lignes de la table jointe avant d’en sauvegarder de nouvelles. Par exemple, si vous avez un Club qui a 10 Children (Enfant) associés. Vous mettez ensuite à jour le Club avec 2 Children. Le Club aura seulement 2 Children, et pas 12.

Notez aussi que si vous voulez ajouter plus de champs à joindre (quand il a été crée ou les meta informations), c’est possible avec les tables jointes HABTM, mais il est important de comprendre que vous avez une option facile.

HasAndBelongsToMany entre deux models est en réalité un raccourci pour trois models associés à travers les deux associations hasMany et belongsTo.

Etudiez cet exemple:

Child hasAndBelongsToMany Club

Une autre façon de regarder cela est d’ajouter un model Membership:

Child hasMany Membership
Membership belongsTo Child, Club
Club hasMany Membership.

Ces deux exemples sont presque les mêmes. Ils utilisent le même nombre de champs nommés dans la base de données et le même nombre de models. Les différences importantes sont que le model « join » est nommé différemment et que son comportement est plus prévisible.

Astuce

Quand votre table jointe contient des champs supplémentaires en plus des deux clés étrangères, vous pouvez éviter de perdre les valeurs des champs supplémentaires en définissant la clé 'unique' du tableau à 'keepExisting'. Vous pouvez le penser comme quelque chose de similaire à “unique” => true, mais sans perdre les données des champs supplémentaires pendant l’opération de sauvegarde. Regardez: les tablaux des associations HABTM.

Cependant, dans la plupart des cas, il est plus facile de faire un model pour la table jointe et de configurer les associations hasMany, belongsTo comme montré dans l’exemple ci-dessus au lieu d’utiliser une association HABTM.

Datatables

Tandis que CakePHP peut avoir des sources de données qui ne sont pas des driven de base de données, la plupart du temps, elles le sont. CakePHP est pensé pour être agnostique et va fonctionner avec MySQL, Microsoft SQL Server, PostgreSQL et autres. Vous pouvez créer vos tables de base de données comme vous l’auriez fait normalement. Quand vous créez vos classes Model, elles seront automatiquement liées aux tables que vous avez créées. Les noms de table sont par convention en minuscules et au pluriel avec tous les mots de la table séparés par des underscores. Par exemple, un nom de model Ingredient s’attendra à un nom de table ingredients. Un nom de Model de EventRegistration s’attendra à un nom de table de event_registrations. CakePHP va inspecter vos tables pour déterminer le type de données de chaque champ et utiliser cette information pour automatiser plusieurs fonctionnalités comme l’affichage des champs de formulaires dans la vue. Les noms de champ sont par convention en minuscules et séparés par des underscores.

Utiliser created et modified

En définissant un champ created ou modified dans votre table de base de données en type datetime (par défaut à null), CakePHP va reconnaître ces champs et les remplir automatiquement dès qu’un enregistrement est crée ou sauvegardé dans la base de données (à moins que les données déjà sauvegardées contiennent une valeur pour ces champs).

Les champs created et modified vont être définis à la date et heure courante quand l’enregistrement est ajouté pour la première fois. Le champ modifié sera mis à jour avec la date et l’heure courante dès que l’enregistrement sera sauvegardé.

Si vous avez created ou modified des données dans votre $this->data (par ex à partir d’un Model::read ou d’un Model::set) avant un Model::save(), alors les valeurs seront prises à partir de $this->data et ne seront pas mises à jour automagiquement. Si vous ne souhaitez pas cela, vous pouvez utiliser unset($this->data['Model']['modified']), etc… Alternativement vous pouvez surcharger Model::save() pour toujours le faire pour vous:

class AppModel extends Model {

    public function save($data = null, $validate = true, $fieldList = array()) {
        // Nettoie la valeur du champ modified avant chaque sauvegarde
        $this->set($data);
        if (isset($this->data[$this->alias]['modified'])) {
            unset($this->data[$this->alias]['modified']);
        }
        return parent::save($this->data, $validate, $fieldList);
    }

}

Si vous sauvegardez des données avec un fieldList et que les champs created et modified ne sont pas présents dans la liste blanche, les valeurs pour ces champs vont continuer à être automatiquement remplies. Si les champs created et modified sont dans fieldList, ils fonctionneront comme n’importe quels autres champs.