Custom error pages for PROD environment only? - symfony

How can I setup custom error pages for PROD environment only? I want to show custom ones for production but ordinary ones with exceptions for dev environment.
Any ideas?

I had the same issue and solution was pretty easy. You have to modify parameter twig.exception_listener.contoller to redirect rendering of error page to your own controller, which may extend original Twig exception controller.
Example (YourBundle/Resources/config/services.xml):
<parameter key="twig.exception_listener.controller">YourBundle\Controller\ExceptionController::showAction</parameter>
Then you have to create your own ExceptionController with method showAction, check for environment and do what you want to do or pass request to parent::showAction().
namespace YourBundle\Controller;
use Symfony\Bundle\TwigBundle\Controller\ExceptionController as BaseExceptionController;
use Symfony\Bundle\FrameworkBundle\Templating\TemplateReference;
use Symfony\Component\HttpKernel\Exception\FlattenException;
use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
use Symfony\Component\HttpFoundation\Response;
class ExceptionController extends BaseExceptionController {
public function showAction(FlattenException $exception, DebugLoggerInterface $logger = null, $format = 'html') {
$kernel = $this->container->get('kernel');
if ($kernel->getEnvironment() == 'prod') {
$request = $this->container->get('request');
$request->setRequestFormat($format);
$templating = $this->container->get('templating');
$code = $exception->getStatusCode();
$template = new TemplateReference('YourBundle', 'Exception', 'errorpage', $format, 'twig');
if ($templating->exists($template)) {
$response = $templating->renderResponse($template, array(
'status_code' => $code,
'message_code' => 'error_' . $code,
'status_text' => Response::$statusTexts[$code],
'requested_url' => $request->getUri(),
));
$response->setStatusCode($code);
$response->headers->replace($exception->getHeaders());
return $response;
}
}
return parent::showAction($exception, $logger, $format);
}
}
Beware of errors in errorpage.html.twig, because exceptions in twig processing are not handled as usual.

If you don't want to override the exception controller :
You can first copy the entire folder (or specific layout file) at
\vendor\symfony\symfony\src\Symfony\Bundle\TwigBundle\Resources\views\
to
\app\Resources\TwigBundle\views
Then customise the view in each layout file to match your design.
Then in the layout file, customise the message for each environment as follow
{% if app.environment == 'prod' %}
// message for prod
{% else %}
// message for dev
{% endif %}

Related

Symfony2 Route prefix querystring dynamic

My symfony2 project is setup with normal YAML routes to any normal project.
Routes are setup with annotation and final URLs are
http://examplecom/artices/{id}
http://example.com/comments/{id}
I want to add prefix querystring to all the path, only if there is querystring called preview
So If I access http://example.com/?preview=something - I want this querystring to append to all the routes, so it continue to pass on every page and if this does not exist, then it will continue to be used as normally.
How can I accomplish this?
service.yml
parameters:
router.options.generator_base_class: "Acme\\DemoBundle\\Routing\\Generator\\UrlGenerator"
UrlGenerator.php
<?php
namespace Acme\DemoBundle\Routing\Generator;
use Symfony\Component\Routing\Generator\UrlGenerator as BaseUrlGenerator;
class UrlGenerator extends BaseUrlGenerator
{
protected function doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $referenceType, $hostTokens)
{
return parent::doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $referenceType, $hostTokens).'?preview=something';
}
}
reference: http://h4cc.tumblr.com/post/56874277802/generate-external-urls-from-a-symfony2-route

SilverStripe framework only - how to handle 404

I'm using just the framework without the CMS module for the first time. When I visit the app via a URL that is not handled by a controller/action, I just get a page with the text "No URL rule was matched". This results from Director::handleRequest() not matching any controllers to the url segments... Or "Action 'ABC' isn't available on class XYZController."
I'd like to direct any unmached requests to a controller equivalent of a nice 404 page. What is the correct or best way to do this?
The error templates are only included in the CMS. The framework just returns the HTTP response code with a message in plain text.
I've just started on my own framework-only project too and this is my solution:
[routes.yml]
---
Name: rootroutes
---
Director:
rules:
'': 'MyController'
'$URLSegment': 'MyController'
[MyController]
class MyController extends Controller {
private static $url_handlers = array(
'$URLSegment' => 'handleAction',
);
public function index() {
return $this->httpError(404, "Not Found");
}
/**
* Creates custom error pages. This will look for a template with the
* name ErrorPage_$code (ie ErrorPage_404) or fall back to "ErrorPage".
*
* #param $code int
* #param $message string
*
* #return SS_HTTPResponse
**/
public function httpError($code, $message = null) {
// Check for theme with related error code template.
if(SSViewer::hasTemplate("ErrorPage_" . $code)) {
$this->template = "ErrorPage_" . $code;
} else if(SSViewer::hasTemplate("ErrorPage")) {
$this->template = "ErrorPage";
}
if($this->template) {
$this->response->setBody($this->render(array(
"Code" => $code,
"Message" => $message,
)));
$message =& $this->response;
}
return parent::httpError($code, $message);
}
}
[ErrorPage.ss]
<h1>$Code</h1>
<p>$Message</p>
You can also create more specific error templates using ErrorPage_404.ss, ErrorPage_500.ss etc.
Without updating the routes as previously mentioned, there's a module that I've been recently working on which will allow regular expression redirection mappings, hooking into a page not found (404). This has been designed to function with or without CMS present :)
https://github.com/nglasl/silverstripe-misdirection
It basically makes use of a request filter to process the current request/response, appropriately directing you towards any mappings that may have been defined.

Symfony 2. How to catch Exception parameter in Twig template?

I'm throwing some exception in my controller.
For example:
throw new AccessDeniedHttpException('some_text');
How can i catch it's 'some_text' parameter in my Twig template?
I found the {{ status_code }} and {{ status_text }} variables, but can't find something similar that solve my problem.
P.S. I'm already use custom error page. I just want give users specific error explanations.
Thnx.
By default Symfony uses the showAction of Symfony\Bundle\TwigBundle\Controller\ExceptionController to render your error page. The Implementation in Symfony 2.3 is like:
public function showAction(Request $request, FlattenException $exception, DebugLoggerInterface $logger = null, $_format = 'html')
{
$currentContent = $this->getAndCleanOutputBuffering($request->headers->get('X-Php-Ob-Level', -1));
$code = $exception->getStatusCode();
return new Response($this->twig->render(
$this->findTemplate($request, $_format, $code, $this->debug),
array(
'status_code' => $code,
'status_text' => isset(Response::$statusTexts[$code]) ? Response::$statusTexts[$code] : '',
'exception' => $exception,
'logger' => $logger,
'currentContent' => $currentContent,
)
));
}
From there you can see that there is 'exception' => $exception passed to your twig template. $exception is of type Symfony\Component\HttpKernel\Exception\FlattenException which is a wrapper for the original PHP Exception.
FlattenException::getMessage is probably what you want to access your error message. See FlattenException API for more Information.
Ok. The TWIG code is
{{ exception.message|nl2br }}

Access Service from Controller and/or Twig template

Disclaimer: I'm slowly starting to get into Symfony and still have some problems understanding how the architecture works.
Currently I set up different Bundles (Services, right?) that should deliver different output for different routes. So far I got around adding a simple Twig template that loads stylesheets and scripts via Assetics and Twig-blocks. Now I added another Bundle that queries data via Buzz from a remote location, which worked fine as a standalone script, but I don't get around printing output in a Twig template.
The architecture of the original script is like the following (names made more generic):
Vendors - abstract class that serves as base for all remote request Bundles.
ServiceABC - abstract class that extends Vendors and defines Error handling and output preparation for the ABC service.
ClientXYZ - final class that extends Service_ABC, defines output parsing and normalization of the returned data.
This Bundle got a services.yml file:
# ~/MyApp/Bundle/ServiceABCBundle/Resources/config/services.yml
parameters:
service_abc_manager.class: MyApp\Bundle\ServiceABCBundle\Models\Service_ABC
location_manager.class: MyApp\Bundle\ServiceABCBundle\Models\Clients\ClientLocation
monitor_manager.class: MyApp\Bundle\ServiceABCBundle\Models\Clients\ClientMonitor
services:
service_abc_manager:
abstract: true
location_manager:
class: %location_manager.class%
parent: service_abc_manager
monitor_manager:
class: %monitor_manager.class%
parent: service_abc_manager
Names changed for easier reference - Typos by accident possible.
Now my problem/question is, that I don't really get behind the Symfony2 concept of how to get the output into the template.
namespace MyApp\Bundle\ServiceABCBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use MyApp\Bundle\ServiceABCBundle\Models\Clients\ClientLocation;
class DefaultController extends Controller
{
public function indexAction()
{
$services = array();
$services[] = $this->container->has('service_abc_manager');
$services[] = $this->container->has('location_manager');
$services[] = $this->container->has('client_location');
$services[] = $this->container->has('ClientLocation');
var_dump( $services );
$client = new ClientLocation();
var_dump( $client );
$response = $this->render(
'Service_ABC:Default:index.html.twig'
);
# $response->setCharset( 'utf-8' );
# $response->headers->set( 'Content-Type', 'text/html' );
return $response;
}
}
The output of the first array() named $services is always false and the $client = new ClientLocation(); throws an Exception that the class name wasn't found.
How can I access those Services/Bundle(parts)/Classes? And how would I render the output to a template?
Update
After I added the complete tree definition to Configuration()->getConfigTreeBuilder(), I'm able to see the definitions in the CLI:
class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root( 'myapp_service_abc' );
$rootNode
->children()
->scalarNode('service_abc_manager')->end()
->scalarNode('location_manager')->end()
->scalarNode('monitor_manager')->end()
->end()
;
return $treeBuilder;
}
}
The CLI command php app/console config:dump-reference myapp_service_abc now gives me the following output:
myapp_service_abc:
service_abc_manager: ~
location_manager: ~
monitor_manager: ~
I can as well see that the config data was loaded, when I var_dump( $loader ); inside MyAppServiceABCExtension right after $loader->load( 'services.yml' ); was called.
The output is the following:
object(Symfony\Component\DependencyInjection\Loader\YamlFileLoader)
protected 'container' =>
object(Symfony\Component\DependencyInjection\ContainerBuilder)
private 'definitions' =>
array
'service_abc_manager' =>
object(Symfony\Component\DependencyInjection\Definition)
'location_manager' =>
object(Symfony\Component\DependencyInjection\DefinitionDecorator)
private 'parent' => string 'service_abc_manager'
// etc.
The problem itself remains: There's still a FALSE return value inside DefaultController()->indexAction() when I var_dump( $this->container->has( 'service_abc_manager' );. I as well tried var_dump( $this->container->has( 'location_manager' ); and var_dump( $this->container->has( 'myapp.service_abc_manager' ); with the same result.
You should not call your services from the twig file, but from the controller.
The role of the controller is to :
validate your forms if there were a form posted
call your services to get some stuffs to display in a view
initialize forms if there is a form to display
return a Response that typically contains a rendered twig view
Do not call your services using something like $client = new ClientLocation();, but call it using the service container. This will allow you to take the whole power of the dependancy injection offered by Symfony2.
Your controller will look like :
<?php
namespace MyApp\Bundle\ServiceABCBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class DefaultController extends Controller
{
public function indexAction()
{
$locationService = $this->container->get('location_manager');
$someStuffs = $locationService->someMethod();
$response = $this->render(
'ServiceABCBundle:Default:index.html.twig', array('stuffs' => $someStuffs)
);
return $response;
}
}
From your twig file, you'll be able to use the stuffs variable :
{{ stuffs }} if your variable is a terminal ( a string, a number... )
{{ stuffs.attribute }} if your variable is an object or an array
About your services file, I am a bit confused, because your architecture does not look to be the standard Symfony2's one :
# ~/MyApp/Bundle/ServiceABCBundle/Resources/config/services.yml
Why your services.yml file isn't in the src/MyApp/SomethingBundle/Resources/config/ directory?
If you didn't already read it, I suggest you to have a look to the Symfony2 : The Big Picture documentation, which is the best way to start with Symfony2.

Symfony2 - ExceptionController - NotFoundHttpException

I am trying to create a custom exception error page and I have been following Mike's tutorial. I have inserted the following code into config.yml, and created an ExceptionController in StoreBundle\Controller\ExceptionController.php. When I try to test my 404 error page by going local.store.com/fake-page, I get a NotFoundHttpException: No route found for "GET /fake-page" error. I thought my ExceptionController is suppose to redirect all users if a page is not found so I added a var_dump('testing') in it but it never dumped. I tried to remove the code I injected into config.yml and I get the default Symfony error page instead. Am I doing something wrong in config.yml?
Inserted into app/config/config.yml:
twig:
exception_controller: StoreBundle\Controller\ExceptionController::showAction
EDIT
I now think my problem is in my controller. This is what I have in StoreBundle\ControlleExceptionController.php. My error pages are in StoreBundle\Resources\views\Pages\Errors\404.html.twig and StoreBundle\Resources\views\Pages\Errors\500.html.twig
<?php
namespace StoreBundle\Controller;
use Symfony\Component\HttpKernel\Exception\FlattenException;
use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Bundle\TwigBundle\Controller\ExceptionController as BaseExceptionController;
class ExceptionController extends BaseExceptionController
{
public function showAction(FlattenException $exception, DebugLoggerInterface $logger = null, $format = 'html')
{
$template = $this->container->get('kernel')->isDebug() ? 'exception' : 'error';
$code = $exception->getStatusCode();
return $this->container->get('templating')->renderResponse(
'StoreBundle:Exception:Pages/Errors:' . $code . '.html.twig', array(
'status_code' => $code,
'status_text' => Response::$statusTexts[$code],
'exception' => $exception,
'logger' => null,
'currentContent' => '',
));
}
}
}

Resources