Skip to content
This repository has been archived by the owner on Mar 24, 2020. It is now read-only.

[WIP] Static construction of objects #95

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from

Conversation

mathiasverraes
Copy link

As discussed in #79.

The code is just a POC and there are no specs yet. I figured I'd first try to get it working before diving in too deep.

This fixes my use case where I want to instantiate an object without using it's constructor directly.

Simplified example:

class Payment
{
    private $amount;
    private function __construct($amount)
    {
        $this->amount = $amount;
    }

    public static function create($amount)
    {
        return new static($amount);
    }
}

In production code, you'd instantiate the Payment like this:

$payment = Payment::create(500);

In phpspec:

$this->beCreatedStaticallyWith(array('Payment', 'create'), array($amount));

@marijn
Copy link

marijn commented Jan 8, 2013

👍

@mathiasverraes
Copy link
Author

Perhaps beCreatedStaticallyWith is a bit weird, how about:

$this->beCreateByFactory($factoryCallable);
$this->beConstructedWith($arguments);

@everzet
Copy link
Member

everzet commented Jan 8, 2013

No need to. $newObject = $this->staticCall(...); is enough.

@mathiasverraes
Copy link
Author

Could you expand on that? How would I use $this->staticCall to spec an object with a private constructor?

@marijn
Copy link

marijn commented Jan 8, 2013

@everzet I disagree, you want to tell phpspec in the let method how to construct the object and reuse it in the rest/most of the specs.

@everzet
Copy link
Member

everzet commented Jan 8, 2013

Ok. Wait. Lets make a couple steps back and you'll try to explain me why do you use this static factory method instead of constructor... What's the point?

@mathiasverraes
Copy link
Author

See https://github.com/phpspec/phpspec2/issues/79#issuecomment-11992498

My example is very silly of course, let me try to explain it with a real life example.

Imagine an ORM. In DDD, a Repository "mediates between the domain and data mapping layers, acting like an in-memory domain object collection." (http://martinfowler.com/eaaCatalog/repository.html)
Because the Repository persists the object and recreates it when needed, we don't want it to use the constructor in that case. Doctrine2 solves this with a hack using unserialize. A more elegant way imho is to have ways to create the object: One method is what you use in your client code, using domain language, like Payment::create($amount). The other is a method that only the Repository uses, to restore the Entity to it's original state when it was persisted: Payment::restoreFromPersistence($completeEntityState).
In my example, it's still not obvious why this would be important, but as soon as we have business logic that happens when we make a new Entity, it becomes important, because we don't want to repeat that business logic whenever the Entity is restored from the db.

In any case, I don't think phpspec should force me to only have public constructors. It might be a legacy thing or some other reason. It's up to the user, so phpspec should support it.

@everzet
Copy link
Member

everzet commented Jan 8, 2013

Ok, makes sense now, interesting pattern. Not sure I fully support it though, but it totally have right to exist and i see nothing specifically wrong in it.

I would suggest this syntax then:

$this->beConstructedThrough('createInstance');
$this->beConstructedWith($arguments);

This way you'll still have ability to redefine construction arguments without knowing anything about how object is actually constructed in the examples.

@everzet
Copy link
Member

everzet commented Jan 8, 2013

Keep in mind that default behavior should still be the same - beConstructedThrough(...) should be set to __construct. Also, setting this to __construct explicitly should cause phpspec to start using constructor instead of static creator.

@mathiasverraes
Copy link
Author

Looks like Travis breaks because of a github issue.

[Composer\Downloader\TransportException]                                     
  The 'https://api.github.com/repos/padraic/mockery/zipball/0.7.2' URL could   
  not be accessed: HTTP/1.1 403 Forbidden  

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
3 participants