Skip to content
This repository has been archived by the owner on Jan 29, 2020. It is now read-only.

How to change layout of error pages with Zend View #360

Closed
pdrosos opened this issue Jun 23, 2016 · 4 comments
Closed

How to change layout of error pages with Zend View #360

pdrosos opened this issue Jun 23, 2016 · 4 comments

Comments

@pdrosos
Copy link

pdrosos commented Jun 23, 2016

Hi,

how can I change the layout of the Expressive error pages? For example 401 (Unauthorized), 404 (Not found), 405 (Method not allowed) etc. I am using Zend View.

Currently the error pages use the default layout, which is set in the templates configuration. But as I use the default layout on many other places too, I would like to set a different lightweight layout especially for the error pages without being forces to change the default layout and specifically set the layout everywhere else on rendering.

@pdrosos pdrosos changed the title How to change layout of error pages How to change layout of error pages with Zend View Jun 23, 2016
@fkhadra
Copy link

fkhadra commented Jun 27, 2016

Hello @pdrosos ,

I apologize in advance for the long answer.

When you request an instance of TemplateRendererInterface the ZendViewRendererFactory is used. This factory return an instance of ZendViewRenderer. Unfortunately ZendViewRenderer allow you to set the template only during the instantiation.

Expressive error pages are handled by Zend\Expressive\FinalHandler. By default the FinalHandler resolve to TemplatedErrorHandlerFactory.
If you are using Whoops the FinalHandler resolve to WhoopsErrorHandlerFactory

These factories request an instance of TemplateRendererInterface this is how expressive is able to use the layout when displaying error I guess.

To solve your issue I found 2 solutions

Using reflection :

Inside templates.global.php under the map array i added the following
'layout/error' => 'path/to/my/error/template',
Then I created my own TemplatedErrorHandlerFactory

namespace App\Factory;

use Interop\Container\ContainerInterface;
use Zend\Expressive\Template\TemplateRendererInterface;
use Zend\Expressive\TemplatedErrorHandler;
use Zend\ServiceManager\Factory\FactoryInterface;
use Zend\View\Resolver;
use Zend\View\Model\ViewModel;

class TemplatedErrorHandlerFactory implements FactoryInterface
{
    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        $template = $container->has(TemplateRendererInterface::class)
            ? $container->get(TemplateRendererInterface::class)
            : null;

        $r = new \ReflectionClass($template);
        $view = new ViewModel();
        $view->setTemplate('layout/error');
        $prop = $r->getProperty('layout');
        $prop->setAccessible(true);
        $prop->setValue($template, $view);

        return new TemplatedErrorHandler($template);
    }
}

Don't forget to map the final handler to this factory Inside templates.global.php :

'factories' => [
    'Zend\Expressive\FinalHandler' => App\Factory\TemplatedErrorHandlerFactory::class        
],

Without reflection :

Inside templates.global.php I added a layout_error key under the templates array.

// ...
templates' => [
        'layout' => 'layout/default',
        'layout_error' => 'layout/error',
        'map' => [
          ...
            'layout/error'   => 'templates/layout/error.phtml',
            ...
        ],
// ...
]

I created my own ZendViewRendererFactory. Depending on the requested service name, the right layout is used. The code is almost identical as the original factory :

class ZendViewRendererFactory implements FactoryInterface 
{
    const DEFAULT_TEMPLATE = TemplateRendererInterface::class;
    const ERROR_TEMPLATE   = 'ERROR_TEMPLATE_RENDERER';

    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        // ...
        $view = new ZendViewRenderer($renderer, $this->getLayout($requestedName, $config));
        // ...
    }

    private function getLayout($requestedName, $config)
    {
        switch($requestedName) {
            case self::DEFAULT_TEMPLATE:
            default:
                return isset($config['layout']) ? $config['layout'] : null;
            case self::ERROR_TEMPLATE:
                return isset($config['layout_error']) ? $config['layout_error'] : null;
        }
    }
}

Register my new factory as follow :

'factories' => [
    Zend\Expressive\Template\TemplateRendererInterface::class => 
App\Factory\ZendViewRendererFactory::class,
App\Factory\ZendViewRendererFactory::ERROR_TEMPLATE =>App\Factory\ZendViewRendererFactory::class,
...
],

Then my TemplatedErrorHandlerFactory become :

class TemplatedErrorHandlerFactory implements FactoryInterface
{
    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        $template = $container->has(ZendViewRendererFactory::ERROR_TEMPLATE)
            ? $container->get(ZendViewRendererFactory::ERROR_TEMPLATE)
            : null;

        return new TemplatedErrorHandler($template);
    }
}

For whoops is almost the same I can provide the snippet if you want. I hope this is clear enough, if not feel free to ask 😁

@pdrosos
Copy link
Author

pdrosos commented Jun 27, 2016

@sniphpet thank you for your answer! I like the approach you suggest and would use it in the future.
For now I ended up defining own 404 and error handlers and I specify their layout on rendering.

@weierophinney
Copy link
Member

Please see #314 as well, as it provides several succinct ways to accomplish this.

@mano87
Copy link

mano87 commented Sep 15, 2017

This good workaround is not possible in expressive 2.x :-( Are there new approaches?

Can not you simply extend the NotFoundDelegateFactory and the ErrorHandlerFactory by a parameter layout? should I create a feature request?

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants