Symfony 4 - Custom Folder structure and Services - symfony

I'm looking to implement a folder structure recommended by Nikola Posa.
The structure I would like is something like what is below.
src/
Domain/
User/
UserEntity.php
UserController.php
Pages/
DefaultPageController.php
The idea is to logically group/namespace features or similar content. I seem to be getting this error:
The file "../src/Controller" does not exist (in: /Users/dev/Sites/web/html/sandbox/php/crud/config) in /Users/dev/Sites/web/html/sandbox/php/crud/config/services.yaml (which is loaded in resource "/Users/dev/Sites/web/html/sandbox/php/crud/config/services.yaml").
I'm not sure how important it is to wire up these as services. If I comment out the App\Controller property of the services.yaml, it goes away.
How can I load controllers in service.yaml with a src/Domain/Feature/FeatureController.php structure?

You could of course go old school and just define each controller service individually:
# config/services.yaml
Domain\Feature\FeatureController:
tags: ['controller.service_arguments']
However, once you get used to autowire then spelling out each service is a pain. As an alternative you can use the autoconfigure capability to add the controller tag to selected classes. Start by declaring an empty interface and have your controllers implement it:
interface ControllerInterface {}
class SomeController implements ControllerInterface
Then adjust src/Kernel.php
# src/Kernel.php
class Kernel {
protected function build(ContainerBuilder $container)
{
$container->registerForAutoconfiguration(ControllerInterface::class)
->addTag('controller.service_arguments');
Of course this just takes care of the controller issue. You will probably encounter a number of other autowire related issues.

I would go and create a Controller interface like Cerad suggest. However, since Symfony 3.3 you don't have to touch the kernel:
services:
_instanceof:
YourApp\Ui\ControllerInterface:
public: true
tags: ['controller.service_arguments']
Et voila.

Related

Is it possible to use a container parameter defined in a bundle to autowire an optional interface for services in that bundle only?

A custom Symfony bundle is used to add code different projects of mine. One services uses an optional parameter with an interface as type hint. If the parameter is null depends on whether autowire knows what to inject for the interface. In the following example the project tells autowire to use SomeClass for all SomeInterface parameters:
// The interface with in the bundle
interface SomeInterface { }
// The method to be autowired within the bundle
public function doSomething(SomeInterface $some) {
if (null !== $some) {
$some->thing();
}
}
// service.yaml within the PROJECT
Name\Space\SomeInterface: '#Name\Space\SomeClass'
This works fine and when the line Name\Space\SomeInterface: '#Name\Space\SomeClass' is removed, the services works with $some = null.
While it works, this approach has one major draw back: All parameters of type SomeInterface are autowired with SomeClass. If multiple bundles would use SomeInterface it would not be possible to tell autowire to use SomeClass for BundleA and OtherClass for BundleB.
To solve this, I could move the autowire config to the bundle and move it with a container parameter:
// service.yaml within the BUNDLE
Name\Space\SomeInterface: '%mybundle.some_interface%'
// config/packages/bundleA.yaml in PROJECT
mybundle:
some_interface: '#Name\Space\SomeClass'
While this looks promising, it does not work:
A service definition must be an array or a string starting with "#"
but "string" found for service "Name\Space\SomeInterface" in
"/bundleA/config/services.yaml". Check your YAML syntax.
It seems that %bundleA.some_interface% is not properly replaced by #Name\Space\SomeClass (although php bin/console debug:container --parameters shows, that bundleA.some_interface is properly set).
So, is there any way to define an interface for autowire using a container parameter?

How to set a custom ErrorRenderer in symfony 5?

Problem:
I tried to register a CustomErrorRenderer on Symfony 5 to render my CustomException, but all the time the TwigErrorRenderer is called. I thought probably self-defined renderers might be preferred?
CustomErrorRenderer.php:
namespace App\ErrorRenderer;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
use Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRendererInterface;
class CustomErrorRenderer implements ErrorRendererInterface
{
public function render(\Throwable $exception): FlattenException
{
dd('CustomErrorRenderer.render() called');
// TODO check if CustomException, else refer to preset renderer...
}
}
services.yaml:
services:
App\ErrorRenderer\CustomErrorRenderer:
tags: ['error_renderer.renderer']
# also tried: ['error_renderer.renderer', 'error_renderer.html','error_handler.error_renderer' , 'error_handler.error_renderer.html']
What is wrong with that?
Background:
I have a web application and want to handle exceptions that are related to my internal business logic separately. E.g., a user wants to book a resource but it is currently not available. While current errors/exceptions are handled by the TwigErrorRenderer (or default HtmlErrorRenderer), I would like to add my own Renderer (ideally just extending the TwigErrorRenderer, so that I can use some specific, self-defined twig templates). By that, I aim to have a better UI, e.g., my custom exceptions being rendered while still the menu of the web application is shown. As my exceptions are not related to how the data is accessed (e.g. http), I do not want to use HttpExceptions and their status code.
This was not particularly easy to figure out.
I made a copy of vendor/symfony/twig-bridge/ErrorRenderer/TwigErrorRenderer.php into my app src\CustomerErrorRenderer.php.
Then override the error_renderer in services.yaml:
services:
error_renderer:
class: App\CustomErrorRenderer
arguments: ['#twig', '#error_handler.error_renderer.html','%kernel.debug%']
Of course, that's not exactly how they call in their code when I tried to test using /_error/404 for instance.
They actually use arguments more like:
error_renderer:
class: App\Twig\CustomErrorRenderer
arguments:
- '#twig'
- '#error_handler.error_renderer.html'
- !service
factory: [ 'Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer', 'getAndCleanOutputBuffer' ]
arguments: ['#request_stack']

Akénéo service pim_catalog.saver.product is private and can't be used in my bundle's Controller

According to the docs, https://docs.akeneo.com/4.0/manipulate_pim_data/product/save.html, I should be able to call $saver = $this->get('pim_catalog.saver.product'); in my bundle's controller, like this:
<?php
namespace XXX\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
class ExportController extends Controller
{
public function exportProduct($id): Response
{
$saver = $this->get('pim_catalog.saver.product');
$saver->save($id);
return new Response(
'<html><body>foo</body></html>'
);
}
}
However I get this error:
[2020-11-05 13:44:58] request.CRITICAL: Uncaught PHP Exception Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException: "The "pim_catalog.saver.product" service or alias has been removed or inlined when the container was compiled. You should either make it public, or stop using the container directly and use dependency injection instead." at /var/www/html/pim/vendor/symfony/dependency-injection/Container.php line 275 {"exception":"[object] (Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException(code: 0): The "pim_catalog.saver.product" service or alias has been removed or inlined when the container was compiled. You should either make it public, or stop using the container directly and use dependency injection instead. at /var/www/html/pim/vendor/symfony/dependency-injection/Container.php:275)"} []
So I would want to override the declaration of this service by adding a services.YML file into my bundle's configuration directory (mybundle/Resources/config/services.YML) :
services:
pim_catalog.saver.product:
public: true
priority: 999
However it still doesn't work.
According to the documentation of Symfony 4, I should create an Extension class maybe. It should have the same name than Akénéo's one, but I don't find the latter.
What should I do?
You have some hints in the error message.
The "pim_catalog.saver.product" service or alias has been removed or
inlined when the container was compiled. You should either make it
public, or stop using the container directly and use dependency
injection instead
As a best practice, I would discourage making it public since you are not the service provider and there is a better and easier way to solve your issue. So you are left with the second option which is the way to go in my humble opinion.
I don't know specifically about Akeneo but I do know about Symfony and dependency injection. So I would suggest using the argument typehint in your controller méthod.
1. Check the autowiring class/interface to use for service 'pim_catalog.saver.product'
List all defined autowiring and look for the one mentioning 'pim_catalog.saver.product'
console debug:autowiring
OR just check for all autowired class/interface with akeneo in the name (this is likely)
console debug:autowiring akeneo
2. Update your controller code to add the type-hinted argument
Something in the lines of the following
<?php
namespace XXX\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
// I just picked that interface after looking it up in Akeneo documentation
// But there is another one that can be used if you want to save multiple
// products, so beware.
use Akeneo\Tool\Component\StorageUtils\Saver\SaverInterface;
class ExportController extends Controller
{
public function exportProduct($id, SaverInterface $saver): Response
{
$saver->save($id);
return new Response(
'<html><body>foo</body></html>'
);
}
}
The process is documented here https://symfony.com/doc/4.4/controller.html#fetching-services
3. (Hopefully) Enjoy a working code !
;-)

What's the difference between $this->container->get('someservice') and $this->get('someservice')?

I am working in a project with several services called inside controllers in this way :
$service = $this->get('myservice');
but i noticed that i could call 'myservice' in this other way:
$service = $this->container->get('myservice');
Services of course take advantage of DI
Example of service declaration:
myservice:
public: true
class: path/to/service
arguments:
- '#someEntityRepository'
someEntityRepository:
class: Doctrine\ORM\EntityRepository
factory: ['#doctrine.orm.entity_manager', getRepository]
arguments: [path\to\Entity\someEntity]
Is there any difference between this calls? If yes, which should i use? Why?
No difference. Use $this->get() when you extend symfony's Controller class. Its shorter to type.
$this->get('myservice'); is a shortcut for $this->container->get('myservice');. And is available in the Controller base class (Symfony\Bundle\FrameworkBundle\Controller).
Petter was right about the difference between $this-get and $this->container->get(), it is no difference. But you will create a good code if you define Controller as service and inject your services via dependency injections. It adds more flexibility and performance.

Why set factory in DefinitionDecorator instead of set new class with calls directive?

There is part of code in FOSElasticaExtension Extension class
$Def = new DefinitionDecorator('foo');
$Def->replaceArgument(0, $bar);
$Def->addTag('baz', array( 'name' => $qux, ));
$Def->setFactory(array(new Reference('quux'), 'corge'));
so in yaml it might look like this:
services:
foo:
arguments:
- '$bar'
tags:
- { name: baz }
factory: ["#quux", corge]
Why set factory in DefinitionDecorator instead of set new class with calls: directive?
services:
foo:
arguments:
- '$bar'
tags:
- { name: baz }
class: #quux
calls: corge
Can you please write a code example how you would expect it to look like? I don't really get the point of your question.
To generically answer why it's done like this you have to understand how Symfony compiles the service container. The service container has a huge impact on performance that's why it's compiled after the tree is completely built, which is after instantiating all the extensions. That also means that your extension doesn't really have the classes, but only references to classes. I assume the index class is not registered as a service and that's why it must be retrieved via the registered client service which is used as a factory. I hope this answers your question, if not feel free to expand your question or add a comment.
edit: That is an interesting question. I checked Symfony's DependencyInjection, but from a cursory glance I can't find how exactly both approaches are different. From checking DefinitionTest I assume it's possible to do something like:
$def->setMethodCalls(array(array($factoryReference,'getIndex')));
which looks a bit more complicated. This might be why setFactory was preferred.

Resources