Using decoration stack to decorate an existing service? - php

Symfony docs show a very neat way to create a stack of decorators:
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
return function(ContainerConfigurator $container) {
$container>stack('decorated_foo_stack', [
inline_service(\Baz::class),
inline_service(\Bar::class),
inline_service(\Foo::class),
])
;
};
And show this as an alternative to doing:
// config/services.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
return function(ContainerConfigurator $configurator) {
$services = $configurator->services();
$services->set(\Foo::class);
$services->set(\Bar::class)
->decorate(\Foo::class, null, 5)
->args([service('.inner')]);
$services->set(\Baz::class)
->decorate(\Foo::class, null, 1)
->args([service('.inner')]);
};
Problem is, the "neater" approach leaves service Foo::class undecorated. Applications that use the original definition do not go through the stack, but access the original service.
In my case, I have to decorate a service called api_platform.serializer.context_builder. Doing this works in creating a decorated stack:
$services->stack(
'decorated_context_builder',
[
inline_service(SupportTicketMessageContextBuilder::class),
inline_service(LeadContextBuilder::class),
inline_service(BidContextBuilder::class),
inline_service(PartnerContextBuilder::class),
inline_service(WebProfileContextBuilder::class),
service('api_platform.serializer.context_builder'),
]
);
The service is provided by a vendor dependency, and it's used by that dependency. When it uses the injected api_platform.serializer.context_builder it completely ignores my newly created decorated_context_builder stack.
Instead, if I create the stack manually:
$services->set(LeadContextBuilder::class)
->decorate('api_platform.serializer.context_builder', priority: 4)
;
$services->set(BidContextBuilder::class)
->decorate('api_platform.serializer.context_builder', priority: 3)
;
// etc, etc, etc
... it works as expected.
How can I use a decoration stack to decorate an existing service definition, so that the existing definition gets decorated?

Apparently this is not possible with stacking decorators, as they are not currently compatible with decorating other services, just to create stand-alone stacks.

Related

Testing Laravel Service Providers

I'm (we're) creating a package that acts as a core component for our future CMS and of course that package needs some unit tests.
When the package registeres, the first thing it does is set the back/frontend context like this:
class FoundationServiceProvider extends ServiceProvider
{
// ... stuff ...
public function register()
{
// Switch the context.
// Url's containing '/admin' will get the backend context
// all other urls will get the frontend context.
$this->app['build.context'] = request()->segment(1) === 'admin'
? Context::BACKEND
: Context::FRONTEND;
}
}
So when I visit the /admin url, the app('build.context') variable will be set to backend otherwise it will be set to `frontend.
To test this I've created the following test:
class ServiceProviderTest extends \TestCase
{
public function test_that_we_get_the_backend_context()
{
$this->visit('admin');
$this->assertEquals(Context::BACKEND, app('build.context'));
}
}
When I'm running the code in the browser (navigating to /admin) the context will get picked up and calling app('build.context') will return backend, but when running this test, I always get 'frontend'.
Is there something I did not notice or some incorrect code while using phpunit?
Thanks in advance
Well, this is a tricky situation. As I understand it, laravel initiates two instances of the framework when running tests - one that is running the tests and another that is being manipulated through instructions. You can see it in tests/TestCase.php file.
So in your case you are manipulating one instance, but checking the context of another (the one that did not visit /admin and is just running the tests). I don't know if there's a way to access the manipulated instance directly - there's nothing helpful in documentation on this issue.
One workaround would be to create a route just for testing purposes, something like /admin/test_context, which would output the current context, and the check it with
$this->visit('admin/test_context')->see(Context::BACKEND);
Not too elegant, but that should work. Otherwise, look around in laravel, maybe you will find some undocumented feature.

Symfony2. Force a service to get instanced

I'm working with symfony 2.8, I'm facing a situation where a service must be instanced even if it is not requested somewhere.
Why ? Because this Core service configure tagged services that are transfered by method calling into Core ( I did this in a compiler pass ).
And then, if I request one of these tagged services without request Core, it will not be configured and then be unusable.
Here is the compiler pass :
$coreDefinition = $container->findDefinition(
'app.improvements.core'
);
$taggedAppliers = $container->findTaggedServiceIds('app.improvement.applier');
foreach ($taggedAppliers as $id => $tags) {
$coreDefinition->addMethodCall(
'registerApplier',
[new Reference($id)]
);
}
$taggedImprovements = $container->findTaggedServiceIds(
'app.improvement'
);
// Only method found to initialize improvements.
foreach ($taggedImprovements as $id => $tags) {
$coreDefinition->addMethodCall(
'registerImprovement',
[new Reference($id)]
);
}
To sum up, the Appliers registers Improvement and Core registers Appliers. The core associate improvements with appliers because each improvement must be registered in a specific applier that the core stores.
The problem I that when I only request a Applier, its improvements are not registered into it because the core isn't instancied.
Thank you.
Design problems aside, the easiest way to instantiate a service is to use an event listener. In your case you would listen for the kernel.request and pull your service from the container.
class RequestListener
{
public function onKernelRequest(GetResponseEvent $event)
{
$event->getKernel()->getContainer()->get('service_id');
}
}

What's the difference between Commands and Events in the context of Laravel 5?

So Laravel 5 was finally released yesterday with the final implementation of the command bus but I was wandering, what's the real difference in using a command bus over event mechanisms that we have in the previous releases?
Ok, I see the reason that it can be used to create commands from Request objects which is pretty useful but beyond that it seems to behave in a similar way even down to the whole queuing functionality for events now?
Can you please provide examples of use cases and where the pros and cons of either are?
Commands are things about to happen right now. i.e. "CreateUser"
Events are things that have just occured right now - i.e. "UserSuccessfullyCreated"
The differences appear minor - but have some key differences.
Commands must be specifically called/dispatched. I.e. if you want to
do CommandX - you must call CommandX somewhere.
Events respond to an event firing anywhere in your application.
The great thing is multiple event handling classes can respond to the
same event.
Lets do an example to illustrate it best. Lets say we create a user, and we want to send them a welcome email and also update our newsletter list.
In a Command Scenario would would do
AdminController {
function create() {
Bus::dispatch(new CreateUser(Auth::user());
}
}
then in our CommandClass - we would do
public function handle(CreateUser $auth)
{
// 1. Create the user here
// 2. Send welcome email
// 3. Update our newsletter
}
But if we use events - we would do something like this in our CommandClass
public function handle(CreateUser $auth)
{
// 1. Create the user here
Event::fire(new UserWasCreated($user));
}
then we can create as many events as we want to listen to that event and do something:
EventClassA
Event::listen('UserWasCreated', function($event)
{
// 2. Send welcome email
});
EventClassB
Event::listen('UserWasCreated', function($event)
{
// 3. Update our newsletter
});
The great thing is separation of concerns. The command "createuser" now does not need to worry itself about what happens after a user is created. It just needs to CreateUser.
Also - if we want to add another function after a user signs up - say enter them in a lotto draw - you can just add another Event Class and add a new event listener.
EventClassC
Event::listen('UserWasCreated', function($event)
{
// 4. Register them in lotto
});
Notice how we didnt need to touch the command CreateUser class code at all? This provides a true separation concerns of classes in a OOP style approach.
I just want to share my understanding of this concept on top of the correct answer:
The main difference is that Commands can change a Model state, while Events just react to a state change.
COMMANDS:
Commands in Laravel represent the implementation of the Command design pattern.
The main adventages of Commands:
The can be accessed from anywhere
They are very easy to read by any other developer
To create a Command in Laravel 5:
You need to generate a command DTO (which can implement the SelfHandling interface). Using php artisan make:command {command-name}
Example: php artisan make:command Course/PostCourseCommand
The naming convention for commands: speak the business language and add postfix Command to it
To call (dispatch) the command from you controller, you can use:
$this->dispatch(new PostCourseCommand())
or
Bus::dispatch(new PostCourseCommand());
Side Note:
The "dispatch from request” feature is a nice way to skip passing the variables to the command constructor one by one, instead it will resolve this for you:
Example:
$test_request = Request::create('/test', 'GET', [
'name' => 'Mahmoud',
'nickname' => 'Mega'
]);
$result = Bus::dispatchFrom(
CreateCourse::class, $test_request
);
Finally:
You can separate the handler function and it’s logic from the command DTO to the Handlers directory, to do so:
Generate a command handler via artisan
art handler:command --command="Course/PoatCourseCommand"
remove the SelfHandling interface from the Command class so it will search for a handler to handle it.
EVENTS:
Events in Laravel represent the implementation of the Observer design pattern.
To create an Event in Laravel 5:
use artisan: art make:event {event-name}
Example: art make:event LogSomething
generate an event handler for that event
art handler:event LogSomething --event="LogSomething"
register the event and it’s handler in the event service provider (app/Providers/EventServiceProvider.php)
Example:
protected $listen = [
\Zzz\Events\LogSomething::class => [ // event.name
\Zzz\Handlers\Events\LogSomething::class, //EventListener
],
],
To call (fire) an Event:
use:
Event::fire(New LogSomething());
or you can use the event helper
event(New LogSomething());
Side Note:
alternatively you can generate an event by simply registering the event in the service provider then running this command.
php artisan event:generate << this will automatically add the two classes for you
Also you can listen to an event without creating an event handler or registering a lister in the listeners array, by just going to the event service prover and inside the boot function writing your event and its action (NOT RECOMMENDED). Example:
Event::listen('XXX\Events\DoSomethingElse', function($event)
{
dd('handle me :)');
});
Finally: you can queue an event or even subscribe to multiple events from within the class itself..

Symfony 2 - how to parse %parameter% in my own Yaml file loader?

I have a Yaml loader that loads additional config items for a "profile" (where one application can use different profiles, e.g. for different local editions of the same site).
My loader is very simple:
# YamlProfileLoader.php
use Symfony\Component\Config\Loader\FileLoader;
use Symfony\Component\Yaml\Yaml;
class YamlProfileLoader extends FileLoader
{
public function load($resource, $type = null)
{
$configValues = Yaml::parse($resource);
return $configValues;
}
public function supports($resource, $type = null)
{
return is_string($resource) && 'yml' === pathinfo(
$resource,
PATHINFO_EXTENSION
);
}
}
The loader is used more or less like this (simplified a bit, because there is caching too):
$loaderResolver = new LoaderResolver(array(new YamlProfileLoader($locator)));
$delegatingLoader = new DelegatingLoader($loaderResolver);
foreach ($yamlProfileFiles as $yamlProfileFile) {
$profileName = basename($yamlProfileFile, '.yml');
$profiles[$profileName] = $delegatingLoader->load($yamlProfileFile);
}
So is the Yaml file it's parsing:
# profiles/germany.yml
locale: de_DE
hostname: %profiles.germany.host_name%
At the moment, the resulting array contains literally '%profiles.germany.host_name%' for the 'hostname' array key.
So, how can I parse the % parameters to get the actual parameter values?
I've been trawling through the Symfony 2 code and docs (and this SO question and can't find where this is done within the framework itself. I could probably write my own parameter parser - get the parameters from the kernel, search for the %foo% strings and look-up/replace... but if there's a component ready to be used, I prefer to use this.
To give a bit more background, why I can't just include it into the main config.yml: I want to be able to load app/config/profiles/*.yml, where * is the profile name, and I am using my own Loader to accomplish this. If there's a way to wildcard import config files, then that might also work for me.
Note: currently using 2.4 but just about ready to upgrade to 2.5 if that helps.
I've been trawling through the Symfony 2 code and docs (and this SO question and can't find where this is done within the framework itself.
Symfony's dependency injection component uses a compiler pass to resolve parameter references during the optimisation phase.
The Compiler gets the registered compiler passes from its PassConfig instance. This class configures a few compiler passes by default, which includes the ResolveParameterPlaceHoldersPass.
During container compilation, the ResolveParameterPlaceHoldersPass uses the Container's ParameterBag to resolve strings containing %parameters%. The compiler pass then sets that resolved value back into the container.
So, how can I parse the % parameters to get the actual parameter values?
You'd need access to the container in your ProfileLoader (or wherever you see fit). Using the container, you can recursively iterate over your parsed yaml config and pass values to the container's parameter bag to be resolved via the resolveValue() method.
Seems to me like perhaps a cleaner approach would be for you to implement this in your bundle configuration. That way your config will be validated against a defined structure, which can catch configuration errors early. See the docs on bundle configuration for more information (that link is for v2.7, but hopefully will apply to your version also).
I realise this is an old question, but I have spent quite a while figuring this out for my own projects, so I'm posting the answer here for future reference.
I tried a lot of options to resolve %parameter% to parameters.yml but no luck at all. All I can think of is parsing %parameter% and fetch it from container, no innovation yet.
On the other hand I don't have enough information about your environment to see the big picture but I just come up with another idea. It can be quite handy if you declare your profiles in your parameters.yml file and load it as an array in your controller or service via container.
app/config/parameters.yml
parameters:
profiles:
germany:
locale: de_DE
host_name: http://de.example.com
uk:
locale: en_EN
host_name: http://uk.example.com
turkey:
locale: tr_TR
host_name: http://tr.example.com
You can have all your profiles as an array in your controller.
<?php
namespace Acme\DemoBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class DefaultController extends Controller
{
public function indexAction()
{
$profiles = $this->container->getParameter('profiles');
var_dump($profiles);
return $this->render('AcmeDemoBundle:Default:index.html.twig');
}
}
With this approach
you don't have to code a custom YamlLoader
you don't have to worry about importing parameters into other yml files
you can have your profiles as an array anytime you have the $container in your hand
you don't have to load/cache profile files one by one
you don't have to find a wildcard file loading solution
If I got your question correctly, this approach can help you.

Symfony2 explanation of CompilerPass?

Can someone explain what a compilerpass is?
CompilerPass implementations are some kind of listeners that are executed after dependency injection container is built from configuration files and before it is saved as plain PHP in cache. They are used to build some structures that requires access to definitions from outer resources or need some programming that is not available in XML/YAML configuration. You can consider them as "final filters" that can modify entire DIC.
Let's consider a TwigBundle and its TwigEnvironmentPass. What it does is quite simple:
Fetch a reference to twig service (defined as <service id="twig" class="..." ...>)
Find all services that has been tagged with twig.extension tag. To do that you have work on complete DIC (built from XML configuration files) as those services might be defined in any bundle.
Build a custom code for service creation method.
As a final result the following code will be generated:
protected function getTwigService()
{
$this->services['twig'] = $instance = new \Twig_Environment($this->get('twig.loader'), ...);
// THIS HAS BEEN ADDED THANKS TO THE TwigEnvironmentPass:
$instance->addExtension(new \Symfony\Bundle\SecurityBundle\Twig\Extension\SecurityExtension($this->get('security.context')));
$instance->addExtension(new \Symfony\Bundle\TwigBundle\Extension\TransExtension($this->get('translator')));
$instance->addExtension(new \Symfony\Bundle\TwigBundle\Extension\TemplatingExtension($this));
$instance->addExtension(new \Symfony\Bundle\TwigBundle\Extension\FormExtension(array(0 => 'TwigBundle::form.html.twig', 1 => 'SiteBundle::widgets.html.twig')));
$instance->addExtension(new \MyProject\SiteBundle\Twig\Extension\MyVeryOwnExtensionToTwig($this));
return $instance;
}

Categories