PHP Use Autowiring with Slim3
In this post; I will show you how to use a middleware to create an object inside of PHP-DI for your Action classes to use. One of the most annoying things I have ever found is that Security Code pops-up everywhere. Adding context to a request is hard; but Auto-wiring makes it less hard!
Security is hard
Any web-dev will tell you compliance and security is a huge PITA. Every project starts out nice and easy and productive! But then Mr. Compliance/Whitebeard/Security Guru comes along and asks how you are going to deal with X,Y,Z... Suddenly your HTTP Actions are cluttered with stuff like this.
$user = $this->getUserFromToken($request->getHeader('Authorization')[0]);
Ugh; what a pain; what happens when we change that header? Well you use FInd and Replace to swap them all. This sort of code is horrible and for those with OCD; you likely cringe everytime you open a Http class b/c it's likely in there somewhere. Sure it might be hidden through your 4 levels of class structure; but it's somewhere lurking and eating at your brain.
Autowiring is the solution
What if I were to tell you you could use Auto-Wiring to fix this problem. You would probably think I am crazy. I am but that's a separate issue. PHP-DI with Slim is pretty much the best Rapid Development Framework Stack I have ever come across and if you are unfamilar with PHP-DI you should totally go check it out.
This function needs to happen on every call of "SOME" routes
Then by definition it's a middleware. You might ask; well then dear sir how do you modify the DI Container after execution is started. HA. Well #1 don't use pimple; use PHP-DI. #2 You make Rob Allen cringe; and you do something that is a bit unconvential and might make some purists really angry .... You inject the container into your middleware.
Let's take a look.
use DI\Container;
use Slim\Http\Request;
use Slim\Http\Response;
use Domain\Account\User;
use Domain\Account\UserSecurityService;
class SecurityCheckMiddleware
{
/**
* @var Container
*/
private $container;
/**
* SecurityCheckMiddleware constructor.
*
* @param Container $container
*/
public function __construct(Container $container) //Inject Container
{
$this->container = $container;
}
public function __invoke(Request $request, Response $response, $next)
{
if ($request->hasHeader('Authorization')) { //Our request has the header
$token = $request->getHeader('Authorization')[0];
//Fetch the User
$securityTokenRepo = $this->container->get(UserSecurityService::class);
$token = $securityTokenRepo->findToken($token);
//We then tell the container that the Class should use this instance!
$this->container->set(User::class, $token->getUser());
//Continue executing this request
return $next($request, $response);
}
return $response->withStatus(401, 'Unauthorized');
}
}
Very straight-forward. You can use more OOP to reduce the code complexity of this section; Ultimately you are just turning a String into a User class.
Ok ... But how is that useful?
My friends ... Now we can Type-Hint against User::class and get the authenticated user .... ANYWHERE IN OUR APPLICATION! Magic you say? Yes. Auto-Wiring is amazing.
Observe!
use Slim\Http\Request;
use Slim\Http\Response;
use Core\Identity\Uuid;
use Domain\Account\User;
use Domain\Game\Entities\Game;
use Domain\Game\Repositories\GameRepository;
class CreateGame
{
/**
* @var GameRepository
*/
private $gameRepository;
/**
* @var User
*/
private $user;
/**
* CreateGame constructor.
*
* @param GameRepository $gameRepository
*/
public function __construct(GameRepository $gameRepository, User $user)
{
$this->gameRepository = $gameRepository;
$this->user = $user;
}
public function __invoke(Request $request, Response $response)
{
$body = $request->getParsedBody();
try {
$game = new Game(new Uuid(), $body['name']);
$this->gameRepository->store($game);
return $response->withJson(['message' => 'Created, ' . $this->user->getId()]);
} catch (\Exception $exception) {
return $response->withJson(['message' => 'Something went wrong, A game with that name probably exists']);
}
}
}
Mwhahahahahaha. Now your HTTP Classes no longer need that security garbage.
You can thank me by following me on Twitter!