phplaraveleloquentnullabletable-relationships

Laravel Eloquent can relationship method return null instead of MorphMany relationship object?


My problem could be more philosophical than concrete. I'm using Laravel 8, I have one Trait that is used by several classes and some of its methods perform two different computation by taking into account if object has or not a specific morphMany relationship (here called entries). This trait is almost implemented with the following code:

trait MyTrait {
  // Some methods...

  // Entries methods
  abstract public function hasEntries();
  abstract public function entries();

  // Here is a method that depends on entries
  public function computFunc() {
    return $this->hasEntries()
      ? $this->computWithEntries()
      : $this->computWithoutEntries();
  }

  private function computWithEntries() {
    // Perform computation with entries by using entries() relationship...
  }
  private function computWithoutEntries() {
    // Perform computation without entries...
  }

  // Some other methods...
}

Obviously, classes that have entries relationship also implement some criteria inside hasEntries() method to check if there are entries for the current object, as shown in this example:

class ModelA {
  use MyTrait;

  public function entries() {
    return $this->morphMany(Entry::class, 'owner');
  }
  public function hasEntries() {
    return $this->entries()->where([ /* Some condition */ ])->exists();
  }
}

Now, my question is: what should return entries() method for classes that don't have entries relationship, meaning that is not possible/allowed for entries table to have column owner_type equal to one of those classes.

Here's an instance of this case (ModelB can't own any entry):

class ModelB {
  use MyTrait;

  public function entries() {
    // Should return null?
    return null;
  }
  public function hasEntries() {
    return false;
  }
}

What value/object should return entries() method for objects of class ModelB? I'm quite sure that to return null instead of relationship object isn't the correct thing to do, am I wrong?


Solution

  • I think you may accomplish this with a different approach. Try with this:

    1. Split your trait in 2 different traits (one to create the relationship and the other to check if has entries and compute the result).
    2. Create an interface (contract) that contains the methods of the second trait (just to get sure those methods exists in the class) so you can specify if the value to return should be boolean.

    With that, you wont have problem in the computing part of the code. Hope my answer helps.

    Edit - Super quick example:

    //EntriesRelationTrait.php
    trait EntriesRelationTrait {
        public function entries() {
            return $this->morphMany(Entry::class, 'owner');
        }
    }
    
    //ComputeByEntriesTrait.php
    
    trait ComputeByEntries {
        public function computFunc() {
            return $this->hasEntries()
              ? $this->computWithEntries()
              : $this->computWithoutEntries();
        }
    
        private function computWithEntries() {
          // Perform computation with entries by using entries() relationship...
        }
        private function computWithoutEntries() {
          // Perform computation without entries...
        }
    }
    
    //HasEntriesContract.php
    interface HasEntriesContract
    {
        /**
         * @return bool
         */
        public function hasEntries(): bool; 
        //function type declaration available only after PHP 7.0
    
    }
    
    class ModelA implements HasEntriesContract {
        use EntriesRelationTrait;
        use ComputeByEntriesTrait;
    
        public function hasEntries(): bool {
            return $this->entries()->where([ /* Some condition */ ])->exists();
        }
    }
    
    class ModelB implements HasEntriesContract {
        use ComputeByEntriesTrait;
    
        public function hasEntries(): bool {
            return false;
        }
    }
    

    The contract ensures that the created function follows a standard, in this case, a boolean response from the hasEntries method.