Completed
Pull Request — 1.4 (#711)
by Paweł
10:54
created

CachedTenantContext::reset()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Superdesk Web Publisher Core Bundle.
7
 *
8
 * Copyright 2017 Sourcefabric z.u. and contributors.
9
 *
10
 * For the full copyright and license information, please see the
11
 * AUTHORS and LICENSE files distributed with this source code.
12
 *
13
 * @copyright 2017 Sourcefabric z.ú
14
 * @license http://www.superdesk.org/license
15
 */
16
17
namespace SWP\Bundle\CoreBundle\Context;
18
19
use Doctrine\Common\Cache\Cache;
20
use Doctrine\ORM\EntityManager;
21
use SWP\Bundle\CoreBundle\Model\OutputChannel;
22
use SWP\Bundle\CoreBundle\Model\Route;
23
use SWP\Bundle\MultiTenancyBundle\Context\TenantContext;
24
use SWP\Bundle\MultiTenancyBundle\MultiTenancyEvents;
25
use SWP\Component\MultiTenancy\Exception\TenantNotFoundException;
26
use SWP\Component\MultiTenancy\Model\OrganizationInterface;
27
use SWP\Component\MultiTenancy\Model\TenantInterface;
28
use SWP\Component\MultiTenancy\Resolver\TenantResolverInterface;
29
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
30
use Symfony\Component\HttpFoundation\RequestStack;
31
32
class CachedTenantContext extends TenantContext implements CachedTenantContextInterface
33
{
34
    /**
35
     * @var Cache
36
     */
37
    protected $cacheProvider;
38
39
    /**
40
     * @var EntityManager
41
     */
42
    protected $entityManager;
43
44
    private $cacheKeys = [];
45
46
    /**
47
     * CachedTenantContext constructor.
48
     *
49
     * @param TenantResolverInterface  $tenantResolver
50
     * @param RequestStack             $requestStack
51
     * @param EventDispatcherInterface $dispatcher
52
     * @param Cache                    $cacheProvider
53
     * @param EntityManager            $entityManager
54
     */
55
    public function __construct(TenantResolverInterface $tenantResolver, RequestStack $requestStack, EventDispatcherInterface $dispatcher, Cache $cacheProvider, EntityManager $entityManager)
0 ignored issues
show
Bug introduced by
You have injected the EntityManager via parameter $entityManager. This is generally not recommended as it might get closed and become unusable. Instead, it is recommended to inject the ManagerRegistry and retrieve the EntityManager via getManager() each time you need it.

The EntityManager might become unusable for example if a transaction is rolled back and it gets closed. Let’s assume that somewhere in your application, or in a third-party library, there is code such as the following:

function someFunction(ManagerRegistry $registry) {
    $em = $registry->getManager();
    $em->getConnection()->beginTransaction();
    try {
        // Do something.
        $em->getConnection()->commit();
    } catch (\Exception $ex) {
        $em->getConnection()->rollback();
        $em->close();

        throw $ex;
    }
}

If that code throws an exception and the EntityManager is closed. Any other code which depends on the same instance of the EntityManager during this request will fail.

On the other hand, if you instead inject the ManagerRegistry, the getManager() method guarantees that you will always get a usable manager instance.

Loading history...
56
    {
57
        $this->cacheProvider = $cacheProvider;
58
        $this->entityManager = $entityManager;
59
60
        parent::__construct($tenantResolver, $requestStack, $dispatcher);
61
    }
62
63
    /**
64
     * {@inheritdoc}
65
     */
66
    public function getTenant(): ?TenantInterface
67
    {
68
        $currentRequest = $this->requestStack->getCurrentRequest();
69
        if ($currentRequest && $this->requestStack->getCurrentRequest()->attributes->get('exception') instanceof TenantNotFoundException) {
70
            return null;
71
        }
72
73
        if (null === $this->tenant) {
74
            if (null !== $currentRequest) {
75
                $cacheKey = self::getCacheKey($currentRequest->getHost());
76
77
                if ($this->cacheProvider->contains($cacheKey) && ($tenant = $this->cacheProvider->fetch($cacheKey)) instanceof TenantInterface) {
78
                    // solution for serialization
79
                    if (null !== $tenant->getHomepage()) {
80
                        $tenant->setHomepage($this->entityManager->find(Route::class, $tenant->getHomepage()->getId()));
81
                    }
82
                    if (null !== $tenant->getOutputChannel()) {
83
                        $tenant->setOutputChannel($this->entityManager->find(OutputChannel::class, $tenant->getOutputChannel()->getId()));
84
                    }
85
                    parent::setTenant($this->attachToEntityManager($tenant));
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (setTenant() instead of getTenant()). Are you sure this is correct? If so, you might want to change this to $this->setTenant().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
86
                } else {
87
                    $tenant = $this->tenantResolver->resolve(
88
                        $currentRequest ? $currentRequest->getHost() : null
89
                    );
90
91
                    parent::setTenant($tenant);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (setTenant() instead of getTenant()). Are you sure this is correct? If so, you might want to change this to $this->setTenant().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
92
93
                    $this->cacheProvider->save($cacheKey, $tenant);
94
                }
95
            }
96
        }
97
98
        return $this->tenant;
99
    }
100
101
    /**
102
     * {@inheritdoc}
103
     */
104
    public function setTenant(TenantInterface $tenant): void
105
    {
106
        parent::setTenant($this->attachToEntityManager($tenant));
107
108
        $host = $tenant->getDomainName();
109
        if ($subdomain = $tenant->getSubdomain()) {
110
            $host = $subdomain.'.'.$host;
111
        }
112
113
        $this->dispatcher->dispatch(MultiTenancyEvents::TENANTABLE_ENABLE);
114
        $cacheKey = self::getCacheKey($host);
115
        $this->cacheKeys[] = $cacheKey;
116
        $this->cacheProvider->save($cacheKey, $tenant);
117
    }
118
119
    /**
120
     * {@inheritdoc}
121
     */
122
    public static function getCacheKey(string $host): string
123
    {
124
        return md5('tenant_cache__'.$host);
125
    }
126
127
    public function reset()
128
    {
129
        $this->tenant = null;
130
        foreach ($this->cacheKeys as $cacheKey) {
131
            $this->cacheProvider->delete($cacheKey);
132
        }
133
    }
134
135
    private function attachToEntityManager(TenantInterface $tenant): TenantInterface
136
    {
137
        if ($this->entityManager->contains($tenant)) {
138
            return $tenant;
139
        }
140
141
        /** @var OrganizationInterface $organization */
142
        $organization = $this->entityManager->merge($tenant->getOrganization());
143
        $tenant->setOrganization($organization);
144
145
        return  $this->entityManager->merge($tenant);
146
    }
147
}
148