Proper way to access the container from a class not in a slim controller - slim

I have a regular php class outside of a controller, so it doesn't benefit from automatic injection of container. I need to access the response object from that class, and I guess I should get it from the container.
What's the proper way to access it ? Just pass it as argument so the outside class can use it ? Is there a better way ?

You need to use middleware for that because the response object is immutable so "changing" it will not update the response which will be used by slim.
$app->add(function($request, $response, $next) {
if($shouldRedirect === true) {
return $response->withRedirect('myurl'); // do not execute next middleware/route and redirect
}
return $next($request, $response); // execute next middleware/ the route
});
For more information about middleware have a look at this.

If you need to send a subrequest, Slim provides such functionality. Use it carefully though, as in some situations its result is not obvious.
<?php
class MySortOfOutsideClass
{
/**
* If you need to send a subrequest, you have to access application instance,
* so let's inject it here.
*/
public function __construct(\Slim\App $app)
{
$this->$app = $app;
}
/**
* Method that makes a subrequest, and returns the result of it.
*/
public function myMethod()
{
if ($subRequestIsRequired) {
return $this->app->subRequest('GET', '/hello');
}
}
}

Related

Symfony 4 setter injection in parent class

Just a quick question. I'm building some API. I was thinking about creating simple parent class that would deal with form requests.
So for example if you would like to easily handle form request you just extend this class and you get access to request object, request data extracted from that object and bunch of methods that do some things for you out of the box. It doesn't matter what and why exactly.
The problem is:
I send request through postman.
I try to use request object in class that extends parent class but instead of request I get null.
How do I set up the whole thing?:
Now in Symfony every controller is by default registered as a service so I override this definition like this:
#generic api form controller
App\Controller\Api\ApiFormController:
calls:
- [setDependencies, ['#request_stack', '#App\Service\Serialization\Serializer']]
So as you can see I am using setter injection.
I extend above class in my other class. Let's call it PostController. So:
<?php
namespace App\Controller\Api;
use Symfony\Component\HttpFoundation\RequestStack;
class ApiFormController
{
/**
* #var Request
*/
public $request;
/**
* #param RequestStack $requestStack
*/
public function setDependencies(
RequestStack $requestStack
) {
$this->request = $requestStack;
}
}
And now PostController:
public function get(int $post = null)
{
dump($this->request); exit;
}
I was expecting to get access like this and I think I understand why I don't have access to this object. I'm looking for some ideas how I could achieve this goal in cleanest possible way. I'm not expecting ready answers but hints.
I was thinking about using events to set it up in the background?
I also think it has something to do with the way I'm hooking up my controller as a service.
The core of it all: Symfony does not pick up service definition for subclasses. So if you define dependencies for a class and extend it in another class, you have to define the dependencies for this second class too.
The easiest way is to use the parent keyword for this, so your example would work in the following way:
App\Controller\Api\ApiFormController:
calls:
- [setDependencies, ['#request_stack', '#App\Service\Serialization\Serializer']]
PostController:
parent: App\Controller\Api\ApiFormController
If you are using autowiring, you can use #required to make Symfony call the setter automatically. https://symfony.com/doc/current/service_container/autowiring.html#autowiring-other-methods-e-g-setters
/**
* #param RequestStack $requestStack
* #required
*/
public function setDependencies(
RequestStack $requestStack
) {
$this->request = $requestStack;
}
This should do the trick.
I see several problems here.
If you want to inject dependencies in such a way you should define controller as service. You can read more here.
Routing should be something like this:
# config/routes.yaml
get_something:
path: /
defaults: { _controller: App\Controller\Api\PostController:get }
Also, you should define PostController as service, not ApiFormController.
You injected RequestStack but type hint for the attribute is Request.
Instead of:
$this->request = $requestStack;
You need to use:
$this->request = $requestStack->getMasterRequest();

FOSRestBundle—create singular route

I am creating a RESTFul application using the FOSRestBundle (2.0) for Symfony3. I have a Controller like so:
class GroupsController extends FOSRestController
{
public function getGroupAction($id) { … }
public function getGroupAddAction() { … }
}
running bin/console debug:router gives:
…
get_group GET ANY ajax.localhost.dev /groups/{id}
get_group_add GET ANY ajax.localhost.dev /group/add
…
But getting a single Group is the request for a single Item, so the route should be: [GET] /group/{id}, in my opinion.
Am I wrong with that? Should the correct route, to get a single group be plural?
So if my opinion is right, how can I change this single route manually? I have tried:
/**
* #NamePrefix("group")
*/
public function getGroupAction() { … }
But that results in an Error telling me, that I can use that annotation only for the Class, not the method.
Yes it should be plural.
If you want to have singular-routes, you can use this in your controller.
You can find it in the Symfony documentation.
/**
* #RouteResource("User", pluralize=false)
*/

Laravel rest API middleware

I have a rest API where a lot of routes will be prefixed by /org/{id}. This prefix will scope the nested data and also the user will need to be checked if they are a member of the org. Would middleware be the best place to run these checks and also initialise an orgcontext class that can be accessed throughout the rest of the request?
A middleware is probably the perfect place to do those checks:
<?php
namespace App\Http\Middleware;
use Closure;
use App\Repositories\Eloquent\UserRepository;
class CanAccessOrg
{
public function handle($request, Closure $next, UserRepository $userRepository)
{
if (! $userRepository->currentUserCanAccessOrg(Session::get('org'))) {
Auth::logout();
return redirect()->route('login')->withErrors(['msg', 'You dont have access to this org']);;
}
return $next($request);
}
}

Is splitting an index action into multiple ones a restful-friendly approach?

I need to display two different index pages to two different user groups. For example, a regular user should see one page, and a privileged user - another one. I see two ways of approaching this issue:
One index action with conditionals:
public function index()
{
// view for privileged users
if(request()->user()->hasRole('privileged')){
return view('index_privileged');
}
// view for regular users
if(request()->user()->hasRole('regular')){
return view('index_regular');
}
return redirect('/');
}
Multiple actions:
public function index_privileged()
{
return view('index_privileged');
}
public function index_regular()
{
return view('index_regular');
}
Which approach is more "restful-friendly" and generally better?
I'm a big fan of light controllers. This might be a little overboard for a simple problem but if something like this pops up again, you'd already have everything all setup.
With that said, it might be best to create a PrivilegedUser class and a RegularUser class and give them both an index method which returns their respective views. Code them both to an interface UserInterface and make sure they both implement that.
Here is what those looked like in my test.
class RegularUser implements UserInterface
{
public function index()
{
return view('index_regular');
}
}
class PrivilegedUser implements UserInterface
{
public function index()
{
return view('index_privileged');
}
}
interface UserInterface
{
public function index();
}
Then you can add a listener which should run for the event Illuminate\Auth\Events\Login. Laravel will fire this event for you automatically when someone logs in. This goes into the file EventServiceProvider.php.
protected $listen = [
'Illuminate\Auth\Events\Login' => [
'App\Listeners\AuthLoginListener',
],
];
Now you can run php artisan event:generate to generate the new listener. Here is what my listener looks like, it should work for you.
namespace App\Listeners;
use Illuminate\Auth\Events\Login;
use Illuminate\Foundation\Application;
class AuthLoginListener
{
/**
* Create the event listener.
*
* #param Application $app
*/
public function __construct(Application $app)
{
$this->app = $app;
}
/**
* Handle the event.
*
* #param Login $event
* #return void
*/
public function handle(Login $event)
{
if ($event->user->hasRole('privileged')) {
$this->app->bind('App\Repositories\UserInterface', 'App\Repositories\PrivilegedUser');
} else if ($event->user->hasRole('regular')) {
$this->app->bind('App\Repositories\UserInterface', 'App\Repositories\RegularUser');
}
}
}
Essentially what this is doing is telling Laravel to load up a certain class based on the type of user that just logged in. The User instance is available through the Login object which was automatically passed in by Laravel.
Now that everything is setup, we barely have to do anything in our controller and if you need to do more things that are different depending on the user, just add them to the RegularUser or PrivilegedUser class. If you get more types of users, simply write a new class for them that implements the interface, add an additional else if to your AuthLoginListener and you should be good to go.
To use this, in your controller, you'd do something like the following...
// Have Laravel make our user class
$userRepository = App::make('App\Repositories\UserInterface');
return $userRepository->index()->with('someData', $data);
Or even better, inject it as a dependency.
use App\Repositories\UserInterface;
class YourController extends Controller
{
public function index(UserInterface $user)
{
return $user->index();
}
}
Edit:
I just realized I forgot the part where you wanted to return redirect('/'); if no condition was met. You could create a new class GuestUser (I know this sounds like an oxymoron) which implements UserInterface but instead of using the AuthLoginListener, I'd bind it in a service provider when Laravel boots. This way Laravel will always have something to return when it needs an implementation of UserInterface in the event it needs this class if no one is logged in.
Well, its more like a refactoring "issue" than a rest-friendly issue. Check this guideline and you can see that most of the things that makes an api friendly is concerned to the url.
But, lets answer what you are asking. The thing you wanna do is a refactoring method but it is not only the move method but something like the extract variable.
The second option would make the code more readable, either ways are right but the second is more developer friendly. It enhances the code readability from any developer. I would recommend using the second option.
Refactoring is never enough, but read something like this, it will help you a lot writing more readable codes.

ZF2 Use Redirect in outside of controller

I'm working on an ACL which is called in Module.php and attached to the bootstrap.
Obviously the ACL restricts access to certain areas of the site, which brings the need for redirects. However, when trying to use the controller plugin for redirects it doesn't work as the plugin appears to require a controller.
What's the best way to redirect outside from outside of a controller? The vanilla header() function is not suitable as I need to use defined routes.
Any help would be great!
Cheers-
In general, you want to short-circuit the dispatch process by returning a response. During route or dispatch you can return a response to stop the usual code flow stop and directly finish the result. In case of an ACL check it is very likely you want to return that response early and redirect to the user's login page.
You either construct the response in the controller or you check the plugin's return value and redirect when it's a response. Notice the second method is like how the PRG plugin works.
An example of the first method:
use Zend\Mvc\Controller\AbstractActionController;
class MyController extends AbstractActionController
{
public function fooAction()
{
if (!$this->aclAllowsAccess()) {
// Use redirect plugin to redirect
return $this->redirect('user/login');
}
// normal code flow
}
}
An example like the PRG plugin works:
use Zend\Mvc\Controller\AbstractActionController;
use Zend\Http\Response;
class MyController extends AbstractActionController
{
public function fooAction()
{
$result = $this->aclCheck();
if ($result instanceof Response) {
// Use return value to short-circuit
return $result
}
// normal code flow
}
}
The plugin could then look like this (in the second case):
use Zend\Mvc\Controller\Plugin\AbstractPlugin;
class AclCheck extends AbstractPlugin
{
public function __invoke()
{
// Check the ACL
if (false === $result) {
$controller = $this->getController();
$redirector = $controller->getPluginManager()->get('Redirect');
$response = $redirector->toRoute('user/login');
return $response;
}
}
}
In your question you say:
[...] it doesn't work as the plugin appears to require a controller.
This can be a problem inside the controller plugin when you want to do $this->getController() in the plugin. You either must extend Zend\Mvc\Controller\Plugin\AbstractPlugin or implement Zend\Mvc\Controller\Plugin\PluginInterface to make sure your ACL plugin is injected with the controller.
If you do not want this, there is an alternative you directly return a response you create yourself. It is a bit less flexible and you create a response object while there is already a response object (causing possible conflicts with both responses), but the plugin code would change like this:
use Zend\Mvc\Controller\Plugin\AbstractPlugin;
use Zend\Http\PhpEnvironment\Response;
class AclCheck extends AbstractPlugin
{
public function __invoke()
{
// Check the ACL
if (false === $result) {
$response = new Response;
$response->setStatusCode(302);
$response->getHeaders()
->addHeaderLine('Location', '/user/login');
return $response;
}
}
}