web-dev-qa-db-fra.com

Laravel: Retourner le propriétaire de l'espace de noms d'une relation polymorphe

Je peux trouver un certain nombre de discussions à ce sujet mais pas de solution claire. Voici deux liens, bien que je couvrirai tout dans ma propre question ici.

Problèmes Github

discussion Laravel.io

Explication simple

Ceci est une explication simple de mon problème pour quiconque connaît déjà les relations polymorphes de Laravel.

Lorsque vous utilisez $ morphClass, le contenu de $ morphClass qui est enregistré dans la base de données en tant que morph "type" est utilisé pour le nom de classe lorsque vous essayez de trouver le propriétaire d'une relation polymorphe. Il en résulte une erreur car le point entier de $ morphClass est qu'il ne s'agit pas d'un nom à espace de noms complet de la classe.

Comment définissez-vous le nom de classe que la relation polymorphe doit utiliser?

Explication plus détaillée

Ceci est une explication plus détaillée expliquant exactement ce que j'essaie de faire et pourquoi j'essaye de le faire avec des exemples.

Lorsque vous utilisez des relations polymorphes dans Laravel tout ce qui est enregistré en tant que type "morph_type" dans la base de données est supposé être la classe.

Donc, dans cet exemple:

class Photo extends Eloquent {

    public function imageable()
    {
        return $this->morphTo();
    }

}

class Staff extends Eloquent {

    public function photos()
    {
        return $this->morphOne('Photo', 'imageable');
    }

}

class Order extends Eloquent {

    public function photos()
    {
        return $this->morphOne('Photo', 'imageable');
    }

}

La base de données ressemblerait à ceci:

staff

 - id - integer
 - name - string

orders

 - id - integer
 - price - integer

photos

 - id - integer
 - path - string
 - imageable_id - integer
 - imageable_type - string

Maintenant, la première rangée de photos pourrait ressembler à ceci:

id, chemin, imageable_id, imageable_type

1, image.png, 1, Personnel

Maintenant, je peux accéder à la photo à partir d'un modèle personnel ou à un membre du personnel à partir d'un modèle photo.

//Find a staff member and dump their photos
$staff = Staff::find(1);

var_dump($staff->photos);

//Find a photo and dump the related staff member
$photo = Photo::find(1);

var_dump($photo->imageable);

Jusqu'ici tout va bien. Cependant, lorsque je les nomme, je rencontre un problème.

namespace App/Store;
class Order {}

namespace App/Users;
class Staff {}

namespace App/Photos;
class Photo {}

Maintenant, ce qui est enregistré dans ma base de données est le suivant:

id, chemin, imageable_id, imageable_type

1, image.png, 1, App/Utilisateurs/Personnel

Mais je ne veux pas ça. C'est une idée terrible d'avoir des noms de classe d'espaces de noms complets enregistrés dans la base de données comme ça!

Heureusement Laravel a une option pour définir une variable $ morphClass. Comme ceci:

class Staff extends Eloquent {

    protected $morphClass = 'staff';

    public function photos()
    {
        return $this->morphOne('Photo', 'imageable');
    }

}

Maintenant, la ligne de ma base de données ressemblera à ceci, ce qui est génial!

id, chemin, imageable_id, imageable_type

1, image.png, 1, personnel

Et obtenir les photos d'un membre du personnel fonctionne parfaitement.

//Find a staff member and dump their photos
$staff = Staff::find(1);

//This works!
var_dump($staff->photos);

Cependant, la magie polymorphe de trouver le propriétaire d'une photo ne fonctionne pas:

//Find a photo and dump the related staff member
$photo = Photo::find(1);

//This doesn't work!
var_dump($photo->imageable);

//Error: Class 'staff' not found

Vraisemblablement, il doit y avoir un moyen d'informer la relation polymorphe du nom de classe à utiliser lors de l'utilisation de $ morphClass, mais je ne trouve aucune référence à la façon dont cela devrait fonctionner dans les documents, dans le code source ou via Google.

De l'aide?

34
John Mellor

Il y a 2 façons simples - l'une ci-dessous, l'autre dans la réponse de @ lukasgeiter telle que proposée par Taylor Otwell, que je suggère certainement de vérifier également:

// app/config/app.php or anywhere you like
'aliases' => [
    ...
    'MorphOrder' => 'Some\Namespace\Order',
    'MorphStaff' => 'Maybe\Another\Namespace\Staff',
    ...
]

// Staff model
protected $morphClass = 'MorphStaff';

// Order model
protected $morphClass = 'MorphOrder';

terminé:

$photo = Photo::find(5);
$photo->imageable_type; // MorphOrder
$photo->imageable; // Some\Namespace\Order

$anotherPhoto = Photo::find(10);
$anotherPhoto->imageable_type; // MorphStaff
$anotherPhoto->imageable; // Maybe\Another\Namespace\Staff

Je n'utiliserais pas de vrais noms de classe (Order et Staff) pour éviter d'éventuels doublons. Il y a très peu de chances que quelque chose soit appelé MorphXxxx donc c'est assez sûr.

C'est mieux que de stocker des espaces de noms (Cela ne me dérange pas l'apparence dans la base de données, mais ce serait gênant si vous changez quelque chose - dites au lieu de App\Models\User utilisation Cartalyst\Sentinel\User etc) parce que tout ce dont vous avez besoin est d'échanger l'implémentation via la configuration des alias.

Cependant, il y a aussi un inconvénient - vous ne saurez pas quel est le modèle, en vérifiant simplement la base de données - au cas où cela vous importerait.

45
Jarek Tkaczyk

J'aime la solution @JarekTkaczyks et je vous suggère de l'utiliser. Mais, par souci d'exhaustivité, il y a une autre façon dont Taylor mentionne brièvement sur github

Vous pouvez ajouter un accesseur d'attribut pour le imageable_type attribut, puis utilisez un tableau "classmap" pour rechercher la bonne classe.

class Photo extends Eloquent {

    protected $types = [
        'order' => 'App\Store\Order',
        'staff' => 'App\Users\Staff'
    ];

    public function imageable()
    {
        return $this->morphTo();
    }

    public function getImageableTypeAttribute($type) {
        // transform to lower case
        $type = strtolower($type);

        // to make sure this returns value from the array
        return array_get($this->types, $type, $type);

        // which is always safe, because new 'class'
        // will work just the same as new 'Class'
    }

}

Notez que vous aurez toujours besoin de l'attribut morphClass pour interroger de l'autre côté de la relation.

22
lukasgeiter

Lorsque vous utilisez Laravel 5.2 (ou plus récent), vous pouvez utiliser la nouvelle fonctionnalité morphMap pour résoudre ce problème. Ajoutez simplement cela à la fonction boot dans app/Providers/AppServiceProvider:

Relation::morphMap([
    'post' => \App\Models\Post::class,
    'video' => \App\Models\Video::class,
]);

En savoir plus à ce sujet: https://nicolaswidart.com/blog/laravel-52-morph-map

19
dersvenhesse

Laissez laravel le mettre dans l'espace de noms db et tout. Si vous avez besoin du nom de classe court pour autre chose que pour rendre votre base de données plus jolie, définissez un accesseur pour quelque chose comme:

<?php namespace App\Users;

class Staff extends Eloquent {

    // you may or may not want this attribute added to all model instances
    // protected $appends = ['morph_object'];

    public function photos()
    {
        return $this->morphOne('App\Photos\Photo', 'imageable');
    }

    public function getMorphObjectAttribute()
    {
        return (new \ReflectionClass($this))->getShortName();
    }

}

Le raisonnement auquel je reviens toujours dans des scénarios comme celui-ci est que Laravel est assez bien testé et fonctionne comme prévu pour la plupart. Pourquoi lutter contre le cadre si vous n'êtes pas obligé - en particulier si vous êtes simplement ennuyé par l'espace de noms étant dans la base de données. Je suis d'accord que ce n'est pas une bonne idée de le faire, mais je pense également que je peux passer mon temps plus utilement à le surmonter et à travailler sur le code de domaine.

3
Abba Bryant

Pour un chargement rapide des relations polymorphes, par exemple Photo::with('imageable')->get();, il est nécessaire de retourner null si le type est vide.

class Photo extends Eloquent {

    protected $types = [
        'order' => 'App\Store\Order',
        'staff' => 'App\Users\Staff'
    ];

    public function imageable()
    {
        return $this->morphTo();
    }

    public function getImageableTypeAttribute($type) {
        // Illuminate/Database/Eloquent/Model::morphTo checks for null in order
        // to handle eager-loading relationships
        if(!$type) {
            return null;
        }

        // transform to lower case
        $type = strtolower($type);

        // to make sure this returns value from the array
        return array_get($this->types, $type, $type);

        // which is always safe, because new 'class'
        // will work just the same as new 'Class'
    }

}
2
DTownsend