Skip to content

RelationHelper

The RelationHelper is a helper class that is used to work with model relations. It allows easy query building based on model relations.

Class

php
<?php

namespace App\Helpers;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\Relation;
use InvalidArgumentException;

/**
 * @template TModel of Model
 * @template TRelated of Model
 */
class RelationHelper
{
    /**
     * @param  TModel|class-string<TModel>  $model  Model we're using for the source relation
     * @param  string|int  $modelId  `id` of the Model we're using for the source
     * @param  string  $relation  Method name on TModel that returns Relation<TRelated>
     * @param  TRelated|class-string<TRelated>  $relatedModel  Model type we want to return
     * @return Builder<TRelated> Builder for models related to the given model via the provided relation.
     */
    public static function fromRelation(Model|string $model, string|int $modelId, string $relation, Model|string $relatedModel): Builder
    {
        if (! $model instanceof Model) {
            $model = new $model;
        }

        // TODO: Handle MorphTo relations
        $modelInstance = $model->query()->findOrFail($modelId);

        if (! method_exists($modelInstance, $relation)) {
            throw new InvalidArgumentException(sprintf(
                'The %s method doesn\'t exist on %s',
                $relation,
                $model::class
            ));
        }

        $relationInstance = $modelInstance->$relation();

        if (! $relationInstance instanceof Relation) {
            throw new InvalidArgumentException(sprintf(
                'The %s model function must return an instance of %s but %s was returned',
                $relation,
                Relation::class,
                $relationInstance
            ));
        }

        $related = $relationInstance->getRelated();

        if (! $related instanceof $relatedModel) {
            throw new InvalidArgumentException(sprintf(
                'The %s relation must return instances of %s, but %s was returned.',
                $relation,
                $relatedModel,
                $related::class
            ));
        }

        /** @var Relation<TRelated, TModel, mixed> $relationInstance */
        return $relationInstance->getQuery();
    }

    /**
     * @param  TModel|class-string<TModel>  $model  Model we're using for the source relation
     * @param  string|int  $modelId  `id` of the Model we're using for the source
     * @param  string  $relation  Method name on TModel that returns Relation<TRelated>
     * @param  TRelated|class-string<TRelated>  $relatedModel  Model type we want to return
     * @return Builder<TRelated> Builder for models not related to the given model via the provided relation.
     */
    public static function notFromRelation(Model|string $model, string|int $modelId, string $relation, Model|string $relatedModel): Builder
    {
        if (! $model instanceof Model) {
            $model = new $model;
        }

        $modelInstance = $model->query()->findOrFail($modelId);

        if (! method_exists($modelInstance, $relation)) {
            throw new InvalidArgumentException(sprintf(
                'The %s method doesn\'t exist on %s',
                $relation,
                $model::class
            ));
        }

        $relationInstance = $modelInstance->$relation();

        if (! $relationInstance instanceof Relation) {
            throw new InvalidArgumentException(sprintf(
                'The %s model function must return an instance of %s but %s was returned',
                $relation,
                Relation::class,
                $relationInstance
            ));
        }

        $related = $relationInstance->getRelated();

        if (! $related instanceof $relatedModel) {
            throw new InvalidArgumentException(sprintf(
                'The %s relation must return instances of %s, but %s was returned.',
                $relation,
                $relatedModel,
                $related::class
            ));
        }

        return $related->query()->whereNotIn(
            $related->getQualifiedKeyName(),
            $relationInstance->select($related->getQualifiedKeyName())
        );
    }
}

Methods

fromRelation

  • Type: function (Model|string $model, string|int $modelId, string $relation, Model|string $relatedModel): Builder
  • Description: Returns builder for models related to the given model via the provided relation.

notFromRelation

  • Type: function (Model|string $model, string|int $modelId, string $relation, Model|string $relatedModel): Builder
  • Description: Returns builder for models not related to the given model via the provided relation.