CreateTenantCommand   A
last analyzed

Complexity

Total Complexity 18

Size/Duplication

Total Lines 146
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 11

Importance

Changes 0
Metric Value
wmc 18
lcom 1
cbo 11
dl 0
loc 146
rs 10
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 15 1
A configure() 0 19 1
B execute() 0 52 8
A interact() 0 6 2
A askAndValidateInteract() 0 20 5
A createTenant() 0 12 1
1
<?php
2
3
/*
4
 * This file is part of the Superdesk Web Publisher MultiTenancyBundle.
5
 *
6
 * Copyright 2016 Sourcefabric z.u. and contributors.
7
 *
8
 * For the full copyright and license information, please see the
9
 * AUTHORS and LICENSE files distributed with this source code.
10
 *
11
 * @copyright 2016 Sourcefabric z.ú
12
 * @license http://www.superdesk.org/license
13
 */
14
15
namespace SWP\Bundle\MultiTenancyBundle\Command;
16
17
use Doctrine\Common\Persistence\ObjectManager;
18
use SWP\Component\MultiTenancy\Factory\TenantFactoryInterface;
19
use SWP\Component\MultiTenancy\Model\OrganizationInterface;
20
use SWP\Component\MultiTenancy\Model\TenantInterface;
21
use SWP\Component\MultiTenancy\Repository\OrganizationRepositoryInterface;
22
use SWP\Component\MultiTenancy\Repository\TenantRepositoryInterface;
23
use Symfony\Component\Console\Command\Command;
24
use Symfony\Component\Console\Input\InputArgument;
25
use Symfony\Component\Console\Input\InputInterface;
26
use Symfony\Component\Console\Input\InputOption;
27
use Symfony\Component\Console\Output\OutputInterface;
28
use Symfony\Component\Console\Question\Question;
29
30
class CreateTenantCommand extends Command
31
{
32
    protected static $defaultName = 'swp:tenant:create';
33
34
    protected $arguments = ['domain', 'subdomain', 'name', 'organization code'];
35
36
    private $swpDomain;
37
38
    private $tenantFactory;
39
40
    private $tenantObjectManager;
41
42
    private $tenantRepository;
43
44
    private $organizationRepository;
45
46
    public function __construct(
47
        string $swpDomain,
48
        TenantFactoryInterface $tenantFactory,
49
        ObjectManager $tenantObjectManager,
50
        TenantRepositoryInterface $tenantRepository,
51
        OrganizationRepositoryInterface $organizationRepository
52
    ) {
53
        parent::__construct();
54
55
        $this->swpDomain = $swpDomain;
56
        $this->tenantFactory = $tenantFactory;
57
        $this->tenantObjectManager = $tenantObjectManager;
58
        $this->tenantRepository = $tenantRepository;
59
        $this->organizationRepository = $organizationRepository;
60
    }
61
62
    protected function configure(): void
63
    {
64
        $this
65
            ->setName('swp:tenant:create')
66
            ->setDescription('Creates a new tenant.')
67
            ->setDefinition([
68
                new InputArgument($this->arguments[3], InputArgument::OPTIONAL, 'Organization code'),
69
                new InputArgument($this->arguments[0], InputArgument::OPTIONAL, 'Domain name'),
70
                new InputArgument($this->arguments[2], InputArgument::OPTIONAL, 'Tenant name'),
71
                new InputArgument($this->arguments[1], InputArgument::OPTIONAL, 'Subdomain name', null),
72
                new InputOption('disabled', null, InputOption::VALUE_NONE, 'Set the tenant as a disabled'),
73
                new InputOption('default', null, InputOption::VALUE_NONE, 'Creates the default tenant'),
74
            ])
75
            ->setHelp(
76
                <<<'EOT'
77
                The <info>%command.name%</info> command creates a new tenant.
78
EOT
79
            );
80
    }
81
82
    protected function execute(InputInterface $input, OutputInterface $output): int
83
    {
84
        $domain = $input->getArgument($this->arguments[0]);
85
        $subdomain = $input->getArgument($this->arguments[1]);
86
        $name = $input->getArgument($this->arguments[2]);
87
        $organizationCode = $input->getArgument($this->arguments[3]);
88
        $default = $input->getOption('default');
89
        $disabled = $input->getOption('disabled');
90
91
        if ($default) {
92
            $name = TenantInterface::DEFAULT_TENANT_NAME;
93
            $domain = $this->swpDomain;
94
            $organization = $this->organizationRepository->findOneByName(OrganizationInterface::DEFAULT_NAME);
95
            if (null === $organization) {
96
                throw new \InvalidArgumentException('Default organization doesn\'t exist!');
97
            }
98
        } else {
99
            $organization = $this->organizationRepository->findOneByCode($organizationCode);
0 ignored issues
show
Bug introduced by
It seems like $organizationCode defined by $input->getArgument($this->arguments[3]) on line 87 can also be of type array<integer,string> or null; however, SWP\Component\MultiTenan...erface::findOneByCode() 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...
100
101
            if (null === $organization) {
102
                throw new \InvalidArgumentException(sprintf('Organization with "%s" code doesn\'t exist!', $organizationCode));
103
            }
104
        }
105
106
        if (null !== $subdomain) {
107
            $tenant = $this->tenantRepository->findOneBySubdomainAndDomain($subdomain, $domain);
0 ignored issues
show
Bug introduced by
It seems like $subdomain defined by $input->getArgument($this->arguments[1]) on line 85 can also be of type array<integer,string>; however, SWP\Component\MultiTenan...eBySubdomainAndDomain() 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...
Bug introduced by
It seems like $domain defined by $input->getArgument($this->arguments[0]) on line 84 can also be of type array<integer,string> or null; however, SWP\Component\MultiTenan...eBySubdomainAndDomain() 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...
108
        } else {
109
            $tenant = $this->tenantRepository->findOneByDomain($domain);
0 ignored issues
show
Bug introduced by
It seems like $domain defined by $input->getArgument($this->arguments[0]) on line 84 can also be of type array<integer,string> or null; however, SWP\Component\MultiTenan...face::findOneByDomain() 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...
110
        }
111
112
        if (null !== $tenant) {
113
            throw new \InvalidArgumentException(sprintf('Tenant with domain %s and subdomain "%s" already exists!', $domain, $subdomain));
114
        }
115
116
        $tenant = $this->createTenant($domain, $subdomain, $name, $disabled, $organization);
0 ignored issues
show
Bug introduced by
It seems like $domain defined by $input->getArgument($this->arguments[0]) on line 84 can also be of type array<integer,string> or null; however, SWP\Bundle\MultiTenancyB...Command::createTenant() 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...
117
        if ($default) {
118
            $tenant->setCode(TenantInterface::DEFAULT_TENANT_CODE);
119
        }
120
        $this->tenantObjectManager->persist($tenant);
121
        $this->tenantObjectManager->flush();
122
123
        $output->writeln(
124
            sprintf(
125
                'Tenant <info>%s</info> (code: <info>%s</info>) has been created and <info>%s</info>!',
126
                $name,
127
                $tenant->getCode(),
128
                $tenant->isEnabled() ? 'enabled' : 'disabled'
129
            )
130
        );
131
132
        return 1;
133
    }
134
135
    protected function interact(InputInterface $input, OutputInterface $output): void
136
    {
137
        foreach ($this->arguments as $value) {
138
            $this->askAndValidateInteract($input, $output, $value);
139
        }
140
    }
141
142
    protected function askAndValidateInteract(InputInterface $input, OutputInterface $output, $name): void
143
    {
144
        $default = $input->getOption('default');
145
        if (!$input->getArgument($name) && !$default) {
146
            $question = new Question(sprintf('<question>Please enter %s:</question>', $name));
147
            $question->setValidator(function ($argument) use ($name) {
148
                if (empty($argument) && $name !== $this->arguments[1]) {
149
                    throw new \RuntimeException(sprintf('The %s can not be empty', $name));
150
                }
151
152
                return $argument;
153
            });
154
155
            $question->setMaxAttempts(3);
156
157
            $argument = $this->getHelper('question')->ask($input, $output, $question);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Symfony\Component\Console\Helper\HelperInterface as the method ask() does only exist in the following implementations of said interface: Symfony\Component\Console\Helper\QuestionHelper, Symfony\Component\Consol...r\SymfonyQuestionHelper.

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...
158
159
            $input->setArgument($name, $argument);
160
        }
161
    }
162
163
    protected function createTenant(string $domain, ?string $subdomain, string $name, bool $disabled, OrganizationInterface $organization): TenantInterface
164
    {
165
        /** @var TenantInterface $tenant */
166
        $tenant = $this->tenantFactory->create();
167
        $tenant->setSubdomain($subdomain);
168
        $tenant->setDomainName($domain);
169
        $tenant->setName($name);
170
        $tenant->setEnabled(!$disabled);
171
        $tenant->setOrganization($organization);
172
173
        return $tenant;
174
    }
175
}
176