I am struggling with this detail while defining a route in Symfony2
Mi routing:
blog:
path: /blog/{page}
defaults: { _controller: ManualRouteBundle:Blog:show, page: 33 }
My controller:
<?php
namespace Manual\RouteBundle\Controller ;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class BlogController extends Controller{
public function showAction($page){
return $this->render('ManualRouteBundle:Blog:show.html.twig') ;
}
}
My view:
Blog # {{page}}
When I try to access with this address
http://test/web/blog
instead of
http://test/web/blog/1
I get this error
Variable "page" does not exist in ManualRouteBundle:Blog:show.html.twig at line 1
500 Internal Server Error - Twig_Error_Runtime
Isn't page value supposed to be 33?
I got the answer on #symfony, I have to pass the variable to the view.
$this->render() like this: $this->render('show.html.twig', array('page' => $page));
Weird behavior imho.
Related
No route found for "GET http://pogodynka.localhost:46530/weather"
This is the error I get when I try to access:
http://pogodynka.localhost:46530/weather
config/Routes.yaml
weather_in_city:
path: /weather/{country}/{cityName}
controller: App\Controller\WeatherController:cityAction
requirements:
city: \d+
config/packages/Routing.yaml
router:
utf8: true
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
#default_uri: http://localhost
when#prod:
framework:
router:
strict_requirements: null
src/Controller/WeatherController.php
namespace App\Controller;
use App\Entity\Location;
use App\Repository\MeasurementRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
class WeatherController extends AbstractController
{
public function cityAction($cityName, MeasurementRepository $measurementRepository, CityRepository $cityRepository): Response
{
$cities = $cityRepository->findCityByName($cityName);
$city = $cities[0];
$measurements = $measurementRepository->findByLocation($cityName);
return $this->render('weather/city.html.twig', [
'location' => $city,
'measurements' => $measurements,
]);
}
}
debug:router
You should use route annotations. This way you can define the route directly in the controller class. Here is a nice write up and example: SymfonyCasts: Annotation & Wildcard Routes
My personal opinion but I find annotations to be the way to go.
I have a route
detail:
path: /{code}
defaults: { _controller: controller.main:detailAction }
I also have a controller for this route
public function detailAction(Request $request, string $code, int $size, array $params): Response
{
}
My question is: how can I say to controller which parameters he should take as int $size and array $params ? I have found in symfony docs that I may specifically mention params in defaults section with default values like this
detail:
path: /{code}
defaults: { _controller: controller.main:detailAction }
size: 1
params: "Hello world!"
But that is not what I want since I shouldn't have a default value for this params but it ought to be taken directly from request. How do I do this without making my route like /{code}/{size} ?
And even in this case what do I do with an array?
You can generate a url like this by passing parameters in your controller:
$url = $this->generateUrl("detail", array("code" => $code, ...));
return $this->redirect($url);
And routing:
detail:
path: /
defaults: { _controller: controller.main:detailAction }
If you want to specify parameters like this
someurl.io/action/?filter=allopenissues&orderby=created
You should typehint Request object in your action and access its query parameters bag. If your controller extends Symfony Controllers, Request will be automatically passed.
use Symfony\Component\HttpFoundation\Request;
....
public function updateAction(Request $request)
{
$request->query->get('myParam'); // get myParam from query string
}
In Symfony, when a user attempts to access a route which is forbidden for that specific user (according to the user roles), a page with response code 403 will be returned.
So the user can still see that there is a valid route there.
I would like to overwrite this behavior by replacing the status code 403 with 404, so the user will just see that there is no valid route when she/he is not allowed to access that resource.
How can I accomplish that?
This is doable, however almost undocumented. I'm aware of two ways but there might be even more:
Using access_denied_url configuration option. See security config reference. With this option you can set URL where the user is redirected when the user in unauthorized (I think it should work also with route name). See a similar question: Symfony2 Redirection for unauthorised page with access_denied_url
There're also "Entry Points" as mentioned in The Firewall and Authorization. However, no examples, no explanation how to use it.
I looks like this option expects a service name as can be seen in security config reference (search for entry_point option).
One possible solution, as partially explained here, can be the following:
1) Defining a new service controller in services.yml
exception_controller:
class: Path\To\MyBundle\Controller\MyExceptionController
arguments: ['#twig', '%kernel.debug%']
2) Creating the new class which overrides Symfony\Bundle\TwigBundle\Controller\ExceptionController:
namespace Path\To\MyBundle\Controller;
use Symfony\Bundle\TwigBundle\Controller\ExceptionController;
use Symfony\Component\Debug\Exception\FlattenException;
use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class MyExceptionController extends ExceptionController
{
public function showAction(Request $request, FlattenException $exception, DebugLoggerInterface $logger = null)
{
$currentContent = $this->getAndCleanOutputBuffering($request->headers->get('X-Php-Ob-Level', -1));
$showException = $request->attributes->get('showException', $this->debug); // As opposed to an additional parameter, this maintains BC
$code = $exception->getStatusCode();
if ($code == 403) {
$code = 404;
// other customizations ...
}
return new Response($this->twig->render(
(string) $this->findTemplate($request, $request->getRequestFormat(), $code, $showException),
array(
'status_code' => $code,
'status_text' => isset(Response::$statusTexts[$code]) ? Response::$statusTexts[$code] : '',
'exception' => $exception,
'logger' => $logger,
'currentContent' => $currentContent,
)
));
}
}
3) Setting the following in config.yml under twig:
twig:
exception_controller: 'exception_controller:showAction'
Even though my original goal was to prevent such an exception to be thrown at all with that code.
Another solution can be overwriting the AccessListener service of the Symfony Security component.
The generic procedure about how to override a service of a bundle is documented here. The following is the concrete example about this particular situation.
First of all let's create the class which overrides the AccessListener class:
<?php
namespace Path\To\My\Bundle\Services;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Security\Http\Firewall\AccessListener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
class OverrideAccessListener extends AccessListener
{
public function handle(GetResponseEvent $event)
{
try {
parent::handle($event);
} catch (AccessDeniedException $e) {
$request = $event->getRequest();
$message = sprintf('No route found for "%s %s"', $request->getMethod(), $request->getPathInfo());
if ($referer = $request->headers->get('referer')) {
$message .= sprintf(' (from "%s")', $referer);
}
throw new NotFoundHttpException($message);
}
}
}
then we need to create a Compiler Pass in order to change the class attribute of the original service with the new class:
<?php
namespace Path\To\My\Bundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class OverrideServiceCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$definition = $container->getDefinition('security.access_listener');
$definition->setClass('Path\To\My\Bundle\Services\OverrideAccessListener');
}
}
finally we need to register the Compiler Pass in the build method of the bundle:
<?php
namespace Path\To\My\Bundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Path\To\My\Bundle\DependencyInjection\Compiler\OverrideServiceCompilerPass;
class MyBundleName extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new OverrideServiceCompilerPass());
}
}
Finally I found a simpler solution: using an access denied handler.
Unfortunately there is no much documentation about how to create an access denied handler, but it is very simple.
First create a class that implements the AccessDeniedHandlerInterface and set it as a service (for example naming it my_access_denied_handler_service).
In the handle method a Response should be created and returned (in my case I wanted a 404 response).
Then in the security.yml configuration file we have to set the access_denied_handler under the firewall:
...
firewalls:
my_firewall:
...
access_denied_handler: my_access_denied_handler_service
...
...
In Symfony1 i can:
blog:
url: /blog/slug
param: { module: blog, action: index }
and in action/controller i can get slug with: $request->getParameter('slug');
In Symfony2:
blog:
path: /blog/{slug}
defaults: { _controller: AcmeBlogBundle:Blog:show }
and i create "components" same as Symfony1 - http://symfony.com/doc/current/book/templating.html#templating-embedding-controller
How can i get slug in embedding controller? I tried:
$request->query->get('foo');
$request->request->get('bar');
but this still return null. In AcmeBlogBundle:Blog:show controller working ok.
The param converter will populate the argument with the string from the route. So here is what your method looks like.
class BlogController extends Controller {
public function showAction($slug) {
// $slug will contain the value of the string from the route
}
}
So if you wanted to embed this into a twig template it would look like this:
{{ render( controller('AcmeBlogBundle:Blog:show', {'slug': 'this-is-the-slug' })) }}
or from another controller
$this->render('AcmeBlogBundle:Blog:show.html.twig', array('slug' => 'this-is-the-slug'));
I'm trying to write a symfony 2 functional test. This is my code:
<?php
namespace WebSite\MainBundle\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class ProductControllerTest extends WebTestCase
{
public function testPageContents()
{
$domCategoryLinksExpr = '.catalog-col-block > ul > li > a';
$client = static::createClient();
$crawler = $client->request('GET', '/catalog/');
$this->assertTrue($client->getResponse()->getStatusCode() == '200');
$countCategories = $crawler->filter($domCategoryLinksExpr)->count();
$this->assertTrue($crawler->filter($domCategoryLinksExpr)->count() > 0);
$categoryLink = $crawler->filter($domCategoryLinksExpr)->eq(rand(1, $countCategories))->link();
$crawler = $client->click($categoryLink);
}
}
But when i run this test:
phpunit -c app src/WebSite/MainBundle/Tests/Controller/
I got this:
1) WebSite\MainBundle\Tests\Controller\ProductControllerTest::testPageContents
Symfony\Component\HttpKernel\Exception\NotFoundHttpException: No route found for "GET /app_dev.php/catalog/psp"
...
/app_dev.php/catalog/psp is the dynamic value of $categoryLink->getUri(). And
this route exists and correctly works in web browser. Any ideas?
UPD:
This is my routing rules:
routing_dev.yml:
...
_main:
resource: routing.yml
....
routing.yml:
....
WebSiteCategoryBundle:
resource: "#WebSiteCategoryBundle/Controller/"
type: annotation
prefix: /
....
src/WebSite/CategoryBundle/CategoryController.php:
/**
* #Route("/catalog")
*/
class CategoryController extends Controller
{
/**
* #Route("/{alias}", name="show_category" )
* #Template()
*/
public function showAction( $alias )
{
// some action here
}
}
It works fine in browser, but seems like $crowler does`not see this annotation rules.
UPD2: The problem was in "routing_test.yml" which missing in Symfony 2 standard edition.
So I create it:
routing_test.yml:
_main:
resource: routing_dev.yml
and exception disappear. Thanks to all.
It would be nice if you posted your routing.yml
You can also take a look at:
http://symfony.com/doc/master/bundles/SensioFrameworkExtraBundle/annotations/routing.html
You can use routing annotation at your actions.
To solve your problem I would like to see your routing config.
echo $client->getResponse()->getContent() will help you with debugging (even there is an exception). It will output html of the request.
It looks like your route does not exist and might be in wrong location (wrong environment specified?)