Symfony ExceptionListener: how to handle dependencies? - php

I'm experimenting with my own framework based on symfony2 components, like described in a tutorial series by Fabien Potencier. Everything is clear to me so far except for one thing.
I've established a container builder described in the last part of the tutorial:
http://fabien.potencier.org/article/62/create-your-own-framework-on-top-of-the-symfony2-components-part-12
I've added twig to the containerbuilder:
$sc->register('twigLoader', 'Twig_Loader_Filesystem')
->setArguments(array('%templatePath%'));
$sc->register('twig', 'Twig_Environment')
->setArguments(array(new Reference('twigLoader'), array()));
Than there is the part with the ErrorController which will be called when an exception occurs:
$sc->register('listener.exception', 'Symfony\Component\HttpKernel\EventListener\ExceptionListener')
->setArguments(array('Calendar\\Controller\\ErrorController::exceptionAction'));
But to display a proper error page which is rendered by twig I need the twig dependency in the error controller. How can I do this?
Please note that I don't use the full symfony framework, just the components!

D'Oh... I already had it. The only thing I had to change is that the error controller doesn't have to return a response object. Instead it's returning an array now and than my normal response listener with the twig object is reacting and I have a custom error page processed by twig...

Related

You have requested a non-existent parameter "name" error when trying to load a template using Symfony templating component

I am using the Symfony templating component to add templating functionality to the project I am working on, I am following the docs here but I am using the service container Symfony component and adding the code in the docs using this code:
$containerBuilder->register('template_name_parser', Symfony\Component\Templating\TemplateNameParser::class);
$containerBuilder->register('file_system_loader', Symfony\Component\Templating\Loader\FilesystemLoader::class)
->setArguments([realpath('./') . '/app/Views/%name%']);
$containerBuilder->register('templating', Symfony\Component\Templating\PhpEngine::class)
->setArguments([new Reference('template_name_parser'), new Reference('file_system_loader')]);
But when I try to load a template file using this code:
container->get('templating')->render('home.php')
I get this error:
Something went wrong! (You have requested a non-existent parameter
"name".)
So as #yceruto mentioned Symfony DI will treat strings between %% as parameters so to escape it we need to add % in front of those strings as mentioned here, so I updated my code like this:
$containerBuilder->register('file_system_loader', Symfony\Component\Templating\Loader\FilesystemLoader::class)
->setArguments([realpath('./') . '/app/Views/%%name%']);

rendering symfony/form in non-symfony application

I'm trying to rewrite a "custom framework" application to the Symfony, but I can not do everything at once, so I've divided the process into steps.
From important notes - I've already implemented the symfony/templating component and the symfony/twig-bridge component.
That's how I want to output the form in the template:
<?php echo $view['form']->form($form) ?>
As I'm doing so the following error is thrown:
Symfony\Component\Form\Exception\LogicException
No block "form" found while rendering the form.
/var/www/html/vendor/symfony/form/FormRenderer.php on line 98
To render the templates I'm using the DelegatingEngine which uses the PhpEngine and the TwigEngine.
Setting up the Twig with the \Symfony\Bridge\Twig\Extension\FormExtension is well documented, but what I'm missing is the php setup. This is how I'm doing this:
new \Symfony\Component\Form\Extension\Templating\TemplatingExtension($phpEngine, $this->csrfManager());
Could you point me what am I missing or what's wrong with my setup?
I think the simplest way would have been to install the Symfony 3.3 standard edition next to your app (pending the release of Symfony Flex).
After this, find a way to use the router of Symfony with the router of your application.
So you could have the full Symfony framework, create your form type in it and let Symfony render it :
With an ajax call
With a new Symfony Kernel in your legacy app
I've found the answer:
I was using the wrong FormRendererEngineInterface. Instead of relying on the \Symfony\Component\Form\Extension\Templating\TemplatingExtension class I've registered the form helper by myself:
$phpEngine = new PhpEngine(new TemplateNameParser(), new FilesystemLoader(realpath(__DIR__.'/../Template').'/%name%'));
$twigEngine = new TwigEngine($this->twig(), new TemplateNameParser());
$this->TemplateEngine = new DelegatingEngine(array(
$phpEngine,
$twigEngine,
));
$phpEngine->addHelpers(array(
new FormHelper(new FormRenderer($this->twigFormRendererEngine())),
));
As you can see in the TemplatingEngine:
public function __construct(PhpEngine $engine, CsrfTokenManagerInterface $csrfTokenManager = null, array $defaultThemes = array())
{
$engine->addHelpers(array(
new FormHelper(new FormRenderer(new TemplatingRendererEngine($engine, $defaultThemes), $csrfTokenManager)),
));
}
It relies on the TemplatingRendererEngine while I need the TwigRendererEngine instance, as the form templates are the twig files.
Correct me if my explanation is wrong, but the solution is working.

Symfony form error message parameters usage

What is the purpose of the message parameters array in a Symfony form error ?
For example, I have the following case: on a form I have a subscriber in which, based on the information given by the user an API may be called and some additional errors could be added to the Symfony form.
As such, when an error occurs, I add a new error on a field:
$myForm->get('name')->addError(
new FormError('name.api_invalid', null, array('{{ api_name }}' => $someValue))
);
where 'name.api_invalid' is defined in message.en.yml as
name.api_invalid: "The API says the name is actually {{ api_name }}. Please fix before proceeding."
While the message is translated, the "parameters" are not replaced.
Is this not how form error parameters are supposed to work ?
Note: I can make it work by using
$myForm->get('name')->addError(
new FormError(
$this->translator->trans('name.api_invalid', array('{{ api_name }}' => $someValue))
)
);
but I'm really curious about those error parameters.
Thank you!
The behavior you are looking at was changed in Symfony 2.2:
Translating validation errors is now optional. You can still do so manually if you like, or you can simplify your templates to simply output the already translated message.
If you look at the form_errors block in 2.1 vs. the form_errors block in 2.2 you will see the difference in how the errors are displayed. You could override the block and do it the old way and then import that template wherever you need it, or you can simply translate the error message the way you're doing above (which is how I've typically done it and is perfectly acceptable).
If you're going the route of $translator->trans then I would use %..% in the parameters, as stated in the Symfony documentation:
The placeholders can take on any form as the full message is reconstructed using the PHP strtr function. But the %...% form is recommended, to avoid problems when using Twig.
The {{ }} you are using is more relegated to using Symfony Validators and how they build their violations (example here).
Now, if you want your messages and parameters automatically translated without manually throwing a FormError, then I would simply suggest creating a custom validator for whatever you are trying to do, and build out the message there. Otherwise, just translate it manually the way you already figured it out.

The best way to deal with 404 errors in MVC?

So I wrote my PHP MVC framework, and have Dispatcher class that can instantiate appropriate Controller class and call defined method passing arguments.
Now inside Dispatcher I check if this Controller exists and if method exists, what should I do if controller or method does not exist?
At the moment I just return HTTP Object that prints 404 - Page not found.
But there is no way for me to customize this message from inside application, and I want to provide users a way to customize 404 messages without editing dispatcher.
Is a good way to go to always have Error controller that would get instantiated when there is a error, and that would load lets say Error404.html view file?
So users would be able to customize this view file to fit their application design.
Is there any other way to achive this? And what would be the best way to return error from dispatcher, and let "users" or developers that are working on that MVC to easily customize 404 and other messages?
Thanks!
Since i do not know your API, I am going to guess. Lets assume that you have a bootstrap stage in your application, when the dispatcher is actually used. Something like:
$dispatcher->dispatch( $request );
Then for handling request, that try to access non-existent controllers or methods within those controllers, you can do something like this:
try
{
$dispatcher->dispatch( $request );
}
catch ( ClassNotFoundException $e )
{
$dispatcher->dispatch( new Request('/error/404/controller'));
}
catch ( MethodNotFoundException $e )
{
$dispatcher->dispatch( new Request('/error/404/method'));
}
The ClassNotFoundException can be thrown by your classloader, while dispatcher itself would always be responsible for throwing the MethodNotFoundException.
You can check, whether controller has a particular method, with method_exists(), before executing it in your dispatcher.
P.S. in my humble opinion, the Dispatcher conept is better suited for event driven architectures and not for MVC-inspired patterns in web applications.
I would propose you have an error controller that takes in an error code (number or string) as an argument. This allows you to gracefully handle various kinds of errors and be able to provide a stack trace if necessary. You can even utilize this work for 500 errors.
My answer comes with the assumption that a controller can return various actions and each action can have it's own template.
Symfony also seems to handle errors in a similar fashion. They have a separate module and action for each error.
sfContext::getInstance()->getController()->forward(sfConfig::get('sf_error_404_m‌​odule'), sfConfig::get('sf_error_404_action'));

How to enable ORM annotation prefix outside of Symfony2?

I'm converting an old PHP project to the Symfony2 framework. Some of the pages are now handled by my Symfony2 front controller (index.php), but many pages have not yet been converted.
The problem is that, within Symfony, all of my Doctrine entity annotations must begin with the ORM\ prefix, but outside of Symfony, that prefix does not appear to be enabled, and so I get the following error:
Class MyProject\MyBundle\Entity\MyClass is not a valid entity or mapped super class.
I've tried to duplicate whatever magic Symfony does to set this up, including following these instructions [doctrine-project.org], and actually including app/autoload.php entirely into my legacy bootstrap process. But nothing works.
Does anyone know how I can manually replicate whatever it is that Symfony does to enable the ORM\ prefix for my Doctrine annotations?
I got the answer from the Symfony2 Google group. The problem is that the Doctrine configuration shown in the documentation uses SimpleAnnotationReader behind the scenes, but you need regular AnnotationReader to use the ORM\ namespace prefix. I got it to work by replacing this:
$config = new Doctrine\ORM\Configuration();
$driver = $config->newDefaultAnnotationDriver('/path/to/my/entities');
with this:
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
// ...
$config = new Doctrine\ORM\Configuration();
$reader = new AnnotationReader();
$driver = new AnnotationDriver($reader, '/path/to/my/entities');
I ended up with:
Doctrine\ORM\Tools\Setup::createAnnotationMetadataConfiguration($paths, $devMode, null, null, false);`
The 3rd and 4th null arguments are default. The 5th false argument tells it to make a standard AnnotationReader rather than a basic one.
I'm using Doctrine 2.5.6.
Explanation
I found I couldn't get Ian's solution working without calling Doctrine\ORM\Tools\Setup::createAnnotationMetadataConfiguration before making my own config. I was getting this error:
'[Semantical Error] The annotation "#Doctrine\ORM\Mapping\Entity" in class My\Class does not exist, or could not be auto-loaded.'
I was really confused so I took a look at the source code.
It turns out createAnnotationMetadataConfiguration calls Doctrine\ORM\Configuration::newDefaultAnnotationDriver rather than creating the annotation driver directly. This calls AnnotationRegistry::registerFile(__DIR__ . '/Mapping/Driver/DoctrineAnnotations.php'); which seems to be critical. After that, newDefaultAnnotationDriver just creates a new AnnotationDriver().

Categories