Bonsoir à tous,

Etant en pleine découverte (et migration) de la version 3 de CakePHP, je rencontre un problème qui n'en était pas un dans la version 2. Il est cependant pas impossible que je m'y prenne mal ou que je loupe une manière intelligente de contourner le problème.

En gros, je rencontre une collision entre le nom <u>d'un champ</u> d'un modèle/table qui est identique au nom d'un modèle/table associé lorsque celui-ci est mis au singulier lors de sa transformation en entité. Je m'explique :

Prenons l'exemple des modèles suivants (sans trop tenir compte des conventions cake et surtout du sens qu'ils peuvent avoir) :

Modèle: "articles"
Champs: "id", "titre", "contenu", "lamba"

et

Modèle : "lambdas"
Champs: "champ_a", "champ_b"

Si les deux champs sont associés et que nous effectuons un find, dans la version 2, nous pouvions accéder sans aucun problème à :

$resultat'article']'lambda']
et
$resultat'lambda']'champ_a']

Voulant tenter la même chose avec la version 3, j'ai naïvement essayé :

$resultat->lambda (du modèle articles)
et
$resultat->lambda->champ_a (du modèle lambdas)

Sauf que comme on peut le voir, la mise au singulier du modèle "lambdas" lors de son passage en entité rentre en collision avec le champs "lambda" de l'entité article. Du coup, si on fait un debug de l'objet $resultat, on peut se rendre compte qu'il ne contient que $resultat->lambda->champ_a et pas de $resultat->lambda. Ce qui est assez logique vu la collision. Je soupçonne en fait un écrasement de la valeur.

Alors bien évidemment, je pourrais renommer quelque chose pour éviter le problème, mais je me demandais si je m'y prenais mal où s'il y avait une solution pour contourner ça.

Merci!

11 réponses


root0x90
Auteur
Réponse acceptée

Hello,

Pas de souci, merci pour la réponse. J'ai effectivement contourné le problème en renommant le champ. Je vais tenter d'ouvrir un ticket sur Github à titre d'information, on verra bien si la team considère que ça vaut la peine d'être corrigé ou pas.

Merci!

root0x90
Auteur
Réponse acceptée

Pour revenir clore le sujet, après avoir ouvert un ticket sur Github, je confirme que la réponse est soit qu'il faut renommer le champ du modèle courant, soit utiliser des alias :

https://github.com/cakephp/cakephp/issues/4872

Par contre, il ne semble pas que ça aille été considéré comme un "bug" à proprement parlé.

Hello,
Comment sont faites les associations et avec quel foreignKey ?

root0x90
Auteur

Hello,

L'exemple de mon post initial étant un exemple un peu restreint, voici un cas plus concret qui me pose problème :

Modèle: "articles"
Champs: "id", "lambda_id", "champ1", "champ2"
PrimaryKey: "id"
ForeignKey: "lambda_id" -> pointant sur le champ "id" du modèle "lambdas"

Modèle: "lambdas"
Champs: "id", "champ3"
PrimaryKey: "id"

Quant aux associations :

Modèle "articles":

$this->belongsTo('Lambdas');

et modèle "lambdas":
Au début, aucune association, mais ta question m'a mis un doute, donc je viens de refaire le test avec (je confirme que le résultat est le même):

$this->hasMany('Articles');

et en gros, j'obtiens toujours :

$resultat->lambda->champ_3

--> ok! existe dans l'objet résultat.

$resultat->lambda

--> n'existe pas.

Merci!

Je comprend pas tout la.
Le

$resultat->lambda

est censé retourné quoi ?
Sa serais mieux si tu mettrais le code entier de la requête.

C'est pas clair ton truc, partage ton code car il n'y a aucune raison que ça ne fonctionne pas

root0x90
Auteur

Hello,

Merci pour vos réponses.

Lorsque je parle de

$resultat->lambda

il s'agit du champ nommé "lambda" du modèle "articles" (que j'ai oublié dans mon précédent post, mille excuses...).

Mais à la demande d'antograssiot, voici le code d'un jeu de test complet basé sur deux modèles (articles et auteurs), ça devrait être plus clair:

la création de la base:

CREATE DATABASE IF NOT EXISTS `collisiondb` /*!40100 DEFAULT CHARACTER SET utf8 */;
USE `collisiondb`;
CREATE TABLE IF NOT EXISTS `articles` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `author_id` int(10) unsigned NOT NULL DEFAULT '0',
  `title` varchar(50) NOT NULL DEFAULT '0',
  `author` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `FK_articles_authors` (`author_id`),
  CONSTRAINT `FK_articles_authors` FOREIGN KEY (`author_id`) REFERENCES `authors` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO `articles` (`id`, `author_id`, `title`, `author`) VALUES
    (1, 2, 'les fleurs du mal', 'une note sur l\'auteur');
CREATE TABLE IF NOT EXISTS `authors` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(50) DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
INSERT INTO `authors` (`id`, `name`) VALUES
    (1, 'jean'),
    (2, 'paul'),
    (3, 'pierre');

Le modèle Articles :

<?php
namespace App\Model\Table;
use Cake\ORM\Table;
class ArticlesTable extends Table {

    public function initialize(array $config) {
        $this->belongsTo('Authors');

    }
}

Le modèle Authors :

<?php
namespace App\Model\Table;
use Cake\ORM\Table;
class AuthorsTable extends Table {

    public function initialize(array $config) {

    }
}

Le controller :

<?php
namespace App\Controller;
class TestsController extends AppController {
    public function index() {

        $this->loadModel('Articles');
        $this->loadModel('Authors');
        $articles = $this->Articles
                ->find()->contain('Authors'])
                ->select('Articles.id', 'Articles.title', 'Articles.author', 'Authors.name']);
        $this->set('articles', $articles->all());
    }
}

La vue (avec un petit debug) :

<?php foreach ($articles as $article): ?>
    <?php
    debug($article, $showHtml = null, $showFrom = true);
    die();
    ?>
<?php endforeach; ?>
<?php unset($article); ?>

et pour finir, le résultat produit:

object(Cake\ORM\Entity) {
    'new' => false,
    'accessible' => 
        '*' => true
    ],
    'properties' => 
        'id' => (int) 1,
        'title' => 'les fleurs du mal',
        'author' => object(Cake\ORM\Entity) {
            'new' => false,
            'accessible' => 
                '*' => true
            ],
            'properties' => 
                'name' => 'paul'
            ],
            'dirty' => ],
            'original' => ],
            'virtual' => ],
            'errors' => ],
            'repository' => 'Authors'

        }
    ],
    'dirty' => ],
    'original' => ],
    'virtual' => ],
    'errors' => ],
    'repository' => 'Articles'
}

Comme on peut le voir, il y a bien une entrée 'author' mais qui est en fait une entité (logique, il s'agit d'une entité du modèle authors associé) mais l'on devrait également retrouver une entrée 'author' étant le champs du modèle articles. Mais comme ces deux entrées ont le même nom,à mon sens, il y a collision. J'espère qu'avec cet exemple, c'est un peu plus clair.

Merci pour votre aide!

Voila, la ca va mieux pour te répondre. ^^

Alors oui effectivement tu as raison, je viens de reproduire le bug.
J'attend de voir se que va dire @ antograssiot (Cake Team Member), mais je pense qu'il va prendre en charge le soucis et poster une issue sur le GitHub de CakePHP.

En attendant le fix, je te conseil juste de renommé le champ "author" dans la table "articles".

Ok j'ai compris.
Je peux pas tester aujourd'hui mais effectivement renommer le champs "author" paraît le plus simple.

Si c'est impossible, je crois que tu peux créer un alias pour ta table authors dans la fonction initialize et même définir une class entity différente :

TableRegistry::config('Writers', 'table' => 'authors', 'entityClass' => 'App\Writer']);

.
C'est juste une poste je ne l'ai personnellement jamais utilisé encore car c'est dommage de se passer de ce qui est automatique :p
voir la doc

J'essairai de te faire un retour avant le weekend si possible

root0x90
Auteur

Hello,

Merci à vous deux pour votre réactivité. ça me rassure que vous arriviez à reproduire le cas, au moins ça veut dire que je n'ai pas oublié une instruction qui changerait la donne ou simplement pas compris un concept de base... ;-)

C'est vrai que le plus simple est de renommer le champ. Car le simple fait de se retrouver dans ce cas de figure est à la limite de l'erreur de conception du design de la DB, voir même presque un souci de normalisation.

Par contre, ce qui est dommage, c'est qu'il était possible de faire ça en Cake 2 avec

$variable'Model']'champ'] que ce soit pour le modèle courant ou le/les modèles associés. Par contre en cake 3, même si ça permet de moins écrire de code, le fait d'accéder directement au champs du modèle courant par $variable->champ pose problème. Une idée serait peut-être de reproduire le comportement de cake 2 dans cake 3 avec quelque chose du style :

$variable->modèle_courant->champ
et
$variable->modèle_associé->champ

ça ferait un raccourci en moins, mais ça éviterait les collisions. A moins qu'il y ait d'autres implications qui m'échappent et qui rendent ce raisonnement impossible.

Quant à l'alias, l'idée m'avait effleuré l'esprit, mais j'ai pas encore essayé. Et effectivement, ça me semble cohérent que ça doit rester une mesure de la dernière chance... ;-)

Merci!

Salut désolé je n'ai pas pris le temps d'y regarder. Comme tu le précise c'est à la limite de l'erreur de conception de la BDD.
N'hésite pas à ouvrir un ticket en y incluant le dump SQL si tu penses que c'est important.
Il est probable que la réponse soit également "renomme ton champs" mais l'argument de dire que ça marchait sous CakePHP 2.x et que cela rend un peu plus diifficile la migration aura peut-être du sens.