4

So I am a laravel developer and even though I have worked with it for a while now, and I love how the magic happens beneath the surface, how it automatically binds implementations when instantiating classes via the IoC container, right now I am trying to go to the basics of design patters and learn how things actually work.

So I started with the Animal example:

abstract class Animal
{
    abstract function makeSound();
}

class Dog extends Animal
{
    public function makeSound()
    {
        echo "Bark!\n";
    }
}

class Cat extends Animal
{
    public function makeSound()
    {
        echo "Bark!\n";
    }
}

So I am reading Head First Design Patterns and I am trying to make the most of the book. At this point every time I create a new Animal, I will have to implement the make sound method which will differ in most cases.

So the book tells me that I should code a Soundable interface and then have implementations of that interface in the Animal extended classes.

I finally came up with something like this:

interface Soundable
{
    function sound();
}

class Bark implements Soundable
{
    public function sound()
    {
        return "Bark!\n";
    }
}

class Meow implements Soundable
{
    public function sound()
    {
        return "Meow!\n";
    }
}

class Animal
{
    public $soundable;

    public function __construct(Soundable $soundable)
    {
        $this->soundable = $soundable;
    }

    public function makeSound()
    {
        echo $this->soundable->sound();
    }
}

class Dog extends Animal
{

}

class Cat extends Animal
{

}

function makeAnimal(Animal $animal){
    return $animal; 
}

// Making a dog
$animal = makeAnimal(new Dog(new Bark()));
$animal->makeSound();

// Making a cat
$animal = makeAnimal(new Cat(new Meow()));
$animal->makeSound();

So now when I have another animal that barks or meows, I can simple instantiate that implementation while making an animal.

Now my question is how do I tell PHP to automatically pass the new Bark() while instantiating the Dog class since it will bark and I don't want to write it every time I instantiate a new Dog object.

So how do I use a similar magic that Laravel uses to pass the Bark object automatically while instantiating Dog.

PS: I am still learning so I might be going in the wrong direction altogether while understanding these principles. Please guide me if you know better.

Rohan
  • 13,308
  • 21
  • 81
  • 154
  • 1
    Your function `makeAnimal()` is not needed. Just do: `$animal = new Dog(new Bark())`. It's the same thing since your function just returns what it gets. – M. Eriksson Aug 31 '16 at 09:58
  • In generic cases using factory goes with OP. Injecting instances which implements interface helps you keep your code reusable. Global function makeAnimal replace with factory which can be later injected with some stuff eg. an `Owner` or.... etc. – brzuchal Aug 31 '16 at 10:23
  • So I just looked up the Factory pattern. So @brzuchal, you mean to say, I should have a method which returns me a dog class with the bark so that I can just call that method and get the dog and not have to instantiate it again and again? – Rohan Aug 31 '16 at 10:32
  • With factory you wouldn't need to instantiate `new Bark()`. everytime you call that method you'll receive `Dog` instance (not class) with propriet `Soundable` object instance for eg. `Bark` in case of `Dog`. – brzuchal Aug 31 '16 at 10:54
  • Basic question: does `Dog` instance is single at runtime/request time and has some responsibility, or there may be lot's of `Dog` instances and each of them represents them selves (they are not equal) ?? – brzuchal Aug 31 '16 at 12:42

4 Answers4

2

First of all, a small note: the book is wrong. The interface should be called Audible or Vocal. "Soundable" is not a real word an the author should be embarrassed. Also, calling a variable in the same name as an interface is kinda bad.

Another thing is: the Laravel's IoC is actually just a glorified service locator, so it wouldn't really help you here.

Usually, you would have two options:

  • use a factory (which in this particular case would painful and/or tricky)
  • use a dependency injection container - preferably a reflection-base one

I tend to recommend Auryn in these case. Though, if you are willing to jump few additional hoops and suffer through limited configuration, you can also use Symfony's DIC.

If you were using Auryn, initialization of your dog and cat would be just:

$injector = new Auryn\Injector;
$dog = $injector->make('Dog'); 
$cat = $injector->make('Cat'); 

The library would on its own look up the reflection of the constructor for Dog and detect that it will also need to create a new Bark instance.

tereško
  • 58,060
  • 25
  • 98
  • 150
  • I think it depends on purpose of `Dog` and `Cat`. There is no destiny for those objects. Using DIC for instantiating objects which are not services doesn't convince me enought. What if `Dog` and `Cat` are simply entities with injected dependencies? – brzuchal Sep 01 '16 at 02:58
  • The example was not from the book, I just created it on the go. And yes you are right. I should change it to Audible. But I am more interested in the programming concept than vocabulary right now. Anyway, this is what I wanted I think as an answer. I will look into Auryn. Thanks for the detailed answer. – Rohan Sep 01 '16 at 07:20
1

You can create simply animal factory (sounds strange) with factory methods.

Factories

Factory classes are often implemented because they allow the project to follow the SOLID principles more closely. In particular, the interface segregation and dependency inversion principles.

For more information look here https://www.sitepoint.com/understanding-the-factory-method-design-pattern/ Just remember if you want to write down some unit test don't use static methods, just instantiate factory, there may be need in future to create factory with some dependencies.

<?php
class AnimalFactory
{
    public function createDog() : Dog
    {
        return new Dog(new Bark());
    }
}

$factory = new AnimalFactory();
$dog = $factory->createDog();
brzuchal
  • 649
  • 8
  • 19
  • I was thinking about solving this problem via factories too. But I am looking for something different here. Just FYI, I did not down vote your answer. Any help is appreciated by me. :) – Rohan Aug 31 '16 at 10:50
  • Generally that is what factories are for: creating instances of some objects specific way. So in case of `Dog` the factory knows that it is barking and not moew-ing. Any additional pre setup eg. additional dependency can be injected into factory and it'ss know what to do with it, like injecting a new `Owner` and receive a new `Dog` with injected `Owner` at creation time. – brzuchal Aug 31 '16 at 10:58
0

I think using IoC here would be good solution.

From Laravel Docs (Contextual binding)

Sometimes you may have two classes that utilize the same interface, but you wish to inject different implementations into each class. For example, when our system receives a new Order, we may want to send an event via PubNub rather than Pusher. Laravel provides a simple, fluent interface for defining this behavior:

$this->app->when('App\Handlers\Commands\CreateOrderHandler')
          ->needs('App\Contracts\EventPusher')
          ->give('App\Services\PubNubEventPusher');

So your service provider in laravel could look like

public function register()
{
    $this->app->when(Dog::class)->needs(Soundable::class)->give(Bark::class);
    $this->app->when(Cat::class)->needs(Soundable::class)->give(Meow::class);
}

And then you could instantiate your class using Laravel's dependency injection containter

$dog = app()->make(Dog::class);
$dog->sound(); // Bark!    
$cat = app()->make(Cat::class);
$cat->sound(); // Meow!

So summing it up, and relating to your question:

So how do I use a similar magic that Laravel uses to pass the Bark object automatically while instantiating Dog.

Dependency Injection Container will suit your needs

Skysplit
  • 1,875
  • 12
  • 16
  • Yes, this is what I am asking. So you are saying, that I should pull the IoC container package and start using it in my code, right? What I am trying to understand is that how the IoC container works in the first place and trying to replicate it myself not because I think I'll do it better, but because I want to understand what is going on beneath the hood. – Rohan Aug 31 '16 at 12:08
  • Oh, you did provide a wiki link there. Thanks. I will look into it and try to implement it. – Rohan Aug 31 '16 at 12:09
  • Dependency Injection in PHP relies on [Reflection](http://php.net/manual/en/book.reflection.php). Simplyfing it: before every class is being instantiated, container checks it's constructor/methods and tries to resolve class dependencies. – Skysplit Aug 31 '16 at 12:11
  • Using DIC for creating objects does it really suite your needs? DIC is designed to inject instances not to create them all the time! DiC is designed to store services and creating them. Why should you use them to create objects? When you're creating your Entities you don't use DIC! Creating `Dog` is just like creating Entities! You're mixing resposibilities, don't do that! – brzuchal Aug 31 '16 at 12:11
  • @brzuchal He clearly asked `How do I tell PHP to automatically pass the new Bark() while instantiating the Dog.` How would you like it to be done without DI Container? – Skysplit Aug 31 '16 at 12:14
  • @Skysplit Simply with an factory service. You're not using DIC to instantiate your entities, right? A simple `new Bark()` somehow can be handled by DIC and then there will be one single instance, but not creation of entities `Dog`'s! also I don't think that in real world every `Dog` own's one shared `Bark`! – brzuchal Aug 31 '16 at 12:17
  • @brzuchal For this particular example factory service could be a solution, but this is just an example and author asked `So how do I use a similar magic that Laravel uses to pass the Bark object automatically while instantiating Dog.`. My post relates to this question directly: laravel uses DI Container and that's how you can instantiate classes like laravel do. – Skysplit Aug 31 '16 at 12:25
  • @Skysplit you're mixing responsibilities. Every framework has DIC it's not related case to Laravel only! I know this is an example but thinking of `Dog` is like thinking about entities and they are not services. – brzuchal Aug 31 '16 at 12:31
  • I do know that every modern framewrk (probably, did not used them all) has DIC or some sort of IoC implemented, but I'll have to stick to my previous comment - I did answer OP's question. I do not see how `Dog` being entity not service is related here. – Skysplit Aug 31 '16 at 12:39
  • @brzuchal To be clear - I'm not saying your approach is invalid :) (IMO) It just isn't related to OP's question. – Skysplit Aug 31 '16 at 12:51
-1

I don't know laravel, but in my opinion your code should have been:

interface Soundable
{
    function sound();
}

class Animal
{
    protected name;

    public function __construct($name) {
        $this->name=name;
    }

    public function get_name() { return $this->name; }
}

class Dog extends Animal implements Soundable
{
    public function sound()
    {
        return "Bark!\n";
    }
}

class Cat extends Animal implements Soundable
{
    public function sound()
    {
        return "Meow!\n";
    }
}

// Making a dog
$animal = new Dog("Fido");
$animal->sound();

// Making a cat
$animal = new Cat("Fuffi");
$animal->sound();
echo $animal->get_name(); // print "Fuffi"

So, you can see that if you want only to implement the interface, you don't even need the Animal class. In fact, I have mantained it only to shot that it can still be useful in order to implement some method that can be useful to all descending classes (for example, get_name, that return the protected attribute "name").

Davide Visentin
  • 735
  • 5
  • 19