Symfony 2 route error when adding parameters - php

I seem to have come across an issue with Symfony 2 that I have not seen before (or more likley im missing something really obvious).
I had a route which took no parameters and worked perfectly, linking up to the controller and displaying the correct view. I then updated the route to take a single parameter like so:
# Note routing
gibbo_dummy_notes_Add:
pattern: /notes/add/{notebook}
defaults: { _controller: GibboDummyBundle:Note:add, notebook: 0}
requirements:
_method: POST|GET
However if I now try and access this route notes/add/test I get the error The controller must return a response (null given). Did you forget to add a return statement somewhere in your controller?. If I remove the parameter it works perfectly notes/add.
This setup is exactly the same as the rest of the routes that use parameters in my app.
The action in the controller definitely returns a response object. If I place a die('test'); at the top of the action and remove the parameter from the URL I reach the die statement and see 'test', but if I add the parameter to the URL it shows the error, so its clearly not reaching my controller or action when including the parameter but as far as I can tell the route is setup correctly, and im not getting the normal route not defined error I would expect to see if there was an issue with the url.
Im running it under the dev environment and I have tried other browsers / cleared cache etc. The action inside the controller NoteController looks like
public function addAction($notebook)
{
$pageTitle = 'Note';
$form = $this->createForm(new NoteType(), null, array(
'validation_groups' => array('add')
));
$request = $this->getRequest();
if ($request->isMethod('POST')) {
$form->bind($request);
if ($form->isValid()) {
/** #var $em \Doctrine\ORM\EntityManager */
$em = $this->getDoctrine()->getManager();
/** #var $note Note */
$note = $form->getData();
$note->setNotebook();
$em->persist($note);
$em->flush();
return $this->redirect($this->generateUrl('gibbo_dummy_note_View'));
}
}
return $this->render('GibboDummyBundle:Note:new.html.twig', array(
'pageTitle' => $pageTitle,
'form_add' => $form->createView(),
'selected' => 'codebooks'
));
}
Can anyone shed some light about what I might be doing wrong?

Please add the controller code for gibbo_dummy_note_View route.
Most likely, your redirection ends up in a bad controller method - the one that does not have return statement

Related

How to invoke a service after pressing submit in Drupal

I am very new to Drupal and I am asked to create form with a submit button and a service that makes a get request to an API with the values from the form. The API is a simple API that the user can enter a country and it will return a response with the correct greeting from that country.
This is my routing file:
hello_world.salutation:
path: '/hello'
defaults:
_controller: Drupal\hello_world\Controller\HelloWorldSalutation::salutation
_form: Drupal\hello_world\Form\GreetingForm
_title: 'Get a greeting from a different language'
requirements:
_permission: 'administer site configuration'
First problem is that I do not know how to make the form and the controller in the same routing,
and second is that I do not know how to invoke that service when the user has entered submit.
Here is my services file:
services:
hello_world.salutation:
class: Drupal\hello_world\HelloWorldSalutation
arguments: [ '#config.factory' ,'#tempstore.private']
cache.nameofbin:
class: Drupal\Core\Cache\CacheBackendInterface
tags:
- { name: cache.bin }
factory: [ '#cache_factory', 'get' ]
arguments: [ nameofbin ]
I will skip some lines from the GreetingFrom class to keep it simple, but I can add them if it is required.
Here is the submitForm function from the GreetingForm class. The idea is to put the input in a global tempstore so I cal access the values from the controller I guess.
public function submitForm(array &$form, FormStateInterface $form_state)
{
$search_str = $form_state->getValue('greeting');
// check the input
$params['items'] = $form_state->getValue('greeting');
// 2. Create a PrivateTempStore object with the collection 'greetingForm_values'.
$tempstore = $this->tempStoreFactory->get('greetingForm_values');
// 3. Store the $params array with the key 'params'.
try {
$tempstore->set('params', $params);
} catch (\Exception $error) {
// dump the error for now, read error, --fix this!
dpm($error);
}
}
And the salutation function from the controller looks like this:
public function salutation()
{
$tempstore = $this->tempStoreFactory->get('greetingForm_values');
$params = $tempstore->get('params'); // this value should come from the search form
return [
'#markup' => $this->salutation->getGreeting($params),
];
}
Any help is greatly appreciated, and please ask for more information if it is needed.
Routing file
In your use case I believe you can stick to the use of a Form. Please discard the Controller specification from your hello_world.salutation route, because it should be either _form or _controller, not both for a single route.
Service method invocation
For your service definition, you can do this by either statically calling the service as:
$salutation_service = \Drupal::service('hello_world.salutation');
$salutation_service->somePublicMethodCall();
or via Dependency Injection which I assume you are already doing when I look at this->salutation->getGreeting($params)?
Form x Controller
From the provided details, I can't really tell why you need the Controller, but if you need to redirect to the Controller, then you could create a separate route for your HelloWorldSalutation::salutation() method and redirect to it from GreetingForm ::submitForm() via the $form_state object:
$url = \Drupal\Core\Url::fromRoute('hello_world.salutation');
$form_state->setRedirectUrl($url);

PHPunit for my UserController is not covering all lines

Symfony project PHPunit coverage test
UserController
public function userEdit($id, Request $request)
{
$user = $this->userRepository->findOneByCode($id);
if (!$user) {
throw new Exception("User not found!");
}
$userForm = $this->createForm(UserForm::class, $user);
$userForm->handleRequest($request);
if ($userForm->isSubmitted() && $userForm->isValid()) {
$this->userService->save($user);
return $this->redirectToRoute('user_list');
}
return $this->render(
'user/user.html.twig', [
'form' => $userForm->createView(),
]
);
}
TestUserController
public function testUserEdit()
{
$client = static::createClient();
$crawler = $client->request('GET', '/user/test/edit');
$formData = array(
'username' => 'test',
'email' => 'test#test.nl',
'roles' => 'ROLE_ADMIN'
);
$this->assertEquals(
200,
$client->getResponse()->getStatusCode()
);
$form = $this->factory->create(UserForm::class);
$object = User::fromArray($formData);
$form->submit($formData);
$this->assertTrue($form->isSynchronized());
$this->assertEquals($object, $form->getData());
$view = $form->createView();
$children = $view->children;
foreach (array_keys($formData) as $key) {
$this->assertArrayHasKey($key, $children);
}
}
In the userEdit method we have a if loop. But When we run PHPunit coverage test the if loop is not executed. The other if loop for submit is also not covered.
What goes wrong and what can I do in order to cover the test ? Also is this the best solution for Symfony form test since I am new to PHPunit.
I noticed a few things in your code that seem wrong:
The userEdit method name should be userEditAction.
Select the form like this:
$form = $crawler->selectButton('submit')->form();
That 'submit' text is the label on the submit button (e.g. Save).
And then, after filling the fields:
$crawler = $client->submit($form);
You check if the submit was successful by asserting that the resulting HTML page contains expected element (e.g.):
$this->assertGreaterThan(0, $crawler->filter('h1')->count());
Btw. put: $client->followRedirects(true); after instantiating the Client.
Examples are from the official docs.
Regarding the some lines that were not covered by test: whenever you have if clause, you need to test for both conditions. In your case, first you probably have a valid user, instance of User and the other case should be that you pass there an invalid user (null or whatever else). That is usually accomplished by using #dataProvider annotation and method. The data provider method supplies sets of data to the test method. There can be more than one set, so another set contains invalid data to cover the other outcome of the if() clause.
This blog has great examples.
To cover the content of the if-conditions you have to fulfill the conditions new tests. To enter the first if for example you have to write a test where you mock the userRepository and make findOneByCode return null. Then the following if-condition will be executed and throw an exception. Finally you test for a thrown exception in the test.
For the other if-condition you proceed in a similar manner. Write a new test which is designed to fulfill the condition and test the code inside it.

Render controller in TWIG and show form errors

I have indexAction and contactAction
contactAction is a simple form with no mapped fields (FormType) like below:
/**
* #Route("/contact", name="contact")
* #Template()
* #param Request $request
* #return array
*/
public function contactAction(Request $request)
{
$form = $this->createForm(new ContactType());
$form->handleRequest($request);
if ($form->isValid()) {
$firstName = $form->get('first_name')->getData();
$lastName = $form->get('last_name')->getData();
$email = $form->get('email')->getData();
$message = $form->get('message')->getData();
}
return array(
'form' => $form->createView()
);
}
and i render this form in my indexAction with this TWIG command:
{{ render(controller('RusselBundle:Default:contact')) }}
Everything is okey, if page is not reloaded, HTML5 validators works fine, but if form have some errors like: firstName length, error's not show at all, how can i do, so that errors showed up in the form indexAction? Any help would be appreciated. I'm just curious it's possible, and if - how ? Sorry for my english....
Rather than using the request passed into the action you should get the master request from the request stack. As #DebreczeniAndrás says, when you use the render(controller()) you are using a newly created sub-request rather than the request that was actually passed to the page on load (the master request).
public function contactAction(Request $request)
{
$request = $this->get('request_stack')->getMasterRequest();
$form = $this->createForm(new ContactType());
//...
}
On symfony3 use render function like this
{{ render(controller('RusselBundle:Default:contact', {'request':app.request})) }}
If you use the render function in your twig, then that creates a subrequest, thus your original posted (i.e. in your main request) values get lost.
You can pass your main request to your form render action as follows:
{{ render(controller('RusselBundle:Default:contact'), 'request' : app.request ) }}
This will pass all the main request parameters appropriately to your subrequest.

How to call a controller action in Laravel 4.1 and get the filters executed

So the title describes my problem pretty well I think, but let me explain why I want to do this as theremight be an other solution to my problem that I haven't thought about.
Let's say that I have a route specifying the class of the object it will patch:
Route::patch('{class}/{id}', array(
'as' => 'object.update',
function ($class, $id) {
$response = ...;
// here I want to call the update action of the right controller which will
// be named for instance CarController if $class is set to "car")
return $response;
}
));
This is something pretty easy to do with $app->make($controllerClass)->callAction($action, $parameters); but doing it this way won't call the filters set on the controller.
I was able to do it with laravel 4.0 with the callAction method, passing the app and its router, but the method has changed now and the filters are called in the ControllerDispatcher class instead of the Controller class.
If you have routes declared for your classes then you may use something like this:
$request = Request::create('car/update', 'POST', array('id' => 10));
return Route::dispatch($request)->getContent();
In this case you have to declare this in routes.php file:
Route::post('car/update/{id}', 'CarController#update');
If you Use this approach then filters will be executed automatically.
Also you may call any filter like this (not tested but should work IMO):
$response = Route::callRouteFilter('filtername', 'filter parameter array', Route::current(), Request::instance());
If your filter returns any response then $response will contain that, here filter parameter array is the parameter for the filter (if there is any used) for example:
Route::filter('aFilter', function($route, $request, $param){
// ...
});
If you have a route like this:
Route::get('someurl', array('before' => 'aFilter:a_parameter', 'uses' => 'someClass'));
Then the a_parameter will be available in the $param variable in your aFilter filter's action.
So I might have found a solution to my problem, it might not be the best solution but it works. Don't hesitate to propose a better solution!
Route::patch('{class}/{id}', array(
'as' => 'object.update',
function ($class, $id) {
$router = app()['router']; // get router
$route = $router->current(); // get current route
$request = Request::instance(); // get http request
$controller = camel_case($class) . 'Controller'; // generate controller name
$action = 'update'; // action is update
$dispatcher = $router->getControllerDispatcher(); // get the dispatcher
// now we can call the dispatch method from the dispatcher which returns the
// controller action's response executing the filters
return $dispatcher->dispatch($route, $request, $controller, $action);
}
));

Symfony2 - How to render a view from another controller

I have two controllers, homepage and Security.
In the homepage, I am displaying one view and in the security, I am doing some things, and one of them is the email address validation.
What I would like is that when the email validation code is not valid, display the homepage with a flash message. For that, I will have to render the indexAction of the HomepageController, from the Security controller, by giving him as parameter the flash message.
How can this be done? Can I render a route or an action from another controleller?
Thank you in advance.
I believe the checking should not be done in the Security controller. Right place in my opinion is a separate validator service or right in the entity which uses the email address.
But to your question, you can call another controller's action with $this->forward() method:
public function indexAction($name)
{
$response = $this->forward('AcmeHelloBundle:Hello:fancy', array(
'name' => $name,
'color' => 'green',
));
return $response;
}
The sample comes from symfony2 documentation on: http://symfony.com/doc/2.0/book/controller.html#forwarding
I have found the solution, simply use the forward function by specifying the controller and the action nanme:
return $this->forward('MerrinMainBundle:Homepage:Index', array('flash_message'=>$flash_message));
redirectToRoute : Just a recap with current symfony versions (as of 2016/11/25 with v2.3+)
public function genericAction(Request $request)
{
if ($this->evalSomething())
{
$request->getSession()->getFlashBag()
->add('warning', 'some.flash.message');
$response = $this->redirectToRoute('app_index', [
'flash_message' => $request->getSession()->getFlashBag(),
]);
} else {
//... other logic
}
return $response;
}

Categories