Add a route to handle multiple HTTP requests in Slim PHP - php

I am using the following code for "Routing using methods inside classes:"
$app->any('/contacts', 'Contacts:home');
My class looks like:
class Contacts {
public function home() {
return 'something';
}
}
The above code works fine for me and when I open "http://localhost:3000/contacts"
The Problem is when I try to handle multuple HTTP request
$app->group('/users/{id:[0-9]+}', function() {
$this->map(['GET', 'POST'], '', 'Users');
});
Is there anyway, I can pass class name such as Users in the above pseudo code and the code works for me, The class would be something like:
class Users {
function get() {
return 'asd';
}
function post() {
return 'post';
}
}
In such a way, that my request listens to the appropriate method.

You would need to create a method that sorts out the current route's details than calls the correct method.
You can determine which method was used by calling the $request->getOriginalMethod(); function, then using call_user_func_array(); function you can call whichever of your functions is appropriate for the current method.

Related

Using Request, Response in constructor in router class Slim Framework 3

I am using Slim Framework for my application. I am using routes. All is working fine. But now I want to do some pre-process working under my constructor on Request and Response.
So that I should not rework on every function of the class. Like getting host and token in every function. I am using middle-ware for many pre-process. But I also want to do some work in class constructor. When I am trying to access request and response interface in constructor, It is showing the error, Please show me the right way of using Request and Response in a class constructor. Will I have to append $app, or will need to work with container.
If it can be done without help of middleware, It will be great for me.
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
$app->group('/products', function() {
new \Products($this);
});
And I have a class called Products.
class Products
{
public function __construct($app)
{
$app->map(['GET','POST'], '/createupdate', array($this, 'createupdate'));
//I want to use Request and Response here in constructor. But it is showing error.
$this->req_data['request_token'] = $request->getAttribute('request_token');
}
public function createupdate($request, $response, $args) {
//This is working fine.
$this->req_data['request_token'] = $request->getAttribute('request_token');
}
}
When you really want to do this, then you could get the request/response object from the container.
class Products
{
public function __construct($app)
{
$app->map(['GET','POST'], '/createupdate', array($this, 'createupdate'));
$request = $app->getContainer()->get('request');
$this->req_data['request_token'] = $request->getAttribute('request_token');
}
// [..]
}
But, also this will not make much difference $this->req_data['request_token'] is nearly as long as $request->getAttribute('request_token'); so you should use this inside the code.
Note: I expect you to set this attribute already inside middleware, so it may not be available here, because first the container will create a new request object and second cause the middleware is not run when php executes your constructor code.
When you now still want to use $this->req_data['request_token'] inside your class then you should do this:
$products = new \Products();
$app->group('/products', function() use ($products) {
$products->addRoutes($this);
})->add($products); // add the class as middleware as well to set there the class attributes (__invoke function)
class Products
{
public function addRoutes($app)
{
$app->map(['GET','POST'], '/createupdate', array($this, 'createupdate'));
}
public function __invoke($request, $response, $next) // this is middleware function
{
$this->req_data['request_token'] = $request->getAttribute('request_token');
return $next($request, $response); // next in this example would be your route function like createupdate()
}
}
Ofcourse you will get an error, $request is not defined.
public function __construct($app)
{
$app->map(['GET','POST'], '/createupdate', array($this, 'createupdate'));
// Where does $request comes from?!
$this->req_data['request_token'] = $request->getAttribute('request_token');
}
Slim way of doing pre-processing is by using middlewares.
When a route is called, it is automatically injected with the Request and Response objects (and the request route params if any), but when a class is created for the route, it is not automatically injects those instances to the constructor, so they are not available "out of the blue".
If you have pre-processing, I would stick to middlewares, it is much cleaner code (although this is my opinion).

DI object method

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).

Laravel Resource Controller missing Method

I have a laravel resource controller as follow:
BlogController.php
class AdminBlogController extends BaseController {
public function index()
{
// some code
}
public function create()
{
// some code
}
// etc.....
in route.php I have this :
Route::resource('blog', 'AdminBlogController');
now I understand that when you go to URL /blog , it goes to index() and when you go to /blog/create goes to create() method.
My question is how do I handle missing method? for example when some types /blog/test , I get an error there , how can I redirect back missing methods to /blog?
Thanks
Taken from the Laravel Documentation:
If you are using resource controllers, you should define a __call
magic method on the controller to handle any missing methods.
In your AdminBlogController, add a __call magic method:
public function __call($method,$parameters = array())
{
if (!is_numeric($parameters[0])) {
return Redirect::to('blog');
}
else {
$this->myShow($parameters[0]);
}
}
...and, importantly, you need to rename your show method to something else (myShow in this example). Otherwise, /blog/test will route to show with the expectation that test is an id of a blog that you want to show. You also need to specify, in your __call method, which parameters after blog/ should be considered IDs, and which should be considered missing methods. In this example, I allow any numeric parameter to be treated as an ID, while non-numeric parameters will redirect to Index.

Call function in Controller using URL

Im comming from CodeIgniter.
There if you had a controller like this:
class Article extends CI_Controller{
public function comments()
{
echo 'Look at this!';
}
}
You could access the comments() function using the URL like this: example.com/Article/comments
How could I do something similar in Laravel?
The way I do it right now is specifiying a route like this:
Route::get('/Article/comments}', 'ArticleController#comments');
But I was hoping for a more dynamic way to do it as I don't want to keep on creating new routes for every function
The recommended way of dynamically calling controllers methods via URL, for Laravel users, is via RESTful Controllers:
<?php
class ArticleController extends controller {
public function getComment()
{
return 'This is only accesible via GET method';
}
public function postComment()
{
return 'This is only accesible via POST method';
}
}
And create your route using telling Laravel this is a RESTful Controller:
Route::controller('articles', 'ArticlesController');
Then if you follow
http://laravel.dev/articles/comments
Using your browser, you should receive:
This is only accesible via GET method
The way you name your controllers methods (getComment, postComment, deleteComment...) tells Laravel wich HTTP method should be used to call those methods.
Check the docs: http://laravel.com/docs/controllers#restful-controllers
But you can also make it dynamic using PHP:
class ArticlesController extends Controller {
public function comments()
{
return 'Look at this!';
}
public function execute($method)
{
return $this->{$method}();
}
}
Use a controller like this one:
Route::get('Article/{method}', 'ArticleController#execute');
Then you just have to
http://laravel.dev/Article/comments
I'll recommend that you stick with the laravel's way to create REST controllers, because that way you can have control over what HTTP Verb is being called with the controller method. The laravel way of doing this is just to add the HTTP Verb in front of the controller method, for your method comments if you want to specify a GET request in Laravel the name of the method would look like getComments.
For example, if you need to do a GET request for the article/comments URI, and then to create a new comment you want to use the same URI with another HTTP verb, lets say POST, you just need to do something like this:
class ArticleController extends BaseController{
// GET: article/comments
public function getComments()
{
echo 'Look at this!';
}
// POST: article/comments
public function postComments()
{
// Do Something
}
}
Further reading:
http://laravel.com/docs/controllers#restful-controllers
Now for your specific answer, this is the Laravel way of doing what you requested:
class ArticleController extends BaseController{
public function getComments()
{
echo 'Look at this!';
}
}
and in the routes.php file you'll need to add the controller as follows:
Route::controller('articles', 'ArticleController');

Slim PHP Route in Middleware

In Slim is it possible to get the current route within middleware?
class Auth extends \Slim\Middleware{
public function call(){
$currentRoute = $this->app->getRoute(); // Something like this?
}
}
I know you can call $app->router()->getCurrentRoute() after the slim.before.dispatch hook is called, but when you call this from middleware it returns a non-object. Any help would be greatly appreciated.
Yes and no. If you look at the source code for Slim, you will see that registered Middlewares are called in LIFO order when the Slim::run method is called, and then Slim runs it's own "call" method where the processing of the request begins. It is in this method that Slim parses and processes the route. In which case, you cannot access $app->router()->getCurrentRoute() in the Middleware::call method because it won't have been parsed and defined yet.
The only way to do this is to register a listener on slim.before.dispatch inside your Middleware, and implement whatever you want to do in that method.
From the name of your class I assume you are trying to create a basic authentication module? I've done something similar to this before, and it went something like this:
class AuthMiddleware extends \Slim\Middleware
{
public function call()
{
$this->app->hook('slim.before.dispatch', array($this, 'onBeforeDispatch'));
$this->next->call();
}
public function onBeforeDispatch()
{
$route = $this->app->router()->getCurrentRoute();
//Here I check if the route is "protected" some how, and if it is, check the
//user has permission, if not, throw either 404 or redirect.
if (is_route_protected() && !user_has_permission())
{
$this->app->redirect('/login?return=' . urlencode(filter_input(INPUT_SERVER, 'REQUEST_URI')));
}
}
}
In this example, the onBeforeDispatch method will be run before of the route handlers are invoked. If you look at the source code, you can see the events are fired inside a try/catch block that is listening for the exceptions thrown by $app->redirect() and $app->pass(), etc. This means we can implement our check/redirect logic here just as if this was a route handler function.
Above is_route_protected and user_has_permission are just pseudo-code to illustrate how my auth middleware worked. I structured the class so that you could specify a list of routes or regex for routes in the Middleware constructor that were protected, as well as passing a service object that implemented the user permission checking, etc. Hope this helps.
There is an alternative method of doing this, as I've been in the same situation. What I wanted to avoid was matching anything by route and wanted to use route names instead, so you could try the following:
public function call() {
$routeIWantToCheckAgainst = $this->slimApp->router()->urlFor('my.route.name');
$requestRoute = $this->slimApp->request()->getPathInfo();
if ($routeIWantToCheckAgainst !== $requestRoute) {
// Do stuff you need to in here
}
$this->next->call();
}
You could even have an array of routes you DON'T want the middleware to run on and then just check if it's in_array() etc and if not, do what you need to.
You should use app->request()->getPathInfo() instead of app->getRoute().
class Auth extends \Slim\Middleware{
public function call(){
$currentRoute = $this->app->request()->getPathInfo();
}
}

Categories