Skip to content

Instantly share code, notes, and snippets.

@k1paris
Last active April 13, 2024 05:49
Show Gist options
  • Save k1paris/14548413e57c190d3701b5fcb095e061 to your computer and use it in GitHub Desktop.
Save k1paris/14548413e57c190d3701b5fcb095e061 to your computer and use it in GitHub Desktop.
Law of Demeter with PHP (LoD)

Law of Demeter with PHP (LoD)

Law of Demeter or principle of least knowledge is a design guideline, that is using to make code more simple and stable.

Rules for module

  • Each unit should have only limited knowledge about other units: only units "closely" related to the current unit.
  • Each unit should only talk to his friends; don't talk to strangers.
  • Only talk to your immediate friends.

Formal rules for object's (O) methods (m)

m should only invoke the methods of the following kind of objects:

  1. O itself
  2. m's parameters
  3. Any objects created/instantiated with m
  4. O's direct component objects
  5. A global variable, accessible by O in the scope of m

Note: m should't invoke methods of a member object returned by another method.

this breaks law:

$a->$b->method();

this doesn't:

$a->method();

Examples

Wrong:

class Seller
{
   private $priceCalculator = new PriceCalculator();

   public function sell(ProductCollection $products, Wallet $wallet)
   {
       /** @var $actualSum Money */
       $actualSum = $wallet->getMoney(); //ok, rule #2
       /** @var $requiredSum Money */
       $requiredSum = $this->priceCalculator->calculate($products); //ok, rule #4

       if($actualSum->isBiggerThan($requiredSum)) { //wrong
           $balance = $actualSum->substract($requiredSum); //wrong
           $wallet->setMoney($balance);
       }
       else {
           throw new Exception('Required sum is bigger than actual');
       }

   }
}

Right:

class Seller
{
  private $priceCalculator = new PriceCalculator();

  public function sell(ProductCollection $products, Money $moneyForProducts)
  {
      /** @var $requiredSum Money */
      $requiredSum = $this->priceCalculator->calculate($products); //ok, rule #4

      if($moneyForProducts->isBiggerThan($requiredSum)) { //ok, rule #2
          return $moneyForProducts->substract($requiredSum); //ok, rule #2
      }
      else {
          throw new Exception('Required sum is bigger than actual');
      }

  }
}

Formally correct, but has the same problems as the first example:

class Seller
{
    private $priceCalculator = new PriceCalculator();

    public function sell(ProductCollection $products, Wallet $wallet)
    {
        /** @var $actualSum Money */
        $actualSum = $wallet->getMoney(); //ok, rule #2
        /** @var $requiredSum Money */
        $requiredSum = $this->priceCalculator->calculate($products); //ok, rule #4

        $balance = $this->subtract($actualSum, $requiredSum); //ok, rule #1
    }

    private function subtract(Money $reduced, Money $subtracted): Money
    {
        if($reduced->isBiggerThan($subtracted)) { //ok, rule #2
            return $reduced->substract($subtracted); //ok, rule #2
        }
        else {
            throw new Exception('Subtracted summ is bigger than reduced');
        }
    }
}

Formally follow rules is not enough. You need to analyze methods, arguments, relations and calls inside class.

Conclusion

Pros (because of using nearest objects and calling with one arrow):

  • Reduce coupling.
  • Increases encapsulation.
  • Increases single responsibility.

Cons:

  • Need to use many wrapper methods.
  • May to increase overhead.

LoD is not good in all cases. When using middle objects such a DTO (this object don't have logic inside), or ORM's relations, don't follow LoD is fine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment