I got two Controllers:
class FirstController extends Controller
{
/**
* #Route("/first")
*/
public function indexAction()
{
$second = new SecondController();
$second->doit();
}
}
class SecondController extends Controller
{
public function doit()
{
$render = $this->renderView('::base.html.twig');
//write $render to disk
}
}
Instead of 'done' being written to my screen, I got error:
What am I doing wrong?
You are not supposed to instantiate controllers yourself. The framework is supposed to do that for you. If you are in controller1, and want controller2 to actually handle the request, which could use some explanation on your part, you need to forward to it, like this : $this->forward('MyBundle:MyController:myaction)
Also, you should maybe have your doit method in a service. Controllers are supposed to stay skinny, and care only about HTTP:
they receive the request
they call a service based on the request, this service should know nothing about HTTP, and be callable from a command as well as from a controller
they return a response based on what the service answers.
Related
Symfony's SessionInterface is still a little bit vague / magic for me. So if someone can enlighten me, please.
Naturally, the request contains a session object and can be used within a controller.
class SimpleController extends AbstractController
{
public function index(Request $request): Response
{
$request->getSession()->set('session-var', 10);
}
}
However, within a service you can also include the SessionInterface as a service and work with, presumably, the same ParameterBag.
class SimpleService
{
public function __construct(SessionInterface $session)
{
$session->set('session-var', 10);
}
}
My colleague pointed out that a session is (and should always be) part of the request. This makes sense to me, but also got me thinking: why are you able to us the SessionInterface as a service when it is a object/property of the request.
What I want to achieve in the end is to include a service in my controller and in this service work with the current session.
Code example of my use case would look something like this.
class SimpleController extends AbstractController
{
private $simpleService;
public function __construct(SimpleService $simpleService)
{
$this->simpleService = $simpleService;
}
public function index(Request $request): Response
{
$this->simpleService->doSomething();
}
}
class SimpleService
{
private $session;
public function __construct(SessionInterface $session)
{
$this->session = $session;
}
public function doSomething()
{
// Do some things..... and save the result in the session.
$this->session->set('just_a_preference', 50);
}
}
So:
Is this the/a correct way to work with sessions in Symfony?
Is 'just_a_preference' saved in the same session parameter bag as my $request->getSession()?
If not, what is the correct way to work with the current request session outside the controller?
If work with Symfony 4.4.
Both methods are fine. They both reference the same object. Accessing the session via the interface is preferred though. I can come up with at least three reasons why:
extend the functionality: the service behind the interface can be easily decorated – eg. LoggingSessionDecorator which would log session activity
proper OOP code: with $request->getSession()->set(…) you violate the Law of Demeter principle, that said:
it's easier to mock the session in your unit tests
In the end – yes, it doesn't matter. Both methods reference the same object
As said in 1, better to use the interface. At least in my opinion.
Is it a good practice to have a service getter for frequently used services in a controller? For example I mean:
class SomeController Extends Contorller {
private function getSomethingManager()
{
return $this->get('myvendorname.something.manager');
}
}
Your example is a bit confusing because you can use the Doctrine service directly with your controller. You can inject it in your Action if you use the Autowire function.
public function test(EntityManagerInterface $em) {
}
Then you have the entity manager injected or you can load it over the controller with:
$this->getDoctrine()->getManager()
So this is not a real good example. When you use autowire all classes are registered as service and you can use it.
For database queries you have to use entities and repositories.
https://symfony.com/doc/current/doctrine.html
If you are above Symfony 3.3 you can use a Service Locater. You list all common services in Service Locator class. When you need to fetch a specific service from anywhere (from example, Controller, Command, Service so on), all you have to do is, inject ServiceLocator class and fetch required service via ServiceLocator:locate.
It is pretty simple and useful. It helps you to reduce dependency injection as well. Have a look at the full example in the link above.
class ServiceLocator implements ServiceLocatorInterface, ServiceSubscriberInterface
{
private $locator;
public function __construct(ContainerInterface $locator)
{
$this->locator = $locator;
}
public static function getSubscribedServices()
{
return [
ModelFactoryInterface::class,
CalculatorUtilInterface::class,
EntityManagerInterface::class,
AnotherClass::class,
AndAnother::class,
];
}
public function get(string $id)
{
if (!$this->locator->has($id)) {
throw new ServiceLocatorException(sprintf(
'The entry for the given "%s" identifier was not found.',
$id
));
}
try {
return $this->locator->get($id);
} catch (ContainerExceptionInterface $e) {
throw new ServiceLocatorException(sprintf(
'Failed to fetch the entry for the given "%s" identifier.',
$id
));
}
}
}
And this is how you use it: ServiceLocator->locate(AnotherClass::class);
If I have a controller which is with the name space
Src/ApiMap/ApiMapBundle/controller/Getapicontroller
class GetApiController extends Controller {
fitch data on the bases of entities define in entites classes and then save that data in in table
and save it in database
}
Now I define this as service in
App/config/service.yml
services:
app.get_controller:
class: ApiMaps\ApiMapBundle\Controller\GetApiController
3rd I define a service class on the location
app/container/getApi.php
Now I want to access this controller-service in this class I means in this service how can i access because if i use
this->get function
in simple class what would i need for it.
the point is i can not move my logic to service bcz it is using entities and other stuff in controller i want to move this logic to some 3rd class service...
it location is right now
src/container
getapi.php
and its definition is right now...
<?php
namespace dino_container\GetApi;
class GetApi {
public function __construct() {
i want to incorport this controller service in this class...
}
Seems like we had this conversation before.
It makes no sense to try calling a controller method from a command. A controller method's job is to convert a Request into a Response. Commands have no request object nor would they know what to do with a Response object.
Instead you want to share functionality between a controller and a command by extracting the functionality into it's own service. You then access that service from both the command and the controller.
# services.yml
my_service:
class: MyService
arguments: ['#doctrine.orm.default_entity_manager']
# Controller
public function myAction(Request $request)
{
$myService = $this->container->get('my_service');
$myService->doSomethingShared();
# Command
public function execute()
{
$myService = $this->container->get('my_service');
$myService->doSomethingShared();
This seems to be the fastest and simpliest way to use a controller as service, but I am still missing a step because it doesn't work.
Here is my code:
Controller/service:
// Test\TestBundle\Controller\TestController.php
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
/**
* #Route(service="test_service")
*/
class TestController extends Controller {
// My actions
}
Use :
// Test\TestBundle\Controller\UseController.php
// ...
public function useAction() {
$testService = $this->get('test_service');
}
When I do that, I get the error
You have requested a non-existent service "test_service".
When I check the list of services with app/console container:debug, I don't see my newly created service.
What am I missing?
From Controller as Service in SensioFrameworkExtraBundle:
The #Route annotation on a controller class can also be used to assign the controller class to a service so that the controller resolver will instantiate the controller by fetching it from the DI container instead of calling new PostController() itself:
/**
* #Route(service="my_post_controller_service")
*/
class PostController
{
// ...
}
The service attribute in the annotation is just to tell Symfony it should use the specified service, instead of instantiating the controller with the new statement. It does not register the service on its own.
Given your controller:
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
/**
* #Route(service="test_service")
*/
class TestController
{
public function myAction()
{
}
}
You need to actually register the controller as a service with the test_service id:
services:
test_service:
class: Test\TestBundle\Controller\TestController
The advantage of this approach is that you can inject your dependencies into the constructor by specifying them in the service definition, and you don't need to extend the base Controller class.
See How to define controllers as services and Controller as Service in SensioFrameworkExtraBundle.
For future folks, if you decide to use controller-as-a-service, you should better inject your services into the controller via the constructor instead of getting them through a service locator. The former is considered to be an antipattern, while the latter allows easy unit testing and is simply more verbose.
So instead of:
public function useAction() {
$testService = $this->get('test_service');
}
You should:
private $yourService;
public function __construct(YourService $yourService)
{
$this->yourService = $yourService;
}
public function useAction()
{
$this->yourService->use(...);
}
Don't create shortcuts, write solid, maintainable code.
For Symfony 3.4, we don't need to register controllers as services because they are already registered as services with the default services.yml configuration.
You just have to write this :
// current controller
public function myAction() {
$test = $this->get(SomeController::class)->someAction($param);
}
is there any way to preprocess controller data somehow. I'm going to take param from session, validate it and assign it as controller property and use it as $this->myVar inside actions of some controller or all of them if possible. Using controller's constructor gives me nothing, I couldn't access request and session data. Thanks!
UPD:
Thanks, jkucharovic, very good solution.
Also there is a bit dirtier solution, without injecting: setContainer() method, which has been called straight after $controller = new Controller();
use Symfony\Component\DependencyInjection\ContainerAwareInterface,
Symfony\Component\DependencyInjection\ContainerInterface;
class AppServiceController extends Controller {
private $my_property;
/**
* Used as constructor
*/
public function setContainer(ContainerInterface $container = null)
{
parent::setContainer($container);
$this->my_property = 'foo';
// your controller code
}
}
I'm not sure what you wan't to do is very usefull. A Controller instance will be created each time the controller is called. The session and request will be different each time you call the controller.
I think you should create a BaseController class extending Controller class with a shortcut method to access your MyVar value in session.
class BaseController extends Controller
{
public function getMyVar()
{
return $this->get('session')->get('MyVarSessionKey');
}
}
All your other Controller will extend from this BaseController.
To get the request, just use the shortcut method provided by Controller class, Controller::getRequest().
If you want to use services in __construct method, you have to inject that services first. Then you can use them before any other methods. For example:
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session;
public function __construct(Request $request, Session $session)
{
…
}