Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented method calling via __calls #666

Merged
merged 5 commits into from
Jan 30, 2017
Merged

Conversation

alekitto
Copy link
Contributor

Added a SimpleCaller class to handle method calls via __calls as documented here: https://github.com/nelmio/alice/blob/master/doc/complete-reference.md#calling-methods

Copy link
Member

@theofidry theofidry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Besides my comment, a few missing changes:

  • Update the service definition for the Symfony bridge as well
  • For the local tests you should have a bin/tests.sh script you can run (which runs the tests for the Symfony bridge as well)
  • You can remove the TODO from the doc
  • I would like to see an integration test as well (in LoaderIntegrationTest), which ensures it works with both the NativeLoader and the loader registered in Symfony as well

/**
* @param string $title
*/
public function setTitle($title)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can add the typehint in favour of the phpdoc here

class Dummy
{
private $title;
private $foo = 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$counter? Even if we don't know the counter of what we would at least get that it's a number

@@ -23,9 +24,9 @@ class FakeCaller implements CallerInterface
use NotCallableTrait;

/**
* @inheritdoc
* {@inheritdoc}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove the brackets, they are here for when there is text:

interface IFoo
{
  /**
   * Something
   */
  function bar();
}

class Foo implements IFoo
{
  /**
   * @inherit // equivalent to "pick all the stuff of the parent method"
   */
  function bar();

  /**
   * {@inherit} // equivalent to "replace this bit by the stuff of the parent method"
   *
   * My comments/phpdoc
   */
  function bar();
}

  /**
   * My comments //ignore the phpdoc of the parent method
   */
  function bar();

I've taken that from a draft of the PSR-5, it's still a draft so may change, but as the whole project follows the convention I would prefer to keep it (changing it doesn't make much sense at this point IMO)

@@ -50,4 +50,20 @@ public function isEmpty(): bool
{
return [] === $this->methodCalls;
}

/**
* {@inheritdoc}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above

}

/**
* {@inheritdoc}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above


foreach ($calls as $methodCall) {
$methodCall = $this->processArguments($methodCall, $fixture, $fixtureSet, $scope, $context);
$scope[$methodCall->getMethod()] = $methodCall;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need to add the method name to the scope. When we inject a property value, we do so because we may need the evaluated value later:

Dummy:
  dummy:
    foo: '<evaluate_foo()>'
    bar: '$foo' # value of '<evaluate_foo()>' (which may differ from '@self->foo')*

*: @self->foo will try to access to the foo property of the object, which might have a different value if the value has been set via a setter which doesn't just do a $this>foo = $foo.

What you are doing is enabling:

Dummy:
  dummy:
    __calls:
      - setFoo: [ '<evaluate_foo()>' ]
      - setFoo: [ '$setFoo' ]

which I don't think is very useful and even error prone as as you can see in the case above, you may override the value if doing successive calls

$dummyProphecy = $this->prophesize(Dummy::class);
$dummyProphecy->setTitle('foo_title')->shouldBeCalled();
$dummyProphecy->addFoo()->shouldBeCalledTimes(2);
$dummy = $dummyProphecy->reveal();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to be consistent with the existing codebase, I would add a /** @var Dummy $dummy*/ inline phpdoc between L73 and L74

{
$dummyProphecy = $this->prophesize(Dummy::class);
$dummyProphecy->setTitle('foo_title')->shouldBeCalled();
$dummyProphecy->addFoo()->shouldBeCalledTimes(2);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would put shouldBeCalled() here as when you are creating the prophecy, you don't care of how much calls are being made at this point, it's a check you are doing in the end. So at the end of the test you should add:

$dummyProphecy->setTitle(Argument:any())->shouldHaveCalledTimes(1);
$dummyProphecy->addFoo()->shouldBeCalledTimes(2);

granted that in this case, this might seem a bit redundant, but it's a practice that usually makes it easier to follow IMO, and again let's try to stay consistent with the codebase

null,
new PropertyBag(),
(new MethodCallBag())
->with(new SimpleMethodCall('setTitle', [$titleValue = new FakeValue()]))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add a second call on setTitle to make sure the second overrides the first

}
}

return $methodCall->withArguments($arguments);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you should return [$methodCall->withArguments($arguments), $fixtureSet]. The reason $result contains both the a value and the set is that the set may have changed during the resolution. A simple example:

Dummy:
  dummy2:
    __construct:
      - '@dummy1' # while resolving the value, the fixture `@dummy1` will be generated
  dummy1:
    foo: 'foo'

And if the $fixtureSet is not properly updated, when it will be the turn of dummy1, instead of finding the object already generated, it will generated again (which will also break the dependency between dummy2 and dummy1 as dymmy2#dummy1 will no longer be dummy1)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$fixtureSet here is passed by-ref, so it is already updated.
Do you prefer returning an array with the new fixtureSet instead of passing it by reference?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I missed that, yes please, I prefer explicit returned values over passing them by references :)

@theofidry
Copy link
Member

Thanks for the PR @alekitto, that's some big work :) There is a few changes still to do, but the hardest has been made.

How was the dive in the code? I tried to make it readable but if you have some feedbacks and things that looked really weird don't hesitate to point them out!

@alekitto
Copy link
Contributor Author

I made some changes, tomorrow i'll write an integration test.
The code looks quite readable and it was pretty easy to implement and find where to modify the Loader to call the new SimpleCaller class.
The only thing that was unclear to me is where to find services definitions for Symfony: I didn't understand that the caller was a service and not instantiated from the Loader. Have I missed something on the doc?

@theofidry
Copy link
Member

The only thing that was unclear to me is where to find services definitions for Symfony: I didn't understand that the caller was a service and not instantiated from the Loader. Have I missed something on the doc?

The services can be found in src/Bridge/Symfony/Resources/config. Basically NativeLoader is a loader to be able to easily use alice out of the box, without any DIC. The Symfony bridge however ensures everything is registered as services to the Symfony DIC (via the bundle NelmioAliceBundle) to be able to take advantage of the features the DIC provides.

It's like a traditional bundle, but shipped here instead of a dedicated library (it's easier for maintenance on my end).

@theofidry
Copy link
Member

@alekitto tell me when you need a last review, AFAICS there's only one comment left and should be ready to ship.

@alekitto
Copy link
Contributor Author

Should be all done now

@theofidry
Copy link
Member

Thanks very much @alekitto, that's a nice one :)

@theofidry theofidry merged commit 1630700 into nelmio:master Jan 30, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants