According to :
Silex - Service Controller Doc
I can define a route like this (after a couple of extra code of corse):
$app->get('/posts.json', "posts.controller:indexJsonAction");
But ... how can I pass the url used to the indexJsonAction function?
You should be mapping that directly to the route, such as:
$app->get('/posts.json/{param1}/{param2}, 'posts.controller:indexJsonAction');
This way, in your controller, you can expect those parameters:
public function indexJsonAction($param1, $param2) {
//now you have access to these variables.
}
Furthermore, silex uses Symfony's request under the hood, so you could also just inject the Request into the controller and get any input from the Request;
public function indexJsonAction(Request $request) {
// use $request->get('param1'); etc
}
Related
I am developing app in laravel (REST server), using Basic Auth. Using Postman, all GET requests I have implemented seem to work, but unfortunately POST requests not.
routes.php:
Route::post('my/action', 'MyController#postMyAction');
My Controller:
public function __construct()
{
$this->middleware('auth.basic.once');
}
public function postMyAction($request)
{
// some logic here
}
The problem is, that this way, after setting credentials and some params in Postman, following exception appears:
Missing argument 1 for
App\Http\Controllers\MyController::postMyAction()
Does anybody knows how to put request into post-processing function defined in routes?
Thanks in advance.
Laravel provides dependency injection for controller methods, however you need to typehint exactly what you want so Laravel knows what to inject:
public function postMyAction(\Illuminate\Http\Request $request)
{
// Now $request is available
Now Laravel knows you want an instance of Illuminate\Http\Request and it will give it to you.
Of course you can also stick use Illuminate\Http\Request; at the top of your controller then just typehint Request $request as the argument.
With Slim I group my controllers and generally have an abstract BaseController I extend for each group. I use class based routing:
/* SLIM 2.0 */
// Users API - extends BaseApiController
$app->post('/users/insert/' , 'Controller\Api\UserApiController:insert');
.
.
// Campaigns - extends BaseAdminController
$app->get('/campaigns/', 'Controller\CampaignController:index')->name('campaigns');
and needed to password protect some routes, at other times I needed to have a slightly different configuration. BaseApiController, BaseAdminController... etc. There were times I needed to know which route I was in so I could execute a certain behavior for just that route. In those cases I would have a helper function like so:
/* SLIM 2.0 */
// returns the current route's name
function getRouteName()
{
return Slim\Slim::getInstance()->router()->getCurrentRoute()->getName();
}
This would give me the route name that is currently being used. So I could do something like...
namespace Controller;
abstract class BaseController
{
public function __construct()
{
/* SLIM 2.0 */
// Do not force to login page if in the following routes
if(!in_array(getRouteName(), ['login', 'register', 'sign-out']))
{
header('Location: ' . urlFor('login'));
}
}
}
I cannot find a way to access the route name being executed. I found this link
Slim 3 get current route in middleware
but I get NULL when I try
$request->getAttribute('routeInfo');
I have also tried the suggested:
'determineRouteBeforeAppMiddleware' => true
I've inspected every Slim3 object for properties and methods, I can't seem to find the equivalent for Slim3, or get access to the named route. It doesn't appear that Slim3 even keeps track of what route it executed, it just... executes it.
These are the following methods the router class has and where I suspect this value would be:
//get_class_methods($container->get('router'));
setBasePath
map
dispatch
setDispatcher
getRoutes
getNamedRoute
pushGroup
popGroup
lookupRoute
relativePathFor
pathFor
urlFor
I was hoping someone has done something similar. Sure, there are other hacky ways I could do this ( some I'm already contemplating now ) but I'd prefer using Slim to give me this data. Any Ideas?
NOTE: I'm aware you can do this with middleware, however I'm looking for a solution that will not require middleware. Something that I can use inside the class thats being instantiated by the triggered route. It was possible with Slim2, was hoping that Slim3 had a similar feature.
It's available via the request object, like this:
$request->getAttribute('route')->getName();
Some more details available here
The methods in your controller will all accept request and response as parameters - slim will pass them through for you, so for example in your insert() method:
use \Psr\Http\Message\ServerRequestInterface as request;
class UserApiController {
public function insert( request $request ) {
// handle request here, or pass it on to a getRouteName() method
}
}
After playing around I found a way to do it. It may not be the most efficient way but it works, and although it uses Middleware to accomplish this I think there are other applications for sharing data in the Middleware with controller classes.
First you create a middleware but you use a "Class:Method" string just like you would in a route. Name it whatever you like.
//Middleware to get route name
$app->add('\Middleware\RouteMiddleware:getName');
Then your middleware:
// RouteMiddleware.php
namespace Middleware;
class RouteMiddleware
{
protected $c; // container
public function __construct($c)
{
$this->c = $c; // store the instance as a property
}
public function getName($request, $response, $next)
{
// create a new property in the container to hold the route name
// for later use in ANY controller constructor being
// instantiated by the router
$this->c['currentRoute'] = $request->getAttribute('route')->getName();
return $next($request, $response);
}
}
Then in your routes you create a route with a route name, in this case I'll use "homePage" as the name
// routes.php
$app->get('/home/', 'Controller\HomeController:index')->setName('homePage');
And in your class controller
// HomeController.php
namespace Controller;
class HomeController
{
public function __construct($c)
{
$c->get('currentRoute'); // will give you "homePage"
}
}
This would allow you to do much more then just get a route name, you can also pass values from the middleware to your class constructors.
If anyone else has a better solution please share!
$app->getCurrentRoute()->getName();
$request->getAttribute('route')->getName();
How do I inject dependency in some of the object methods instead of the constructor?
The example below works fine for __constructor injection
How do I inject the DateTime object in indexAction?
app.php
$app['index.controller'] = $app->share(function() use ($app) {
return new Controllers\IndexController(new \DateTime());
});
IndexController.php
namespace Moo\Controllers;
class IndexController
{
private $date;
public function __construct(\DateTime $date)
{
$this->date = $date;
}
public function indexAction()
{
return $this->date->format('y-m-d');
}
}
If your class has different dependencies depending on which method is called, than these methods should probably be defined in separate classes.
In case of controllers I think the rules are simple. Dependencies your action methods need should be passed via the constructor. Anything coming with a request should come as a method argument.
I'm not sure what kind of dependencies you're trying to inject. If they're just services, than you should split your controller to multiple classes. Big number of constructor arguments is a code smell. It's good you're concerned by it, but you're trying to solve it in a wrong way.
If the dependency is coming with a request, you should inject it into your controller method (action). A controller method should accept a request and return a response.
All route placeholders are automatically registered as a Request attribute. So if your date comes from a request:
$app->get('/my/path/{date}', 'index.controller:indexAction');
It will be available as a request attribute:
public function indexAction(Request $request)
{
$request->attributes->get('date');
}
Any request attributes can be directly injected into the controller:
public function indexAction($date)
{
}
This also means, that if you manually set a request attribute, it will be possible to inject it to your controller. It's matched by name:
// somewhere in your code (event perhaps)
$request->attributes->set('myDate', new \DateTime());
// in your controller
public function indexAction(\DateTime $myDate)
{
}
Finally you can convert simple types coming with the request, to more complex ones with route variable converters.
$callback = function ($post, Request $request) {
return new Post($request->attributes->get('slug'));
};
$app->get('/blog/{id}/{slug}', 'your.controller:indexAction')
->convert('post', $callback);
Read the docs for more.
Just for the record, as you won't probably want to do this, its is, indeed, possible to do method injection in Silex (in fact it is the Pimple container the one responsible to do it) by using the extend method:
<?php
$app['some_service'] = $app->share(function() use ($app) {
return SomeClass($app['some_dependency']);
});
$app['some_service'] = $app->extend('some_service', function($instance, $app) {
$instance->setSomeDependency($app['another_dependency']);
return $instance;
});
Having said that you should take into account what #JakubZalas is explaining as you will not want to call your controller action inside the extend method (you want to be called by the dispatcher).
I am building a RESTful api using Laravel. I am confused on how to do the routing.
I have the following api controller
class APIController extends BaseController{
public function sendMsg($authid, $roomid, $msg){
}
public function getMsg($roomid, $timestamp){
}
}
The URL format I want this to be accessible looks like this:
http://example.com/api/{functionName}/{parameter1}/{parameter2}/.../
Here, in the first parameter, I will have the function name which should map to the function in the controller class and following that the parameters the controller needs.
For example To access the sendMsg() function, the url should look like this:
http://example.com/api/sendMsg/sdf879s8/2/hi+there+whats+up
To access the getMsg() function, the url should look like
http://example.com/api/getMsg/2/1395796678
So... how can I write my routes so that it can handle the dynamic number and different parameters need?
I can write one route for each function name like so:
Route::get('/api/sendmsg/{authid}/{msg}', function($authid, $msg){
//call function...
});
and same for the other function. This if fine but is there a way to combine all function to the APIController in one route?
Yes, you can combine all the function to your APIController in one route by using a resourceful controller which is best suited for building an API:
Route::resource('api' ,'APIController');
But, technically, it's not one route at all, instead Laravel generates multiple routes for each function, to check routes, you may run php artisan routes command from your command prompt/terminal.
To, create a resourceful controller you may run the following command from your command line:
php artisan controller:make APIController
This will create a controller with 6 functions (skeleton/structure only) and each function would be mapped to a HTTP verb. It means, depending on the request type (GET/POST etc) the function will be invoked. For example, if a request is made using http://domain.com/api using GET request then the getIndex method will be invoked.
public function getIndex()
{
// ...
}
You should check the documentation for proper understanding in depth. This is known as RESTful api.
I recently wrote a post about request forwarding in Silex, which used a blog example to explain sub requests in Silex.
I use a slightly modified version of this example for a domain controller.
The path to the domain endpoint = /product/domain
A domain can also have a webhosting package attached to it.
The url path for this endpoint would be /product/domain/(id)/webhosting/
You can fetch info about a webhosting package by using the url path.
The url path for this endpoint would be /product/domain/(id)/webhosting/(id)
To handle these sub requests, I have a method called forwardRequest, which has no parameters in it's method signature, but uses func_get_args to keep it dynamic.
Unfortunately this doesn't work as Silex uses the named parameters in your route to call your method. So if you have /product/domain/domain_id/webhosting/webhosting_id, your method should have a signature of method($domain_id, $webhosting_id), which is a PITA if you want to forward multiple endpoints through one method. If you have additional /product/domain/domain_id/emailhosting and /product/domain/domain_id/dns endpoints, you have to create a method for each in order to forward the request.
Does anyone have a solution in which I can use only 1 method to forward all these sub requests?
Note: I'm using PHP 5.3.
The part of silex that decides which arguments to pass to the controller is called the "controller resolver". The default controller resolver uses reflection. You can override the controller_resolver service with a custom implementation though.
Defining a custom controller resolver that wraps the existing one but replaces the arguments with a single one, the request:
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface;
class RequestArgumentControllerResolver implements ControllerResolverInterface
{
protected $resolver;
public function __construct(ControllerResolverInterface $resolver)
{
$this->resolver = $resolver;
}
public function getController(Request $request)
{
return $this->resolver->getController($request, $controller);
}
public function getArguments(Request $request, $controller)
{
return [$request];
}
}
Extend the existing controller resolver with the newly defined decorator:
$app['controller_resolver'] = $app->share($app->extend('controller_resolver', function ($resolver, $app) {
return new RequestArgumentControllerResolver($resolver);
}));
Note: This is just one way of doing it. You don't have to decorate, you can also replace the resolver completely if you like. And obviously this is just a very basic example of only passing a single arg to the controller, you can do something more sophisticated.