Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3,809 changes: 2,027 additions & 1,782 deletions composer.lock

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,7 @@ services:
App\Infrastructure\Post\Repository\InFilePostRepository: ~

App\Domain\Post\Repository\PostRepositoryInterface: '@App\Infrastructure\Post\Repository\DoctrinePostRepository'

# HTTP Response Services
App\Domain\Shared\Http\HttpResponseFactoryInterface: '@App\Infrastructure\Shared\Http\SymfonyHttpResponseFactory'
App\Domain\Shared\Http\FlashMessageServiceInterface: '@App\Infrastructure\Shared\Http\SymfonyFlashMessageService'
15 changes: 15 additions & 0 deletions src/Domain/Shared/Http/FlashMessageServiceInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace App\Domain\Shared\Http;

interface FlashMessageServiceInterface
{
/**
* Add a flash message
*
* @param string $type
* @param string $message
* @return void
*/
public function add(string $type, string $message): void;
}
35 changes: 35 additions & 0 deletions src/Domain/Shared/Http/HttpResponseFactoryInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace App\Domain\Shared\Http;

interface HttpResponseFactoryInterface
{
/**
* Create an HTTP response
*
* @param mixed $data
* @param int $statusCode
* @param array $headers
* @return HttpResponseInterface
*/
public function create($data, int $statusCode = 200, array $headers = []): HttpResponseInterface;

/**
* Create a redirect response
*
* @param string $url
* @param int $statusCode
* @return HttpResponseInterface
*/
public function redirect(string $url, int $statusCode = 302): HttpResponseInterface;

/**
* Create a rendered template response
*
* @param string $template
* @param array $parameters
* @param int $statusCode
* @return HttpResponseInterface
*/
public function render(string $template, array $parameters = [], int $statusCode = 200): HttpResponseInterface;
}
27 changes: 27 additions & 0 deletions src/Domain/Shared/Http/HttpResponseInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace App\Domain\Shared\Http;

interface HttpResponseInterface
{
/**
* Get the response status code
*
* @return int
*/
public function getStatusCode(): int;

/**
* Get response headers
*
* @return array
*/
public function getHeaders(): array;

/**
* Get response content
*
* @return string
*/
public function getContent(): string;
}
31 changes: 31 additions & 0 deletions src/Infrastructure/Shared/Http/DomainResponseListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace App\Infrastructure\Shared\Http;

use App\Domain\Shared\Http\HttpResponseInterface;
use Symfony\Component\HttpKernel\Event\ViewEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\KernelEvents;

class DomainResponseListener implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
KernelEvents::VIEW => 'onKernelView',
];
}

public function onKernelView(ViewEvent $event): void
{
$result = $event->getControllerResult();

if ($result instanceof HttpResponseInterface) {
// If our controller returns a domain HttpResponseInterface,
// convert it back to a Symfony Response
if ($result instanceof SymfonyHttpResponse) {
$event->setResponse($result->getSymfonyResponse());
}
}
}
}
24 changes: 24 additions & 0 deletions src/Infrastructure/Shared/Http/SymfonyFlashMessageService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace App\Infrastructure\Shared\Http;

use App\Domain\Shared\Http\FlashMessageServiceInterface;
use Symfony\Component\HttpFoundation\RequestStack;

class SymfonyFlashMessageService implements FlashMessageServiceInterface
{
private RequestStack $requestStack;

public function __construct(RequestStack $requestStack)
{
$this->requestStack = $requestStack;
}

public function add(string $type, string $message): void
{
$session = $this->requestStack->getSession();
if ($session) {
$session->getFlashBag()->add($type, $message);
}
}
}
42 changes: 42 additions & 0 deletions src/Infrastructure/Shared/Http/SymfonyHttpResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace App\Infrastructure\Shared\Http;

use App\Domain\Shared\Http\HttpResponseInterface;
use Symfony\Component\HttpFoundation\Response;

class SymfonyHttpResponse implements HttpResponseInterface
{
private Response $response;

public function __construct(Response $response)
{
$this->response = $response;
}

public function getStatusCode(): int
{
return $this->response->getStatusCode();
}

public function getHeaders(): array
{
return $this->response->headers->all();
}

public function getContent(): string
{
return $this->response->getContent() ?: '';
}

/**
* Get the underlying Symfony Response object
* This method is used by the Symfony framework to send the response
*
* @return Response
*/
public function getSymfonyResponse(): Response
{
return $this->response;
}
}
54 changes: 54 additions & 0 deletions src/Infrastructure/Shared/Http/SymfonyHttpResponseFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace App\Infrastructure\Shared\Http;

use App\Domain\Shared\Http\HttpResponseFactoryInterface;
use App\Domain\Shared\Http\HttpResponseInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Twig\Environment;

class SymfonyHttpResponseFactory implements HttpResponseFactoryInterface
{
private UrlGeneratorInterface $urlGenerator;
private Environment $twig;

public function __construct(UrlGeneratorInterface $urlGenerator, Environment $twig)
{
$this->urlGenerator = $urlGenerator;
$this->twig = $twig;
}

public function create($data, int $statusCode = 200, array $headers = []): HttpResponseInterface
{
if (is_array($data) || is_object($data)) {
$response = new JsonResponse($data, $statusCode, $headers);
} else {
$response = new Response((string) $data, $statusCode, $headers);
}

return new SymfonyHttpResponse($response);
}

public function redirect(string $url, int $statusCode = 302): HttpResponseInterface
{
// Check if $url is a route name or a full URL
if (!filter_var($url, FILTER_VALIDATE_URL)) {
// It's a route name, generate the URL
$url = $this->urlGenerator->generate($url);
}

$response = new RedirectResponse($url, $statusCode);
return new SymfonyHttpResponse($response);
}

public function render(string $template, array $parameters = [], int $statusCode = 200): HttpResponseInterface
{
$content = $this->twig->render($template, $parameters);
$response = new Response($content, $statusCode);

return new SymfonyHttpResponse($response);
}
}
27 changes: 20 additions & 7 deletions src/UI/Http/Web/Controller/Post/CreatePostController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,38 @@
use App\Application\UseCase\Command\Post\Create\CreatePostCommand;
use App\Application\UseCase\Command\Post\Create\CreatePostUseCase;
use App\Domain\Post\Exception\InvalidPostDataException;
use App\Domain\Shared\Http\FlashMessageServiceInterface;
use App\Domain\Shared\Http\HttpResponseFactoryInterface;
use App\Domain\Shared\Http\HttpResponseInterface;
use App\UI\Http\Web\Form\Post\PostType;
use DateTime;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

/**
* @Route("/posts/create", name="app.post.create")
*/
class CreatePostController extends AbstractController
{
private HttpResponseFactoryInterface $responseFactory;
private FlashMessageServiceInterface $flashMessageService;

public function __construct(
HttpResponseFactoryInterface $responseFactory,
FlashMessageServiceInterface $flashMessageService
) {
$this->responseFactory = $responseFactory;
$this->flashMessageService = $flashMessageService;
}

/**
* @param Request $request
* @param CreatePostUseCase $createPostUseCase
*
* @return Response
* @return HttpResponseInterface
*/
public function __invoke(Request $request, CreatePostUseCase $createPostUseCase): Response
public function __invoke(Request $request, CreatePostUseCase $createPostUseCase): HttpResponseInterface
{
$form = $this->createForm(PostType::class);
$form->handleRequest($request);
Expand All @@ -46,15 +59,15 @@ public function __invoke(Request $request, CreatePostUseCase $createPostUseCase)
try {
$post = $createPostUseCase->create($createPostCommand);

$this->addFlash('success', "{$post->getPost()->getTitle()} created.");
$this->flashMessageService->add('success', "{$post->getPost()->getTitle()} created.");

return $this->redirectToRoute('app.post.create');
return $this->responseFactory->redirect('app.post.create');
} catch (InvalidPostDataException $dataException) {
$this->addFlash('error', $dataException->getMessage());
$this->flashMessageService->add('error', $dataException->getMessage());
}
}

return $this->render('post/form.html.twig', [
return $this->responseFactory->render('post/form.html.twig', [
'form' => $form->createView(),
]);
}
Expand Down