ApplicationCommand::executeClear()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 24
ccs 8
cts 8
cp 1
rs 9.536
c 0
b 0
f 0
cc 3
nc 3
nop 2
crap 3
1
<?php declare(strict_types=1);
2
3
namespace Limoncello\Application\Commands;
4
5
/**
6
 * Copyright 2015-2020 [email protected]
7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this file except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 * http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
20
21
use Limoncello\Application\Exceptions\ConfigurationException;
22
use Limoncello\Contracts\Application\ApplicationConfigurationInterface;
23
use Limoncello\Contracts\Application\CacheSettingsProviderInterface;
24
use Limoncello\Contracts\Commands\CommandInterface;
25
use Limoncello\Contracts\Commands\IoInterface;
26
use Limoncello\Contracts\FileSystem\FileSystemInterface;
27
use Psr\Container\ContainerExceptionInterface;
28
use Psr\Container\ContainerInterface;
29
use Psr\Container\NotFoundExceptionInterface;
30
use ReflectionException;
31
use ReflectionMethod;
32
use function array_filter;
33
use function array_pop;
34
use function assert;
35
use function count;
36
use function explode;
37
use function implode;
38
use function is_array;
39
use function is_string;
40
use function preg_match;
41
42
/**
43
 * @package Limoncello\Application
44
 */
45
class ApplicationCommand implements CommandInterface
46
{
47
    /**
48
     * Command name.
49
     */
50
    const NAME = 'l:app';
51
52
    /** Argument name */
53 1
    const ARG_ACTION = 'action';
54
55 1
    /** Command action */
56
    const ACTION_CLEAR_CACHE = 'clear-cache';
57
58
    /** Command action */
59
    const ACTION_CREATE_CACHE = 'cache';
60
61 1
    /**
62
     * @inheritdoc
63 1
     */
64
    public static function getName(): string
65
    {
66
        return static::NAME;
67
    }
68
69 1
    /**
70
     * @inheritdoc
71 1
     */
72
    public static function getDescription(): string
73
    {
74
        return 'Creates and cleans application cache.';
75
    }
76
77 1
    /**
78
     * @inheritdoc
79 1
     */
80 1
    public static function getHelp(): string
81
    {
82
        return 'This command creates and cleans caches for routes, settings and etc.';
83
    }
84 1
85 1
    /**
86 1
     * @inheritdoc
87
     */
88
    public static function getArguments(): array
89
    {
90
        $cache = static::ACTION_CREATE_CACHE;
91
        $clear = static::ACTION_CLEAR_CACHE;
92
93
        return [
94 1
            [
95
                static::ARGUMENT_NAME        => static::ARG_ACTION,
96 1
                static::ARGUMENT_DESCRIPTION => "Action such as `$cache` or `$clear`.",
97
                static::ARGUMENT_MODE        => static::ARGUMENT_MODE__REQUIRED,
98
            ],
99
        ];
100
    }
101
102 1
    /**
103
     * @inheritdoc
104 1
     */
105
    public static function getOptions(): array
106
    {
107
        return [];
108
    }
109
110
    /**
111
     * @inheritdoc
112
     */
113
    public static function execute(ContainerInterface $container, IoInterface $inOut): void
114
    {
115
        (new static())->run($container, $inOut);
116
    }
117 5
118
    /**
119 5
     * @param ContainerInterface $container
120
     * @param IoInterface $inOut
121 5
     *
122 2
     * @return void
123 1
     *
124 3
     * @throws ContainerExceptionInterface
125 2
     * @throws NotFoundExceptionInterface
126 2
     * @throws ReflectionException
127
     */
128 1
    protected function run(ContainerInterface $container, IoInterface $inOut): void
129 1
    {
130
        $action = $inOut->getArgument(static::ARG_ACTION);
131
        switch ($action) {
132
            case static::ACTION_CREATE_CACHE:
133
                $this->executeCache($container, $inOut);
134
                break;
135
            case static::ACTION_CLEAR_CACHE:
136
                $this->executeClear($container, $inOut);
137
                break;
138
            default:
139
                $inOut->writeError("Unsupported action `$action`." . PHP_EOL);
140
                break;
141
        }
142
    }
143 2
144
    /**
145 2
     * @param ContainerInterface $container
146
     * @param IoInterface $inOut
147 2
     *
148 2
     * @return void
149 2
     *
150
     * @throws ContainerExceptionInterface
151
     * @throws NotFoundExceptionInterface
152 2
     * @throws ReflectionException
153
     */
154 1
    protected function executeClear(ContainerInterface $container, IoInterface $inOut): void
155
    {
156 1
        assert($inOut);
157 1
158 1
        $appConfig     = $this->getApplicationConfiguration($container);
159 1
        $cacheCallable = $appConfig[ApplicationConfigurationInterface::KEY_CACHE_CALLABLE];
160
        assert(is_string($cacheCallable));
161 1
162
        // if exists
163
        if (is_callable($cacheCallable) === true) {
164
            // location of the cache file
165 1
            $path = (new ReflectionMethod($cacheCallable))->getDeclaringClass()->getFileName();
166
167
            $fileSystem = $this->getFileSystem($container);
168
            if ($fileSystem->exists($path) === true) {
169
                $fileSystem->delete($path);
170
                $inOut->writeInfo("Cache file deleted `$path`." . PHP_EOL, IoInterface::VERBOSITY_VERBOSE);
171
172
                return;
173
            }
174
        }
175
176
        $inOut->writeInfo('Cache already clean.' . PHP_EOL);
177 2
    }
178
179 2
    /**
180
     * @param ContainerInterface $container
181 2
     * @param IoInterface        $inOut
182 2
     *
183 2
     * @return void
184 2
     *
185 2
     * @throws ContainerExceptionInterface
186
     * @throws NotFoundExceptionInterface
187 1
     */
188
    protected function executeCache(ContainerInterface $container, IoInterface $inOut): void
189
    {
190
        assert($inOut);
191 1
192 1
        $appConfig     = $this->getApplicationConfiguration($container);
193 1
        $cacheDir      = $appConfig[ApplicationConfigurationInterface::KEY_CACHE_FOLDER];
194
        $cacheCallable = $appConfig[ApplicationConfigurationInterface::KEY_CACHE_CALLABLE];
195 1
        list ($namespace, $class, $method) = $this->parseCacheCallable($cacheCallable);
196 1
        if ($class === null || $namespace === null || $method === null) {
197
            // parsing of cache callable failed (most likely error in settings)
198 1
            throw new ConfigurationException();
199 1
        }
200
201
        /** @var CacheSettingsProviderInterface $settingsProvider */
202
        $settingsProvider = $container->get(CacheSettingsProviderInterface::class);
203
        $settingsData     = $settingsProvider->serialize();
204
        $content          = $this->composeContent($settingsData, $namespace, $class, $method);
205
206
        $path = $cacheDir . DIRECTORY_SEPARATOR . $class . '.php';
207
        $this->getFileSystem($container)->write($path, $content);
208
209
        $inOut->writeInfo('Cache created.' . PHP_EOL);
210 5
        $inOut->writeInfo("Cache written to `$path`." . PHP_EOL, IoInterface::VERBOSITY_VERBOSE);
211
    }
212 5
213 5
    /**
214 5
     * @param mixed $mightBeCallable
215
     *
216 1
     * @return array
217 1
     *
218 1
     * @SuppressWarnings(PHPMD.ElseExpression)
219 4
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
220 4
     * @SuppressWarnings(PHPMD.IfStatementAssignment)
221 4
     */
222
    protected function parseCacheCallable($mightBeCallable): array
223 2
    {
224 2
        if (is_string($mightBeCallable) === true &&
225 2
            count($nsClassMethod = explode('::', $mightBeCallable, 2)) === 2 &&
226
            count($nsClass = explode('\\', $nsClassMethod[0])) > 1
227 2
        ) {
228
            $canBeClass     = array_pop($nsClass);
229
            $canBeNamespace = array_filter($nsClass);
230 3
            $canBeMethod    = $nsClassMethod[1];
231
        } elseif (is_array($mightBeCallable) === true &&
232 3
            count($mightBeCallable) === 2 &&
233 3
            count($nsClass = explode('\\', $mightBeCallable[0])) > 1
234 3
        ) {
235
            $canBeClass     = array_pop($nsClass);
236 3
            $canBeNamespace = array_filter($nsClass);
237
            $canBeMethod    = $mightBeCallable[1];
238
        } else {
239
            return [null, null, null];
240 2
        }
241 2
242 2
        foreach (array_merge($canBeNamespace, [$canBeClass, $canBeMethod]) as $value) {
243
            // is string might have a-z, A-Z, _, numbers but has at least one a-z or A-Z.
244 2
            if (is_string($value) === false ||
245
                preg_match('/^\\w+$/i', $value) !== 1 ||
246
                preg_match('/^[a-z]+$/i', $value) !== 1
247
            ) {
248
                return [null, null, null];
249
            }
250
        }
251
252
        $namespace = implode('\\', $canBeNamespace);
253
        $class     = $canBeClass;
254
        $method    = $canBeMethod;
255 1
256
        return [$namespace, $class, $method];
257
    }
258
259
    /**
260
     * @param mixed  $value
261 1
     * @param string $className
262 1
     * @param string $methodName
263
     * @param string $namespace
264 1
     *
265 1
     * @return string
266
     */
267
    protected function composeContent(
268 1
        $value,
269
        string $namespace,
270
        string $className,
271
        string $methodName
272 1
    ): string {
273
        $now  = date(DATE_RFC2822);
274
        $data = var_export($value, true);
275 1
276
        assert(
277 1
            $data !== null,
278
            'It seems the data are not exportable. It is likely to be caused by class instances ' .
279 1
            'that do not implement ` __set_state` magic method required by `var_export`. ' .
280
            'See http://php.net/manual/en/language.oop5.magic.php#object.set-state for more details.'
281 1
        );
282
283
        $content = <<<EOT
284
<?php declare(strict_types=1);
285
286
namespace $namespace;
287
288
// THIS FILE IS AUTO GENERATED. DO NOT EDIT IT MANUALLY.
289 1
// Generated at: $now
290
291
class $className
292
{
293
    const DATA = $data;
294
295
    public static function $methodName()
296
    {
297
        return static::DATA;
298
    }
299
}
300 4
301
EOT;
302
303 4
        return $content;
304 4
    }
305
306 4
    /**
307
     * @param ContainerInterface $container
308
     *
309
     * @return array
310
     *
311
     * @throws ContainerExceptionInterface
312
     * @throws NotFoundExceptionInterface
313
     */
314
    protected function getApplicationConfiguration(ContainerInterface $container): array
315
    {
316
        /** @var CacheSettingsProviderInterface $settingsProvider */
317 2
        $settingsProvider = $container->get(CacheSettingsProviderInterface::class);
318
        $appConfig        = $settingsProvider->getApplicationConfiguration();
319 2
320
        return $appConfig;
321
    }
322
323
    /**
324
     * @param ContainerInterface $container
325
     *
326
     * @return FileSystemInterface
327
     *
328
     * @throws ContainerExceptionInterface
329
     * @throws NotFoundExceptionInterface
330
     */
331
    protected function getFileSystem(ContainerInterface $container): FileSystemInterface
332
    {
333
        return $container->get(FileSystemInterface::class);
334
    }
335
}
336