Completed
Push — master ( b4949b...b69366 )
by Kamil
10:51 queued 03:28
created

EventListener/ResourceDeleteSubscriber.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/*
4
 * This file is part of the Sylius package.
5
 *
6
 * (c) Paweł Jędrzejewski
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace Sylius\Bundle\AdminBundle\EventListener;
15
16
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
17
use Sylius\Component\Resource\ResourceActions;
18
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
19
use Symfony\Component\HttpFoundation\RedirectResponse;
20
use Symfony\Component\HttpFoundation\Request;
21
use Symfony\Component\HttpFoundation\Session\SessionInterface;
22
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
23
use Symfony\Component\HttpKernel\KernelEvents;
24
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
25
26
final class ResourceDeleteSubscriber implements EventSubscriberInterface
27
{
28
    /**
29
     * @var UrlGeneratorInterface
30
     */
31
    private $router;
32
33
    /**
34
     * @var SessionInterface
35
     */
36
    private $session;
37
38
    /**
39
     * @param UrlGeneratorInterface $router
40
     * @param SessionInterface $session
41
     */
42
    public function __construct(UrlGeneratorInterface $router, SessionInterface $session)
43
    {
44
        $this->router = $router;
45
        $this->session = $session;
46
    }
47
48
    /**
49
     * {@inheritdoc}
50
     */
51
    public static function getSubscribedEvents(): array
52
    {
53
        return [
54
            KernelEvents::EXCEPTION => 'onResourceDelete',
55
        ];
56
    }
57
58
    /**
59
     * @param GetResponseForExceptionEvent $event
60
     */
61
    public function onResourceDelete(GetResponseForExceptionEvent $event): void
62
    {
63
        $exception = $event->getException();
64
        if (!$exception instanceof ForeignKeyConstraintViolationException) {
65
            return;
66
        }
67
68
        if (!$event->isMasterRequest() || 'html' !== $event->getRequest()->getRequestFormat()) {
69
            return;
70
        }
71
72
        $eventRequest = $event->getRequest();
73
        $requestAttributes = $eventRequest->attributes;
74
        $originalRoute = $requestAttributes->get('_route');
75
76
        if (!$this->isMethodDelete($eventRequest) ||
77
            !$this->isSyliusRoute($originalRoute) ||
78
            !$this->isAdminSection($requestAttributes->get('_sylius', []))
79
        ) {
80
            return;
81
        }
82
83
        $resourceName = $this->getResourceNameFromRoute($originalRoute);
84
85
        if (null === $requestAttributes->get('_controller')) {
86
            return;
87
        }
88
89
        $this->session->getBag('flashes')->add('error', [
0 ignored issues
show
It seems like you code against a concrete implementation and not the interface Symfony\Component\HttpFo...ion\SessionBagInterface as the method add() does only exist in the following implementations of said interface: Symfony\Component\HttpFo...lash\AutoExpireFlashBag, Symfony\Component\HttpFo...\Session\Flash\FlashBag.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
90
            'message' => 'sylius.resource.delete_error',
91
            'parameters' => ['%resource%' => $resourceName],
92
        ]);
93
94
        $referrer = $eventRequest->headers->get('referer');
95
        if (null !== $referrer) {
96
            $event->setResponse(new RedirectResponse($referrer));
0 ignored issues
show
It seems like $referrer defined by $eventRequest->headers->get('referer') on line 94 can also be of type array<integer,string>; however, Symfony\Component\HttpFo...Response::__construct() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
97
98
            return;
99
        }
100
101
        $event->setResponse($this->createRedirectResponse($originalRoute, ResourceActions::INDEX));
102
    }
103
104
    /**
105
     * @param string $route
106
     *
107
     * @return string
108
     */
109
    private function getResourceNameFromRoute(string $route): string
110
    {
111
        $route = str_replace('_bulk', '', $route);
112
        $routeArray = explode('_', $route);
113
        $routeArrayWithoutAction = array_slice($routeArray, 0, count($routeArray) - 1);
114
        $routeArrayWithoutPrefixes = array_slice($routeArrayWithoutAction, 2);
115
116
        return trim(implode(' ', $routeArrayWithoutPrefixes));
117
    }
118
119
    /**
120
     * @param string $originalRoute
121
     * @param string $targetAction
122
     *
123
     * @return RedirectResponse
124
     */
125
    private function createRedirectResponse(string $originalRoute, string $targetAction): RedirectResponse
126
    {
127
        $redirectRoute = str_replace(ResourceActions::DELETE, $targetAction, $originalRoute);
128
129
        return new RedirectResponse($this->router->generate($redirectRoute));
130
    }
131
132
    /**
133
     * @param Request $request
134
     *
135
     * @return bool
136
     */
137
    private function isMethodDelete(Request $request): bool
138
    {
139
        return Request::METHOD_DELETE === $request->getMethod();
140
    }
141
142
    /**
143
     * @param string $route
144
     *
145
     * @return bool
146
     */
147
    private function isSyliusRoute(string $route): bool
148
    {
149
        return 0 === strpos($route, 'sylius');
150
    }
151
152
    /**
153
     * @param array $syliusParameters
154
     *
155
     * @return bool
156
     */
157
    private function isAdminSection(array $syliusParameters): bool
158
    {
159
        return array_key_exists('section', $syliusParameters) && 'admin' === $syliusParameters['section'];
160
    }
161
}
162