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).
Related
Today I have used a Klein Semi Framework and I wanted to understand something which is really important for me to know.
$klein->respond('/report/latest', function ($request, $response, $service) {
$response->file('/tmp/cached_report.zip');
});
for example in this code we pass into the function three variables request, response and service. How does it know to put into the request variable the request class, to put into the response variable the response class, etc. no matter the order of the varaibles?
Is there any example of code that will help me to understand this?
Thanks!
Not familiar with the framework, but based on your explanation, I'd assume they are using reflection to get the name and order of the parameters you provided to the closure. You can see an example of this here: (https://3v4l.org/jjWa1)
$closure = function ($request, $response, $service) {
$response->file('/tmp/cached_report.zip');
};
$reflected = new ReflectionFunction($closure);
var_dump($reflected->getParameters());
ReflectionFunction allows you to get details about a function's definition.
$request, $response, and $service aren't variables in your current scope that you're passing into that function.
function ($request, $response, $service) { $response->file('/tmp/cached_report.zip'); }
Is an anonymous function, a callback that you're passing as the second argument to $respond. So the parameters for that function aren't defined at the time you call $klein->respond(). What you're doing there is creating a route by assigning that callback to the $klein object to handle the '/report/latest' route.
The idea of a router class like that is that you define functions to handle requests matching various routes, and when a route is matched, the router object will call the function you've defined for it and supply the necessary arguments to it at that time.
I'm not sure what you mean by "no matter the order of the variables". I think the callback needs to have those variables defined in the correct order.
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.
I am running CakePHP 2.8.X, and am trying to write a unit test for a Model function.
Let's call the model Item, and I'm trying to test its getStatus method.
However, that model makes a call to its find within the getStatus method.
So something like this:
class Item extends Model
{
public function getStatus($id) {
// Calls our `$this->Item-find` method
$item = $this->find('first', [
'fields' => ['status'],
'conditions' => ['Item.id' => $id]
]);
$status = $item['status'];
$new_status = null;
// Some logic below sets `$new_status` based on `$status`
// ...
return $new_status;
}
}
The logic to set "$new_status" is a bit complex, which is why I want to write some tests for it.
However, I'm not entirely sure how to override the find call within Item::getStatus.
Normally when I want to mock a Model's function, I use $this->getMock coupled with method('find')->will($this->returnValue($val_here)), but I don't want to completely mock my Item since I want to test its actual getStatus function.
That is, in my test function, I'm going to be calling:
// This doesn't work since `$this->Item->getStatus` calls out to
// `$this->Item->find`, which my test suite doesn't know how to compute.
$returned_status = $this->Item->getStatus($id);
$this->assertEquals($expected_status, $returned_status);
So how do I communicate to my real Item model within my test that it should override its internal call to its find method?
I knew this had to be an issue others have faced, and it turns out PHPUnit has a very easy way to address this!
This tutorial essentially gave me the answer.
I do need to create a mock, but by only passing in 'find' as the methods I'd like to mock, PHPUnit helpfully leaves all other methods in my Model alone and does not override them.
The relevant part from the above tutorial is:
Passing an array of method names to your getMock second argument produces a mock object where the methods you have identified
Are all stubs,
All return null by default,
Are easily overridable
Whereas methods you did not identify
Are all mocks,
Run the actual code contained within the method when called (emphasis mine),
Do not allow you to override the return value
Meaning, I can take that mocked model, and call my getStatus method directly from it. That method will run its real code, and when it gets to find(), it'll just return whatever I passed into $this->returnValue.
I use a dataProvider to pass in what I want the find method to return, as well as the result to test against in my assertEquals call.
So my test function looks something like:
/**
* #dataProvider provideGetItemStatus
*/
public function testGetItemStatus($item, $status_to_test) {
// Only mock the `find` method, leave all other methods as is
$item_model = $this->getMock('Item', ['find']);
// Override our `find` method (should only be called once)
$item_model
->expects($this->once())
->method('find')
->will($this->returnValue($item));
// Call `getStatus` from our mocked model.
//
// The key part here is I am only mocking the `find` method,
// so when I call `$item_model->getStatus` it is actually
// going to run the real `getStatus` code. The only method
// that will return an overridden value is `find`.
//
// NOTE: the param for `getStatus` doesn't matter since I only use it in my `find` call, which I'm overriding
$result = $item_model->getStatus('dummy_id');
$this->assertEquals($status_to_test, $result);
}
public function provideGetItemStatus() {
return [
[
// $item
['Item' => ['id' = 1, 'status' => 1, /* etc. */]],
// status_to_test
1
],
// etc...
];
}
one way to mock find could be to use a test specific subclass.
You could create a TestItem that extends item and overrides find so it doesn't perform a db call.
Another way could be to encapsulate the new_status logic and unittests it independent of the model
I have a class that is responseible for calling a 3rd party payment solution.
As a part of this, there are various merchant id/shared secret parameters.
Theses will depend on who's logged into the application.
The class I'm working with takes this info in the constructor when the class is built. Is there a way to pass this in the service provider, perhaps like this:
$this->app->bind( 'App\BokaKanot\Interfaces\BillingInterface',function ($merchantId)
{
return new KlarnaBilling($merchantId);
} );
If so, is it still possible to do this through a constructor or do I need to manaully use App:make. If I do have to use App::make, how can I not hide this inside my calling class?
Or should I refactor the class I'm using to not need this in the constructor, and perhaps have an init method?
You can pass parameters to App::make and pass the parameters to the constructor like this:
$this->app->bind( 'App\BokaKanot\Interfaces\BillingInterface',
function( $app, array $parameters)
{
//call the constructor passing the first element of $parameters
return new KlarnaBilling( $parameters[0] );
} );
//pass the parameter to App::Make
App::make( 'App\BokaKanot\Interfaces\BillingInterface', [ $merchantId ] );
On laravel 5.4 (https://github.com/laravel/framework/pull/18271), you need to use the method makeWith of the IoC container.
App::makeWith( 'App\MyNameSpace\MyClass', [ $id ] );
if you still use 5.3 or below, the above answers will work
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)