1

I have the following working code that gives me a collection of a model type that each have any of the given relationship values (like a tag with id 1, 2 or 3):

<?php

public function getEntitiesWithRelationValues($entityType, $relations = []) {
    $related = new EloquentCollection();
    $locale = App::getLocale();

    $entityType = new $entityType(); // bad?
    // $entityType = new ReflectionClass($entityType); // not working

    foreach ($relations as $relation => $modelKeys) {
        if ($entityType->{$relation}()->exists()) {
            

            $relatedClass = get_class($entityType->{$relation}()->getRelated());
            $relationPrimaryKeyName = ($instance = new $relatedClass)->getQualifiedKeyName();
            $relationEntities = $entityType::where('published->' . $locale, true)
                ->whereHas($relation, function (Builder $query) use($modelKeys, $relationPrimaryKeyName) {
                    $query->whereIn($relationPrimaryKeyName, $modelKeys);
                })
                ->get()
                ->sortKeysDesc()
                ->take(10)
                ;

            $related = $related->concat($relationEntities->except($related->modelKeys()));
        }
    }
    return $related;
}

I feel $entityType = new $entityType(); is bad code because I dont want to create a new model. The reflection class throws the error "ReflectionClass undefined method {$relation}". How can I get the relationship data of a model type without actually loading/ instantiating a model?

A few weeks ago I asked something similar here but in that case I did have a model loaded.

Alex
  • 9,911
  • 5
  • 33
  • 52

1 Answers1

0

You could consider the following solution:

  1. Actually use a Model instance as the input to your getEntitiesWithRelationValues() function, since one way or another you are going to retrieve the relationships for a specific instance right?
  2. Create a property for your model:
public static $relationNames = ['tags', 'posts', 'comments'];
  1. Retrieve that property in your code using MyModel::$relationNames and call whichever functions you need.

Alternative solution using reflection

The snippet below uses reflection to find all public methods of User::class, then filter return types based on if it is a subclass of \Illuminate\Database\Eloquent\Relations\Relation. Note that you need to annotate the return types specifically using the new language struct like public function myMethod() : MyReturnType { }.

use App\Models\User;
use Illuminate\Database\Eloquent\Relations\Relation;

$relationshipMethods = collect(
(new ReflectionClass(User::class))
    ->getMethods(\ReflectionMethod::IS_PUBLIC)
)
->filter(function(\ReflectionMethod $method) {
    // The `?` makes the code work for methods without an explicit return type.
    return is_subclass_of($method->getReturnType()?->getName(), Relation::class);
})->values();

// $relationshipMethods now is a collection with \ReflectionMethod objects.
Flame
  • 6,663
  • 3
  • 33
  • 53
  • Thanks for your input. No, I am not actually loading these for a specific instance, just "Give me models of type X that have the following relationship entries". I could use a random model instance for the sake of it, but that doesnt seem right aswell. For instance, I noticed `if ($entityType->{$relation}()->exists()) {` is only true if the current model actually has entries in that relationship, so my take also doesnt work here. I have to use `if (method_exists($entityType, $relation)) {` (which is kind of redundant anyways bc I should know which kind of relationships a model type has) – Alex Jan 26 '23 at 15:35
  • I think declaring it, or hardcoding it, is not dynamic. That seems like a hack and I wonder what the right solution to this is. – Alex Jan 26 '23 at 15:37
  • You could use reflection and traverse every single method (which there are quite many if i quickly run the code `(new ReflectionClass(User::class))->getMethods(ReflectionMethod::IS_PUBLIC)`), and then check for return types that belong to relations. However you have to use the PHP 7/8 syntax to annotate your return types e.g. `public function foobar() : User {...}`. I havent benchmarked any of this. See the answer for a code snippet. – Flame Jan 26 '23 at 17:10
  • 1
    Hi again alex, I'm not sure if I understand the question 100%, but do you want to get all relationships of a model without having a list, without calling the model and without knowing what exists or not? If so I actually did a snippet of how to do this in a different question, but if that is what you are asking, I can create a cleaner solution for you or maybe @Flame can use my snippet to adjust his answer a tiny bit to get there: https://stackoverflow.com/questions/75123767/i-want-make-array-for-eloquent-relationship-laravel/75123958#75123958 – RG Servers Jan 27 '23 at 17:03