Skip to content

Instantly share code, notes, and snippets.

@thepsion5
Created February 1, 2022 17:50
Show Gist options
  • Save thepsion5/334313ebefd336b073495220f10792a6 to your computer and use it in GitHub Desktop.
Save thepsion5/334313ebefd336b073495220f10792a6 to your computer and use it in GitHub Desktop.
Example of Using Composition via Traits to Remove Duplicate Controller Logic
<?php declare(strict_types=1);
namespace Personly\Music\Presentation;
use Personly\Framework\Csrf\StoredTokenValidator;
use Personly\Framework\Csrf\Token;
use Personly\Framework\Rendering\TemplateRenderer;
use Personly\FrontPage\Presentation\RootPageController;
use Personly\Music\Domain\MusicRepository;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
final class MusicController
{
private TemplateRenderer $templateRenderer;
private MusicRepository $musicRepository;
private StoredTokenValidator $storedTokenValidator;
public function __construct
(
TemplateRenderer $templateRenderer,
MusicRepository $musicRepository,
StoredTokenValidator $storedTokenValidator
) {
$this->templateRenderer = $templateRenderer;
$this->musicRepository = $musicRepository;
$this->storedTokenValidator = $storedTokenValidator;
}
public function listAlbums(Request $request, array $vars): Response
{
$layoutToken = $request->headers->get('X-Layout');
if(!$this->storedTokenValidator->validate('layout', new Token($layoutToken ?? ''))) {
return new RedirectResponse('/?destination=music', 302);
}
$albums = $this->musicRepository->getAlbums();
$content = $this->templateRenderer->render('music/AlbumsList.html.twig', [
'albums' => $albums
]);
return new Response($content);
}
public function getAlbumInfo(Request $request, array $vars = []): Response
{
$layoutToken = $request->headers->get('X-Layout');
if(!$this->storedTokenValidator->validate('layout', new Token($layoutToken ?? ''))) {
return new RedirectResponse('/?destination=music', 302);
}
$albumInfo = $this->musicRepository->getTracks((int) $vars['albumId']);
$image = $albumInfo[0] ? $albumInfo[0]->image : '';
$content = $this->templateRenderer->render('music/AlbumInfo.html.twig', [
'album' => $albumInfo,
'image' => $image
]);
return new Response($content);
}
}
<?php
declare(strict_types=1);
use Personly\Framework\{Csrf\StoredTokenValidator, Rendering\TemplateRenderer};
use Personly\Http\Traits\{ValidatesCsrfTokens, RendersTemplates};
use Symfony\Component\HttpFoundation\{JsonResponse, Request, Response};
final class MusicController
{
use ValidatesCsrfTokens, RendersTemplates;
/**
* @var StoredTokenValidator
*/
private StoredTokenValidator $validator;
/**
* @var TemplateRenderer
*/
private TemplateRenderer $renderer;
/**
* @var MusicRepository
*/
private MusicRepository $musicRepository;
public function __construct(
StoredTokenValidator $storedTokenValidator,
TemplateRenderer $templateRenderer,
MusicRepository $musicRepository
) {
$this->validator = $storedTokenValidator;
$this->renderer = $templateRenderer;
$this->musicRepository = $musicRepository;
}
/**
* @return TemplateRenderer Retrieves the current Template Renderer instance
*/
protected function getTemplateRenderer(): TemplateRenderer
{
return $this->renderer;
}
/**
* @param Request $request
* @param array $vars
* @return Response
*/
public function listAlbums(Request $request, array $vars)
{
$validOrRedirect = $this->validateRequest($request, $this->validator);
if(!$validOrRedirect === true) {
return $validOrRedirect;
}
$template = 'music/AlbumsList.html.twig';
$data = [
'albums' => $this->musicRepository->getAlbums()
];
$responseType = $request->header('Accepts');
return $this->sendResponse($responseType, $template, $data);
}
}
<?php
declare(strict_types=1);
namespace Personly\Http\Traits;
use Personly\Framework\Rendering\TemplateRenderer;
use Symfony\Component\HttpFoundation\{JsonResponse, Response};
trait RendersTemplates
{
/**
* Returns a Template Renderer instance
*
* I debated whether to keep this as an abstract function or have it passed
* as a parameter for sendResponse() similar to the ValidatesCsrfTokens
* trait, but eventually settled on an abstract function to keep the number
* of arguments to sendResponse() down
*
* @return TemplateRenderer
*/
protected abstract function getTemplateRenderer(): TemplateRenderer;
/**
* Sends the appropriate HTTP response based on the template and response type
*
* @param string $responseType The HTTP response type
* @param string $template The name of the template to use for non JSON responses
* @param array $responseData Any response data to be included
* @return Response|JsonResponse The correct HTTP Response
*/
protected function sendResponse(string $responseType, string $template, array $responseData): Response
{
if($responseType === 'application/json') {
return new JsonResponse($responseData);
}
$content = $this->getTemplateRenderer()->render($template, $responseData);
return new Response($content);
}
}
<?php
declare(strict_types=1);
namespace Personly\Http\Traits;
use Personly\Framework\Csrf\{StoredTokenValidator, Token};
use Symfony\Component\HttpFoundation\{RedirectResponse, Request};
trait ValidatesCsrfTokens
{
/**
* Returns a redirect response to the previous page
*
* @return RedirectResponse
*/
protected abstract function redirect(): RedirectResponse;
/**
* Validates a non-JSON request using a supplied CSRF token
*
* @param Request $request The incoming request to validate
* @param StoredTokenValidator $validator The validator instance used to validate the token
* @return bool|RedirectResponse True if validation is successful, or a Redirect response otherwise
*/
protected function validateRequest(Request $request, StoredTokenValidator $validator)
{
$this->responseType = $request->headers->get('Accept');
if($this->responseType === 'application/json') {
return true;
}
$layoutToken = $request->headers->get('X-Layout');
if(!$validator->validate('layout', new Token($layoutToken ?? ''))) {
return $this->redirect();
}
return true;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment