I need replace string in view after rendered.
My all controllere use annotaion #Template("path").
My controller:
...
class AboutController extends Controller
{
/**
* #Route("/about-us", name="about")
* #Method("GET")
* #Template("#AppBundle/Resources/views/About/index.html.twig")
*/
public function indexAction()
{
}
}
...
I know to do it without annotaion:
...
class AboutController extends Controller
{
/**
* #Route("/about-us", name="about")
* #Method("GET")
*/
public function indexAction()
{
$content = $this->renderView('AppBundle/Resources/views/About/index.html.twig', []);
$content = str_replace('my text', 'my new text', $content);
return new Response($content);
}
}
...
How I can do it with annotaion (#template)?
I think you should use Symfony's Event system onKernelResponse
This will allow you to grab the response after controller action return it and modify the response before sending it.
To subscribe an event follow Syfmony's doc example.
You did not tell us which version of Symfony you are using, those links are 3.4.
Hope this helps.
Related
i am building an Api with symfony 4.2 and want to use jms-serializer to serialize my data in Json format, after installing it with
composer require jms/serializer-bundle
and when i try to use it this way :
``` demands = $demandRepo->findAll();
return $this->container->get('serializer')->serialize($demands,'json');```
it gives me this errur :
Service "serializer" not found, the container inside "App\Controller\DemandController" is a smaller service locator that only knows about the "doctrine", "http_kernel", "parameter_bag", "request_stack", "router" and "session" services. Try using dependency injection instead.
Finally i found the answer using the Symfony serializer
it's very easy:
first : istall symfony serialzer using the command:
composer require symfony/serializer
second : using the serializerInterface:
.....//
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
// .....
.... //
/**
* #Route("/demand", name="demand")
*/
public function index(SerializerInterface $serializer)
{
$demands = $this->getDoctrine()
->getRepository(Demand::class)
->findAll();
if($demands){
return new JsonResponse(
$serializer->serialize($demands, 'json'),
200,
[],
true
);
}else{
return '["message":"ooooops"]';
}
}
//......
and with it, i don't find any problems with dependencies or DateTime or other problems ;)
As I said in my comment, you could use the default serializer of Symfony and use it injecting it by the constructor.
//...
use Symfony\Component\Serializer\SerializerInterface;
//...
class whatever
{
private $serializer;
public function __constructor(SerializerInterface $serialzer)
{
$this->serializer = $serializer;
}
public function exampleFunction()
{
//...
$data = $this->serializer->serialize($demands, "json");
//...
}
}
Let's say that you have an entity called Foo.php that has id, name and description
And you would like to return only id, and name when consuming a particular API such as foo/summary/ in another situation need to return description as well foo/details
here's serializer is really helpful.
use JMS\Serializer\Annotation as Serializer;
/*
* #Serializer\ExclusionPolicy("all")
*/
class Foo {
/**
* #Serializer\Groups({"summary", "details"})
* #Serializer\Expose()
*/
private $id;
/**
* #Serializer\Groups({"summary"})
* #Serializer\Expose()
*/
private $title;
/**
* #Serializer\Groups({"details"})
* #Serializer\Expose()
*/
private $description;
}
let's use serializer to get data depends on the group
class FooController {
public function summary(Foo $foo, SerializerInterface $serialzer)
{
$context = SerializationContext::create()->setGroups('summary');
$data = $serialzer->serialize($foo, json, $context);
return new JsonResponse($data);
}
public function details(Foo $foo, SerializerInterface $serialzer)
{
$context = SerializationContext::create()->setGroups('details');
$data = $serialzer->serialize($foo, json, $context);
return new JsonResponse($data);
}
}
I use FOSRestBundle in Symfony 4 to API project. I use annotations and in controller I have for example
use FOS\RestBundle\Controller\Annotations as Rest;
/**
* #Rest\Get("/api/user", name="index",)
* #param UserRepository $userRepository
* #return array
*/
public function index(UserRepository $userRepository): array
{
return ['status' => 'OK', 'data' => ['users' => $userRepository->findAll()]];
}
config/packages/fos_rest.yaml
fos_rest:
body_listener: true
format_listener:
rules:
- { path: '^/api', priorities: ['json'], fallback_format: json, prefer_extension: false }
param_fetcher_listener: true
view:
view_response_listener: 'force'
formats:
json: true
Now I'd like to add custom header 'X-Total-Found' to my response. How to do it?
You are relying in FOSRestBundle ViewListener, so that gives you limited options, like not being able to pass custom headers. In order to achieve what you want, you will need to call $this->handleView() from your controller and pass it a valid View instance.
You can use the View::create() factory method or the controller $this->view() shortcut. Both take as arguments the array of your data, the status code, and a response headers array. Then, you can set up your custom header there, but you will have to do that for every call.
The other option you have, which is more maintainable, is register a on_kernel_response event listener/subscriber and somehow pass it the value of your custom header (you could store it in a request attribute for example).
Those are the two options you have. You may have a third one, but I cannot come up with it at the minute.
I ran into the same issue. We wanted to move pagination meta information to the headers and leave the response without an envelope (data and meta properties).
My Environment
Symfony Version 5.2
PHP Version 8
FOS Rest Bundle
STEP 1: Create an object to hold the header info
// src/Rest/ResponseHeaderBag.php
namespace App\Rest;
/**
* Store header information generated in the controller. This same
* object is used in the response subscriber.
* #package App\Rest
*/
class ResponseHeaderBag
{
protected array $data = [];
/**
* #return array
*/
public function getData(): array
{
return $this->data;
}
/**
* #param array $data
* #return ResponseHeaderBag
*/
public function setData(array $data): ResponseHeaderBag
{
$this->data = $data;
return $this;
}
public function addData(string $key, $datum): ResponseHeaderBag
{
$this->data[$key] = $datum;
return $this;
}
}
STEP 2: Inject the ResponseHeaderBag into the controller action
public function searchCustomers(
ResponseHeaderBag $responseHeaderBag
): array {
...
...
...
// replace magic strings and numbers with class constants and real values.
$responseHeaderBag->add('X-Pagination-Count', 8392);
...
...
...
}
STEP 3: Register a Subscriber and listen for the Response Kernel event
// config/services.yaml
App\EventListener\ResponseSubscriber:
tags:
- kernel.event_subscriber
Subscribers are a great way to listen for events.
// src/EventListener/ResponseSubscriber
namespace App\EventListener;
use App\Rest\ResponseHeaderBag;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class ResponseSubscriber implements EventSubscriberInterface
{
public function __construct(
protected ResponseHeaderBag $responseHeaderBag
){
}
/**
* #inheritDoc
*/
public static function getSubscribedEvents()
{
return [
KernelEvents::RESPONSE => ['addAdditionalResponseHeaders']
];
}
/**
* Add the response headers created elsewhere in the code.
* #param ResponseEvent $event
*/
public function addAdditionalResponseHeaders(ResponseEvent $event): void
{
$response = $event->getResponse();
foreach ($this->responseHeaderBag->getData() as $key => $datum) {
$response->headers->set($key, $datum);
}
}
}
i need some help i want to write a unit test about a controler method , i have searched for examples and tested a lot of method's but none of them has worked:
Here is my controller:
class ComputerController extends Controller
{
/**
* #Route("/list-computers.html", name="back_computer_list")
* #return RedirectResponse|Response
*/
function listComputerAction()
{
$ad = $this->get("ldap_service");
$computers = $ad->getAllComputer();
return $this->render('BackBundle:Computer:list.html.twig', array(
"computers" => $computers,
));
}
I have tried to test it with mock like this:
class ComputerController extends Controller
{
/**
* #var EngineInterface
*/
private $templating;
public function setTemplating($templating)
{
$this->templating = $templating;
}
and i have created a test method:
class ComputerControllerTest extends TestCase {
public function testlistComputerAction(){
$templating = $this->getMockBuilder('BackBundle\Controller\ComputerController')->getMock();
$computers = [1,2];
$templating->expects($this->once())
->method('render')
->with('BackBundle:Computer:list.html.twig', array(
"computers" => $computers))
->will($this->returnValue( $computers));
$controller = new ComputerController();
$controller->setTemplating($templating);
$this->assertEquals('success', $controller->listComputerAction());
}
When i start executing phpunit , i have this warning"Trying to configure method "render" which cannot be configured because it does not exist, has not been specified, is final, or is static"
I would be thankful if someone has an idea about this
I tried to Test a method in ldapService : Here is the method's of the service that i want to test
/**
* #return bool|resource
*/
public function getLdapBind()
{
if (!$this->ldapBind) {
if ($this->getLdapConnect()) {
$this->ldapBind = #ldap_bind($this->ldapConnect, $this->ldapUser, $this->ldapPass);
}
}
return $this->ldapBind;
}
/**
* #param $ldapUser
* #param $password
* #return bool
*/
function isAuthorized($ldapUser, $password)
{
$result = false;
if ($this->ldapConnect) {
$result = #ldap_bind($this->ldapConnect, $ldapUser, $password);
}
return $result;
}
Here is the test (using Mock):
<?php
namespace BackBundle\Tests\Service;
use PHPUnit\Framework\TestCase;
use BackBundle\Service\LdapService;
use PHPUnit_Framework_MockObject_InvocationMocker;
class LdapServiceTest extends TestCase {
public function testgetLdapConnect()
{
// $LdapService = new LdapService();
$ldapMock = $this->getMockBuilder( 'LdapService')->setMethods(['getLdapBind'])->disableOriginalConstructor()->getMock();
$ldapMock->expects($this->once())
// ->method()
->with(array('ldap_bind', 'mike', 'password'))
->will($this->returnValue(true));
$ldapMock->isAuthorized('mike', 'password');
}
}
But i have a warning that i can't resolve : "Method name matcher is not defined, cannot define parameter matcher without one"
If someone , has an idea about that please
Honestly, there is nothing useful to test in that three-line controller. #1 is the service container, and #3 is the Twig subsystem. Line #2 can be unit tested on it's own.
With more complex controllers, I have found that making them a service where all the dependencies are passed in, either by constructor, or into the action itself does make slightly more complex controllers quite easy, but very few need that anyway.
I'm trying to set up a Contact Form and all is going well. Set up my Controller with ->send(), all works fine (takes a bit of time). When I set it up to work with ->queue(), seems to work fine (no delay), job is set up, mail is sent when I dispatch. But this time my mail template does not include the data sent to the Mailer.
My Controller:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Mail\Contact;
use Illuminate\Support\Facades\Mail;
class PagesController extends Controller
{
public function sendContact(Request $request)
{
Mail::to('webform#email.com')
->queue(new Contact($request));
return redirect('/contact')->with('status', 'Message sent. Thanks!');
}
}
My Mailer (App\Mail\Contact):
class Contact extends Mailable
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct()
{
}
/**
* Build the message.
*
* #return $this
*/
public function build(Request $request)
{
$subject = 'Web Message from: ' . $request->name;
return $this->from('myemail#email.com')
->subject($subject)
->view('emails.contact-template')
->with([
'name' =>$request->name,
'email' => $request->email,
'message' => $request->message,
'date' => $request->date,
]);
}
}
The problem was that I needed to declare the variables as public. Below is the solution that eventually worked:
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Http\Request;
use Illuminate\Contracts\Queue\ShouldQueue;
class Contact extends Mailable
{
use Queueable, SerializesModels;
public $request;
public $name;
public $from;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct(Request $request)
{
$this->request = $request->all();
$this->name = $request->name;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
$subject = 'Webform messsage from: ' . $this->name;
$from = 'webform#mail.com';
return $this
->from( $from )
->subject($subject)
->view('emails.contact-template');
}
}
I would like to change the default behaviour of the #Template annotation which automatically renders the template named as the controller action.
So in an ArticleController.php
/**
* #Route("/new", name="article_new")
* #Method("GET")
* #Template()
*/
public function newAction()
{
// ...
return array();
}
would render Article/new.html.twig.
I want to change this to referr to the name of the route the action was called with so you could have multiple routes for an action each rendering a different template.
This is the way I currently do it (without #Template):
/**
* #Route("/new", name="article_new")
* #Route("/new_ajax", name="article_new_ajax")
* #Method("GET")
*/
public function newAction()
{
// ...
$request = $this->getRequest();
$route = $request->attributes->get('_route');
$template = 'AcmeDemoBundle:' . $route . '.html.twig';
return $this->render($template, array(
// ...
));
}
I wonder now if there is a way to change the behaviour of #Template to do exactly that. Is there a way to customize the annotations or just some aproach to make it more automated?
Any ideas?
I have now found a solution using the kernelView event. This is independet of the #Template annotation. The kernelView event fires whenever a controller action doesn't return a response object.
(This solution is based on Symfony 2.4)
event listener service:
services:
kernel.listener.route_view:
class: Acme\DemoBundle\Templating\RouteView
arguments: ["#request_stack", "#templating"]
tags:
- { name: kernel.event_listener, event: kernel.view }
event listener class:
namespace Acme\DemoBundle\Templating;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
class RouteView
{
protected $controller;
protected $route;
protected $templating;
function __construct(RequestStack $requestStack, $templating)
{
$this->controller = $requestStack->getCurrentRequest()->attributes->get('_controller');
$this->route = $requestStack->getCurrentRequest()->attributes->get('_route');
$this->templating = $templating;
}
public function onKernelView(GetResponseForControllerResultEvent $event)
{
$controllerAction = substr($this->controller, strrpos($this->controller, '\\') + 1);
$controller = str_replace('Controller', '', substr($controllerAction, 0, strpos($controllerAction, '::')));
$template = 'AcmeDemoBundle:' . $controller . ':' . str_replace(strtolower($controller) . '_', '', $this->route) . '.html.twig';
$response = $this->templating->renderResponse($template, $event->getControllerResult());
$event->setResponse($response);
}
}
Now the controller behaves like this:
/**
* #Route("/new", name="article_new") -> Article:new.html.twig
* #Route("/new_ajax", name="article_new_ajax") -> Article:new_ajax.html.twig
* #Method("GET")
*/
public function newAction()
{
// ...
return array();
}
FOSRestBundle includes similar functionality to #Template but on class-level since my pull request if you use the #View annotation on class-level.
This can be useful if want to your template-filenames to reflect the action-names but not the route-names ( as opposed to what was asked for in the question ).
The rendered template will be i.e. ...
<controller-name>/<action-name>.html.twig
... for HTML views.
Example: AcmeBundle\Controller\PersonController::create() will render
AcmeBundle/Resources/views/Person/create.html.twig
Before the PR you had to annotate every method.
Annotating a method still gives the possibility to override template,template-variable and status-code though.
example:
/**
* #FOSRest\View(templateVar="testdata", statusCode=201)
*/
class PersonController implements ClassResourceInterface
{
public function newAction()
{
return $this->formHandler->createForm();
// template: Person/new.html.twig
// template variable is 'form'
// http status: 201
}
public function helloAction()
{
return "hello";
// template: Person/hello.html.twig
// template variable 'testdata'
// http status: 201
}
/**
* #FOSRest\View("AnotherBundle:Person:get", templatevar="person")
*/
public function getAction(Person $person)
{
return $person;
// template: AnotherBundle:Person:get
// template variable is 'person'
// http status: 201
}
/**
* #FOSRest\View("AnotherBundle:Person:overview", templatevar="persons", statusCode=200)
*/
public function cgetAction()
{
return $this->personManager->findAll();
// template: AnotherBundle:Person:overview
// template variable is 'persons'
// http status: 200
}
// ...