This is my original code:
$app->post('/api/user', function (Request $request, Response $response, array $args) use ($tokenAuth) {
$tokenAuth->parseHeaders(); // validate token
//...do stuff with $tokenAuth data
$response->getBody()->write(json_encode(...)));
return $response;
});
Notice the use ($tokenAuth) this allows me to use this object within the method.
How can I use this method below in the same manner?
$app->post('/api/user', \UserController::class . ':add');
which works fine when the route hits in the class UserController:
class UserController {
public function add($request, $response, $args) { ...}
}
How can I pass $tokenAuth to this?
You can register $tokenAuth and UserController to dependency manager and pass $tokenAuth to UserController via dependency injection merchanism. For example:
<?php
namespace Your\Own\NameSpace;
use Your\Own\NameSpace\TokenAuth;
class UserController
{
private $tokenAuth;
public function __construct(TokenAuth $tokenAuth)
{
$this->tokenAuth = $tokenAuth;
}
public function add($request, $response, $args)
{
$this->tokenAuth->parseHeaders();
...
}
}
In your dependency registration code (assuming using Pimple):
<?php
use Your\Own\NameSpace\TokenAuth;
use Your\Own\NameSpace\UserController;
$container[TokenAuth::class] = function ($c) {
return new TokenAuth();
};
$container[UserController::class] = function ($c) {
$tokenAuth = $c->get(TokenAuth::class);
return new UserController($tokenAuth);
};
I ended up using middleware to authenticate on the route:
$mw = function($request, $response, $next){
$tokenAuth = new \TokenAuth();
$tokenAuth->parseHeaders();
$response = $next($request, $response);
return $response;
};
$app = new \Slim\App();
$app->post('/api/user', \UserController::class . ':add')->add($mw);
$app->run();
Take a look at your original code, you can use $tokenAuth (and it works), it means that you defined it globally before.
So, in your add method, just declare it as a global variable: global $tokenAuth; then use it;
public function add($request, $response, $args) {
global $tokenAuth;
$tokenAuth->parseHeaders();
...
}
Related
I have a class
class Contact {
public function searchByPhone(ServerRequestInterface $request, BaseResponse $response, $args) {
return 'somedata';
}
}
Slim framework uses this method
$app->get("/searchByPhone/2342", Contact::class . ':searchByPhone');
I want use
$app->get("/searchByPhone/2342", Contact::searchByPhone());
So that when I click on a function NetBeans will take me to the right place in the code.
How do I do this?
You could try to use a Single Action Controller with a single __invoke method.
Example
<?php
namespace App\Action;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
final class ContactPhoneSearchAction
{
public function __invoke(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface {
$params = (array)$request->getQueryParams();
$phone = (string)$params['phone'];
// Invoke service method
// ...
// Build response
$response->getBody()->write('Hello, World!');
return $response;
}
}
Usage
$app->get('/contacts/phone', \App\Action\ContactPhoneSearchAction::class);
I'm setting up slim router v4, and I'd like to be be able to dynamically call the controller methods, using the placeholder from the route.
I.e when a request is made to 'example.com/users/{action}', the router would call the method from Users.php controller automatically without me having to specify the routes manually.
Basically I'm trying to avoid manually adding over 100 group->get(...) when they're all under /user route.
namespace core\router;
use Slim\Interfaces\RouteCollectorProxyInterface;
use app\controllers\users;
$app->group('/user', function(RouteCollectorProxyInterface $group){
$group->get('/get-name', '\Users:name')
$group->get('/get-personality', '\Users:personality');
});
Further explanation is provided here but I'm not sure how to go about this.
The way I would suggest doing this is having a single, catch all route with a placeholder. You can then set action to an invocable controller, and execute a method based on the route parameter.
Route:
$app->get('/user/{method}', Users::class);
Controller
class Users
{
public function __invoke(Request $request, Response $response, $args)
{
if (empty($args['method'])) {
throw new InvalidArgumentException();
}
$methodName = toCamelCase($args['method']);
if (!method_exists($this, $methodName)) {
throw new InvalidArgumentException();
}
return $this->{$methodName};
}
public function getName(Request $request, Response $response)
{
// ...
}
public function getPersonality(Request $request, Response $response)
{
// ...
}
}
I'm using a controller/middleware build with slim 3 and i want from the middleware attached to a group, to pass some data to the $args parameter in my controller - action.
Here's some code:
class MyController
{
protected $container;
public function __construct(ContainerInterface $container) {
$this->container = $container;
}
public function index(Request $request, Response $response, $args) {
return $this->container->get('renderer')->render($response, "index.html.twig", $args);
}
}
class MyMiddleware
{
public function __invoke(Request $request, Response $response, $next)
{
// do some stuff to inject further down to $args some data
return $next($request, $response);
}
}
$app->group('/group', function () use ($app){
//routes
})->add(new MyMiddleware());
My use case is to send stuff to all the views rendered by the actions of these controllers, so i'm also fine with other ways to do this :)
Thanks.
so you need just pass data from Middleware to Controller
what about
class MyMiddleware
{
public function __invoke(Request $request, Response $response, $next)
{
$request = $request->withAttribute('myMagicArgument', 42);
return $next($request, $response);
}
}
and then in controller
class MyController
{
//...
public function index(Request $request, Response $response) {
$yourAttributeFromMiddleware = $request->getAttribute('myMagicArgument');
//...
}
}
For completeness, I'm going to extend the excellent answer given #jDolba
Unfortunately, though it got me going in the right direction, it still took a little experimentation to get everything working.
Basically, as explained in the slim router docs
The route callback signature is determined by a route strategy. By
default, Slim expects route callbacks to accept the request, response,
and an array of route placeholder arguments. This is called the
RequestResponse strategy. However, you can change the expected route
callback signature by simply using a different strategy. As an
example, Slim provides an alternative strategy called
RequestResponseArgs that accepts request and response, plus each route
placeholder as a separate argument. Here is an example of using this
alternative strategy; simply replace the foundHandler dependency
provided by the default \Slim\Container:
$c = new \Slim\Container();
$c['foundHandler'] = function() {
return new \Slim\Handlers\Strategies\RequestResponseArgs();
};
$app = new \Slim\App($c);
$app->get('/hello/{name}', function ($request, $response, $name) {
return $response->write($name);
});
You can provide your own route strategy by implementing the
\Slim\Interfaces\InvocationStrategyInterface.
however, for the task of injecting some standardised data into the $args[] array, the default \Slim\Handlers\Strategies\RequestResponse class does everything it needs to minus injecting the data.
As such, I simply extended that class:
<?php
namespace MyProject\Handlers\Strategies;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use \Slim\Handlers\Strategies\RequestResponse;
class SomeNewInvocationStrategy extends RequestResponse
{
/**
* Invoke a route callable.
*
* #param callable $callable The callable to invoke using the strategy.
* #param ServerRequestInterface $request The request object.
* #param ResponseInterface $response The response object.
* #param array $routeArguments The route's placholder arguments
*
* #return ResponseInterface|string The response from the callable.
*/
public function __invoke( callable $callable, ServerRequestInterface $request, ResponseInterface $response, array $routeArguments)
{
$routeArguments['test'] = 'testing testing 123';
return parent::__invoke( $callable, $request, $response, $routeArguments );
}
}
My container declaration looks like this:
<?php
use Slim\App;
return function (App $app) {
$container = $app->getContainer();
$container['foundHandler'] = function() {
return new MyProject\Handlers\Strategies\SomeNewInvocationStrategy();
};
}
Then in all of my controller actions I have access to $args['test']. Further, this can be passed straight through to any Twig views.
This is useful for tasks like access control where by I always want to load the roles of users before processing the request but I'm sure there'll be many other use-cases for it.
I Hope this helps somebody.
App::bind('App\Http\Repositories\languageRepository',
function( $app, array $parameters)
{
return new App\Http\Repositories\languageRepository($parameters[0]);
} );
Route::get('/test/{id}', 'testController#getme');
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Repositories\languageRepository;
class test extends Controller
{
//
protected $language;
public function __construct(languageRepository $rep){
$this->language = $rep;
}
public function getme(){
$this->language->getMe();
}
}
When user accesses the route /test/5 for example, it goes to test Controller. what I'd like to do is that it should automatically pass my route parameter to App:bind function and automatically create languageRepository class with the constructor value passed as my route paramter. what happens is the code actually tells me $parameters[0] is undefined offset. why is that? I've tried App::make but then how do I pass the parameter from route to App::make?
You can accomplish this using the container's request instance, for query parameters:
App::bind('App\Http\Repositories\languageRepository',function($app)
{
$request = $app['request'];
$parameters = $request->all();
return new App\Http\Repositories\languageRepository($parameters[0]);
});
You can accomplish this using the container's request instance, for a route parameter:
App::bind('App\Http\Repositories\languageRepository',function($app)
{
$request = $app['request'];
$segment = $request->segment(1);
return new App\Http\Repositories\languageRepository($segment);
});
I am trying to setup a project for an API using slim framework version 3, I don't know who made the PSR-7 and marked the response object as immutable, I don't see any use in that (IMHO. please explain me if I am wrong). Things were much easier when it was slim 2. Now I came back to slim after a long time.
I have a route which is a post method, I am getting data and saving it to the database and I am trying to send 201 as the response code. all the examples and the documentation is showing you how can you change the response code within the index.php file itself, But I am trying to change it from a response builder which I have tried to use the factory pattern to provide different responses. The problem is the response code always stays 200 no matter what function I call from the response builder class. I tried many forums and different ways of slim but still couldn't able to pull this up. I almost decided to give up on a PSR 7 router implementation and going to implement my own routing solution. But I remember not to reinvent the wheel again so I came here as a final try. Below is the code.
the route definition
$app->post('/users', function(ServerRequestInterface $req, ResponseInterface $res) {
$data = $req->getParsedBody();
$model = new \Apex\Models\User(ApexDB::getInstance());
$jsonBuilder = ApexResponse::getBuilder('JSON', $res);
$control = new \Apex\Controllers\User($model, $jsonBuilder);
$control->create($data);
});
the controller method (abstract I am just setting it up)
public function create($data) {
if($this->model->save($data)) {
$this->response->build($data,201);
} else {
$this->response->build('error',400);
}
}
the JSON builder
class JSONBuilder implements Response
{
public $response;
public function __construct($response)
{
$this->response = $response;
}
public function build($data, $status)
{
$response = $this->response->withJSON($data,$status);
return $response;
}
}
can anyone point me in the right direction?
The PSR-7 decision to use immutable objects for Request and Response is documented in the Why value objects? section of the Meta document.
With Slim 3, you must always return a Response instance from the controller method.
$app->post('/users', function(ServerRequestInterface $req, ResponseInterface $res) {
$data = $req->getParsedBody();
$model = new \Apex\Models\User(ApexDB::getInstance());
$jsonBuilder = ApexResponse::getBuilder('JSON', $res);
$control = new \Apex\Controllers\User($model, $jsonBuilder);
return $control->create($data);
});
and then your create method also needs to return the $response:
public function create($data) {
if($this->model->save($data)) {
$this->response->build($data,201);
} else {
$this->response->build('error',400);
}
return $this->response;
}
It should then work.
However, you can use the controller method directly from the route declaration and avoid the need for a the closure:
$app->post('/users', `Apex\Controllers\User::create`);
The controller's create method would then look like this:
namespace Apex\Controllers;
class User
{
public function create($request, $response)
{
$data = $request->getParsedBody();
$model = new \Apex\Models\User(ApexDB::getInstance());
$jsonBuilder = ApexResponse::getBuilder('JSON', $response);
if ($model->save($data)) {
$response = $jsonBuilder->build($data, 201);
} else {
$response = $jsonBuilder->build('error', 400);
}
return $response;
}
}
Finally, consider rka-content-type-renderer instead of JsonBuilder, though maybe it does more than you've shown.
Update:
Ideally you'd use constructor injection to inject the User model into the controller. To do this:
Update your controller:
namespace Apex\Controllers;
use Apex\Models\User as UserModel;
class User
{
protected $userModel;
public function __construct(UserModel $userModel)
{
$this->userModel = $userModel;
}
public function create($request, $response)
{
$data = $request->getParsedBody();
$jsonBuilder = ApexResponse::getBuilder('JSON', $response);
if ($this->userModel->save($data)) {
$response = $jsonBuilder->build($data, 201);
} else {
$response = $jsonBuilder->build('error', 400);
}
return $response;
}
}
Write a factory for the Pimple dependency injection container:
$container = $app->getContainer();
$container['Apex\Controllers\User'] = function ($c) {
$userModel = new \Apex\Models\User(ApexDB::getInstance());
return new \ApexController\User($userModel);
};