Issues (508)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  Header Injection
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Console/Command/MakeModelCommand.php (9 issues)

1
<?php
2
3
namespace Leonidas\Console\Command;
4
5
use DirectoryIterator;
6
use Leonidas\Console\Command\Abstracts\HopliteCommand;
7
use Leonidas\Console\Library\Printer\Model\ModelComponentFactory;
8
use Leonidas\Console\Library\Printer\Model\PsrPrinterFactory;
9
use Nette\PhpGenerator\PhpFile;
10
use Symfony\Component\Console\Input\InputArgument;
11
use Symfony\Component\Console\Input\InputOption;
12
13
class MakeModelCommand extends HopliteCommand
14
{
15
    public const VALID_TEMPLATES = [
16
        'post',
17
        'post:h',
18
        'attachment',
19
        'term',
20
        'term:h',
21
        'user',
22
        // 'comment',
23
    ];
24
25
    protected const CORE_FILE_METHODS = [
26
        'model' => 'makeModelFiles',
27
        'collection' => 'makeCollectionFiles',
28
        'repository' => 'makeRepositoryFiles',
29
    ];
30
31
    protected const SUPPORT_FILE_METHODS = [
32
        'factories' => 'makeFactoryFiles',
33
        'access' => 'makeAccessProviderFiles',
34
        'facade' => 'makeFacadeFiles',
35
    ];
36
37
    protected const EXTRA_FILE_METHODS = [
38
        'registration' => 'updateRegistrationClass',
39
    ];
40
41
    protected static $defaultName = 'make:model';
42
43
    protected function configure()
44
    {
45
        $this
46
            ->addArgument('model', InputArgument::REQUIRED, '')
47
            ->addArgument('entity', InputArgument::REQUIRED, '')
48
            ->addArgument('single', InputArgument::REQUIRED, '')
49
            ->addArgument('plural', InputArgument::REQUIRED, '')
50
            ->addArgument('components', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, '')
51
            ->addOption('template', 't', InputOption::VALUE_REQUIRED, '', 'post')
52
            ->addOption('namespace', 's', InputOption::VALUE_REQUIRED, '')
53
            ->addOption('contracts', 'c', InputOption::VALUE_REQUIRED, '')
54
            ->addOption('facades', 'x', InputOption::VALUE_REQUIRED, '')
55
            ->addOption('abstracts', 'a', InputOption::VALUE_NONE, '')
56
            ->addOption('bootstrap', 'r', InputOption::VALUE_REQUIRED, '')
57
            ->addOption('design', 'd', InputOption::VALUE_NONE, 'Generate interfaces only')
58
            ->addOption('build', 'b', InputOption::VALUE_NONE, 'Generate classes using interfaces as guide')
59
            ->addOption('omit', 'o', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, '')
60
            ->addOption('force', 'f', InputOption::VALUE_NONE, '');
61
    }
62
63
    protected function handle(): int
64
    {
65
        $template = $this->input->getOption('template');
66
67
        if ($this->isValidTemplate($template)) {
68
            $status = $this->makeFiles($template);
69
        } else {
70
            $this->output->error("Value \"{$template}\" is not an accepted value for template");
71
72
            $status = self::FAILURE;
73
        }
74
75
        return $status;
76
    }
77
78
    protected function isValidTemplate(string $template): bool
79
    {
80
        return in_array($template, static::VALID_TEMPLATES);
81
    }
82
83
    protected function makeFiles(string $template): int
84
    {
85
        $model = $this->convert($this->input->getArgument('model'))->toPascal();
86
        $namespace = $this->configurableOption('namespace', 'make.model.namespace');
87
        $contracts = $this->configurableOption('contracts', 'make.model.contracts');
88
        $facades = $this->configurableOption('facades', 'facades');
89
90
        $factory = $this->getComponentFactory($model, $namespace, $contracts, $facades, $template);
0 ignored issues
show
It seems like $namespace can also be of type null; however, parameter $namespace of Leonidas\Console\Command...::getComponentFactory() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

90
        $factory = $this->getComponentFactory($model, /** @scrutinizer ignore-type */ $namespace, $contracts, $facades, $template);
Loading history...
It seems like $contracts can also be of type null; however, parameter $contracts of Leonidas\Console\Command...::getComponentFactory() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

90
        $factory = $this->getComponentFactory($model, $namespace, /** @scrutinizer ignore-type */ $contracts, $facades, $template);
Loading history...
It seems like $facades can also be of type null; however, parameter $facades of Leonidas\Console\Command...::getComponentFactory() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

90
        $factory = $this->getComponentFactory($model, $namespace, $contracts, /** @scrutinizer ignore-type */ $facades, $template);
Loading history...
91
        $paths = $this->getOutputPaths($model, $namespace, $contracts, $facades);
0 ignored issues
show
It seems like $contracts can also be of type null; however, parameter $contracts of Leonidas\Console\Command...mmand::getOutputPaths() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

91
        $paths = $this->getOutputPaths($model, $namespace, /** @scrutinizer ignore-type */ $contracts, $facades);
Loading history...
It seems like $namespace can also be of type null; however, parameter $namespace of Leonidas\Console\Command...mmand::getOutputPaths() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

91
        $paths = $this->getOutputPaths($model, /** @scrutinizer ignore-type */ $namespace, $contracts, $facades);
Loading history...
It seems like $facades can also be of type null; however, parameter $facades of Leonidas\Console\Command...mmand::getOutputPaths() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

91
        $paths = $this->getOutputPaths($model, $namespace, $contracts, /** @scrutinizer ignore-type */ $facades);
Loading history...
92
        $action = $this->resolveRequestedAction();
93
94
        $force = $this->input->getOption('force');
95
96
        foreach ($this->resolveComponents('core') as $method) {
97
            $status = $this->$method($factory, $action, $paths, $force);
98
99
            if (self::SUCCESS !== $status) {
100
                return $status;
101
            }
102
        }
103
104
        if ('interfaces' !== $action) {
105
            foreach ($this->resolveComponents('support') as $method) {
106
                $status = $this->$method($factory, $paths, $force);
107
108
                if (self::SUCCESS !== $status) {
109
                    return $status;
110
                }
111
            }
112
113
            foreach ($this->resolveComponents('extra') as $method) {
114
                $status = $this->$method($factory, $force);
115
116
                if (self::SUCCESS !== $status) {
117
                    return $status;
118
                }
119
            }
120
        }
121
122
        $this->output->success("Successfully created model files");
123
124
        return self::SUCCESS;
125
    }
126
127
    protected function resolveComponents(string $set): array
128
    {
129
        $set = constant(
130
            sprintf('%s::%s_FILE_METHODS', static::class, strtoupper($set))
131
        );
132
133
        $selected = $this->input->getArgument('components') ?: array_keys($set);
134
        $omitted = $this->input->getOption('omit') ?? [];
135
136
        $resolved = [];
137
138
        foreach ($set as $component => $method) {
139
            if (in_array($component, $selected) && !in_array($component, $omitted)) {
140
                $resolved[] = $method;
141
            }
142
        }
143
144
        return $resolved;
145
    }
146
147
    protected function getComponentFactory(
148
        string $model,
149
        string $namespace,
150
        string $contracts,
151
        string $facades,
152
        string $template
153
    ): ModelComponentFactory {
154
        $entity = $this->input->getArgument('entity');
155
        $single = $this->input->getArgument('single');
156
        $plural = $this->input->getArgument('plural');
157
158
        $namespace = $this->pathToNamespace($namespace, $model);
159
        $contracts = $this->pathToNamespace($contracts, $model);
160
        $abstracts = $this->resolveAbstractNamespace($namespace);
161
        $facades = $this->pathToNamespace($facades);
162
163
        return ModelComponentFactory::build([
164
            'model' => $model,
165
            'namespace' => $namespace,
166
            'contracts' => $contracts,
167
            'abstracts' => $abstracts,
168
            'facades' => $facades,
169
            'entity' => $entity,
170
            'single' => $single,
171
            'plural' => $plural,
172
            'template' => $template,
173
        ]);
174
    }
175
176
    protected function getOutputPaths(string $model, string $namespace, string $contracts, string $facades): array
177
    {
178
        return [
179
            'interfaces' => $contracts . DIRECTORY_SEPARATOR . $model,
180
            'classes' => $namespace = $namespace . DIRECTORY_SEPARATOR . $model,
181
            'abstracts' => $this->resolveAbstractDir($namespace),
182
            'facades' => $facades,
183
        ];
184
    }
185
186
    protected function setupTestDir(): array
187
    {
188
        $playground = $this->external('/.playground/model');
189
190
        if ($this->filesystem->exists($playground)) {
191
            foreach (new DirectoryIterator($playground) as $file) {
192
                if (!$file->isFile()) {
193
                    continue;
194
                }
195
196
                $isInterface = str_ends_with($file->getBasename(), 'Interface.php');
197
                $isRegistrar = $file->getBasename() === 'RegisterModelServices.php';
198
199
                if (!($isInterface || $isRegistrar)) {
200
                    $this->filesystem->remove($file->getPathname());
201
                }
202
203
                if ($isInterface) {
204
                    require $file->getPathname();
205
                }
206
            }
207
208
        // $this->filesystem->remove($playground);
209
        } else {
210
            $this->filesystem->mkdir($playground);
211
        }
212
213
        $registrar = 'RegisterModelServices.php';
214
215
        $this->filesystem->copy(
216
            $this->external("/src/Framework/Bootstrap/{$registrar}"),
217
            "{$playground}/{$registrar}"
218
        );
219
220
        foreach (['Contracts', 'Library'] as $namespace) {
221
            $this->filesystem->remove(
222
                $this->external('/src/' . $namespace . '/System/Model/Test')
223
            );
224
        }
225
226
        return [
227
            'interfaces' => $playground,
228
            'classes' => $playground,
229
            'abstracts' => $playground,
230
            'facades' => $playground,
231
        ];
232
    }
233
234
    protected function resolveRequestedAction(): string
235
    {
236
        if ($this->input->getOption('design')) {
237
            return 'interfaces';
238
        } elseif ($this->input->getOption('build')) {
239
            return 'classes';
240
        } else {
241
            return 'complete';
242
        }
243
    }
244
245
    protected function makeModelFiles(ModelComponentFactory $factory, string $action, array $paths, bool $force): int
246
    {
247
        $interface = $factory->getModelInterfacePrinter();
248
        $class = $factory->getModelPrinter();
249
250
        $interfaceFile = $this->phpFile($paths['interfaces'], $interface->getClass());
251
        $classFile = $this->phpFile($paths['classes'], $class->getClass());
252
253
        if ('interfaces' === $action || 'complete' === $action) {
254
            $this->writeFile($interfaceFile, $interface->printFile(), $force);
255
        } elseif ('classes' === $action) {
256
            if (!interface_exists($interfaceFqn = $interface->getClassFqn())) {
257
                $this->output->error("Interface {$interfaceFqn} does not exist");
258
259
                return self::INVALID;
260
            }
261
262
            $this->writeFile($classFile, $class->printFromType(), $force);
263
        }
264
265
        if ('complete' === $action) {
266
            $this->writeFile($classFile, $class->printFile(), $force);
267
        }
268
269
        return self::SUCCESS;
270
    }
271
272
    protected function makeCollectionFiles(ModelComponentFactory $factory, string $action, array $paths, bool $force): int
273
    {
274
        $isPost = $factory->isPostTemplate();
275
276
        $interface = $factory->getCollectionInterfacePrinter();
277
        $collection = $isPost
278
            ? $factory->getChildCollectionPrinter()
279
            : $factory->getCollectionPrinter();
280
        $abstract = $factory->getAbstractCollectionPrinter();
281
        $query = $factory->getChildQueryPrinter();
282
283
        $interfaceFile = $this->phpFile($paths['interfaces'], $interface->getClass());
284
        $collectionFile = $this->phpFile($paths['classes'], $collection->getClass());
285
        $abstractFile = $this->phpFile($paths['abstracts'], $abstract->getClass());
286
        $queryFile = $this->phpFile($paths['classes'], $query->getClass());
287
288
        if ('interfaces' === $action || 'complete' === $action) {
289
            $this->writeFile($interfaceFile, $interface->printFile(), $force);
290
        } elseif ('classes' === $action) {
291
            if (!interface_exists($interface->getClassFqn())) {
292
                $this->output->error("Interface {$interface->getClassFqn()} does not exist");
293
294
                return self::INVALID;
295
            }
296
297
            if ($isPost) {
298
                $this->writeFile($abstractFile, $abstract->printFromType(), $force);
299
                $this->writeFile($collectionFile, $collection->printFile(), $force);
300
                $this->writeFile($queryFile, $query->printFile(), $force);
301
            } else {
302
                $this->writeFile($collectionFile, $collection->printFromType(), $force);
0 ignored issues
show
The method printFromType() does not exist on Leonidas\Console\Library...ollectionAsChildPrinter. ( Ignorable by Annotation )

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

302
                $this->writeFile($collectionFile, $collection->/** @scrutinizer ignore-call */ printFromType(), $force);

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...
303
            }
304
        }
305
306
        if ('complete' === $action) {
307
            $this->writeFile($collectionFile, $collection->printFile(), $force);
308
309
            if ($isPost) {
310
                $this->writeFile($abstractFile, $abstract->printFile(), $force);
311
                $this->writeFile($queryFile, $query->printFile(), $force);
312
            }
313
        }
314
315
        return self::SUCCESS;
316
    }
317
318
    protected function makeRepositoryFiles(ModelComponentFactory $factory, string $action, array $paths, bool $force): int
319
    {
320
        $interface = $factory->getRepositoryInterfacePrinter();
321
        $class = $factory->getRepositoryPrinter();
322
323
        $interfaceFile = $this->phpFile($paths['interfaces'], $interface->getClass());
324
        $classFile = $this->phpFile($paths['classes'], $class->getClass());
325
326
        if ('interfaces' === $action || 'complete' === $action) {
327
            $this->writeFile($interfaceFile, $interface->printFile(), $force);
328
        } elseif ('classes' === $action) {
329
            if (!interface_exists($interfaceFqn = $interface->getClassFqn())) {
330
                $this->output->error("Interface {$interfaceFqn} does not exist");
331
332
                return self::INVALID;
333
            }
334
335
            $this->writeFile($classFile, $class->printFromType(), $force);
336
        }
337
338
        if ('complete' === $action) {
339
            $this->writeFile($classFile, $class->printFile(), $force);
340
        }
341
342
        return self::SUCCESS;
343
    }
344
345
    protected function makeFactoryFiles(ModelComponentFactory $factory, array $paths, bool $force): int
346
    {
347
        $model = $factory->getModelConverterPrinter();
348
        $collection = $factory->getCollectionFactoryPrinter();
349
350
        $modelFile = $this->phpFile($paths['classes'], $model->getClass());
351
        $collectionFile = $this->phpFile($paths['classes'], $collection->getClass());
352
353
        $this->writeFile($modelFile, $model->printFile(), $force);
354
        $this->writeFile($collectionFile, $collection->printFile(), $force);
355
356
        if ($factory->isPostTemplate()) {
357
            $query = $factory->getQueryFactoryPrinter();
358
            $queryFile = $this->phpFile($paths['classes'], $query->getClass());
359
360
            $this->writeFile($queryFile, $query->printFile(), $force);
361
        }
362
363
        return self::SUCCESS;
364
    }
365
366
    protected function makeAccessProviderFiles(ModelComponentFactory $factory, array $paths, bool $force): int
367
    {
368
        $get = $factory->getGetAccessProviderPrinter();
369
        $set = $factory->getSetAccessProviderPrinter();
370
371
        $getFile = $this->phpFile($paths['classes'], $get->getClass());
372
        $setFile = $this->phpFile($paths['classes'], $set->getClass());
373
374
        $this->writeFile($getFile, $get->printFile(), $force);
375
        $this->writeFile($setFile, $set->printFile(), $force);
376
377
        if ($factory->isPostTemplate()) {
378
            $tag = $factory->getTagAccessProviderPrinter();
379
            $tagFile = $this->phpFile($paths['classes'], $tag->getClass());
380
381
            $this->writeFile($tagFile, $tag->printFile(), $force);
382
        }
383
384
        return self::SUCCESS;
385
    }
386
387
    protected function makeFacadeFiles(ModelComponentFactory $factory, array $paths, bool $force): int
388
    {
389
        $facade = $factory->getRepositoryFacadePrinter();
390
        $facadeFile = $this->phpFile($paths['facades'], $facade->getClass());
391
392
        $this->writeFile($facadeFile, $facade->printFromType(), $force);
393
394
        return self::SUCCESS;
395
    }
396
397
    protected function updateRegistrationClass(ModelComponentFactory $factory, bool $force): int
398
    {
399
        $name = $factory->getSingle();
400
        $model = $factory->getModelPrinter();
401
        $schema = [
402
            'post' => 'post',
403
            'post:h' => 'post',
404
            'attachment' => 'attachment',
405
            'term' => 'term',
406
            'term:h' => 'term',
407
            'user' => 'user',
408
            // 'comment' => 'comment',
409
        ][$factory->getTemplate()];
410
411
        $registrar = $this->configurableOption('bootstrap', 'make.model.bootstrap');
412
        $file = PhpFile::fromCode(file_get_contents($registrar));
0 ignored issues
show
It seems like $registrar can also be of type null; however, parameter $filename of file_get_contents() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

412
        $file = PhpFile::fromCode(file_get_contents(/** @scrutinizer ignore-type */ $registrar));
Loading history...
413
414
        $namespaces = $file->getNamespaces();
415
        $namespace = reset($namespaces);
416
417
        $namespace->addUse($model->getClassFqn());
418
419
        $classes = $namespace->getClasses();
420
        $class = reset($classes);
421
422
        $class->addMethod($name . 'Services')
423
            ->setReturnType('void')
424
            ->setBody(sprintf(
425
                "\$this->register(%s::class, '%s', '%s');",
426
                $model->getClass(),
427
                $factory->getEntity(),
428
                $schema
429
            ));
430
431
        $this->writeFile(
432
            $registrar,
0 ignored issues
show
It seems like $registrar can also be of type null; however, parameter $path of Leonidas\Console\Command...iteCommand::writeFile() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

432
            /** @scrutinizer ignore-type */ $registrar,
Loading history...
433
            PsrPrinterFactory::create()->printFile($file),
434
            true
435
        );
436
437
        return self::SUCCESS;
438
    }
439
}
440