I understand that the $view can be accessed within a rendered template file, but I wonder if there is anyway I can get it elsewhere?
The reason is that I'm building SF2 on top of a classic/legacy system and there are certain templates that cannot be rendered with the normal render method. However, I still want to be able to access the view helper inside those template, so I want to init a global $view which can be used in these legacy templates
These helpers are provided in the PhpEngine class in the Templating component (docs). You can acces this class with the templating.engine.php service (you need to have PHP enabled as a templating engine).
From a controller it will look like this:
// ...
public function fooAction(...)
{
// ...
$template = $this->get('templating.engine.php')->render(...);
}
If you are using another class, you should use DI:
// src/Acme/DemoBundle/Foo.php
namespace Acme\DemoBundle;
use Symfony\Component\Templating\EngineInterface;
class Foo
{
private $templating;
public function __construct(EngineInterface $templating)
{
$this->templating = $templating;
}
public function bar()
{
// ...
$template = $this->templating->render(...);
}
}
// app/config.yml
services:
acme_demo:
foo:
class: Acme\DemoBundle\Foo
arguments: [#templating.engine.php]
More about DI in the docs.
Related
Aware that there is a lot of information around the net regarding this, I am still having a lot of trouble getting this to work.
I have created a custom service:
<?php
namespace App\Service;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\AccommodationType;
use App\Entity\Night;
class AvailabilityChecks {
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
public function nightAvailable(string $RoomCode, string $NightDate) {
$GetRoom = $this->em->getDoctrine()->getRepository(AccommodationType::class)->findOneBy([
'RoomCode' => $RoomCode
]);
$RoomQnt = $GetRoom->getNightlyQnt();
$GetNight = $this->em->getDoctrine()->getRepository(Night::class)->findOneBy([
'RoomCode' => $RoomCode,
'NightDate' => $NightDate
]);
$NumberOfNights = $GetNight->count();
if($NumberOfNights<$RoomQnt) {
return true;
}
else {
return false;
}
}
}
and have put this in services.yaml:
AvailabilityChecks.service:
class: App\Service\AvailabilityChecks
arguments: ['#doctrine.orm.entity_manager']
So when I try and use this in my controller, I get this error:
Too few arguments to function App\Service\AvailabilityChecks::__construct(), 0 passed in /mypath/src/Controller/BookController.php on line 40 and exactly 1 expected
I just can't figure out why it's not injecting the ORM stuff into the constructor! Any help greatly appreciated
The problem is in your BookController. Even though you didn't posted its code I can assume you create new AvailabilityChecks in it (on line 40).
In Symfony every service is intantiated by service container. You should never intantiate service objects by yourself. Instead BookController must ask service container for AvailabilityChecks service. How should it do it ?
In Symfony <3.3 we used generally :
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class MyController extends Controller
{
public function myAction()
{
$em = $this->get('doctrine.orm.entity_manager');
// ...
}
}
Nowadays services can be injected in controllers using autowiring which is way easier:
use Doctrine\ORM\EntityManagerInterface;
class MyController extends Controller
{
public function myAction(EntityManagerInterface $em)
{
// ...
}
}
You are using the wrong service for what you want to do. The alias doctrine that is used, e.g. in the AbstractController when you call getDoctrine() is bound to the service Doctrine\Common\Persistence\ManagerRegistry.
So the code you wrote fits better with that and you should either add #doctrine or #Doctrine\Common\Persistence\ManagerRegistry to the service definition.
Both with your current configuration or the changed one, you don't have to call $this->em->getDoctrine(), because $this->em is already equivalent to $this->getDoctrine() from your controller. Instead you could create a (private) method to make it look more like that code, e.g.:
private function getDoctrine()
{
return $this->em;
}
Then you can call $this->getDoctrine()->getRepository(...) or use $this->em->getRepository(...) directly.
In Symfony 4, you dont need to create it as services. This is automatically now. Just inject the dependencies what you need in the constructor. Be sure that you have autowire property with true value in services.yml (it is by default)
Remove this from services.yml:
AvailabilityChecks.service:
class: App\Service\AvailabilityChecks
arguments: ['#doctrine.orm.entity_manager']
You dont need EntityManagerInterface because you are not persisting anything, so inject repositories only.
<?php
namespace App\Service;
use App\Entity\AccommodationType;
use App\Entity\Night;
use App\Repository\AccommodationTypeRepository;
use App\Repository\NightRepository;
class AvailabilityChecks {
private $accommodationTypeRepository;
private $nightRepository
public function __construct(
AcommodationTypeRepository $acommodationTypeRepository,
NightRepository $nightRepository
)
{
$this->acommodationTypeRepository = $acommodationTypeRepository;
$this->nightRepository = $nightRepository;
}
public function nightAvailable(string $RoomCode, string $NightDate) {
$GetRoom = $this->acommodationTypeRepository->findOneBy([
'RoomCode' => $RoomCode
]);
$RoomQnt = $GetRoom->getNightlyQnt();
$GetNight = $this->nightRepository->findOneBy([
'RoomCode' => $RoomCode,
'NightDate' => $NightDate
]);
$NumberOfNights = $GetNight->count();
if($NumberOfNights<$RoomQnt) {
return true;
}
else {
return false;
}
}
}
In SF4, you no longer need to specify dependencies required by your custom service in the service.yaml file. All you have to do is to use dependency injection.
So remove config lines, and call your service directly in the controller method :
<?php
namespace App\Controller;
use App\Service\AvailabilityChecks ;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class AppController extends AbstractController
{
public function index(AvailabilityChecks $service)
{
...
}
}
Having said that, i think you don't need custom service to do simple operations on database. Use repository instead.
I have a test class that reports an undefined variable and I cannot seem to understand what the issue is.
Basically the listener below is suppose to listen to an application boot event documented in the class below:
<?php
namespace Colleen\Core\Event\Application;
final class ApplicationBootedEvents
{
const APP_BOOTED = 'application.booted';
}
My event class is as shown below which receives an instance of the application itself.
<?php
namespace Colleen\Core\Event\Application;
use Symfony\Component\EventDispatcher\Event;
use Colleen\Core\Application;
/**
* The application.booted event is dispatched each time
* an application instance is created in the system.
*
*/
class ApplicationBootedEvent extends Event
{
protected $app;
public function __construct(Application $app)
{
$this->app = $app;
}
public function getApplication()
{
return $app;
}
}
These two classes to me look perfect according to Symfony's documentation on the Event Dispatcher Component. Following is the listener class that is suppose to listen to ApplicationBootedEvents::APP_BOOTED event.
<?php
namespace Colleen\Core\Event\Application\Listener;
use Colleen\Core\Event\Application\ApplicationBootedEvent;
class ApplicationBootedListener
{
public function onBoot(ApplicationBootedEvent $event)
{
$container = $event->getApplication()->getContainer();
$container->set('class.dispatcher', '\\Symfony\\Component\\EventDispatcher\\EventDispatcher');
}
}
The Listener class does nothing at the moment and my test case is to test whether the "class.dispatcher" key exist on my container which simple extends Pimple and is made available through the Application Object.
Below is my test that shows how these will eventually be used in my front controller or any class that stands between them and the front controller.
<?php
namespace Colleen\Qa\Core\Event\Application\Listener;
use Colleen\Core\Event\Application\Listener\ApplicationBootedListener;
use Colleen\Core\Event\Application\ApplicationBootedEvents;
use Colleen\Core\Event\Application\ApplicationBootedEvent;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Colleen\Core\Container\Container;
use Colleen\Core\Application;
class AppliocationBootedListenerTest extends \PHPUnit_Framework_TestCase
{
public function testApplicationBootListener()
{
$dispatcher = new EventDispatcher();
$dispatcher->addListener(
ApplicationBootedEvents::APP_BOOTED, array(
new ApplicationBootedListener(), 'onBoot'
));
$app = $dispatcher->dispatch(ApplicationBootedEvents::APP_BOOTED, new ApplicationBootedEvent(new Application(new Container())))->getApplication();
$expected = '\\Symfony\\Component\\EventDispatcher\\EventDispatcher';
$actual = $app->getContainer()->get('class.dispatcher');
$this->assertSame($expected, $actual);
}
}
The idea is to test whether the Listener gets called and if it is able to feed our application object's container with all the necesary objects we will need to get our web framework to work.
Below is the output I get as a result if running this test case.
There's an error in your ApplicationBootedEvent.php file, on line 24 as the stack trace suggested..
Change
public function getApplication()
{
return $app;
}
To
public function getApplication()
{
return $this->app;
}
what I am trying to do is to have custom error page, not only will they be extending the base layout but also I want extra up selling content in those pages too so changing templates only is not an option
regardless of the reason (404 Not Found or just missing variable) I would like to show my template and my content instead
I have spent hours trying to get this going with no luck
app/console --version
Symfony version 2.5.6 - app/dev/debug
I tried some resources, but couldn't get it working. The name a few:
http://symfony.com/doc/current/reference/configuration/twig.html
http://symfony.com/doc/current/cookbook/controller/error_pages.html
I'm running in dev with no debug, see app_dev.php below:
$kernel = new AppKernel('dev', false);
following the tutorials i got these extra bits
app/config/config.yml
twig:
exception_controller: SomethingAppBundle:Exception:show
in my bundle
<?php
namespace Something\AppBundle\Controller;
use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
use Symfony\Component\HttpKernel\Exception\FlattenException;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class ExceptionController extends Controller
{
public function showAction( FlattenException $error, DebugLoggerInterface $debug)
{
print_r($error);
}
}
but my error controller does not get executed,
I am on purpose causing error by trying to echo undefined variable in different controller, since it should handle error from entire application
At the beginning you need to create action in the controller:
<?php
namespace AppBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class ErrorController extends Controller
{
public function notFoundAction()
{
return $this->render('error/404.html.twig');
}
}
Then you need to create a Listener:
<?php
namespace AppBundle\EventListener;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\HttpKernelInterface;
class NotFoundHttpExceptionListener
{
private $controller_resolver;
private $request_stack;
private $http_kernel;
public function __construct($controller_resolver, $request_stack, $http_kernel)
{
$this->controller_resolver = $controller_resolver;
$this->request_stack = $request_stack;
$this->http_kernel = $http_kernel;
}
public function onKernelException(GetResponseForExceptionEvent $event)
{
if ($event->getException() instanceof NotFoundHttpException) {
$request = new \Symfony\Component\HttpFoundation\Request();
$request->attributes->set('_controller', 'AppBundle:Error:notFound');
$controller = $this->controller_resolver->getController($request);
$path['_controller'] = $controller;
$subRequest = $this->request_stack->getCurrentRequest()->duplicate(array(), null, $path);
$event->setResponse($this->http_kernel->handle($subRequest, HttpKernelInterface::MASTER_REQUEST)); // Simulating "forward" in order to preserve the "Not Found URL"
}
}
}
Now register the service:
#AppBundle/Resources/config/services.yml
services:
kernel.listener.notFoundHttpException:
class: AppBundle\EventListener\NotFoundHttpExceptionListener
tags:
- { name: kernel.event_listener, event: kernel.exception, method: onKernelException, priority: -10 }
arguments: [ #controller_resolver, #request_stack, #http_kernel ]
Not tested this, but rather it should work;)
EDIT:
Tested, it works. On the rendered page, you have a session, so you have access to app.user, his cart, and other matters related to the session.
I have a form event subscriber which needs an entity repository.
I would like to inject this repository dependency ideally without having to use the constructors of the subscriber and its parents because this subscriber is needed in many different forms.
So basically I have the following chain :
Controller calls -> CustomManagerService instantiates-> Form instantiates -> EventSubscriber needs-> EntityRepository
the maanager is already a service. It is a pain both to transmit a constructor repository argument from the manager through the form to the subscriber and it is a pain to set each form as a service.
Why can't I instantiate the repository in the subscriber directly ? I have read it is a bad practice.
EDIT : this is what I have so far :
in my controller :
$unitRepository = $this->getDoctrine()->getRepository('UnitRepository');
$myManager = $this->get('my_manager')
$form = $myManager->createForm($unitRepository);
in myManager:
public function createForm(UnitRepository $unitRepository){
return $this->formFactory->createForm(
new xxxType($unitRepository)
}
in my form:
use MyBundle/AddUnitFieldSubscriber;
protected $unitRepository;
public function __construct(UnitRepository $unitRepository)
{
$this->unitRepository = $unitRepository;
}
public function buildform()
{
$builder->addEventSubscriber(new AddUnitFieldSubscriber($this->unitRepository));
}
in my subscriber:
protected $unitRepository;
public function __construct(UnitRepository $unitRepository)
{
$this->unitRepository = $unitRepository;
}
public function preSetData(FormEvent $event)
{
$unitRepository = $this->unitRepository;
$unitRepository->doStuff()
}
I found this extremely lenghty, and sometimes I have a form calling a subform which is the one using the eventSubscriber. if I set the forms as services, I also sometimes get errors cause I am instantiating them without the required first constructur parameter.
What would be the shortest path to do it right and to not repeat all this knowing only the subscriber need access to the repository ?
Thanks a lot !
I'm really not sure I understood everything or even anything, but I'm going to try a response.
I would suggest you to define a service SubscriberProvider which will be responsible of the instantiation of the subscriber and the injection of the repository in the subscriber (via a setter of the subscriber). You could retrieve an instance of subscriber using a method get, retrieve, create, provide or whatever you prefer of the service SubscriberProvider. You could then inject this provider in another service.
EDIT
Here is the definition of the service related to your form type:
services:
your_own_bundle.form.type.unit:
class: Your\OwnBundle\Form\Type\UnitType
arguments:
- "#doctrine"
tags:
- { name: form.type, alias: unit }
And its class:
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Doctrine\Common\Persistence\ManagerRegistry;
use Your\OwnBundle\Event\AddUnitFieldSubscriber;
class UnitType extends AbstractType
{
protected $unitRepository;
public function __construct(ManagerRegistry $doctrine)
{
$this->unitRepository = $doctrine->getRepository('UnitRepository');
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addEventSubscriber(
new AddUnitFieldSubscriber($this->unitRepository)
);
}
Then, you can use this type like that:
$builder->add('available_unit', 'unit', array());
This way, you don't have to pass the repository to your manager.
in my symfony2 project i need call the same action in many controllers and this action should return a very simple php array that then will be passed to a twig template by these controllers. How can i do it?
A pratical example can explain my situation better.
1.shared controller
// Acme/DemoBundle/Controller/MetasController
class MetasController extends Controller {
public function metasAction() {
$myArray= array();
return $myAarray;
}
}
page render controller
// Acme/DemoBundle/Controller/PageController
class PageController extends Controller {
protected $property = "test";
public function indexAction() {
$metas= $this->forward('AcmeDemoBundle:Metas:metas');
return $this->render('AcmeDemoBundle:Page:index.html.twig', array('property'=>property, 'metas'=>$metas));
}
}
when i do this i get an error: the controller must be a response array given.
You should create a service
// Acme/DemoBundle/Controller/MetasController
class MetasController {
public function metasAction() {
$myArray= array();
return $myAarray;
}
}
declare as service in Acme\DemoBundle\Resources\config\services.yml
services:
demo.metas:
class: "Acme\DemoBundle\Controller\MetasController"
Then you can use it in any other controller
// Acme/DemoBundle/Controller/PageController
class PageController extends Controller {
protected $property = "test";
public function indexAction() {
$metas= $this->get('demo.metas')->metas();
return $this->render('AcmeDemoBundle:Page:index.html.twig', array('property'=>property, 'metas'=>$metas));
}
}
In your action controller :
<?php
...
$arrayExample = array();
return $this->render('ExampleBundle:ExampleFolder:exampleTemplate', array('myArray' => $arrayExample));
And in your twig template now you have access to your array using myArray
Example :
{% for data in myArray %}
...
{% endfor %}
Try this :
use Symfony\Component\HttpFoundation\Response;
public function indexAction()
{
...
$content = $this->renderView(
'AcmeDemoBundle:Page:index.html.twig',
array('property'=> $property,
'metas' => $metas
));
return new Response($content);
}
Yes, you can register your controller as a service as it said above but I would recommend to isolate this logic in a different place. It might be a service but not controller.
As I understand you need the same array in several places. So, it might be some class registered as service or some simple class with static method providing this array. In this case your code will be much cleaner.
If you need this array only in view you can define custom twig method which will return array you need. If this array might be different time to time (if it might depend on some data) you can pass entity manager to the service providing this array or to the twig extension.
(The best use of controllers is to be just a proxy between view and data layer. It's not a good idea to use it for such purposes as you described (in my opinion of course).)