Skip to content

Commit

Permalink
feat: 🎸 impl custom TwigExtensions and UrlGenerator
Browse files Browse the repository at this point in the history
  • Loading branch information
ttskch committed Dec 24, 2020
1 parent c890fa5 commit 37345f7
Show file tree
Hide file tree
Showing 9 changed files with 199 additions and 32 deletions.
10 changes: 10 additions & 0 deletions config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,13 @@ services:
App\EntityListener\:
resource: '../src/EntityListener'
tags: ['doctrine.orm.entity_listener']

#
# Twig extensions
#

App\Twig\AppExtension:
tags: ['twig.extension']

App\Twig\RoutingExtension:
tags: ['twig.extension']
11 changes: 7 additions & 4 deletions src/Controller/UserController.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use App\Form\UserEditType;
use App\Form\UserType;
use App\Repository\UserRepository;
use App\Routing\ReturnToAwareControllerTrait;
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
use Doctrine\ORM\EntityManagerInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
Expand All @@ -26,6 +27,8 @@
*/
class UserController extends AbstractController
{
use ReturnToAwareControllerTrait;

private EntityManagerInterface $em;
private UserRepository $repository;

Expand Down Expand Up @@ -91,7 +94,7 @@ public function new(Request $request): Response

$this->addFlash('success', 'User is successfully added.');

return $this->redirectToRoute('user_index');
return $this->redirectToRouteOrReturn('user_index');
}

return $this->render('user/new.html.twig', [
Expand Down Expand Up @@ -124,7 +127,7 @@ public function edit(Request $request, User $user): Response

$this->addFlash('success', 'User is successfully updated.');

return $this->redirectToRoute('user_show', ['id' => $user->getId()]);
return $this->redirectToRouteOrReturn('user_show', ['id' => $user->getId()]);
}

return $this->render('user/edit.html.twig', [
Expand All @@ -150,7 +153,7 @@ public function changePassword(Request $request, User $user): Response

$this->addFlash('success', 'Password is successfully updated.');

return $this->redirectToRoute('user_show', ['id' => $user->getId()]);
return $this->redirectToRouteOrReturn('user_show', ['id' => $user->getId()]);
}

return $this->render('user/change_password.html.twig', [
Expand All @@ -176,6 +179,6 @@ public function delete(Request $request, User $user): Response
}
}

return $this->redirectToRoute('user_index');
return $this->redirectToRouteOrReturn('user_index');
}
}
28 changes: 28 additions & 0 deletions src/Routing/ReturnToAwareControllerTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace App\Routing;

use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;

/**
* @property ContainerInterface $container
*/
trait ReturnToAwareControllerTrait
{
protected function redirectOrReturn(string $url, int $status = 302): RedirectResponse
{
if ($returnTo = $this->container->get('request_stack')->getCurrentRequest()->query->get('returnTo')) {
return new RedirectResponse($returnTo, $status);
}

return new RedirectResponse($url, $status);
}

protected function redirectToRouteOrReturn(string $route, array $parameters = [], int $status = 302): RedirectResponse
{
return $this->redirectOrReturn($this->container->get('router')->generate($route, $parameters), $status);
}
}
38 changes: 38 additions & 0 deletions src/Routing/ReturnToAwareUrlGenerator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace App\Routing;

use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

class ReturnToAwareUrlGenerator
{
private RequestStack $requestStack;
private UrlGeneratorInterface $generator;

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

public function generate(string $name, array $parameters = [], int $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH): string
{
if ($returnTo = $this->requestStack->getCurrentRequest()->query->get('returnTo')) {
return $returnTo;
}

return $this->generator->generate($name, $parameters, $referenceType);
}

public function generateWithReturnTo(string $name, array $parameters = [], int $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH): string
{
$request = $this->requestStack->getCurrentRequest();

$parameters = array_merge_recursive($parameters, ['returnTo' => $request->query->get('returnTo') ?? $request->getUri()]);

return $this->generator->generate($name, $parameters, $referenceType);
}
}
63 changes: 63 additions & 0 deletions src/Twig/AppExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

declare(strict_types=1);

namespace App\Twig;

use App\Security\RoleManager;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
use Twig\TwigFunction;

class AppExtension extends AbstractExtension
{
private RoleManager $rm;
private TranslatorInterface $translator;
private RequestStack $reqestStack;

public function __construct(RoleManager $rm, TranslatorInterface $translator, RequestStack $requestStack)
{
$this->rm = $rm;
$this->translator = $translator;
$this->reqestStack = $requestStack;
}

public function getFunctions()
{
return [
new TwigFunction('roles', [$this, 'roles'], ['is_safe' => ['html']]),
];
}

public function getFilters()
{
return [
new TwigFilter('datetime', [$this, 'datetime']),
];
}

public function roles(UserInterface $user): string
{
$badges = [];

foreach ($this->rm->getReachableRoles($user) as $role) {
$badges[] = sprintf('<span class="badge badge-secondary">%s</span>', $this->translator->trans($role));
}

return implode(' ', $badges);
}

public function datetime(?\DateTimeInterface $datetime): string
{
if ($this->reqestStack->getCurrentRequest()->getLocale() === 'ja') {
$days = ['日', '月', '火', '水', '木', '金', '土'];

return $datetime === null ? '' : sprintf($datetime->format('Y/m/d(%\s) H:i:s'), $days[(int) $datetime->format('w')]);
}

return $datetime === null ? '' : sprintf($datetime->format('Y/m/d D H:i:s'));
}
}
41 changes: 41 additions & 0 deletions src/Twig/RoutingExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace App\Twig;

use App\Routing\ReturnToAwareUrlGenerator;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;

class RoutingExtension extends AbstractExtension
{
private ReturnToAwareUrlGenerator $returnToAwareUrlGenerator;

public function __construct(ReturnToAwareUrlGenerator $returnToAwareUrlGenerator)
{
$this->returnToAwareUrlGenerator = $returnToAwareUrlGenerator;
}

public function getFunctions()
{
return [
new TwigFunction('pathOrReturnTo', [$this, 'pathOrReturnTo']),
new TwigFunction('pathWithReturnTo', [$this, 'pathWithReturnTo']),
];
}

/**
* @see \Symfony\Bridge\Twig\Extension\RoutingExtension::getPath()
*/
public function pathOrReturnTo(string $name, array $parameters = [], bool $relative = false)
{
return $this->returnToAwareUrlGenerator->generate($name, $parameters, $relative ? UrlGeneratorInterface::RELATIVE_PATH : UrlGeneratorInterface::ABSOLUTE_PATH);
}

public function pathWithReturnTo(string $name, array $parameters = [], bool $relative = false)
{
return $this->returnToAwareUrlGenerator->generateWithReturnTo($name, $parameters, $relative ? UrlGeneratorInterface::RELATIVE_PATH : UrlGeneratorInterface::ABSOLUTE_PATH);
}
}
18 changes: 5 additions & 13 deletions templates/user/_detail.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,24 @@
<td>{{ user.email }}</td>
</tr>
<tr>
<th> {{ 'Roles'|trans }}</th>
<td>
{% for role in user.roles %}
{% if role == 'ROLE_USER' %}
<span class="badge badge-secondary">{{ 'ROLE_ALLOWED_TO_VIEW'|trans }}</span>
{% else %}
<span class="badge badge-secondary">{{ role|trans }}</span>
{% endif %}
{% endfor %}
</td>
<th>{{ 'Roles'|trans }}</th>
<td>{{ roles(user) }}</td>
</tr>
<tr>
<th>{{ 'Display name'|trans }}</th>
<td>{{ user.displayName }}</td>
</tr>
<tr>
<th>{{ 'Last logged in at'|trans }}</th>
<td>{{ user.lastLoggedInAt|date('Y/m/d H:i:s') }}</td>
<td>{{ user.lastLoggedInAt|datetime }}</td>
</tr>
<tr>
<th>{{ 'Created at'|trans }}</th>
<td>{{ user.createdAt|date('Y/m/d H:i:s') }}</td>
<td>{{ user.createdAt|datetime }}</td>
</tr>
<tr>
<th>{{ 'Updated at'|trans }}</th>
<td>{{ user.updatedAt|date('Y/m/d H:i:s') }}</td>
<td>{{ user.updatedAt|datetime }}</td>
</tr>
</tbody>
</table>
Expand Down
6 changes: 3 additions & 3 deletions templates/user/_form.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
{{ form_row(form.displayName) }}
<div class="float-right">
<button type="submit" class="btn btn-primary float-right ml-2">{{ 'Save'|trans }}</button>
<a href="{{ path('user_index') }}" class="btn btn-outline-secondary">{{ 'Cancel'|trans }}</a>
<a href="{{ pathOrReturnTo('user_index') }}" class="btn btn-outline-secondary">{{ 'Cancel'|trans }}</a>
</div>
{{ form_widget(form._token) }}
{{ form_end(form, {render_rest: false}) }}
Expand All @@ -28,7 +28,7 @@
{{ form_row(form.displayName) }}
<div class="float-right">
<button type="submit" class="btn btn-primary float-right ml-2">{{ 'Save'|trans }}</button>
<a href="{{ path('user_show', {id: user.id}) }}" class="btn btn-outline-secondary">{{ 'Cancel'|trans }}</a>
<a href="{{ pathOrReturnTo('user_show', {id: user.id}) }}" class="btn btn-outline-secondary">{{ 'Cancel'|trans }}</a>
</div>
{{ form_widget(form._token) }}
{{ form_end(form, {render_rest: false}) }}
Expand All @@ -39,7 +39,7 @@
{{ form_rest(form) }}
<div class="float-right">
<button type="submit" class="btn btn-primary float-right ml-2">{{ 'Save'|trans }}</button>
<a href="{{ path('user_edit', {id: user.id}) }}" class="btn btn-outline-secondary">{{ 'Cancel'|trans }}</a>
<a href="{{ pathOrReturnTo('user_edit', {id: user.id}) }}" class="btn btn-outline-secondary">{{ 'Cancel'|trans }}</a>
</div>
{{ form_end(form) }}

Expand Down
16 changes: 4 additions & 12 deletions templates/user/index.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
{% block nav_items %}
{% if is_granted('ROLE_ALLOWED_TO_EDIT_USER') %}
<li class="nav-item">
<a href="{{ path('user_new') }}" class="nav-link">{{ 'Add'|trans }}</a>
<a href="{{ pathWithReturnTo('user_new') }}" class="nav-link">{{ 'Add'|trans }}</a>
</li>
{% endif %}
{% endblock %}
Expand All @@ -34,21 +34,13 @@
<tr>
<td>{{ user.id }}</td>
<td>{{ user.email }}</td>
<td>
{% for role in user.roles %}
{% if role == 'ROLE_USER' %}
<span class="badge badge-secondary">{{ 'ROLE_ALLOWED_TO_VIEW'|trans }}</span>
{% else %}
<span class="badge badge-secondary">{{ role|trans }}</span>
{% endif %}
{% endfor %}
</td>
<td>{{ roles(user) }}</td>
<td>{{ user.displayName }}</td>
<td>{{ user.lastLoggedInAt|date('Y/m/d H:i:s') }}</td>
<td>{{ user.lastLoggedInAt|datetime }}</td>
<td>
<a href="{{ path('user_show', {id: user.id}) }}">{{ 'Show'|trans }}</a>
{% if is_granted('ROLE_ALLOWED_TO_EDIT_USER') %}
<a href="{{ path('user_edit', {id: user.id}) }}" class="ml-sm-3">{{ 'Edit'|trans }}</a>
<a href="{{ pathWithReturnTo('user_edit', {id: user.id}) }}" class="ml-sm-3">{{ 'Edit'|trans }}</a>
{% endif %}
</td>
</tr>
Expand Down

0 comments on commit 37345f7

Please sign in to comment.