181 votes

Laravel orderBy sur une relation

Je passe en revue tous les commentaires postés par l'auteur d'un article particulier.

foreach($post->user->comments as $comment)
{
    echo "<li>" . $comment->title . " (" . $comment->post->id . ")</li>";
}

Cela me donne

I love this post (3)
This is a comment (5)
This is the second Comment (3)

Comment puis-je ordonner par le post_id pour que la liste ci-dessus soit ordonnée comme 3,3,5.

358voto

Rob Gordijn Points 6051

Il est possible d'étendre la relation avec des fonctions d'interrogation :

<?php
public function comments()
{
    return $this->hasMany('Comment')->orderBy('column');
}

[modifier après le commentaire]

<?php
class User
{
    public function comments()
    {
        return $this->hasMany('Comment');
    }
}

class Controller
{
    public function index()
    {
        $column = Input::get('orderBy', 'defaultColumn');
        $comments = User::find(1)->comments()->orderBy($column)->get();

        // use $comments in the template
    }
}

Modèle utilisateur par défaut + exemple de contrôleur simple ; lors de la récupération de la liste des commentaires, il suffit d'appliquer la fonction orderBy() basée sur Input::get(). (assurez-vous de faire un contrôle de l'entrée ;) )

36voto

agm1984 Points 3539

Je crois que vous pouvez aussi le faire :

$sortDirection = 'desc';

$user->with(['comments' => function ($query) use ($sortDirection) {
    $query->orderBy('column', $sortDirection);
}]);

Cela vous permet d'exécuter une logique arbitraire sur chaque enregistrement de commentaire lié. Vous pourriez avoir des choses là-dedans comme :

$query->where('timestamp', '<', $someTime)->orderBy('timestamp', $sortDirection);

18voto

Harry Bosh Points 1673

Utilisation de sortBy... pourrait aider.

$users = User::all()->with('rated')->get()->sortByDesc('rated.rating');

11voto

PHP Worm... Points 3258

Essayez cette solution.

$mainModelData = mainModel::where('column', $value)
    ->join('relationModal', 'main_table_name.relation_table_column', '=', 'relation_table.id')
    ->orderBy('relation_table.title', 'ASC')
    ->with(['relationModal' => function ($q) {
        $q->where('column', 'value');
    }])->get();

Exemple :

$user = User::where('city', 'kullu')
    ->join('salaries', 'users.id', '=', 'salaries.user_id')
    ->orderBy('salaries.amount', 'ASC')
    ->with(['salaries' => function ($q) {
        $q->where('amount', '>', '500000');
    }])->get();

Vous pouvez modifier le nom de la colonne dans join() selon la structure de votre base de données.

1voto

Sebastiaan Points 1

J'ai créé un trait pour commander sur un champ de relation. J'ai eu ce problème avec des commandes de webshop qui ont une relation de statut, et le statut a un champ de nom.

Exemple de la situation

Il n'est pas possible de passer commande avec des "jointures" de modèles éloquents, car ce ne sont pas des jointures. Ce sont des requêtes qui sont exécutées après que la première requête soit terminée. J'ai donc fait un petit hack pour lire les données de la relation éloquente (comme la table, les clés de jonction et les wheres supplémentaires s'ils sont inclus) et les joindre à la requête principale. Cela ne fonctionne qu'avec des relations un à un.

La première étape consiste à créer un trait et à l'utiliser sur un modèle. Dans ce trait, vous avez 2 fonctions. La première :

/**
 * @param string $relation - The relation to create the query for
 * @param string|null $overwrite_table - In case if you want to overwrite the table (join as)
 * @return Builder
 */
public static function RelationToJoin(string $relation, $overwrite_table = false) {
    $instance = (new self());
    if(!method_exists($instance, $relation))
        throw new \Error('Method ' . $relation . ' does not exists on class ' . self::class);
    $relationData = $instance->{$relation}();
    if(gettype($relationData) !== 'object')
        throw new \Error('Method ' . $relation . ' is not a relation of class ' . self::class);
    if(!is_subclass_of(get_class($relationData), Relation::class))
        throw new \Error('Method ' . $relation . ' is not a relation of class ' . self::class);
    $related = $relationData->getRelated();
    $me = new self();
    $query = $relationData->getQuery()->getQuery();
    switch(get_class($relationData)) {
        case HasOne::class:
            $keys = [
                'foreign' => $relationData->getForeignKeyName(),
                'local' => $relationData->getLocalKeyName()
            ];
        break;
        case BelongsTo::class:
            $keys = [
                'foreign' => $relationData->getOwnerKeyName(),
                'local' => $relationData->getForeignKeyName()
            ];
        break;
        default:
            throw new \Error('Relation join only works with one to one relationships');
    }
    $checks = [];
    $other_table = ($overwrite_table ? $overwrite_table : $related->getTable());
    foreach($keys as $key) {
        array_push($checks, $key);
        array_push($checks, $related->getTable() . '.' . $key);
    }
    foreach($query->wheres as $key => $where)
        if(in_array($where['type'], ['Null', 'NotNull']) && in_array($where['column'], $checks))
            unset($query->wheres[$key]);
    $query = $query->whereRaw('`' . $other_table . '`.`' . $keys['foreign'] . '` = `' . $me->getTable() . '`.`' . $keys['local'] . '`');
    return (object) [
        'query' => $query,
        'table' => $related->getTable(),
        'wheres' => $query->wheres,
        'bindings' => $query->bindings
    ];
}

C'est la fonction "détection" qui lit les données éloquentes.

Le deuxième :

/**
 * @param Builder $builder
 * @param string $relation - The relation to join
 */
public function scopeJoinRelation(Builder $query, string $relation) {
    $join_query = self::RelationToJoin($relation, $relation);
    $query->join($join_query->table . ' AS ' . $relation, function(JoinClause $builder) use($join_query) {
        return $builder->mergeWheres($join_query->wheres, $join_query->bindings);
    });
    return $query;
}

C'est la fonction qui ajoute une portée au modèle pour l'utiliser dans les requêtes. Maintenant, il suffit d'utiliser le trait sur votre modèle et vous pouvez l'utiliser comme ceci :

Order::joinRelation('status')->select([
    'orders.*',
    'status.name AS status_name'
])->orderBy('status_name')->get();

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X