Completed
Pull Request — 4.2 (#140)
by David
06:30
created

TDBMDaoGenerator::setComposerFile()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
1
<?php
2
3
namespace Mouf\Database\TDBM\Utils;
4
5
use Doctrine\Common\Inflector\Inflector;
6
use Doctrine\DBAL\Schema\Column;
7
use Doctrine\DBAL\Schema\Schema;
8
use Doctrine\DBAL\Schema\Table;
9
use Doctrine\DBAL\Types\Type;
10
use Mouf\Composer\ClassNameMapper;
11
use Mouf\Database\SchemaAnalyzer\SchemaAnalyzer;
12
use Mouf\Database\TDBM\ConfigurationInterface;
13
use Mouf\Database\TDBM\TDBMException;
14
use Mouf\Database\TDBM\TDBMSchemaAnalyzer;
15
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
16
17
/**
18
 * This class generates automatically DAOs and Beans for TDBM.
19
 */
20
class TDBMDaoGenerator
21
{
22
    /**
23
     * @var Schema
24
     */
25
    private $schema;
26
27
    /**
28
     * The root directory of the project.
29
     *
30
     * @var string
31
     */
32
    private $rootPath;
33
34
    /**
35
     * Name of composer file.
36
     *
37
     * @var string
38
     */
39
    private $composerFile;
40
41
    /**
42
     * @var TDBMSchemaAnalyzer
43
     */
44
    private $tdbmSchemaAnalyzer;
45
46
    /**
47
     * @var EventDispatcherInterface
48
     */
49
    private $eventDispatcher;
50
51
    /**
52
     * @var NamingStrategyInterface
53
     */
54
    private $namingStrategy;
55
    /**
56
     * @var ConfigurationInterface
57
     */
58
    private $configuration;
59
60
    /**
61
     * Constructor.
62
     *
63
     * @param ConfigurationInterface $configuration
64
     * @param Schema $schema
65
     * @param TDBMSchemaAnalyzer $tdbmSchemaAnalyzer
66
     */
67
    public function __construct(ConfigurationInterface $configuration, Schema $schema, TDBMSchemaAnalyzer $tdbmSchemaAnalyzer)
68
    {
69
        $this->configuration = $configuration;
70
        $this->schema = $schema;
71
        $this->tdbmSchemaAnalyzer = $tdbmSchemaAnalyzer;
72
        $this->rootPath = __DIR__.'/../../../../../../../../';
73
        $this->composerFile = 'composer.json';
74
        $this->namingStrategy = $configuration->getNamingStrategy();
75
        $this->eventDispatcher = $configuration->getGeneratorEventDispatcher();
0 ignored issues
show
Documentation Bug introduced by
It seems like $configuration->getGeneratorEventDispatcher() of type object<Mouf\Database\TDB...ratorListenerInterface> is incompatible with the declared type object<Symfony\Component...entDispatcherInterface> of property $eventDispatcher.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
76
    }
77
78
    /**
79
     * Generates all the daos and beans.
80
     *
81
     * @throws TDBMException
82
     */
83
    public function generateAllDaosAndBeans(): void
84
    {
85
        $classNameMapper = ClassNameMapper::createFromComposerFile($this->rootPath.$this->composerFile);
86
        // TODO: check that no class name ends with "Base". Otherwise, there will be name clash.
87
88
        $tableList = $this->schema->getTables();
89
90
        // Remove all beans and daos from junction tables
91
        $junctionTables = $this->configuration->getSchemaAnalyzer()->detectJunctionTables(true);
92
        $junctionTableNames = array_map(function (Table $table) {
93
            return $table->getName();
94
        }, $junctionTables);
95
96
        $tableList = array_filter($tableList, function (Table $table) use ($junctionTableNames) {
97
            return !in_array($table->getName(), $junctionTableNames);
98
        });
99
100
        $beanDescriptors = [];
101
102
        foreach ($tableList as $table) {
103
            $beanDescriptors[] = $this->generateDaoAndBean($table, $classNameMapper);
104
        }
105
106
107
        $this->generateFactory($tableList, $classNameMapper);
108
109
        // Let's call the list of listeners
110
        $this->eventDispatcher->onGenerate($this->configuration, $beanDescriptors);
0 ignored issues
show
Bug introduced by
The method onGenerate() does not seem to exist on object<Symfony\Component...entDispatcherInterface>.

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...
111
    }
112
113
    /**
114
     * Generates in one method call the daos and the beans for one table.
115
     *
116
     * @param Table $table
117
     * @param ClassNameMapper $classNameMapper
118
     *
119
     * @return BeanDescriptor
120
     * @throws TDBMException
121
     */
122
    private function generateDaoAndBean(Table $table, ClassNameMapper $classNameMapper) : BeanDescriptor
123
    {
124
        // TODO: $storeInUtc is NOT USED.
125
        $tableName = $table->getName();
126
        $daoName = $this->namingStrategy->getDaoClassName($tableName);
127
        $beanName = $this->namingStrategy->getBeanClassName($tableName);
128
        $baseBeanName = $this->namingStrategy->getBaseBeanClassName($tableName);
129
        $baseDaoName = $this->namingStrategy->getBaseDaoClassName($tableName);
130
131
        $beanDescriptor = new BeanDescriptor($table, $this->configuration->getSchemaAnalyzer(), $this->schema, $this->tdbmSchemaAnalyzer, $this->namingStrategy);
132
        $this->generateBean($beanDescriptor, $beanName, $baseBeanName, $table, $classNameMapper);
133
        $this->generateDao($beanDescriptor, $daoName, $baseDaoName, $beanName, $table, $classNameMapper);
134
        return $beanDescriptor;
135
    }
136
137
    /**
138
     * Writes the PHP bean file with all getters and setters from the table passed in parameter.
139
     *
140
     * @param BeanDescriptor  $beanDescriptor
141
     * @param string          $className       The name of the class
142
     * @param string          $baseClassName   The name of the base class which will be extended (name only, no directory)
143
     * @param Table           $table           The table
144
     * @param ClassNameMapper $classNameMapper
145
     *
146
     * @throws TDBMException
147
     */
148
    public function generateBean(BeanDescriptor $beanDescriptor, $className, $baseClassName, Table $table, ClassNameMapper $classNameMapper)
149
    {
150
        $beannamespace = $this->configuration->getBeanNamespace();
151
        $str = $beanDescriptor->generatePhpCode($beannamespace);
152
153
        $possibleBaseFileNames = $classNameMapper->getPossibleFileNames($beannamespace.'\\Generated\\'.$baseClassName);
154
        if (empty($possibleBaseFileNames)) {
155
            throw new TDBMException('Sorry, autoload namespace issue. The class "'.$beannamespace.'\\'.$baseClassName.'" is not autoloadable.');
156
        }
157
        $possibleBaseFileName = $this->rootPath.$possibleBaseFileNames[0];
158
159
        $this->ensureDirectoryExist($possibleBaseFileName);
160
        file_put_contents($possibleBaseFileName, $str);
161
        @chmod($possibleBaseFileName, 0664);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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...
162
163
        $possibleFileNames = $classNameMapper->getPossibleFileNames($beannamespace.'\\'.$className);
164
        if (empty($possibleFileNames)) {
165
            // @codeCoverageIgnoreStart
166
            throw new TDBMException('Sorry, autoload namespace issue. The class "'.$beannamespace.'\\'.$className.'" is not autoloadable.');
167
            // @codeCoverageIgnoreEnd
168
        }
169
        $possibleFileName = $this->rootPath.$possibleFileNames[0];
170 View Code Duplication
        if (!file_exists($possibleFileName)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
171
            $tableName = $table->getName();
172
            $str = "<?php
173
/*
174
 * This file has been automatically generated by TDBM.
175
 * You can edit this file as it will not be overwritten.
176
 */
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
            $this->ensureDirectoryExist($possibleFileName);
190
            file_put_contents($possibleFileName, $str);
191
            @chmod($possibleFileName, 0664);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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...
192
        }
193
    }
194
195
    /**
196
     * Tries to find a @defaultSort annotation in one of the columns.
197
     *
198
     * @param Table $table
199
     *
200
     * @return array First item: column name, Second item: column order (asc/desc)
201
     */
202
    private function getDefaultSortColumnFromAnnotation(Table $table)
203
    {
204
        $defaultSort = null;
205
        $defaultSortDirection = null;
206
        foreach ($table->getColumns() as $column) {
207
            $comments = $column->getComment();
208
            $matches = [];
209
            if (preg_match('/@defaultSort(\((desc|asc)\))*/', $comments, $matches) != 0) {
210
                $defaultSort = $column->getName();
211
                if (count($matches) === 3) {
212
                    $defaultSortDirection = $matches[2];
213
                } else {
214
                    $defaultSortDirection = 'ASC';
215
                }
216
            }
217
        }
218
219
        return [$defaultSort, $defaultSortDirection];
220
    }
221
222
    /**
223
     * Writes the PHP bean DAO with simple functions to create/get/save objects.
224
     *
225
     * @param BeanDescriptor  $beanDescriptor
226
     * @param string          $className       The name of the class
227
     * @param string          $baseClassName
228
     * @param string          $beanClassName
229
     * @param Table           $table
230
     * @param ClassNameMapper $classNameMapper
231
     *
232
     * @throws TDBMException
233
     */
234
    private function generateDao(BeanDescriptor $beanDescriptor, string $className, string $baseClassName, string $beanClassName, Table $table, ClassNameMapper $classNameMapper)
235
    {
236
        $daonamespace = $this->configuration->getDaoNamespace();
237
        $beannamespace = $this->configuration->getBeanNamespace();
238
        $tableName = $table->getName();
239
        $primaryKeyColumns = $table->getPrimaryKeyColumns();
240
241
        list($defaultSort, $defaultSortDirection) = $this->getDefaultSortColumnFromAnnotation($table);
242
243
        // FIXME: lowercase tables with _ in the name should work!
244
        $tableCamel = self::toSingular(self::toCamelCase($tableName));
245
246
        $beanClassWithoutNameSpace = $beanClassName;
247
        $beanClassName = $beannamespace.'\\'.$beanClassName;
248
249
        list($usedBeans, $findByDaoCode) = $beanDescriptor->generateFindByDaoCode($beannamespace, $beanClassWithoutNameSpace);
250
251
        $usedBeans[] = $beanClassName;
252
        // Let's suppress duplicates in used beans (if any)
253
        $usedBeans = array_flip(array_flip($usedBeans));
254
        $useStatements = array_map(function ($usedBean) {
255
            return "use $usedBean;\n";
256
        }, $usedBeans);
257
258
        $str = "<?php
259
260
/*
261
 * This file has been automatically generated by TDBM.
262
 * DO NOT edit this file, as it might be overwritten.
263
 * If you need to perform changes, edit the $className class instead!
264
 */
265
266
namespace {$daonamespace}\\Generated;
267
268
use Mouf\\Database\\TDBM\\TDBMService;
269
use Mouf\\Database\\TDBM\\ResultIterator;
270
use Mouf\\Database\\TDBM\\ArrayIterator;
271
".implode('', $useStatements)."
272
/**
273
 * The $baseClassName class will maintain the persistence of $beanClassWithoutNameSpace class into the $tableName table.
274
 *
275
 */
276
class $baseClassName
277
{
278
279
    /**
280
     * @var TDBMService
281
     */
282
    protected \$tdbmService;
283
284
    /**
285
     * The default sort column.
286
     *
287
     * @var string
288
     */
289
    private \$defaultSort = ".($defaultSort ? "'$defaultSort'" : 'null').';
290
291
    /**
292
     * The default sort direction.
293
     *
294
     * @var string
295
     */
296
    private $defaultDirection = '.($defaultSort && $defaultSortDirection ? "'$defaultSortDirection'" : "'asc'").";
0 ignored issues
show
Bug Best Practice introduced by
The expression $defaultSort of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
Bug Best Practice introduced by
The expression $defaultSortDirection of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
297
298
    /**
299
     * Sets the TDBM service used by this DAO.
300
     *
301
     * @param TDBMService \$tdbmService
302
     */
303
    public function __construct(TDBMService \$tdbmService)
304
    {
305
        \$this->tdbmService = \$tdbmService;
306
    }
307
308
    /**
309
     * Persist the $beanClassWithoutNameSpace instance.
310
     *
311
     * @param $beanClassWithoutNameSpace \$obj The bean to save.
312
     */
313
    public function save($beanClassWithoutNameSpace \$obj)
314
    {
315
        \$this->tdbmService->save(\$obj);
316
    }
317
318
    /**
319
     * Get all $tableCamel records.
320
     *
321
     * @return {$beanClassWithoutNameSpace}[]|ResultIterator|ResultArray
322
     */
323
    public function findAll() : iterable
324
    {
325
        if (\$this->defaultSort) {
326
            \$orderBy = '$tableName.'.\$this->defaultSort.' '.\$this->defaultDirection;
327
        } else {
328
            \$orderBy = null;
329
        }
330
        return \$this->tdbmService->findObjects('$tableName', null, [], \$orderBy);
331
    }
332
    ";
333
334
        if (count($primaryKeyColumns) === 1) {
335
            $primaryKeyColumn = $primaryKeyColumns[0];
336
            $primaryKeyPhpType = self::dbalTypeToPhpType($table->getColumn($primaryKeyColumn)->getType());
337
            $str .= "
338
    /**
339
     * Get $beanClassWithoutNameSpace specified by its ID (its primary key)
340
     * If the primary key does not exist, an exception is thrown.
341
     *
342
     * @param string|int \$id
343
     * @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.
344
     * @return $beanClassWithoutNameSpace
345
     * @throws TDBMException
346
     */
347
    public function getById($primaryKeyPhpType \$id, \$lazyLoading = false) : $beanClassWithoutNameSpace
348
    {
349
        return \$this->tdbmService->findObjectByPk('$tableName', ['$primaryKeyColumn' => \$id], [], \$lazyLoading);
350
    }
351
    ";
352
        }
353
        $str .= "
354
    /**
355
     * Deletes the $beanClassWithoutNameSpace passed in parameter.
356
     *
357
     * @param $beanClassWithoutNameSpace \$obj object to delete
358
     * @param bool \$cascade if true, it will delete all object linked to \$obj
359
     */
360
    public function delete($beanClassWithoutNameSpace \$obj, \$cascade = false) : void
361
    {
362
        if (\$cascade === true) {
363
            \$this->tdbmService->deleteCascade(\$obj);
364
        } else {
365
            \$this->tdbmService->delete(\$obj);
366
        }
367
    }
368
369
370
    /**
371
     * Get a list of $beanClassWithoutNameSpace specified by its filters.
372
     *
373
     * @param mixed \$filter The filter bag (see TDBMService::findObjects for complete description)
374
     * @param array \$parameters The parameters associated with the filter
375
     * @param mixed \$orderBy The order string
376
     * @param array \$additionalTablesFetch A list of additional tables to fetch (for performance improvement)
377
     * @param int \$mode Either TDBMService::MODE_ARRAY or TDBMService::MODE_CURSOR (for large datasets). Defaults to TDBMService::MODE_ARRAY.
378
     * @return {$beanClassWithoutNameSpace}[]|ResultIterator|ResultArray
379
     */
380
    protected function find(\$filter = null, array \$parameters = [], \$orderBy=null, array \$additionalTablesFetch = [], \$mode = null) : iterable
381
    {
382
        if (\$this->defaultSort && \$orderBy == null) {
383
            \$orderBy = '$tableName.'.\$this->defaultSort.' '.\$this->defaultDirection;
384
        }
385
        return \$this->tdbmService->findObjects('$tableName', \$filter, \$parameters, \$orderBy, \$additionalTablesFetch, \$mode);
386
    }
387
388
    /**
389
     * Get a list of $beanClassWithoutNameSpace specified by its filters.
390
     * Unlike the `find` method that guesses the FROM part of the statement, here you can pass the \$from part.
391
     *
392
     * You should not put an alias on the main table name. So your \$from variable should look like:
393
     *
394
     *   \"$tableName JOIN ... ON ...\"
395
     *
396
     * @param string \$from The sql from statement
397
     * @param mixed \$filter The filter bag (see TDBMService::findObjects for complete description)
398
     * @param array \$parameters The parameters associated with the filter
399
     * @param mixed \$orderBy The order string
400
     * @param int \$mode Either TDBMService::MODE_ARRAY or TDBMService::MODE_CURSOR (for large datasets). Defaults to TDBMService::MODE_ARRAY.
401
     * @return {$beanClassWithoutNameSpace}[]|ResultIterator|ResultArray
402
     */
403
    protected function findFromSql(\$from, \$filter = null, array \$parameters = [], \$orderBy = null, \$mode = null) : iterable
404
    {
405
        if (\$this->defaultSort && \$orderBy == null) {
406
            \$orderBy = '$tableName.'.\$this->defaultSort.' '.\$this->defaultDirection;
407
        }
408
        return \$this->tdbmService->findObjectsFromSql('$tableName', \$from, \$filter, \$parameters, \$orderBy, \$mode);
409
    }
410
411
    /**
412
     * Get a single $beanClassWithoutNameSpace specified by its filters.
413
     *
414
     * @param mixed \$filter The filter bag (see TDBMService::findObjects for complete description)
415
     * @param array \$parameters The parameters associated with the filter
416
     * @param array \$additionalTablesFetch A list of additional tables to fetch (for performance improvement)
417
     * @return $beanClassWithoutNameSpace|null
418
     */
419
    protected function findOne(\$filter = null, array \$parameters = [], array \$additionalTablesFetch = []) : ?$beanClassWithoutNameSpace
420
    {
421
        return \$this->tdbmService->findObject('$tableName', \$filter, \$parameters, \$additionalTablesFetch);
422
    }
423
424
    /**
425
     * Get a single $beanClassWithoutNameSpace specified by its filters.
426
     * Unlike the `find` method that guesses the FROM part of the statement, here you can pass the \$from part.
427
     *
428
     * You should not put an alias on the main table name. So your \$from variable should look like:
429
     *
430
     *   \"$tableName JOIN ... ON ...\"
431
     *
432
     * @param string \$from The sql from statement
433
     * @param mixed \$filter The filter bag (see TDBMService::findObjects for complete description)
434
     * @param array \$parameters The parameters associated with the filter
435
     * @return $beanClassWithoutNameSpace|null
436
     */
437
    protected function findOneFromSql(\$from, \$filter = null, array \$parameters = []) : ?$beanClassWithoutNameSpace
438
    {
439
        return \$this->tdbmService->findObjectFromSql('$tableName', \$from, \$filter, \$parameters);
440
    }
441
442
    /**
443
     * Sets the default column for default sorting.
444
     *
445
     * @param string \$defaultSort
446
     */
447
    public function setDefaultSort(string \$defaultSort) : void
448
    {
449
        \$this->defaultSort = \$defaultSort;
450
    }
451
";
452
453
        $str .= $findByDaoCode;
454
        $str .= '}
455
';
456
457
        $possibleBaseFileNames = $classNameMapper->getPossibleFileNames($daonamespace.'\\Generated\\'.$baseClassName);
458
        if (empty($possibleBaseFileNames)) {
459
            // @codeCoverageIgnoreStart
460
            throw new TDBMException('Sorry, autoload namespace issue. The class "'.$daonamespace.'\\Generated\\'.$baseClassName.'" is not autoloadable.');
461
            // @codeCoverageIgnoreEnd
462
        }
463
        $possibleBaseFileName = $this->rootPath.$possibleBaseFileNames[0];
464
465
        $this->ensureDirectoryExist($possibleBaseFileName);
466
        file_put_contents($possibleBaseFileName, $str);
467
        @chmod($possibleBaseFileName, 0664);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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...
468
469
        $possibleFileNames = $classNameMapper->getPossibleFileNames($daonamespace.'\\'.$className);
470
        if (empty($possibleFileNames)) {
471
            // @codeCoverageIgnoreStart
472
            throw new TDBMException('Sorry, autoload namespace issue. The class "'.$daonamespace.'\\'.$className.'" is not autoloadable.');
473
            // @codeCoverageIgnoreEnd
474
        }
475
        $possibleFileName = $this->rootPath.$possibleFileNames[0];
476
477
        // Now, let's generate the "editable" class
478 View Code Duplication
        if (!file_exists($possibleFileName)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
479
            $str = "<?php
480
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
namespace {$daonamespace};
487
488
use {$daonamespace}\\Generated\\{$baseClassName};
489
490
/**
491
 * The $className class will maintain the persistence of $beanClassWithoutNameSpace class into the $tableName table.
492
 */
493
class $className extends $baseClassName
494
{
495
}
496
";
497
            $this->ensureDirectoryExist($possibleFileName);
498
            file_put_contents($possibleFileName, $str);
499
            @chmod($possibleFileName, 0664);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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...
500
        }
501
    }
502
503
    /**
504
     * Generates the factory bean.
505
     *
506
     * @param Table[] $tableList
507
     * @param ClassNameMapper $classNameMapper
508
     * @throws TDBMException
509
     */
510
    private function generateFactory(array $tableList, ClassNameMapper $classNameMapper) : void
511
    {
512
        $daoNamespace = $this->configuration->getDaoNamespace();
513
        $daoFactoryClassName = $this->namingStrategy->getDaoFactoryClassName();
514
515
        // For each table, let's write a property.
516
517
        $str = "<?php
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
        $possibleFileNames = $classNameMapper->getPossibleFileNames($daoNamespace.'\\Generated\\'.$daoFactoryClassName);
578
        if (empty($possibleFileNames)) {
579
            throw new TDBMException('Sorry, autoload namespace issue. The class "'.$daoNamespace.'\\'.$daoFactoryClassName.'" is not autoloadable.');
580
        }
581
        $possibleFileName = $this->rootPath.$possibleFileNames[0];
582
583
        $this->ensureDirectoryExist($possibleFileName);
584
        file_put_contents($possibleFileName, $str);
585
        @chmod($possibleFileName, 0664);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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...
586
    }
587
588
    /**
589
     * Transforms a string to camelCase (except the first letter will be uppercase too).
590
     * Underscores and spaces are removed and the first letter after the underscore is uppercased.
591
     *
592
     * @param $str string
593
     *
594
     * @return string
595
     */
596
    public static function toCamelCase($str)
597
    {
598
        $str = strtoupper(substr($str, 0, 1)).substr($str, 1);
599
        while (true) {
600
            if (strpos($str, '_') === false && strpos($str, ' ') === false) {
601
                break;
602
            }
603
604
            $pos = strpos($str, '_');
605
            if ($pos === false) {
606
                $pos = strpos($str, ' ');
607
            }
608
            $before = substr($str, 0, $pos);
609
            $after = substr($str, $pos + 1);
610
            $str = $before.strtoupper(substr($after, 0, 1)).substr($after, 1);
611
        }
612
613
        return $str;
614
    }
615
616
    /**
617
     * Tries to put string to the singular form (if it is plural).
618
     * We assume the table names are in english.
619
     *
620
     * @param $str string
621
     *
622
     * @return string
623
     */
624
    public static function toSingular($str)
625
    {
626
        return Inflector::singularize($str);
627
    }
628
629
    /**
630
     * Put the first letter of the string in lower case.
631
     * Very useful to transform a class name into a variable name.
632
     *
633
     * @param $str string
634
     *
635
     * @return string
636
     */
637
    public static function toVariableName($str)
638
    {
639
        return strtolower(substr($str, 0, 1)).substr($str, 1);
640
    }
641
642
    /**
643
     * Ensures the file passed in parameter can be written in its directory.
644
     *
645
     * @param string $fileName
646
     *
647
     * @throws TDBMException
648
     */
649
    private function ensureDirectoryExist($fileName)
650
    {
651
        $dirName = dirname($fileName);
652
        if (!file_exists($dirName)) {
653
            $old = umask(0);
654
            $result = mkdir($dirName, 0775, true);
655
            umask($old);
656
            if ($result === false) {
657
                throw new TDBMException("Unable to create directory: '".$dirName."'.");
658
            }
659
        }
660
    }
661
662
    /**
663
     * Absolute path to composer json file.
664
     *
665
     * @param string $composerFile
666
     */
667
    public function setComposerFile($composerFile)
668
    {
669
        $this->rootPath = dirname($composerFile).'/';
670
        $this->composerFile = basename($composerFile);
671
    }
672
673
    /**
674
     * Transforms a DBAL type into a PHP type (for PHPDoc purpose).
675
     *
676
     * @param Type $type The DBAL type
677
     *
678
     * @return string The PHP type
679
     */
680
    public static function dbalTypeToPhpType(Type $type)
681
    {
682
        $map = [
683
            Type::TARRAY => 'array',
684
            Type::SIMPLE_ARRAY => 'array',
685
            Type::JSON_ARRAY => 'array',
686
            Type::BIGINT => 'string',
687
            Type::BOOLEAN => 'bool',
688
            Type::DATETIME => '\DateTimeInterface',
689
            Type::DATETIMETZ => '\DateTimeInterface',
690
            Type::DATE => '\DateTimeInterface',
691
            Type::TIME => '\DateTimeInterface',
692
            Type::DECIMAL => 'float',
693
            Type::INTEGER => 'int',
694
            Type::OBJECT => 'string',
695
            Type::SMALLINT => 'int',
696
            Type::STRING => 'string',
697
            Type::TEXT => 'string',
698
            Type::BINARY => 'string',
699
            Type::BLOB => 'string',
700
            Type::FLOAT => 'float',
701
            Type::GUID => 'string',
702
        ];
703
704
        return isset($map[$type->getName()]) ? $map[$type->getName()] : $type->getName();
705
    }
706
}
707