Passed
Pull Request — master (#97)
by David
02:35
created

TDBMDaoGenerator::dumpFile()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
1
<?php
2
declare(strict_types=1);
3
4
namespace TheCodingMachine\TDBM\Utils;
5
6
use Doctrine\Common\Inflector\Inflector;
7
use Doctrine\DBAL\Schema\Schema;
8
use Doctrine\DBAL\Schema\Table;
9
use Doctrine\DBAL\Types\Type;
10
use TheCodingMachine\TDBM\ConfigurationInterface;
11
use TheCodingMachine\TDBM\TDBMException;
12
use TheCodingMachine\TDBM\TDBMSchemaAnalyzer;
13
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
14
use Symfony\Component\Filesystem\Filesystem;
15
16
/**
17
 * This class generates automatically DAOs and Beans for TDBM.
18
 */
19
class TDBMDaoGenerator
20
{
21
    /**
22
     * @var Schema
23
     */
24
    private $schema;
25
26
    /**
27
     * Name of composer file.
28
     *
29
     * @var string
30
     */
31
    private $composerFile;
0 ignored issues
show
introduced by
The private property $composerFile is not used, and could be removed.
Loading history...
32
33
    /**
34
     * @var TDBMSchemaAnalyzer
35
     */
36
    private $tdbmSchemaAnalyzer;
37
38
    /**
39
     * @var GeneratorListenerInterface
40
     */
41
    private $eventDispatcher;
42
43
    /**
44
     * @var NamingStrategyInterface
45
     */
46
    private $namingStrategy;
47
    /**
48
     * @var ConfigurationInterface
49
     */
50
    private $configuration;
51
52
    /**
53
     * Constructor.
54
     *
55
     * @param ConfigurationInterface $configuration
56
     * @param TDBMSchemaAnalyzer $tdbmSchemaAnalyzer
57
     */
58
    public function __construct(ConfigurationInterface $configuration, TDBMSchemaAnalyzer $tdbmSchemaAnalyzer)
59
    {
60
        $this->configuration = $configuration;
61
        $this->schema = $tdbmSchemaAnalyzer->getSchema();
62
        $this->tdbmSchemaAnalyzer = $tdbmSchemaAnalyzer;
63
        $this->namingStrategy = $configuration->getNamingStrategy();
64
        $this->eventDispatcher = $configuration->getGeneratorEventDispatcher();
65
    }
66
67
    /**
68
     * Generates all the daos and beans.
69
     *
70
     * @throws TDBMException
71
     */
72
    public function generateAllDaosAndBeans(): void
73
    {
74
        // TODO: check that no class name ends with "Base". Otherwise, there will be name clash.
75
76
        $tableList = $this->schema->getTables();
77
78
        // Remove all beans and daos from junction tables
79
        $junctionTables = $this->configuration->getSchemaAnalyzer()->detectJunctionTables(true);
80
        $junctionTableNames = array_map(function (Table $table) {
81
            return $table->getName();
82
        }, $junctionTables);
83
84
        $tableList = array_filter($tableList, function (Table $table) use ($junctionTableNames) {
85
            return !in_array($table->getName(), $junctionTableNames);
86
        });
87
88
        $this->cleanUpGenerated();
89
90
        $beanDescriptors = [];
91
92
        foreach ($tableList as $table) {
93
            $beanDescriptors[] = $this->generateDaoAndBean($table);
94
        }
95
96
97
        $this->generateFactory($tableList);
98
99
        // Let's call the list of listeners
100
        $this->eventDispatcher->onGenerate($this->configuration, $beanDescriptors);
101
    }
102
103
    /**
104
     * Removes all files from the Generated folders.
105
     * This is a way to ensure that when a table is deleted, the matching bean/dao are deleted.
106
     * Note: only abstract generated classes are deleted. We do not delete the code that might have been customized
107
     * by the user. The user will need to delete this code him/herself
108
     */
109
    private function cleanUpGenerated(): void
110
    {
111
        $generatedBeanDir = $this->configuration->getPathFinder()->getPath($this->configuration->getBeanNamespace().'\\Generated\\Xxx')->getPath();
112
        $this->deleteAllPhpFiles($generatedBeanDir);
113
114
        $generatedDaoDir = $this->configuration->getPathFinder()->getPath($this->configuration->getDaoNamespace().'\\Generated\\Xxx')->getPath();
115
        $this->deleteAllPhpFiles($generatedDaoDir);
116
    }
117
118
    private function deleteAllPhpFiles(string $directory): void
119
    {
120
        $files = glob($directory.'/*.php');
121
        $fileSystem = new Filesystem();
122
        $fileSystem->remove($files);
123
    }
124
125
    /**
126
     * Generates in one method call the daos and the beans for one table.
127
     *
128
     * @param Table $table
129
     *
130
     * @return BeanDescriptor
131
     * @throws TDBMException
132
     */
133
    private function generateDaoAndBean(Table $table) : BeanDescriptor
134
    {
135
        $tableName = $table->getName();
136
        $daoName = $this->namingStrategy->getDaoClassName($tableName);
137
        $beanName = $this->namingStrategy->getBeanClassName($tableName);
138
        $baseBeanName = $this->namingStrategy->getBaseBeanClassName($tableName);
139
        $baseDaoName = $this->namingStrategy->getBaseDaoClassName($tableName);
140
141
        $beanDescriptor = new BeanDescriptor($table, $this->configuration->getBeanNamespace(), $this->configuration->getBeanNamespace().'\\Generated', $this->configuration->getSchemaAnalyzer(), $this->schema, $this->tdbmSchemaAnalyzer, $this->namingStrategy, $this->configuration->getAnnotationParser());
142
        $this->generateBean($beanDescriptor, $beanName, $baseBeanName, $table);
143
        $this->generateDao($beanDescriptor, $daoName, $baseDaoName, $beanName, $table);
144
        return $beanDescriptor;
145
    }
146
147
    /**
148
     * Writes the PHP bean file with all getters and setters from the table passed in parameter.
149
     *
150
     * @param BeanDescriptor  $beanDescriptor
151
     * @param string          $className       The name of the class
152
     * @param string          $baseClassName   The name of the base class which will be extended (name only, no directory)
153
     * @param Table           $table           The table
154
     *
155
     * @throws TDBMException
156
     */
157
    public function generateBean(BeanDescriptor $beanDescriptor, string $className, string $baseClassName, Table $table): void
158
    {
159
        $beannamespace = $this->configuration->getBeanNamespace();
160
        $str = $beanDescriptor->generatePhpCode();
161
162
        $possibleBaseFileName = $this->configuration->getPathFinder()->getPath($beannamespace.'\\Generated\\'.$baseClassName)->getPathname();
163
164
        $this->dumpFile($possibleBaseFileName, $str);
165
166
        $possibleFileName = $this->configuration->getPathFinder()->getPath($beannamespace.'\\'.$className)->getPathname();
167
168
        if (!file_exists($possibleFileName)) {
169
            $tableName = $table->getName();
170
            $str = "<?php
171
/*
172
 * This file has been automatically generated by TDBM.
173
 * You can edit this file as it will not be overwritten.
174
 */
175
176
declare(strict_types=1);
177
178
namespace {$beannamespace};
179
180
use {$beannamespace}\\Generated\\{$baseClassName};
181
182
/**
183
 * The $className class maps the '$tableName' table in database.
184
 */
185
class $className extends $baseClassName
186
{
187
}
188
";
189
190
            $this->dumpFile($possibleFileName, $str);
191
        }
192
    }
193
194
    /**
195
     * Tries to find a @defaultSort annotation in one of the columns.
196
     *
197
     * @param Table $table
198
     *
199
     * @return mixed[] First item: column name, Second item: column order (asc/desc)
200
     */
201
    private function getDefaultSortColumnFromAnnotation(Table $table): array
202
    {
203
        $defaultSort = null;
204
        $defaultSortDirection = null;
205
        foreach ($table->getColumns() as $column) {
206
            $comments = $column->getComment();
207
            $matches = [];
208
            if ($comments !== null && preg_match('/@defaultSort(\((desc|asc)\))*/', $comments, $matches) != 0) {
209
                $defaultSort = $column->getName();
210
                if (count($matches) === 3) {
211
                    $defaultSortDirection = $matches[2];
212
                } else {
213
                    $defaultSortDirection = 'ASC';
214
                }
215
            }
216
        }
217
218
        return [$defaultSort, $defaultSortDirection];
219
    }
220
221
    /**
222
     * Writes the PHP bean DAO with simple functions to create/get/save objects.
223
     *
224
     * @param BeanDescriptor  $beanDescriptor
225
     * @param string          $className       The name of the class
226
     * @param string          $baseClassName
227
     * @param string          $beanClassName
228
     * @param Table           $table
229
     *
230
     * @throws TDBMException
231
     */
232
    private function generateDao(BeanDescriptor $beanDescriptor, string $className, string $baseClassName, string $beanClassName, Table $table): void
233
    {
234
        $daonamespace = $this->configuration->getDaoNamespace();
235
        $beannamespace = $this->configuration->getBeanNamespace();
236
        $tableName = $table->getName();
237
        $primaryKeyColumns = self::getPrimaryKeyColumnsOrFail($table);
238
239
        list($defaultSort, $defaultSortDirection) = $this->getDefaultSortColumnFromAnnotation($table);
240
241
        $beanClassWithoutNameSpace = $beanClassName;
242
        $beanClassName = $beannamespace.'\\'.$beanClassName;
243
244
        list($usedBeans, $findByDaoCode) = $beanDescriptor->generateFindByDaoCode($beannamespace, $beanClassWithoutNameSpace);
245
246
        $usedBeans[] = $beanClassName;
247
        // Let's suppress duplicates in used beans (if any)
248
        $usedBeans = array_flip(array_flip($usedBeans));
249
        $useStatements = array_map(function ($usedBean) {
250
            return "use $usedBean;\n";
251
        }, $usedBeans);
252
253
        $str = "<?php
254
/*
255
 * This file has been automatically generated by TDBM.
256
 * DO NOT edit this file, as it might be overwritten.
257
 * If you need to perform changes, edit the $className class instead!
258
 */
259
260
declare(strict_types=1);
261
262
namespace {$daonamespace}\\Generated;
263
264
use TheCodingMachine\\TDBM\\TDBMService;
265
use TheCodingMachine\\TDBM\\ResultIterator;
266
use TheCodingMachine\\TDBM\\TDBMException;
267
".implode('', $useStatements)."
268
/**
269
 * The $baseClassName class will maintain the persistence of $beanClassWithoutNameSpace class into the $tableName table.
270
 *
271
 */
272
class $baseClassName
273
{
274
275
    /**
276
     * @var TDBMService
277
     */
278
    protected \$tdbmService;
279
280
    /**
281
     * The default sort column.
282
     *
283
     * @var string
284
     */
285
    private \$defaultSort = ".($defaultSort ? "'$defaultSort'" : 'null').';
286
287
    /**
288
     * The default sort direction.
289
     *
290
     * @var string
291
     */
292
    private $defaultDirection = '.($defaultSort && $defaultSortDirection ? "'$defaultSortDirection'" : "'asc'").";
293
294
    /**
295
     * Sets the TDBM service used by this DAO.
296
     *
297
     * @param TDBMService \$tdbmService
298
     */
299
    public function __construct(TDBMService \$tdbmService)
300
    {
301
        \$this->tdbmService = \$tdbmService;
302
    }
303
304
    /**
305
     * Persist the $beanClassWithoutNameSpace instance.
306
     *
307
     * @param $beanClassWithoutNameSpace \$obj The bean to save.
308
     */
309
    public function save($beanClassWithoutNameSpace \$obj): void
310
    {
311
        \$this->tdbmService->save(\$obj);
312
    }
313
314
    /**
315
     * Get all $beanClassWithoutNameSpace records.
316
     *
317
     * @return {$beanClassWithoutNameSpace}[]|ResultIterator
318
     */
319
    public function findAll() : iterable
320
    {
321
        if (\$this->defaultSort) {
322
            \$orderBy = '$tableName.'.\$this->defaultSort.' '.\$this->defaultDirection;
323
        } else {
324
            \$orderBy = null;
325
        }
326
        return \$this->tdbmService->findObjects('$tableName', null, [], \$orderBy);
327
    }
328
    ";
329
330
        if (count($primaryKeyColumns) === 1) {
331
            $primaryKeyColumn = $primaryKeyColumns[0];
332
            $primaryKeyPhpType = self::dbalTypeToPhpType($table->getColumn($primaryKeyColumn)->getType());
333
            $str .= "
334
    /**
335
     * Get $beanClassWithoutNameSpace specified by its ID (its primary key)
336
     * If the primary key does not exist, an exception is thrown.
337
     *
338
     * @param $primaryKeyPhpType \$id
339
     * @param bool \$lazyLoading If set to true, the object will not be loaded right away. Instead, it will be loaded when you first try to access a method of the object.
340
     * @return $beanClassWithoutNameSpace
341
     * @throws TDBMException
342
     */
343
    public function getById($primaryKeyPhpType \$id, bool \$lazyLoading = false) : $beanClassWithoutNameSpace
344
    {
345
        return \$this->tdbmService->findObjectByPk('$tableName', ['$primaryKeyColumn' => \$id], [], \$lazyLoading);
346
    }
347
    ";
348
        }
349
        $str .= "
350
    /**
351
     * Deletes the $beanClassWithoutNameSpace passed in parameter.
352
     *
353
     * @param $beanClassWithoutNameSpace \$obj object to delete
354
     * @param bool \$cascade if true, it will delete all object linked to \$obj
355
     */
356
    public function delete($beanClassWithoutNameSpace \$obj, bool \$cascade = false) : void
357
    {
358
        if (\$cascade === true) {
359
            \$this->tdbmService->deleteCascade(\$obj);
360
        } else {
361
            \$this->tdbmService->delete(\$obj);
362
        }
363
    }
364
365
366
    /**
367
     * Get a list of $beanClassWithoutNameSpace specified by its filters.
368
     *
369
     * @param mixed \$filter The filter bag (see TDBMService::findObjects for complete description)
370
     * @param mixed[] \$parameters The parameters associated with the filter
371
     * @param mixed \$orderBy The order string
372
     * @param string[] \$additionalTablesFetch A list of additional tables to fetch (for performance improvement)
373
     * @param int|null \$mode Either TDBMService::MODE_ARRAY or TDBMService::MODE_CURSOR (for large datasets). Defaults to TDBMService::MODE_ARRAY.
374
     * @return {$beanClassWithoutNameSpace}[]|ResultIterator
375
     */
376
    protected function find(\$filter = null, array \$parameters = [], \$orderBy=null, array \$additionalTablesFetch = [], ?int \$mode = null) : iterable
377
    {
378
        if (\$this->defaultSort && \$orderBy == null) {
379
            \$orderBy = '$tableName.'.\$this->defaultSort.' '.\$this->defaultDirection;
380
        }
381
        return \$this->tdbmService->findObjects('$tableName', \$filter, \$parameters, \$orderBy, \$additionalTablesFetch, \$mode);
382
    }
383
384
    /**
385
     * Get a list of $beanClassWithoutNameSpace specified by its filters.
386
     * Unlike the `find` method that guesses the FROM part of the statement, here you can pass the \$from part.
387
     *
388
     * You should not put an alias on the main table name. So your \$from variable should look like:
389
     *
390
     *   \"$tableName JOIN ... ON ...\"
391
     *
392
     * @param string \$from The sql from statement
393
     * @param mixed \$filter The filter bag (see TDBMService::findObjects for complete description)
394
     * @param mixed[] \$parameters The parameters associated with the filter
395
     * @param mixed \$orderBy The order string
396
     * @param int|null \$mode Either TDBMService::MODE_ARRAY or TDBMService::MODE_CURSOR (for large datasets). Defaults to TDBMService::MODE_ARRAY.
397
     * @return {$beanClassWithoutNameSpace}[]|ResultIterator
398
     */
399
    protected function findFromSql(string \$from, \$filter = null, array \$parameters = [], \$orderBy = null, ?int \$mode = null) : iterable
400
    {
401
        if (\$this->defaultSort && \$orderBy == null) {
402
            \$orderBy = '$tableName.'.\$this->defaultSort.' '.\$this->defaultDirection;
403
        }
404
        return \$this->tdbmService->findObjectsFromSql('$tableName', \$from, \$filter, \$parameters, \$orderBy, \$mode);
405
    }
406
407
    /**
408
     * Get a list of $beanClassWithoutNameSpace from a SQL query.
409
     * Unlike the `find` and `findFromSql` methods, here you can pass the whole \$sql query.
410
     *
411
     * You should not put an alias on the main table name, and select its columns using `*`. So the SELECT part of you \$sql should look like:
412
     *
413
     *   \"SELECT $tableName.* FROM ...\"
414
     *
415
     * @param string \$sql The sql query
416
     * @param mixed[] \$parameters The parameters associated with the filter
417
     * @param string|null \$countSql The count sql query (automatically computed if not provided)
418
     * @param int|null \$mode Either TDBMService::MODE_ARRAY or TDBMService::MODE_CURSOR (for large datasets). Defaults to TDBMService::MODE_ARRAY.
419
     * @return {$beanClassWithoutNameSpace}[]|ResultIterator
420
     */
421
    protected function findFromRawSql(string \$sql, array \$parameters = [], ?string \$countSql = null, ?int \$mode = null) : iterable
422
    {
423
        return \$this->tdbmService->findObjectsFromRawSql('$tableName', \$sql, \$parameters, \$mode, null, \$countSql);
424
    }
425
426
    /**
427
     * Get a single $beanClassWithoutNameSpace specified by its filters.
428
     *
429
     * @param mixed \$filter The filter bag (see TDBMService::findObjects for complete description)
430
     * @param mixed[] \$parameters The parameters associated with the filter
431
     * @param string[] \$additionalTablesFetch A list of additional tables to fetch (for performance improvement)
432
     * @return $beanClassWithoutNameSpace|null
433
     */
434
    protected function findOne(\$filter = null, array \$parameters = [], array \$additionalTablesFetch = []) : ?$beanClassWithoutNameSpace
435
    {
436
        return \$this->tdbmService->findObject('$tableName', \$filter, \$parameters, \$additionalTablesFetch);
437
    }
438
439
    /**
440
     * Get a single $beanClassWithoutNameSpace specified by its filters.
441
     * Unlike the `find` method that guesses the FROM part of the statement, here you can pass the \$from part.
442
     *
443
     * You should not put an alias on the main table name. So your \$from variable should look like:
444
     *
445
     *   \"$tableName JOIN ... ON ...\"
446
     *
447
     * @param string \$from The sql from statement
448
     * @param mixed \$filter The filter bag (see TDBMService::findObjects for complete description)
449
     * @param mixed[] \$parameters The parameters associated with the filter
450
     * @return $beanClassWithoutNameSpace|null
451
     */
452
    protected function findOneFromSql(string \$from, \$filter = null, array \$parameters = []) : ?$beanClassWithoutNameSpace
453
    {
454
        return \$this->tdbmService->findObjectFromSql('$tableName', \$from, \$filter, \$parameters);
455
    }
456
457
    /**
458
     * Sets the default column for default sorting.
459
     *
460
     * @param string \$defaultSort
461
     */
462
    public function setDefaultSort(string \$defaultSort) : void
463
    {
464
        \$this->defaultSort = \$defaultSort;
465
    }
466
";
467
468
        $str .= $findByDaoCode;
469
        $str .= '}
470
';
471
472
        $possibleBaseFileName = $this->configuration->getPathFinder()->getPath($daonamespace.'\\Generated\\'.$baseClassName)->getPathname();
473
474
        $this->dumpFile($possibleBaseFileName, $str);
475
476
        $possibleFileName = $this->configuration->getPathFinder()->getPath($daonamespace.'\\'.$className)->getPathname();
477
478
        // Now, let's generate the "editable" class
479
        if (!file_exists($possibleFileName)) {
480
            $str = "<?php
481
/*
482
 * This file has been automatically generated by TDBM.
483
 * You can edit this file as it will not be overwritten.
484
 */
485
486
declare(strict_types=1);
487
488
namespace {$daonamespace};
489
490
use {$daonamespace}\\Generated\\{$baseClassName};
491
492
/**
493
 * The $className class will maintain the persistence of $beanClassWithoutNameSpace class into the $tableName table.
494
 */
495
class $className extends $baseClassName
496
{
497
}
498
";
499
            $this->dumpFile($possibleFileName, $str);
500
        }
501
    }
502
503
    /**
504
     * Generates the factory bean.
505
     *
506
     * @param Table[] $tableList
507
     * @throws TDBMException
508
     */
509
    private function generateFactory(array $tableList) : void
510
    {
511
        $daoNamespace = $this->configuration->getDaoNamespace();
512
        $daoFactoryClassName = $this->namingStrategy->getDaoFactoryClassName();
513
514
        // For each table, let's write a property.
515
516
        $str = "<?php
517
declare(strict_types=1);
518
519
/*
520
 * This file has been automatically generated by TDBM.
521
 * DO NOT edit this file, as it might be overwritten.
522
 */
523
524
namespace {$daoNamespace}\\Generated;
525
526
";
527
        foreach ($tableList as $table) {
528
            $tableName = $table->getName();
529
            $daoClassName = $this->namingStrategy->getDaoClassName($tableName);
530
            $str .= "use {$daoNamespace}\\".$daoClassName.";\n";
531
        }
532
533
        $str .= "
534
/**
535
 * The $daoFactoryClassName provides an easy access to all DAOs generated by TDBM.
536
 *
537
 */
538
class $daoFactoryClassName
539
{
540
";
541
542
        foreach ($tableList as $table) {
543
            $tableName = $table->getName();
544
            $daoClassName = $this->namingStrategy->getDaoClassName($tableName);
545
            $daoInstanceName = self::toVariableName($daoClassName);
546
547
            $str .= '    /**
548
     * @var '.$daoClassName.'
549
     */
550
    private $'.$daoInstanceName.';
551
552
    /**
553
     * Returns an instance of the '.$daoClassName.' class.
554
     *
555
     * @return '.$daoClassName.'
556
     */
557
    public function get'.$daoClassName.'() : '.$daoClassName.'
558
    {
559
        return $this->'.$daoInstanceName.';
560
    }
561
562
    /**
563
     * Sets the instance of the '.$daoClassName.' class that will be returned by the factory getter.
564
     *
565
     * @param '.$daoClassName.' $'.$daoInstanceName.'
566
     */
567
    public function set'.$daoClassName.'('.$daoClassName.' $'.$daoInstanceName.') : void
568
    {
569
        $this->'.$daoInstanceName.' = $'.$daoInstanceName.';
570
    }';
571
        }
572
573
        $str .= '
574
}
575
';
576
577
        $possibleFileName = $this->configuration->getPathFinder()->getPath($daoNamespace.'\\Generated\\'.$daoFactoryClassName)->getPathname();
578
579
        $this->dumpFile($possibleFileName, $str);
580
    }
581
582
    /**
583
     * Transforms a string to camelCase (except the first letter will be uppercase too).
584
     * Underscores and spaces are removed and the first letter after the underscore is uppercased.
585
     * Quoting is removed if present.
586
     *
587
     * @param string $str
588
     *
589
     * @return string
590
     */
591
    public static function toCamelCase(string $str) : string
592
    {
593
        $str = str_replace(array('`', '"', '[', ']'), '', $str);
594
595
        $str = strtoupper(substr($str, 0, 1)).substr($str, 1);
596
        while (true) {
597
            $pos = strpos($str, '_');
598
            if ($pos === false) {
599
                $pos = strpos($str, ' ');
600
                if ($pos === false) {
601
                    break;
602
                }
603
            }
604
605
            $before = substr($str, 0, $pos);
606
            $after = substr($str, $pos + 1);
607
            $str = $before.strtoupper(substr($after, 0, 1)).substr($after, 1);
608
        }
609
610
        return $str;
611
    }
612
613
    /**
614
     * Tries to put string to the singular form (if it is plural).
615
     * We assume the table names are in english.
616
     *
617
     * @param string $str
618
     *
619
     * @return string
620
     */
621
    public static function toSingular(string $str): string
622
    {
623
        return Inflector::singularize($str);
624
    }
625
626
    /**
627
     * Put the first letter of the string in lower case.
628
     * Very useful to transform a class name into a variable name.
629
     *
630
     * @param string $str
631
     *
632
     * @return string
633
     */
634
    public static function toVariableName(string $str): string
635
    {
636
        return strtolower(substr($str, 0, 1)).substr($str, 1);
637
    }
638
639
    /**
640
     * Ensures the file passed in parameter can be written in its directory.
641
     *
642
     * @param string $fileName
643
     *
644
     * @throws TDBMException
645
     */
646
    private function ensureDirectoryExist(string $fileName): void
647
    {
648
        $dirName = dirname($fileName);
649
        if (!file_exists($dirName)) {
650
            $old = umask(0);
651
            $result = mkdir($dirName, 0775, true);
652
            umask($old);
653
            if ($result === false) {
654
                throw new TDBMException("Unable to create directory: '".$dirName."'.");
655
            }
656
        }
657
    }
658
659
    private function dumpFile(string $fileName, string $content) : void
660
    {
661
        $this->ensureDirectoryExist($fileName);
662
        $fileSystem = new Filesystem();
663
        $fileSystem->dumpFile($fileName, $content);
664
        @chmod($fileName, 0664);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for chmod(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

664
        /** @scrutinizer ignore-unhandled */ @chmod($fileName, 0664);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
665
    }
666
667
    /**
668
     * Transforms a DBAL type into a PHP type (for PHPDoc purpose).
669
     *
670
     * @param Type $type The DBAL type
671
     *
672
     * @return string The PHP type
673
     */
674
    public static function dbalTypeToPhpType(Type $type) : string
675
    {
676
        $map = [
677
            Type::TARRAY => 'array',
678
            Type::SIMPLE_ARRAY => 'array',
679
            'json' => 'array',  // 'json' is supported from Doctrine DBAL 2.6 only.
680
            Type::JSON_ARRAY => 'array',
681
            Type::BIGINT => 'string',
682
            Type::BOOLEAN => 'bool',
683
            Type::DATETIME_IMMUTABLE => '\DateTimeImmutable',
684
            Type::DATETIMETZ_IMMUTABLE => '\DateTimeImmutable',
685
            Type::DATE_IMMUTABLE => '\DateTimeImmutable',
686
            Type::TIME_IMMUTABLE => '\DateTimeImmutable',
687
            Type::DECIMAL => 'string',
688
            Type::INTEGER => 'int',
689
            Type::OBJECT => 'string',
690
            Type::SMALLINT => 'int',
691
            Type::STRING => 'string',
692
            Type::TEXT => 'string',
693
            Type::BINARY => 'resource',
694
            Type::BLOB => 'resource',
695
            Type::FLOAT => 'float',
696
            Type::GUID => 'string',
697
        ];
698
699
        return isset($map[$type->getName()]) ? $map[$type->getName()] : $type->getName();
700
    }
701
702
    /**
703
     * @param Table $table
704
     * @return string[]
705
     * @throws TDBMException
706
     */
707
    public static function getPrimaryKeyColumnsOrFail(Table $table): array
708
    {
709
        if ($table->getPrimaryKey() === null) {
710
            // Security check: a table MUST have a primary key
711
            throw new TDBMException(sprintf('Table "%s" does not have any primary key', $table->getName()));
712
        }
713
        return $table->getPrimaryKey()->getUnquotedColumns();
714
    }
715
}
716