Passed
Pull Request — master (#75)
by David
05:52
created

TDBMDaoGenerator::getPrimaryKeyColumnsOrFail()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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

633
        /** @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...
634
    }
635
636
    /**
637
     * Transforms a DBAL type into a PHP type (for PHPDoc purpose).
638
     *
639
     * @param Type $type The DBAL type
640
     *
641
     * @return string The PHP type
642
     */
643
    public static function dbalTypeToPhpType(Type $type) : string
644
    {
645
        $map = [
646
            Type::TARRAY => 'array',
647
            Type::SIMPLE_ARRAY => 'array',
648
            'json' => 'array',  // 'json' is supported from Doctrine DBAL 2.6 only.
649
            Type::JSON_ARRAY => 'array',
650
            Type::BIGINT => 'string',
651
            Type::BOOLEAN => 'bool',
652
            Type::DATETIME_IMMUTABLE => '\DateTimeImmutable',
653
            Type::DATETIMETZ_IMMUTABLE => '\DateTimeImmutable',
654
            Type::DATE_IMMUTABLE => '\DateTimeImmutable',
655
            Type::TIME_IMMUTABLE => '\DateTimeImmutable',
656
            Type::DECIMAL => 'float',
657
            Type::INTEGER => 'int',
658
            Type::OBJECT => 'string',
659
            Type::SMALLINT => 'int',
660
            Type::STRING => 'string',
661
            Type::TEXT => 'string',
662
            Type::BINARY => 'resource',
663
            Type::BLOB => 'resource',
664
            Type::FLOAT => 'float',
665
            Type::GUID => 'string',
666
        ];
667
668
        return isset($map[$type->getName()]) ? $map[$type->getName()] : $type->getName();
669
    }
670
671
    /**
672
     * @param Table $table
673
     * @return string[]
674
     * @throws TDBMException
675
     */
676
    public static function getPrimaryKeyColumnsOrFail(Table $table): array {
677
        if ($table->getPrimaryKey() === null) {
678
            // Security check: a table MUST have a primary key
679
            throw new TDBMException(sprintf('Table "%s" does not have any primary key', $table->getName()));
680
        }
681
        return $table->getPrimaryKey()->getUnquotedColumns();
682
    }
683
}
684