Symfony, route conflicts between controller and bundle - symfony

For example simple controller:
/**
* #Route("/{identifier}", name="page")
*/
public function page(Request $request, string $identifier)
{
$page = $this->pageRepository->findOneBy(['identifier' => $identifier]);
if (!$page || !$page->getEnabled()) {
throw $this->createNotFoundException();
}
return $this->render('cms/index.html.twig', []);
}
And a have a bundle to manage images from admin page elfinder, which will enter the /elfinder link.
And instead of getting the bundle controller, my controller gets.
/{identifier} === /elfinder
How do people usually act in such situations?
I tried to set different priority, but it does not help

Try adding your controllers with the priority required in the annotations.yaml file. Thus, if you get a 404 in the first one, Symfony will try to open the route from the next controller
Add your controllers to config/routes/annotations.yaml
page:
resource: App\Controller\_YourFistController_
type: annotation
elfinder:
resource: FM\ElfinderBundle\Controller\ElFinderController
type: annotation
Or if this option does not suit you, then you can try the optional parameter priority. symfony doc
Add to config file config/routes.yaml:
elfinder:
path: /elfinder/{instance}/{homeFolder}
priority: 2
controller: FM\ElfinderBundle\Controller\ElFinderController::show

I tried to set the priority through the configuration file. But unfortunately it didn't work.
The only thing that helped was to create your own methods that will redirect
/**
* #Route("/elfinder", name="elfinder", priority=10)
*/
public function elfinder()
{
return $this->forward('FM\ElfinderBundle\Controller\ElFinderController::show', [
'homeFolder' => '',
'instance' => 'default',
]);
}
/**
* #Route("/efconnect", name="efconnect", priority=11)
*/
public function elfinderLoad(Request $request, SessionInterface $session, EventDispatcherInterface $dispatcher)
{
return $this->forward('FM\ElfinderBundle\Controller\ElFinderController::load', [
'session' => $session,
'eventDispatcher' => $dispatcher,
'request' => $request,
'homeFolder' => '',
'instance' => 'default',
]);
}

Related

How to have optional parameter for param conversion in Symfony 6 controller

I am trying to use an optional parameter in one of my symfony 6 controllers but it just does not work.
I have a controller with a method/route to create a new item and one to edit an existing item. The route for the "new" method does not accept an item parameter but the one for the "edit" method does. As such:
#[Route('/new', name: 'new', methods: ['GET', 'POST'])]
public function new( Request $request ): Response ...
#[Route('/edit/{id}', name: 'edit', methods: ['GET', 'POST'])]
public function edit( Request $request, Item $item ): Response ...
Since both of the methods have a large amount of the same code in them, I decided to create a new "new_edit" method and route that is then called from the "new" and "edit" methods via "directToRoute".
#[Route('/new_edit/{id}', name: 'new_edit', defaults: ['id' => null], methods: ['GET', 'POST'])]
public function new_edit( Request $request, ?Item $item ) : Response ...
So far so good. When I select an existing item and elect to edit it, all works perfectly. However, when I elect to create a new item I constantly get and error message "Item object not found by the #ParamConverter annotation".
I have tried setting default values, etc. but I am just not able to get this working.
Does anyone have a solution to such a problem?
Many thanks,
Kristian.
It seems I have found the issue and it was not related directly to the optional parameter but to the priority in which the routes are evaluated.
I modified my route priorities and now all works as expected with the following configuration:
#[Route('/new', name: 'new', methods: ['GET', 'POST'])]
public function new(): Response
{
return $this->redirectToRoute( 'item.new_edit', [], Response::HTTP_SEE_OTHER );
}
#[Route('/edit/{id}', name: 'edit', methods: ['GET', 'POST'])]
public function edit( Item $item ): Response
{
return $this->redirectToRoute( 'item.new_edit', ['id' => $item->getId()], Response::HTTP_SEE_OTHER );
}
#[Route('/new_edit/{id}', name: 'new_edit', defaults: ['id' => null], methods: ['GET', 'POST'])]
public function new_edit( Request $request, ?Item $Item ) : Response
{
...
}
I hope that helps anyone who has the same issue.
Kristian
You can use several routes for one action. For the optional param you can define a default value for the PHP parameter ?Item $Item = null
#[
Route('/new', name: 'new', methods: ['GET', 'POST']),
Route('/edit/{id}', name: 'edit', methods: ['GET', 'POST'])
]
public function new_edit(Request $request, ?Item $item = null) : Response
{
}

Set queue name from parameters in symfony 3.4 and enqueue-bundle 0.9.13

This is my Symfony 3.4 configuration for php-queue bundle 0.9.13:
parameters.yml:
enqueue_dsn: 'amqp+lib://192.168.32.1'
services.yml:
messages_processor:
class: 'InteroperabilityBundle\Processor\MessagesProcessor'
tags:
- { name: 'enqueue.transport.processor', processor: 'manager_tasks_processor' }
MessagesProcessor.php:
class MessagesProcessor implements Processor, CommandSubscriberInterface, QueueSubscriberInterface
{
public function process(Message $message, Context $context)
{
// do something ...
}
public static function getSubscribedCommand(): array
{
return [
'command' => 'newMessageFromApi',
'processor' => 'manager_tasks_processor',
'queue' => 'manager_tasks',
'prefix_queue' => false,
'exclusive' => false,
];
}
public static function getSubscribedQueues(): array
{
return ['manager_tasks'];
}
}
And my command to start processor:
bin/console enqueue:transport:consume manager_tasks_processor manager_tasks --time-limit=3600
In the test environment this works. However now I need to specify a queue with a different name for production. And have another one in the testing environment (both on the same VPS).
How can I achieve this? The name of the queue is handcoded, in a static method. How can I get that name from the parameters?

laravel phpunit withexceptionhandling

I'm in the process of writing a web app using Laravel 5.5 and Vue.js. PHPUnit version is 6.3.1.
I'm testing for validation errors when a user registers using Form Requests.
Route:
// web.php
Route::post('/register', 'Auth\RegisterController#store')->name('register.store');
This is my passing test:
/** #test */
function validation_fails_if_username_is_missing()
{
$this->withExceptionHandling();
$this->json('POST', route('register.store'), [
'email' => 'johndoe#example.com',
'password' => 'secret',
'password_confirmation' => 'secret'
])->assertStatus(422);
}
However, it fails when I remove exception handling:
/** #test */
function validation_fails_if_username_is_missing()
{
$this->json('POST', route('register.store'), [
'email' => 'johndoe#example.com',
'password' => 'secret',
'password_confirmation' => 'secret'
])->assertStatus(422);
}
I do not understand why this test fails without exception handling as it's stated in the Laravel documentation that
If the request was an AJAX request, a HTTP response with a 422 status
code will be returned
I already tried to declare this particular route in the api middleware group, but that didn't change anything.
Can someone with more experience than I do explain to me why that is? Thanks in advance.
EDIT: This is the content of my Handler.php class file. I don't think anything was edited.
protected $dontReport = [
\Illuminate\Auth\AuthenticationException::class,
\Illuminate\Auth\Access\AuthorizationException::class,
\Symfony\Component\HttpKernel\Exception\HttpException::class,
\Illuminate\Database\Eloquent\ModelNotFoundException::class,
\Illuminate\Session\TokenMismatchException::class,
\Illuminate\Validation\ValidationException::class,
];
public function report(Exception $exception)
{
parent::report($exception);
}
public function render($request, Exception $exception)
{
return parent::render($request, $exception);
}
protected function unauthenticated($request, AuthenticationException $exception)
{
if ($request->expectsJson()) {
return response()->json(['error' => 'Unauthenticated.'], 401);
}
return redirect()->guest(route('login'));
}

Laravel Testing login Credential using phpunit + multiprocess

I'm pretty new on unit testing and I want to try to test my login page
my Goal for this unit are :
-> if it match in database -> redirect to route '/'
-> if not -> redirect to route '/login'
<?php
namespace Tests\Feature;
use App\Domain\Core\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Session;
use Tests\TestCase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class userTest extends TestCase
{
use DatabaseMigrations;
/**
* A basic test example.
*
* #return void
*/
public function testLoginTrue()
{
$credential = [
'email' => 'user#ad.com',
'password' => 'user'
];
$this->post('login',$credential)->assertRedirect('/');
}
public function testLoginFalse()
{
$credential = [
'email' => 'user#ad.com',
'password' => 'usera'
];
$this->post('login',$credential)->assertRedirect('/login');
}
}
when I test on TestLoginTrue , its successfully return to '/' But when i try the TestLoginFalse ... it return same like TestLoginTrue, it should be stayed on '/login' route,
Any Idea?
Plus I want to try to check if when I already login I couldn't access the login page so my initial idea is :
public function testLoginTrue()
{
$credential = [
'email' => 'user#ad.com',
'password' => 'user'
];
$this->post('login',$credential)
->assertRedirect('/')
->get('/login')
->assertRedirect('/');
}
but... it returns
1) Tests\Feature\userTest::testLoginTrue BadMethodCallException:
Method [get] does not exist on Redirect.
So how to do it correctly?
Thanks in advance
I am also a bit stuck with Laravel 5.4 testing follow redirects case.
As a workaround, you may check $response->assertSessionHasErrors(). This way it should work:
public function testLoginFalse()
{
$credential = [
'email' => 'user#ad.com',
'password' => 'incorrectpass'
];
$response = $this->post('login',$credential);
$response->assertSessionHasErrors();
}
Also, in testLoginTrue() you may check, that session missing errors:
$response = $this->post('login',$credential);
$response->assertSessionMissing('errors');
Hope this helps!

Batch requests on Symfony

I am trying to reproduce the behaviour of the facebook batch requests function on their graph api.
So I think that the easiest solution is to make several requests on a controller to my application like:
public function batchAction (Request $request)
{
$requests = $request->all();
$responses = [];
foreach ($requests as $req) {
$response = $this->get('some_http_client')
->request($req['method'],$req['relative_url'],$req['options']);
$responses[] = [
'method' => $req['method'],
'url' => $req['url'],
'code' => $response->getCode(),
'headers' => $response->getHeaders(),
'body' => $response->getContent()
]
}
return new JsonResponse($responses)
}
So with this solution, I think that my functional tests would be green.
However, I fill like initializing the service container X times might make the application much slower. Because for each request, every bundle is built, the service container is rebuilt each time etc...
Do you see any other solution for my problem?
In other words, do I need to make complete new HTTP requests to my server to get responses from other controllers in my application?
Thank you in advance for your advices!
Internally Symfony handle a Request with the http_kernel component. So you can simulate a Request for every batch action you want to execute and then pass it to the http_kernel component and then elaborate the result.
Consider this Example controller:
/**
* #Route("/batchAction", name="batchAction")
*/
public function batchAction()
{
// Simulate a batch request of existing route
$requests = [
[
'method' => 'GET',
'relative_url' => '/b',
'options' => 'a=b&cd',
],
[
'method' => 'GET',
'relative_url' => '/c',
'options' => 'a=b&cd',
],
];
$kernel = $this->get('http_kernel');
$responses = [];
foreach($requests as $aRequest){
// Construct a query params. Is only an example i don't know your input
$options=[];
parse_str($aRequest['options'], $options);
// Construct a new request object for each batch request
$req = Request::create(
$aRequest['relative_url'],
$aRequest['method'],
$options
);
// process the request
// TODO handle exception
$response = $kernel->handle($req);
$responses[] = [
'method' => $aRequest['method'],
'url' => $aRequest['relative_url'],
'code' => $response->getStatusCode(),
'headers' => $response->headers,
'body' => $response->getContent()
];
}
return new JsonResponse($responses);
}
With the following controller method:
/**
* #Route("/a", name="route_a_")
*/
public function aAction(Request $request)
{
return new Response('A');
}
/**
* #Route("/b", name="route_b_")
*/
public function bAction(Request $request)
{
return new Response('B');
}
/**
* #Route("/c", name="route_c_")
*/
public function cAction(Request $request)
{
return new Response('C');
}
The output of the request will be:
[
{"method":"GET","url":"\/b","code":200,"headers":{},"body":"B"},
{"method":"GET","url":"\/c","code":200,"headers":{},"body":"C"}
]
PS: I hope that I have correctly understand what you need.
There are ways to optimise test-speed, both with PHPunit configuration (for example, xdebug config, or running the tests with the phpdbg SAPI instead of including the Xdebug module into the usual PHP instance).
Because the code will always be running the AppKernel class, you can also put some optimisations in there for specific environments - including initiali[zs]ing the container less often during a test.
I'm using one such example by Kris Wallsmith. Here is his sample code.
class AppKernel extends Kernel
{
// ... registerBundles() etc
// In dev & test, you can also set the cache/log directories
// with getCacheDir() & getLogDir() to a ramdrive (/tmpfs).
// particularly useful when running in VirtualBox
protected function initializeContainer()
{
static $first = true;
if ('test' !== $this->getEnvironment()) {
parent::initializeContainer();
return;
}
$debug = $this->debug;
if (!$first) {
// disable debug mode on all but the first initialization
$this->debug = false;
}
// will not work with --process-isolation
$first = false;
try {
parent::initializeContainer();
} catch (\Exception $e) {
$this->debug = $debug;
throw $e;
}
$this->debug = $debug;
}

Resources