Story #11169

Implement new operator support

Added by Robert Lemke over 8 years ago. Updated over 8 years ago.

Status:
Resolved
Priority:
Should have
Assignee:
Category:
-
Target version:
Start date:
Due date:
% Done:

40%


Description

Refactor FLOW3 so that the PHP "new" operator can be used instead of ObjectManager->create()


Subtasks

Task #13703: Fix file monitoring / optimize compile stepResolvedRobert Lemke

Task #13702: Replace remaining uses of objectManager->create() with new and adjust tests accordinglyResolved

Task #12099: Check feasibility and create rough conceptResolvedRobert Lemke

Task #12100: Implement a generic Proxy BuilderResolvedRobert Lemke

Task #13604: Fix bugs and adjust affected codeResolvedRobert Lemke

History

#1 Updated by Benjamin Eberlei over 8 years ago

I want to throw in https://github.com/sebastianbergmann/php-test-helpers

This is called "test-helpers", however it is exactly useful for overwriting the new operator to implement AOP.

#2 Updated by Sebastian Kurfuerst over 8 years ago

Hey,

as already discussed with you @Robert, I don't see this as a high priority right now. Just wanted to add this to the issue, so it does not get lost :-)

Greets,
Sebastian
PS: @Benjamin: It's great that you're active here, helping us with the Doctrine evaluation :-)

#3 Updated by Robert Lemke over 8 years ago

An important argument against using the new operator is testability. If classes create their own prototypes (prototypes in the context of FLOW3), there is no easy way to mock them. That's exactly the reason why PHP-test-helpers were developed.

On the other hand, if we can afford to require the use of such a custom PHP extension for testing then it might make sense to keep on exploring this topic.

#4 Updated by Benjamin Eberlei over 8 years ago

Hm I disagree!

You already mention prototypes in the context of Flwo3and mix it with php test helpers. Your reasoning would imply that php-test-helpers was developed for Flow3. It was developed for applications that don't use dependency injection.

But Dependency Injection is independent of the problem newable vs injectable, which is discussed here:

http://misko.hevery.com/2008/09/30/to-new-or-not-to-new/

and also in Evans Domain Driven Design (Entities and Services chapter).

I don't say you ObjectManager::create is bad per se. Its just bad for "newables" (Entities, Value objects and such).

#5 Updated by Robert Lemke over 8 years ago

Benjamin Eberlei wrote:

You already mention prototypes in the context of Flwo3and mix it with php test helpers. Your reasoning would imply that php-test-helpers was developed for Flow3. It was developed for applications that don't use dependency injection.

But Dependency Injection is independent of the problem newable vs injectable, which is discussed here:

http://misko.hevery.com/2008/09/30/to-new-or-not-to-new/

Misko argues that there's no need to mock "leafs of the application" because they are more or less like strings. I don't agree with that. I can easily imagine Entities which rely on a complex set of other objects which I'd rather unit-test separately and not alltogether. If I test a "Conference" object I want to see my test pass or fail because "Conference" is implemented right or wrong. It's confusing to see "Conference" tests fail because the "Address" object used by the "Venue" object created by the "Conference" object is buggy.

and also in Evans Domain Driven Design (Entities and Services chapter).
I don't say you ObjectManager::create is bad per se. Its just bad for "newables" (Entities, Value objects and such).

Hmm, I don't know. I guess you can produce okay code either way, it's rather a question of style. What I find appealing with the new-operator approach is that it a) doesn't require an Object Manager and b) is less to type. However, the ObjectManager->create() approach is easier to mock and more explicit about who's responsible for object creation.

Certainly, we could go for a mixture of both (like Symfony tends to do), but that's the option I dislike most. I'd rather have everything consistent following the same conventions.

#6 Updated by Benjamin Eberlei over 8 years ago

Miskos main argument is not about the leafes of an application, its about hard to test code if you violate the injectable vs newable rule, because in this case to be able to test newables you might need injectables. This is cumbersome to setup. In the case of FLOW3 its easy to explain:

Why do i need to configure tons of mocking lines just to be able to test my simple "BlogPost" entity. Its supposed to be a domain object, not knowing anything about technical blabla such as an object manager. There is a a considerable difference between:

class BlogPost
{
    public function addComment($message, $email)
    {
         $comment = $this->objectManager->create("Comment", array($message, $email));
         $this->comments[] = $comment;
         return $comment;
    }
}

class BlogPost
{
    public function addComment($message, $email)
    {
         $comment = new Comment($message, $email);
         $this->comments[] = $comment;
         return $comment;
    }
}

in regards to testing:

class BlogPostTest
{
    public function testSimpleNewable()
    {
        $post = new BlogPost();
        $comment = $post->addComment("hello world!", "kontakt@beberlei.de");

        $this->assertType("Comment", $comment);
        $this->assertEquals("hello world!", $comment->getMessage());
        $this->assertEquals(1, count($post->getcomments());
    }

    public function testComplexObjectManagerTest()
    {
        $message = "hello world!";
        $email = "kontakt@beberlei.de";

        $objectManager = $this->getMock('Some/Very/Long/PathTo/ObjectManager');
        $objectManager->expects($this->at(0));
                     ->method('create');
                     ->with($this->equalTo('Comment), $this->equalTo(array($message, $email))
                     ->will($this->returnValue(new Comment($message, $email));

        $post = new BlogPost();
        $post->setObjectManager($objectManager); // rather simple, but what if you rely on reflection only here in your model?
        $comment = $post->addComment("hello world!", "kontakt@beberlei.de");

        $this->assertType("Comment", $comment);
        $this->assertEquals("hello world!", $comment->getMessage());
        $this->assertEquals(1, count($post->getcomments());
    }
}

This is miles apart in terms of simplicity. Because test two violates the rule, never depend on injectables in your newables.

Please also review the section on factories and aggregate roots in DDD, it specificially says entities / aggregate roots can create objects themselves not some object manager.

Additionally the current approach means that you need the object manager as reference in EVERY object. Becaues whenever i want to create a new object I also need an object manager. That all just leads to very complicated and repetitive code. (as shown in the above test-example).

#7 Updated by Robert Lemke over 8 years ago

  • Assignee set to Robert Lemke

#8 Updated by Robert Lemke over 8 years ago

  • Status changed from New to On Hold

#9 Updated by Sebastian Kurfuerst over 8 years ago

  • Status changed from On Hold to New
  • Parent task changed from #10273 to #11946

#10 Updated by Sebastian Kurfuerst over 8 years ago

  • Tracker changed from Task to Story
  • Start date set to 2011-01-06
  • Parent task deleted (#11946)

#11 Updated by Sebastian Kurfuerst over 8 years ago

  • translation missing: en.field_position set to 2

#12 Updated by Robert Lemke over 8 years ago

  • Status changed from New to Accepted

#13 Updated by Robert Lemke over 8 years ago

  • Subject changed from Explore possibility to use new operator instead of ObjectManager->create() to Implement new operator support

#14 Updated by Benjamin Eberlei over 8 years ago

So how will this work? Your tweet about it didn't release any technical details :-)

#15 Updated by Robert Lemke over 8 years ago

  • Project changed from Core Team to Base Distribution
  • Target version deleted (788)

#16 Updated by Robert Lemke over 8 years ago

  • Project changed from Base Distribution to TYPO3 Flow Base Distribution

#17 Updated by Robert Lemke over 8 years ago

  • Target version set to 1.0 beta 1
  • translation missing: en.field_position deleted (17900)
  • translation missing: en.field_position set to 1

#18 Updated by Robert Lemke over 8 years ago

  • Project changed from TYPO3 Flow Base Distribution to Base Distribution
  • Target version changed from 1.0 beta 1 to 1228
  • translation missing: en.field_position deleted (2)
  • translation missing: en.field_position set to 1

#19 Updated by Robert Lemke over 8 years ago

  • Project changed from Base Distribution to TYPO3 Flow Base Distribution
  • Target version deleted (1228)

#20 Updated by Robert Lemke over 8 years ago

  • Target version set to 1.0 beta 1
  • translation missing: en.field_position deleted (1)
  • translation missing: en.field_position set to 1

#21 Updated by Robert Lemke over 8 years ago

  • Status changed from Accepted to Resolved

Also available in: Atom PDF