QueueFactory   A
last analyzed

Complexity

Total Complexity 34

Size/Duplication

Total Lines 170
Duplicated Lines 0 %

Test Coverage

Coverage 67.65%

Importance

Changes 0
Metric Value
eloc 67
dl 0
loc 170
ccs 46
cts 68
cp 0.6765
rs 9.68
c 0
b 0
f 0
wmc 34

8 Methods

Rating   Name   Duplication   Size   Complexity  
B tryGetFromCallable() 0 30 7
A createFromDefinition() 0 15 3
A getFromContainer() 0 15 5
A tryGetFromArrayDefinition() 0 21 4
A checkDefinitionType() 0 9 5
A get() 0 14 4
A create() 0 16 3
A __construct() 0 13 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Queue;
6
7
use Closure;
8
use InvalidArgumentException;
9
use Psr\Container\ContainerInterface;
10
use WeakReference;
11
use Yiisoft\Definitions\ArrayDefinition;
12
use Yiisoft\Definitions\Exception\InvalidConfigException;
13
use Yiisoft\Definitions\Helpers\DefinitionValidator;
14
use Yiisoft\Injector\Injector;
15
use Yiisoft\Queue\Adapter\AdapterInterface;
16
use Yiisoft\Queue\Exception\AdapterConfiguration\ChannelIncorrectlyConfigured;
17
use Yiisoft\Queue\Exception\AdapterConfiguration\ChannelNotConfiguredException;
18
use Yiisoft\Queue\Middleware\CallableFactory;
19
use Yiisoft\Queue\Middleware\InvalidCallableConfigurationException;
20
21
final class QueueFactory implements QueueFactoryInterface
22
{
23
    private array $queueCollection = [];
24
25
    /**
26
     * QueueFactory constructor.
27
     *
28
     * @param array<string, mixed> $channelConfiguration Configuration array in [channel_name => definition] format.
29
     * "Definition" here is a {@see Factory} definition
30
     * @param QueueInterface $queue A default queue implementation. `$queue->withAdapter()` will be returned
31
     * with the `get` method
32
     * @param bool $enableRuntimeChannelDefinition A flag whether to enable a such behavior when there is no
33
     * explicit channel adapter definition: `return $this->queue->withAdapter($this->adapter->withChannel($channel)`
34
     * When this flag is set to false, only explicit definitions from the $definition parameter are used.
35
     * @param AdapterInterface|null $defaultAdapter A default adapter implementation.
36
     * It must be set when $enableRuntimeChannelDefinition is true.
37
     */
38 8
    public function __construct(
39
        private array $channelConfiguration,
40
        private QueueInterface $queue,
41
        private ContainerInterface $container,
42
        private CallableFactory $callableFactory,
43
        private Injector $injector,
44
        private bool $enableRuntimeChannelDefinition = false,
45
        private ?AdapterInterface $defaultAdapter = null,
46
    ) {
47 8
        if ($enableRuntimeChannelDefinition === true && $defaultAdapter === null) {
48 1
            $message = 'Either $enableRuntimeChannelDefinition must be false, or $defaultAdapter should be provided.';
49
50 1
            throw new InvalidArgumentException($message);
51
        }
52
    }
53
54 7
    public function get(string $channel = self::DEFAULT_CHANNEL_NAME): QueueInterface
55
    {
56 7
        if ($channel === $this->queue->getChannelName()) {
57 1
            return $this->queue;
58
        }
59
60 7
        if (isset($this->queueCollection[$channel]) && $this->queueCollection[$channel]->get() !== null) {
61
            $queue = $this->queueCollection[$channel]->get();
62
        } else {
63 7
            $queue = $this->create($channel);
64 5
            $this->queueCollection[$channel] = WeakReference::create($queue);
65
        }
66
67 5
        return $queue;
68
    }
69
70
    /**
71
     * @throws ChannelIncorrectlyConfigured
72
     * @return QueueInterface
73
     */
74 7
    private function create(string $channel): QueueInterface
75
    {
76 7
        if (isset($this->channelConfiguration[$channel])) {
77 4
            $definition = $this->channelConfiguration[$channel];
78 4
            $this->checkDefinitionType($channel, $definition);
79 3
            $adapter = $this->createFromDefinition($channel, $definition)->withChannel($channel);
80
81 3
            return $this->queue->withChannelName($channel)->withAdapter($adapter);
82
        }
83
84 3
        if ($this->enableRuntimeChannelDefinition === false) {
85 1
            throw new ChannelNotConfiguredException($channel);
86
        }
87
88
        /** @psalm-suppress PossiblyNullReference */
89 2
        return $this->queue->withChannelName($channel)->withAdapter($this->defaultAdapter->withChannel($channel));
0 ignored issues
show
Bug introduced by
The method withChannel() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

89
        return $this->queue->withChannelName($channel)->withAdapter($this->defaultAdapter->/** @scrutinizer ignore-call */ withChannel($channel));

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
90
    }
91
92 4
    private function checkDefinitionType(string $channel, mixed $definition): void
93
    {
94
        if (
95 4
            !$definition instanceof AdapterInterface
96 4
            && !is_array($definition)
97 4
            && !is_callable($definition)
98 4
            && !is_string($definition)
99
        ) {
100 1
            throw new ChannelIncorrectlyConfigured($channel, $definition);
101
        }
102
    }
103
104 3
    public function createFromDefinition(
105
        string $channel,
106
        AdapterInterface|callable|array|string $definition
107
    ): AdapterInterface {
108 3
        if ($definition instanceof AdapterInterface) {
0 ignored issues
show
introduced by
$definition is never a sub-type of Yiisoft\Queue\Adapter\AdapterInterface.
Loading history...
109 2
            return $definition;
110
        }
111
112 1
        if (is_string($definition)) {
0 ignored issues
show
introduced by
The condition is_string($definition) is always false.
Loading history...
113
            return $this->getFromContainer($channel, $definition);
114
        }
115
116 1
        return $this->tryGetFromCallable($channel, $definition)
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->tryGetFromCallable($channel, $definition) targeting Yiisoft\Queue\QueueFactory::tryGetFromCallable() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
117 1
            ?? $this->tryGetFromArrayDefinition($channel, $definition)
118 1
            ?? throw new ChannelIncorrectlyConfigured($channel, $definition);
119
    }
120
121
    private function getFromContainer(string $channel, string $definition): AdapterInterface
122
    {
123
        if (class_exists($definition)) {
124
            if (is_subclass_of($definition, AdapterInterface::class)) {
125
                /** @var AdapterInterface */
126
                return $this->container->get($definition);
127
            }
128
        } elseif ($this->container->has($definition)) {
129
            $middleware = $this->container->get($definition);
130
            if ($middleware instanceof AdapterInterface) {
131
                return $middleware;
132
            }
133
        }
134
135
        throw new ChannelIncorrectlyConfigured($channel, $definition);
136
    }
137
138 1
    private function tryGetFromCallable(
139
        string $channel,
140
        callable|AdapterInterface|array|string $definition
141
    ): ?AdapterInterface {
142 1
        $callable = null;
143
144 1
        if ($definition instanceof Closure) {
0 ignored issues
show
introduced by
$definition is never a sub-type of Closure.
Loading history...
145
            $callable = $definition;
146
        }
147
148
        if (
149 1
            is_array($definition)
150 1
            && array_keys($definition) === [0, 1]
151
        ) {
152
            try {
153
                $callable = $this->callableFactory->create($definition);
154
            } catch (InvalidCallableConfigurationException $exception) {
155
                throw new ChannelIncorrectlyConfigured($channel, $definition, previous: $exception);
156
            }
157
        }
158
159 1
        if ($callable !== null) {
160
            $adapter = $this->injector->invoke($callable);
161
162
            if (!$adapter instanceof AdapterInterface) {
163
                throw new ChannelIncorrectlyConfigured($channel, $definition);
164
            }
165
        }
166
167 1
        return null;
168
    }
169
170 1
    private function tryGetFromArrayDefinition(
171
        string $channel,
172
        callable|AdapterInterface|array|string $definition
173
    ): ?AdapterInterface {
174 1
        if (!is_array($definition)) {
0 ignored issues
show
introduced by
The condition is_array($definition) is always true.
Loading history...
175
            return null;
176
        }
177
178
        try {
179 1
            DefinitionValidator::validateArrayDefinition($definition);
180
181 1
            $middleware = ArrayDefinition::fromConfig($definition)->resolve($this->container);
182 1
            if ($middleware instanceof AdapterInterface) {
183 1
                return $middleware;
184
            }
185
186
            throw new ChannelIncorrectlyConfigured($channel, $definition);
187
        } catch (InvalidConfigException) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
188
        }
189
190
        throw new ChannelIncorrectlyConfigured($channel, $definition);
191
    }
192
}
193