symfony 4 where to remove big part from controller's action - php

I'm using symfony 4 and want to remove big part from controller's action to another method. But in this part using data from Request object and return Response object.
I have 2 options to move this part:
Move to private method in controller.
Move in service method.
And i have 2 options, what to do with method parameters:
Set Request object as method's argument and get all data from it in method.
Get all data from Request object in controller's action and set it as method's arguments.
Which way is better and why? Maybe there is a better way?
Is it normal practice to return Response object to controller's action from service method?
Code example:
public function index(Request $request): Response
{
if(!$this->hasSuccessAuth($request)) {
return $this->authenticateClient();
}
}
private function hasSuccessAuth(Request $request): bool
{
$passwordCookie = $request->cookies->get('secret', NULL);
if(self::CHECK_AUTH_MODE === $request->query->get('mode') and
$this->authService->isCorrectPasswordCookie($passwordCookie)) {
return true;
}
return false;
}
private function authenticateClient(): Response
{
if($this->authService->isSuccessHttpAuth()) {
$passwordForCookie = $this->authService->getPassword();
return new Response("success\nsecret\n".$passwordForCookie."\n");
}
return new Response('', Response::HTTP_FORBIDDEN);
}

I have 2 options to move this part:
Move to private method in controller.
Move in service method.
You can move into private method in controller or in service method in fact. If you move to private method in controller, my advices is that you should create a wrapper controller class that your controller will extend and you put this private method inside. Obviously this controller will extends the base Symfony Controller if your initial controller already extended it. This can be good if you intend to use hasSuccessAuth and authenticateClient with other controller classes only. Because if you put this logic in a service, other services or command will be able to use it. It is up to you.
If now you want this logic to be accessed everywhere in you application, better create a service. But you should ask yourself, if this new service will have its own data or will benefit from dependency injection. If yes, creating a service is a good idea. If no, that means you will use this logic only to deal with data given as parameters and return a result. In this case you should create a helper class with static methods.
And i have 2 options, what to do with method parameters:
Set Request object as method's argument and get all data from it in method.
Get all data from Request object in controller's action and set it as method's arguments.
It is up to you but if it was my application, I would chosen to give the entire Request object as argument of the method and all data from it inside the method.
And one last thing: your authenticateClient method does not use the Request object.

Related

Execute actions both sync and async

In laravel 5.7 I want to be able to have multiple actions,
for example inserting user in database, sending registration Email,
sending notification, ...
I want to be able to execute these actions both sync and async.
the problem is I don't want to create Job class for every action.
each action is a php callable class.
The thing I don't understand in Laravel Job class is It receives dependencies as handle method arguments and receive It's Input which should process on,in the constructor, I think It's kind of odd.
for example when I want to call send register email action, I want to be able to do sth like :
$registerEmailAction->__invoke($user, true);
second parameter indicates whether to do this action sync or async.
Laravel Job classes are simple objects that only need to implement the ShouldQueue interface and an handle() method. You can dispatch them, or run them immediately explicitly calling the handle method. If you want to take the __invoke route you could so something like this:
class RegisterEmailAction implements ShouldQueue
{
//... more code ...
public function __invoke(User $user, bool $async)
{
$this->setUser($user);
if ($async) {
dispatch($this);
}
else {
$this->handle(); // or dispatch_now($this);
}
}
public function handle()
{
if (!$this->user) {
throw new UserNotFoundException();
}
// ... some other code ...
}
}
Since you don't want to pass the $user as a dependency in the constructor, I would suggest to check for it in the handle method, so that you get an error if some client code tries to call the handle method, without taking the __invoke route. You also may needs to use some traits, like SerializeeModels or Dispatchable (check the docs for more info).

Slim 3.11 Callable Resolver TypeError

I can't find my mistake. I am getting the follow TypeError when I try a route in my slim api.
the error is:
Argument 1 passed to
HCC\API\Controllers\FacultyController::__construct() must be an
instance of PDO, instance of Slim\Container given
the constructor for this controller is:
public function __construct(\PDO $db, \MongoDB\Client $mongo, \Monolog\Logger $applogger, \Monolog\Logger $seclogger)
and the DI Factory that I put into the container is:
$container['FacultyController'] = function($c) {
return new FacultyController($c->get('db'), $c->get('mongo'), $c->get('appLogger'), $c->get('secLogger'));
};
I have tried setting each to it's own variable and passing in those variables but same effect. I've ran a successful test that just loads the slim app and checks that the container has an object of class FacultyController and that it has the messages that I have one that controller so I am 99% sure that the controller is actually getting put into the container. I think something might be off with the route. I have both a construct and an invoke method in the controller that are the exam same.
I've found this error in other posts, but what I am finding is issues with not passing something to the construct method and this seems to be the wrong arguments being passed to mine.
I don't want to have to pass the entire container into every controller as these controllers only need set dependencies and there is a lot of unnecessary items in there as far as the controllers are concerned.
This is mostly because Slim can not find HCC\API\Controllers\FacultyController class in dependency container (because you registered it with string 'FacultyController' instead of fully qualified name of class).
When Slim can not find it in dependency container, by default, Slim tries to create HCC\API\Controllers\FacultyController on its own and pass container instance into FacultyController constructor. But because you declare constructor of FacultyController with typehint to PDO class, PHP complain about this type mismatch.
Solution is try to replace 'FacultyController' with full name including namespace to make Slim can find controller in dependency container.
So instead of,
$container['FacultyController'] = function($c) {
return new FacultyController(
$c->get('db'),
$c->get('mongo'),
$c->get('appLogger'),
$c->get('secLogger')
);
};
you should use
$container[\HCC\API\Controllers\FacultyController::class] = function($c) {
return new \HCC\API\Controllers\FacultyController(
$c->get('db'),
$c->get('mongo'),
$c->get('appLogger'),
$c->get('secLogger')
);
};
or
use \HCC\API\Controllers\FacultyController;
$container[FacultyController::class] = function($c) {
return new FacultyController(
$c->get('db'),
$c->get('mongo'),
$c->get('appLogger'),
$c->get('secLogger')
);
};
Then in your route declaration, you may use, for example:
$app->get('/faculty', \HCC\API\Controllers\FacultyController::class);
More information about ::class
Update
If you use code above, FacultyController is considered as invokable class, which means, it is expected to have __invoke() method implemented.
class FacultyController
{
public function __invoke($request, $response, $args)
{
//handle the request
}
}
If you do not want to use invokable class but ordinary method to handle request, include method name when setup route
$app->get('/faculty', \HCC\API\Controllers\FacultyController::class . ':getFacultyCollection');
getFacultyCollection() method will be called to handle request.
class FacultyController
{
public function getFacultyCollection($request, $response, $args)
{
//handle the request
}
}
If getFacultyCollection() call causes application to crash as you said in comment, then it is entirely different problem. Maybe you have unterminated loop?
More information about __invoke() magic method

How can I override a DI container dependency with a specific instance?

Imagine we have a Request object and a Controller object. The Controller object is constructed with a Request object, like so:
abstract class Controller {
public $request;
public function __construct(Request $request)
{
$this->request = $request;
}
}
As you can see, this is an abstract class, so in reality a subclass of Controller will be constructed. Let's imagine the code is something like this:
// Instantiate the request.
$request = new Request($params);
// Instantiate the registration controller.
$controller = new RegistrationController($request);
Now let's say that we add a dependency to our RegistrationController, like so:
class RegistrationController extends Controller {
private $user_repo;
public function __construct(Request $request, UserRepo $user_repo)
{
parent::__construct($request);
$this->user_repo = $user_repo;
}
}
At this point, what I'd like to do is introduce a dependency injection container to automatically inject the dependencies via the constructor. For this, I've been using PHP-DI. Usually, this would go something like so:
// Instantiate the registration controller.
$controller = $container->get('RegistrationController');
This would then instantiate RegistrationController with an instance of Request and an instance of UserRepo. It'd know to construct with those objects thanks to reflection, but if I wanted I could also override this via a configuration file.
The problem is that the Request object takes a parameter which is dynamic. I need the Request object passed to RegistrationController to be a specific instance, one I've just created.
I essentially want to be able to say: "Give me an instance of this class with all of its dependencies injected, but for a particular parameter, pass in this specific instance".
I've looked to see if PHP-DI (and a hand-full of other DI containers) support this kind of "override" for specific parameters, but so far I can't find anything.
What I want to know is:
Is there a DI container out there that can do this?
Is there an alternative approach which would leave the classes clean (I don't want to use annotations or anything else that'll add the container I use as a dependency)?
PHP-DI author here.
So there are two things, first I'll answer your question:
PHP-DI's container provides a make method that you can use like that:
$request = new Request($myParameters);
$controller = $container->make('RegistrationController', array(
'request' => $request
));
This make method, as you can see, is the same as get except it will always create a new instance (which is what you want here since you probably don't want to reuse an existing instance of the controller) and it will take the extra parameters you give it. That's the behavior of a factory, with the benefits of the container that will find the rest of the parameters you didn't provide.
So that's what I would use here. You could also do this:
$request = new Request($myParameters);
$container->set('Request', $request);
$controller = $container->get('RegistrationController');
But that's less clean because it will set the request in the container, which is bad (explained below).
Now the second thing is that a request object is not really a service, it's a "value object". A container should generally only contain service objects, i.e. objects that are stateless.
The reason for this is imagine you have several request in the same process (e.g. you do "sub-requests", or you have a worker process that handles several requests, etc...): your services would be all messed up because they would have the request injected and the request object might change.
Symfony did just that and realized it was a mistake. Since Symfony 2.4, they have deprecated having the Request in the container: http://symfony.com/blog/new-in-symfony-2-4-the-request-stack
Anyway, so what I suggest you to do is not to have the Request object in the container, but instead use the make method I showed you.
Or, even better, I would do that:
class RegistrationController extends Controller {
private $user_repo;
public function __construct(UserRepo $user_repo)
{
$this->user_repo = $user_repo;
}
public function userListAction(Request $request)
{
// ...
}
}
// in the front controller
$controller = $container->make('RegistrationController');
// This is what the router should do:
$action = ... // e.g. 'userListAction'
$controller->$action(new Request($myParameters));
(this is what Symfony and other frameworks do by the way)

PHP - call_user_function_array or Reflection class pass by reference?

I'm trying to dispatch request in MVC framework.
I have a routing object that matches current URI against defined routes. If there is a match, it returns instance of Route object. Through that Route object I can access matched controller, method and method arguments.
I can use it like this:
$route->getClass(); name of controller class to instantiate
$route->getMethod(); name of method to call
$route->getArgs(); array holding arguments that should be passed to method
I can also add new arguments if I need to. For example, I can add Dependency Injection Container and current HTTP Request object instances to args like this:
$route->addArg('container', $container);
$route->addArg('request', $request);
Now $route->getArgs holds all arguments fetched from URI, but also a $container instance and $request instance. $container is dependency injection container i wrote. And $request is object that represents current HTTP Request.
So the idea is to instantiate Router, get current Route, then add objects I would like to use inside every method / action controller. And then pass that Route object to Dispatcher, and when request is dispatched, I would be able to use $container, $request and other arguments in every method / action controller.
The problem I have is that when I use lets say blog controller and post method. By default, and always, it shoud have instance of $container, and $request (since I pushed them into Route::$args, plus any other argument / variable defined in routes definitions or fetched from URI.
So when Im in my Blog Controller, post method I want it to act like this:
public function post($container, $request)
{
if($request->isAjax) {
// -- Its ajax request
}
$twig = $container->getService("Twig");
}
I dispatch request something like this:
Im using call_user_function_array to make it work:
$app = new $controller();
call_user_function_array(array($app, $method), $args);
Now this all works as it should but I have one problem.
When Im using that post controller I must be careful in what order are post method arguments ordered.
So i can do this:
public function post($id, $url, $container, $request) {
// -- SNIP --
}
but I cant use it this way:
public function post($container, $url, $request, $id) {
/-- SNIP --
}
So you get the problem, I cant mix arguments inside my method the way I want, and I need to keep an eye on order in which those arguments get defined in Route:$args array.
Every method / action controller should have $request, and $container instances plus arguments fetched through URI or defined in route definition.
Is there any way to make it work without having to think In what order is post method getting arguments? Does this have something to do with passing by reference? Since php passes variables by value?
Im using call_user_function_array and I want to find solution for call_user_function_array.
Is this possible?
If not, can this be done using Reflection class?
This is the solution to my problem:
So, I have everything needed to dispatch request:
$class = "\\Application\\Controller\\Blog";
$method = "post";
$args = array('id' => 1, 'url' => 'some-url', 'container' => DICOBJ, 'request' => RQSTOBJ);
Now I need to dispatch that request, so user can insert arguments as he wants in any order and if he wants.
This is what should be done:
$app = new $class();
$rm = new \ReflectionMethod($app, $method);
$params = $rm->getParameters();
$argsOrdered = array();
foreach($params as $param) {
$argsOrdered[$param->getName()] = $args[$param->getName()];
}
call_user_func_array(array($app, $method), $argsOrdered);
Ta daaa!!!
So, what this does is it gets defined arguments by user, and returns them as array in order user wrote them. And then we create another $argsOrdered array that holds only arguments user wants, ordered the way he wants :)
Im using ReflectionMethod to inspect method. And at the end call_user_func_array to dispatch request, but you can also use Reflection class to get controller instance.
Thats it. Do you find this solution elegant? Can this be done any faster?
With Reflection class you can get the arguments names of each parameter of the function you want to call and in that way check if they are in the correct order very easily.
You can't do that simply with call_user_function_array.
In particular you can use the getParameters() method in the ReflectionMethod class.
From there on I think you know what to do (check each key of the parameters array with the key of the argument passed and there you go).

Getting Instance Of Silex\Application

I am creating an application with Silex and was wondering if it is possible to somehow get the instance of the Silex\Application in a place where I can't do method_name(Application $application) in the method parameters?
For example, say I have a private method on a controller that is not an action. If I put Application $application as the parameter, it throws an error saying I need to pass it in.
I would rather not have to manually pass that method in if I don't have to.
There's really only two ways to do it.
a) Pass Silex\Application as an argument to the constructor of your class and assign it as an instance variable.
b) Pass the Silex\Application to your private method as an argument by hand.
Are you sure you need the full app in your class? The point of dependency injection is to inject dependencies directly, instead of injecting the container (yes, Silex\Application extends \Pimple, which is a Dependency Injection Container.
From your comment on the other answer, your goal of getting at the Silex/Application is to get at the Twig service there. The way that I've solved getting at the current application in another function for my Silex projects is:
use \Silex\Application;
use \Symfony\Component\HttpFoundation\Request;
class myController {
private $a;
// Route defined as:
// $app->get('/foo', 'myController::showPage');
public function showPage(Application $a) {
$this->a = $a;
return $this->doAwesome();
}
private function doAwesome() {
$twig = $this->a['twig'];
return $twig->render('awesomePage.twig');
}
}
Every function that is an endpoint for a route would then save the Application passed to it as a class property, for other functions to get at. It means you have to remember to do $this->a = $a; in every function that uses doAwesome() (before calling doAwesome()), but that's the cleanest way I've come up with to tackle that.

Categories