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

Override callback resolver #10

Merged
merged 11 commits into from
Feb 4, 2016
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion src/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,28 @@ public function __construct(ContainerBuilder $containerBuilder = null, array $va

parent::__construct($values);

$this['phpdi.callable_resolver'] = $this->share(function () {
return new CallableResolver($this->containerInteropProxy);
});

// Override the controller resolver with ours
$this['resolver'] = $this->share(function () {
return new ControllerResolver(
new CallableResolver($this->containerInteropProxy),
$this['phpdi.callable_resolver'],
new ResolverChain([
new AssociativeArrayResolver,
new TypeHintContainerResolver($this->containerInteropProxy),
])
);
});

// Override the callback resolver with ours
$this['callback_resolver'] = $this->share(function () {
return new CallbackResolver(
$this,
$this['phpdi.callable_resolver']
);
});
}

public function offsetGet($id)
Expand Down
56 changes: 56 additions & 0 deletions src/CallbackResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

namespace DI\Bridge\Silex;

use Invoker\CallableResolver;
use Invoker\Exception\NotCallableException;

/**
* This alternative resolver uses the generic Invoker to support PHP-DI's extended callable syntax
*/
class CallbackResolver extends \Silex\CallbackResolver
{
/**
* @var CallableResolver
*/
private $resolver;

public function __construct(\Pimple $app, CallableResolver $resolver)
{
$this->resolver = $resolver;
parent::__construct($app);
}

/**
* @param string $name
* @return array|callable
*/
public function convertCallback($name)
{
// original pattern
if ($this->isValid($name)) {
return parent::convertCallback($name);
}

// try to resolve callback from container
try {
return $this->resolver->resolve($name);
} catch (NotCallableException $e) {
throw new \InvalidArgumentException(sprintf('Service "%s" does not exist.', $name));
}
}

/**
* @param string|callable $name
* @return callable
Copy link
Collaborator

Choose a reason for hiding this comment

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

Don't know whats the convention for return type hints for callables but it should be consistent with convertCallback(), I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

convertCallback always return callable whatever its form, but the old API only hinted as a callable array. Since we use PHPDI callable resolver return type is not limited to callable array, so I hinted callable as return type.

*/
public function resolveCallback($name)
{
// return immediately if $name is callable
if (is_callable($name)) {
return $name;
}
Copy link
Member

Choose a reason for hiding this comment

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

I'm afraid this might be an issue as method calls are considered as statically callable. For example ['Logger', 'warning'] is considered callable by PHP even though it's not a static method, so calling Logger::warning() would be wrong and the "correct" callable would be [$logger, 'warning']. Have a look at this line: https://github.com/PHP-DI/Invoker/blob/master/src/CallableResolver.php#L69

Anyway all that to say that this shortcut will create issues with some types of callables.


return $this->convertCallback($name);
}
}
10 changes: 10 additions & 0 deletions tests/ApplicationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,14 @@ public function the_controller_resolver_should_be_registered_as_a_service()
$this->assertInstanceOf('Closure', $app->raw('resolver'));
$this->assertInstanceOf('DI\Bridge\Silex\Controller\ControllerResolver', $app['resolver']);
}

/**
* @test
*/
public function the_callback_resolver_should_be_registered_as_a_service()
{
$app = new Application();

$this->assertInstanceOf('DI\Bridge\Silex\CallbackResolver', $app['callback_resolver']);
}
}
64 changes: 64 additions & 0 deletions tests/CallbackResolverTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

namespace DI\Bridge\Silex\Test;

use DI\Bridge\Silex\Application;
use DI\Bridge\Silex\Test\Fixture\InvokableController;

class CallbackResolverTest extends BaseTestCase
{
/**
* @test
*/
public function resolver_must_be_able_to_resolve_original_callback_format()
{
$app = new Application();
$app['controller'] = function () {
return new InvokableController();
};

$callable = $app['callback_resolver']->resolveCallback('controller:__invoke');

$this->assertEquals('Hello world', $callable());
}

/**
* @test
*/
public function resolver_must_be_able_to_resolve_custom_callback_format()
{
$app = new Application();
$app['controller'] = function () {
return new InvokableController();
};

$callable = $app['callback_resolver']->resolveCallback('controller');

$this->assertEquals('Hello world', $callable());
}

/**
* @test
*/
public function resolver_must_be_able_to_resolve_callable_callback()
{
$app = new Application();

$callable = $app['callback_resolver']->resolveCallback(function () {
return 'Hello world';
});

$this->assertEquals('Hello world', $callable());
}

/**
* @test
*/
public function resolver_must_throw_exception_when_callback_not_found_in_container()
{
$app = new Application();

$this->setExpectedException('\InvalidArgumentException');
$app['callback_resolver']->resolveCallback('some.service');
}
}
11 changes: 11 additions & 0 deletions tests/Fixture/HelloController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace DI\Bridge\Silex\Test\Fixture;

class HelloController
{
public function __invoke(\ArrayObject $user)
{
return $user['name'];
}
}
11 changes: 11 additions & 0 deletions tests/Fixture/InvokableConverter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace DI\Bridge\Silex\Test\Fixture;

class InvokableConverter
{
public function __invoke($user)
{
return new \ArrayObject(['name' => $user]);
}
}
13 changes: 13 additions & 0 deletions tests/Fixture/InvokableErrorListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace DI\Bridge\Silex\Test\Fixture;

use Symfony\Component\HttpFoundation\Response;

class InvokableErrorListener
{
public function __invoke(\Exception $e, $code)
{
return new Response('Sad panda :(');
}
}
14 changes: 14 additions & 0 deletions tests/Fixture/InvokableMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace DI\Bridge\Silex\Test\Fixture;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class InvokableMiddleware
{
public function __invoke(Request $request)
{
return new Response('Hello from middleware');
}
}
11 changes: 11 additions & 0 deletions tests/Fixture/InvokableViewListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace DI\Bridge\Silex\Test\Fixture;

class InvokableViewListener
{
public function __invoke($controllerResult)
{
return $controllerResult . ' from mars';
}
}
56 changes: 56 additions & 0 deletions tests/FunctionalTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -188,4 +188,60 @@ public function should_pass_request_object_based_on_type_hint()
$response = $app->handle(Request::create('/?name=john'));
$this->assertEquals('Hello john', $response->getContent());
}

/**
* @test
*/
public function should_be_able_to_convert_request()
{
$app = $this->createApplication();

$app->get('/{user}', 'DI\Bridge\Silex\Test\Fixture\HelloController')
->convert('user', 'DI\Bridge\Silex\Test\Fixture\InvokableConverter');

$response = $app->handle(Request::create('/PHPDI'));
$this->assertEquals('PHPDI', $response->getContent());
}

/**
* @test
*/
public function should_be_able_to_use_invokable_middleware()
{
$app = $this->createApplication();

$app->get('/', 'DI\Bridge\Silex\Test\Fixture\InvokableController')
->before('DI\Bridge\Silex\Test\Fixture\InvokableMiddleware');

$response = $app->handle(Request::create('/'));
$this->assertEquals('Hello from middleware', $response->getContent());
}

/**
* @test
*/
public function should_be_able_to_use_invokable_error_listener()
{
$app = $this->createApplication();

$app->error('DI\Bridge\Silex\Test\Fixture\InvokableErrorListener');

$response = $app->handle(Request::create('/'));
$this->assertEquals('Sad panda :(', $response->getContent());
}

/**
* @test
*/
public function should_be_able_to_use_view_listener()
{
$app = $this->createApplication();

$app->get('/', 'DI\Bridge\Silex\Test\Fixture\InvokableController');

$app->view('DI\Bridge\Silex\Test\Fixture\InvokableViewListener');

$response = $app->handle(Request::create('/'));
$this->assertEquals('Hello world from mars', $response->getContent());
}
}