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
Development

Successfully merging this pull request may close these issues.

3 participants