Where to put custom initialization code in Symfony2? - php

Let's say I've two services:
get('my_first_service')
get('my_second_service')
I want to do something like this:
$this->get('my_first_service')->doSomething($this->get('my_second_service'));
This is some initialization code that must be active everywhere in the project. As far as I see I have a few options (both services originates from the vendor-dir (haven't writtem them myself)):
Add the above code (with some modifications) in app.php and/or app_dev.php
Add the above code to every controller action
Create a service with the services as arguments and add the above code in the constructor
The first option seems a little bit "unusual" as I don't link editing the app.php file for something link this. The second option is too inefficient as I need to do this for dozens of actions. The third action seems better but I still need to call this service from somewhere in order to execute the constructor right?
I have the feeling I'm missing a possibility... there must be a better place for this kind of initialization code right?

If you really want to do execute your code before EVERY action, you can listen for the event kernel.controller.
Check this example in the cookboook.
Your listener is a service so you can inject both of your services if needed.

That looks very similar to setter injection. You could add doSomething as a call, with my_second_service as a parameter. That way when you get the first_service, the method will be called automatically, with the correct parameter passed in, and saves you from an ugly hack.
I assume you are already defining the services in configuration.
Eg in YAML:
services:
my_second_service:
# ...
my_first_service:
class: MyFirstServiceClassName
calls:
- [ doSomething, [ #my_second_service ] ]
If doSomething is returning a different object that you need to use, then it sounds like a factory, which you can also configure. Read through this.

Related

Which MVC thing I need here?

In general there are ActionController, Repositories, Models und Views in TYPO3 Flows domain driven MVC system. In our project we use a general File model that contains the Ressource.
Now we need a special "expert" php script like an action controller that doesn't listen to certain url actions. It should get such a File object, do something internal like logging stuff or manipulate the object after a special procedure and give back an information / return falue.
What mvc thing I need for that? An interface? A manager? How you call that and how do I initialise it in TYPO3 Flow? Or is the FileController (action controller) exact the thing I have to use for that?
This "expert" shouldn't listen to url actions but should be used like an action controller like
$expertyThing = new ../../Expertything();
$expertyThing->doCoolStuff($file);
and should can use thinks like the PersistenceManager (by injection or anyhow).
Thanks for any input for that.
I would say Service but I'm not sure if I understood you correctly.
I guess you have some FileController and you have createFileAction there, which creates new File model from uploaded resource, do some validation, transformations, renaming and save it using injected FileRepository.. And you want something in middle.
So I create FileService for that My/FileManager/Domain/Service/FileService.php - inject repository and other services there. And in action or command controllers I inject those services and they do "expert" stuff (and I don't have to duplicate code), like that:
// FileController
public function createFileAction(Resource $resource) {
try {
$file = $this->fileService->processAndSaveFile($resource);
} catch (\Exception $e) {
$this->addFlashMessage($e->getMessage(), '', Message::SEVERITY_ERROR);
$this->forwardToReferringRequest();
}
$this->addFlashMessage('File created');
$this->redirect('fileList');
}
So for me FileService do expert stuff for File - it creates new File model (maybe using FileFactory), do transformations using other services like ImageService, has repository and logger injected (but you can use Aspects for cases like logging).. and if something goes wrong it throws some FileException.
And of course FileService may implement some FileServiceInterface, and you can inject this interface to your controller and define in Objects.yaml which service should be used (it makes it more flexible, so someone else could implement it and replace your FileService not touching it).
This "Service" approach may be a little bit outdated, so maybe someone will suggest better solution.. If you want follow Flow rules, just check how they handle stuff like that in official packages.

Controller as Service - How to pass and return values in an advanced case?

Using Symfony, I am displaying a table with some entries the user is able to select from. There is a little more complexity as this might include calling some further actions e. g. for filtering the table entries, sorting by different criteria, etc.
I have implemented the whole thing in an own bundle, let's say ChoiceTableBundle (with ChoiceTableController). Now I would like to be able to use this bundle from other bundles, sometimes with some more parametrization.
My desired workflow would then look like this:
User is currently working with Bundle OtherBundle and triggers chooseAction.
chooseAction forwards to ChoiceTableController (resp. its default entry action).
Within ChoiceTableBundle, the user is able to navigate, filter, sort, ... using the actions and routing supplied by this bundle.
When the user has made his choice, he triggers another action (like choiceFinishedAction) and the control flow returns to OtherBundle, handing over the results of the users choice.
Based on these results, OtherBundle can then continue working.
Additionally, OtherOtherBundle (and some more...) should also be able to use this workflow, possibly passing some configuration values to ChoiceTableBundle to make it behave a little different.
I have read about the "Controller as Service" pattern of Symfony 2 and IMHO it's the right approach here (if not, please tell me ;)). So I would make a service out of ChoiceTableController and use it from the other bundles. Anyway, with the workflow above in mind, I don't see a "good" way to achieve this:
How can I pass over configuration parameters to ChoiceTableBundle (resp. ChoiceTableController), if neccessary?
How can ChoiceTableBundle know from where it was called?
How can I return the results to this calling bundle?
Basic approaches could be to store the values in the session or to create an intermediate object being passed. Both do not seem particularly elegant to me. Can you please give me a shove in the right direction? Many thanks in advance!
The main question is if you really need to call your filtering / searching logic as a controller action. Do you really need to make a request?
I would say it could be also doable just by passing all the required data to a service you define.
This service you should create from the guts of your ChoiceTableBundleand let both you ChoiceTableBundle and your OtherBundle to use the extracted service.
service / library way
// register it in your service container
class FilteredDataProvider
{
/**
* #return customObjectInterface or scallar or whatever you like
*/
public function doFiltering($searchString, $order)
{
return $this->filterAndReturnData($searchString, $order)
}
}
...
class OtherBundleController extends Controller {
public function showStuffAction() {
$result = $this->container->get('filter_data_provider')
->doFiltering('text', 'ascending')
}
}
controller way
The whole thing can be accomplished with the same approach as lipp/imagine bundle uses.
Have a controller as service and call/send all the required information to that controller when you need some results, you can also send whole request.
class MyController extends Controller
{
public function indexAction()
{
// RedirectResponse object
$responeFromYourSearchFilterAction = $this->container
->get('my_search_filter_controller')
->filterSearchAction(
$this->request, // http request
'parameter1' // like search string
'parameterX' // like sorting direction
);
// do something with the response
// ..
}
}
A separate service class would be much more flexible. Also if you need other parameters or Request object you can always provide it.
Info how to declare controller as service is here:
http://symfony.com/doc/current/cookbook/controller/service.html
How liip uses it:
https://github.com/liip/LiipImagineBundle#using-the-controller-as-a-service

Symfony2 impersonate listener with parameters injection

i'm currently working on a custom logic impersonate listenener.
i overrided the Symfony\Component\Security\Http\Firewall\SwitchUserListener because i would like to perform call to some entities repositories ( not User entity ) before authorize the switch event :
for example , i would like to authorize the switch if and only if the user to impersonate has already gave rights to user requesting for the switch.
is it possible to inject new parameters such as doctrine service or some arrays values to an overrided listener ?
the call to my custom SwitchUserListenener :
in services.yml
parameters:
security.authentication.switchuser_listener.class: acme\appBundle\EventListener\SwitchUserListener
Your solution may be here:
https://github.com/symfony/symfony/blob/2.6/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php#L597
The service security.authentication.switchuser_listener defined by Symfony is an abstract service. It is not actually the service being used as the listener, but it is the same class.
Go up in the code a bit to:
https://github.com/symfony/symfony/blob/2.6/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php#L233
foreach ($firewalls as $name => $firewall) {
list($matcher, $listeners, $exceptionListener) = $this->createFirewall($container, $name, $firewall, $authenticationProviders, $providerIds);
This is the name of the firewall you created where the user switch will be possible.
Now you have a couple of options to override the listener.
You can continue to override the class parameter, then manipulate the arguments as Symfony is already doing:
$listener->replaceArgument(1, new Reference($userProvider));
$listener->replaceArgument(3, $id);
$listener->replaceArgument(6, $config['parameter']);
$listener->replaceArgument(7, $config['role']);
Or more simply, compile additional method calls in your extension.
The other solution, would be to create a CompilerPass which systematically deletes the original listeners and replaces them with your own. This might be achievable in your own bundles extension provided it is loaded after the SecurityBundle.
$container->removeDefinition($id);
EDIT:
Another solution, perhaps more simple than the above, is to create another event listener.
One which fires on the request. The important thing here is the listener must have a higher priority to the listeners you are overriding (I used listener(s) because there are potentially more than one switch user listener).
The tricky part will be getting a list of the overridden listeners IDs, because you are going to need to loop over them and call your own methods to inject your new dependencies after the listeners are instantiated, but before they triggered.

How do I pass a parameter to a validation constraint in Symfony2 - in yml

I am trying to add a bundle-wide parameter to my application so that I can add it to my Validation Constraint file (validation.yml):
myApp\myBundle\Entity\Contact:
properties:
name:
- NotBlank: { message: "%myvariable%" }
I added my parameter normally in config.yml:
parameters:
# Validation config
myvariable: Please tell us your name.
But the page just renders the %myvariable% text, rather than the desired string. I also wish to use this parameter in my FormBuilderInterface when adding the validation messages to the page for usage in JavaScript. Does yml allow this? If not, how do I include such a parameter at a higher level?
No, it's not currently possible.
It has nothing to do with YAML or XML or even service definitions. Validator component reads validation rules by itself - as you can see, the structure is quite different than for service definitions. Unfortunately, it does not replace the parameters in constraints.
The main logic resides in \Symfony\Component\Validator\Mapping\Loader\YamlFileLoader which is created by \Symfony\Component\Validator\ValidatorBuilder::getValidator.
You could make this happen by:
Overriding definition of validator.builder service.
It's constructed using %validator.builder.factory.class%::createValidatorBuilder, but as you have to get parameter bag somehow, there is not enough dependencies - class factory is in use, not service factory.
Creating new class, which extends ValidatorBuilder.
It should take parameter bag into constructor or via setter. It should be configured in step (1) to be passed here.
This class would create file loaders of another class (see 3), also pass that parameter bag into it.
Creating new classes for YamlFileLoader and YamlFilesLoader. Additional 2 for each format that you would want to support.
It would additionally take parameter bag into constructor and override some functionality. For example, I think all parameter handling could be done in newConstraint method - iterate through options, resolve parameters, then call parent method with replaced options.
It's nice that Symfony could be extended like that (possibly not so nicely in this use-case), but I guess it would be easier to just write your own constraint with custom constraint validator, which would inject that parameter into it.
Also consider a wrapper around validator service - if you just need to replace the validation messages, you could replace the validator service, injecting original one into it. See http://symfony.com/doc/current/service_container/service_decoration.html for more information.

How can I change Zend Framework's routing schema to not use key/value pairs?

Rather than using controller/action/key1/value1/key2/value2 as my URL, I'd like to use controller/action/value1/value2. I think I could do this by defining a custom route in my Bootstrap class, but I want my entire application to behave this way, so adding a custom route for each action is out of the question.
Is this possible? If so, how would I then access valueN? I'd like to be able to define the parameters in my action method's signature. e.x.:
// PostsController.php
public function view($postID) {
echo 'post ID: ' . $postID;
}
I'm using Zend Framework 1.9.3
Thanks!
While I don't think it's possible with the current router to allow N values (a fixed number would work) you could write a custom router that would do it for you.
I would question this approach, however, and suggest that actually listing all of your routes won't take long and will be easier in the long run. A route designed as you've suggested would mean that either your named parameters are always in the same order, i.e.
/controller/action/id/title/colour
or that they are almost anonymous
/controller/action/value1/value2/value3
With code like
$this->getRequest()->getParam('value2'); //fairly meaningless
Does it have to be N or can you say some finite value? For instance can you imagine that you'll never need more than say 5 params? If so you can set up a route:
/:controller/:action/:param0/:param1/:param2/:param3/:param4
Which will work even if you don't specify all 5 params for every action. If you ever need 6 somewhere else you can just add another /:paramN onto the route.
Another solution I've worked with before is to write a plugin which parses the REQUEST_URI and puts all the extra params in the request object in the dispatchLoopStartup() method. I like the first method better as it makes it more obvious where the params are coming from.

Categories