Completed
Pull Request — 4.2 (#140)
by David
26:56
created

TDBMDaoGenerator   C

Complexity

Total Complexity 37

Size/Duplication

Total Lines 687
Duplicated Lines 6.84 %

Coupling/Cohesion

Components 1
Dependencies 11

Importance

Changes 0
Metric Value
wmc 37
lcom 1
cbo 11
dl 47
loc 687
rs 5.993
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 1
B generateAllDaosAndBeans() 0 29 2
A generateDaoAndBean() 0 14 1
B generateBean() 23 46 4
A getDefaultSortColumnFromAnnotation() 0 19 4
C generateDao() 24 268 8
B generateFactory() 0 77 4
B toCamelCase() 0 19 5
A toSingular() 0 4 1
A toVariableName() 0 4 1
A ensureDirectoryExist() 0 12 3
A setComposerFile() 0 5 1
B dbalTypeToPhpType() 0 26 2

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

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
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
     * The root directory of the project.
28
     *
29
     * @var string
30
     */
31
    private $rootPath;
32
33
    /**
34
     * Name of composer file.
35
     *
36
     * @var string
37
     */
38
    private $composerFile;
39
40
    /**
41
     * @var TDBMSchemaAnalyzer
42
     */
43
    private $tdbmSchemaAnalyzer;
44
45
    /**
46
     * @var array
47
     */
48
    private $eventDispatcher;
49
50
    /**
51
     * @var NamingStrategyInterface
52
     */
53
    private $namingStrategy;
54
    /**
55
     * @var ConfigurationInterface
56
     */
57
    private $configuration;
58
59
    /**
60
     * Constructor.
61
     *
62
     * @param ConfigurationInterface $configuration
63
     * @param Schema $schema
64
     * @param TDBMSchemaAnalyzer $tdbmSchemaAnalyzer
65
     */
66
    public function __construct(ConfigurationInterface $configuration, Schema $schema, TDBMSchemaAnalyzer $tdbmSchemaAnalyzer)
67
    {
68
        $this->configuration = $configuration;
69
        $this->schema = $schema;
70
        $this->tdbmSchemaAnalyzer = $tdbmSchemaAnalyzer;
71
        $this->rootPath = __DIR__.'/../../../../../../../../';
72
        $this->composerFile = 'composer.json';
73
        $this->namingStrategy = $configuration->getNamingStrategy();
74
        $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 array 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...
75
    }
76
77
    /**
78
     * Generates all the daos and beans.
79
     *
80
     * @throws TDBMException
81
     */
82
    public function generateAllDaosAndBeans(): void
83
    {
84
        $classNameMapper = ClassNameMapper::createFromComposerFile($this->rootPath.$this->composerFile);
85
        // TODO: check that no class name ends with "Base". Otherwise, there will be name clash.
86
87
        $tableList = $this->schema->getTables();
88
89
        // Remove all beans and daos from junction tables
90
        $junctionTables = $this->configuration->getSchemaAnalyzer()->detectJunctionTables(true);
91
        $junctionTableNames = array_map(function (Table $table) {
92
            return $table->getName();
93
        }, $junctionTables);
94
95
        $tableList = array_filter($tableList, function (Table $table) use ($junctionTableNames) {
96
            return !in_array($table->getName(), $junctionTableNames);
97
        });
98
99
        $beanDescriptors = [];
100
101
        foreach ($tableList as $table) {
102
            $beanDescriptors[] = $this->generateDaoAndBean($table, $classNameMapper);
103
        }
104
105
106
        $this->generateFactory($tableList, $classNameMapper);
107
108
        // Let's call the list of listeners
109
        $this->eventDispatcher->onGenerate($this->configuration, $beanDescriptors);
0 ignored issues
show
Bug introduced by
The method onGenerate cannot be called on $this->eventDispatcher (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
110
    }
111
112
    /**
113
     * Generates in one method call the daos and the beans for one table.
114
     *
115
     * @param Table $table
116
     * @param ClassNameMapper $classNameMapper
117
     *
118
     * @return BeanDescriptor
119
     * @throws TDBMException
120
     */
121
    private function generateDaoAndBean(Table $table, ClassNameMapper $classNameMapper) : BeanDescriptor
122
    {
123
        // TODO: $storeInUtc is NOT USED.
124
        $tableName = $table->getName();
125
        $daoName = $this->namingStrategy->getDaoClassName($tableName);
126
        $beanName = $this->namingStrategy->getBeanClassName($tableName);
127
        $baseBeanName = $this->namingStrategy->getBaseBeanClassName($tableName);
128
        $baseDaoName = $this->namingStrategy->getBaseDaoClassName($tableName);
129
130
        $beanDescriptor = new BeanDescriptor($table, $this->configuration->getSchemaAnalyzer(), $this->schema, $this->tdbmSchemaAnalyzer, $this->namingStrategy);
131
        $this->generateBean($beanDescriptor, $beanName, $baseBeanName, $table, $classNameMapper);
132
        $this->generateDao($beanDescriptor, $daoName, $baseDaoName, $beanName, $table, $classNameMapper);
133
        return $beanDescriptor;
134
    }
135
136
    /**
137
     * Writes the PHP bean file with all getters and setters from the table passed in parameter.
138
     *
139
     * @param BeanDescriptor  $beanDescriptor
140
     * @param string          $className       The name of the class
141
     * @param string          $baseClassName   The name of the base class which will be extended (name only, no directory)
142
     * @param Table           $table           The table
143
     * @param ClassNameMapper $classNameMapper
144
     *
145
     * @throws TDBMException
146
     */
147
    public function generateBean(BeanDescriptor $beanDescriptor, $className, $baseClassName, Table $table, ClassNameMapper $classNameMapper)
148
    {
149
        $beannamespace = $this->configuration->getBeanNamespace();
150
        $str = $beanDescriptor->generatePhpCode($beannamespace);
151
152
        $possibleBaseFileNames = $classNameMapper->getPossibleFileNames($beannamespace.'\\Generated\\'.$baseClassName);
153
        if (empty($possibleBaseFileNames)) {
154
            throw new TDBMException('Sorry, autoload namespace issue. The class "'.$beannamespace.'\\'.$baseClassName.'" is not autoloadable.');
155
        }
156
        $possibleBaseFileName = $this->rootPath.$possibleBaseFileNames[0];
157
158
        $this->ensureDirectoryExist($possibleBaseFileName);
159
        file_put_contents($possibleBaseFileName, $str);
160
        @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...
161
162
        $possibleFileNames = $classNameMapper->getPossibleFileNames($beannamespace.'\\'.$className);
163
        if (empty($possibleFileNames)) {
164
            // @codeCoverageIgnoreStart
165
            throw new TDBMException('Sorry, autoload namespace issue. The class "'.$beannamespace.'\\'.$className.'" is not autoloadable.');
166
            // @codeCoverageIgnoreEnd
167
        }
168
        $possibleFileName = $this->rootPath.$possibleFileNames[0];
169 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...
170
            $tableName = $table->getName();
171
            $str = "<?php
172
/*
173
 * This file has been automatically generated by TDBM.
174
 * You can edit this file as it will not be overwritten.
175
 */
176
177
namespace {$beannamespace};
178
179
use {$beannamespace}\\Generated\\{$baseClassName};
180
181
/**
182
 * The $className class maps the '$tableName' table in database.
183
 */
184
class $className extends $baseClassName
185
{
186
}
187
";
188
            $this->ensureDirectoryExist($possibleFileName);
189
            file_put_contents($possibleFileName, $str);
190
            @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...
191
        }
192
    }
193
194
    /**
195
     * Tries to find a @defaultSort annotation in one of the columns.
196
     *
197
     * @param Table $table
198
     *
199
     * @return array First item: column name, Second item: column order (asc/desc)
200
     */
201
    private function getDefaultSortColumnFromAnnotation(Table $table)
202
    {
203
        $defaultSort = null;
204
        $defaultSortDirection = null;
205
        foreach ($table->getColumns() as $column) {
206
            $comments = $column->getComment();
207
            $matches = [];
208
            if (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
     * @param ClassNameMapper $classNameMapper
230
     *
231
     * @throws TDBMException
232
     */
233
    private function generateDao(BeanDescriptor $beanDescriptor, string $className, string $baseClassName, string $beanClassName, Table $table, ClassNameMapper $classNameMapper)
234
    {
235
        $daonamespace = $this->configuration->getDaoNamespace();
236
        $beannamespace = $this->configuration->getBeanNamespace();
237
        $tableName = $table->getName();
238
        $primaryKeyColumns = $table->getPrimaryKeyColumns();
239
240
        list($defaultSort, $defaultSortDirection) = $this->getDefaultSortColumnFromAnnotation($table);
241
242
        // FIXME: lowercase tables with _ in the name should work!
243
        $tableCamel = self::toSingular(self::toCamelCase($tableName));
244
245
        $beanClassWithoutNameSpace = $beanClassName;
246
        $beanClassName = $beannamespace.'\\'.$beanClassName;
247
248
        list($usedBeans, $findByDaoCode) = $beanDescriptor->generateFindByDaoCode($beannamespace, $beanClassWithoutNameSpace);
249
250
        $usedBeans[] = $beanClassName;
251
        // Let's suppress duplicates in used beans (if any)
252
        $usedBeans = array_flip(array_flip($usedBeans));
253
        $useStatements = array_map(function ($usedBean) {
254
            return "use $usedBean;\n";
255
        }, $usedBeans);
256
257
        $str = "<?php
258
259
/*
260
 * This file has been automatically generated by TDBM.
261
 * DO NOT edit this file, as it might be overwritten.
262
 * If you need to perform changes, edit the $className class instead!
263
 */
264
265
namespace {$daonamespace}\\Generated;
266
267
use Mouf\\Database\\TDBM\\TDBMService;
268
use Mouf\\Database\\TDBM\\ResultIterator;
269
use Mouf\\Database\\TDBM\\ArrayIterator;
270
".implode('', $useStatements)."
271
/**
272
 * The $baseClassName class will maintain the persistence of $beanClassWithoutNameSpace class into the $tableName table.
273
 *
274
 */
275
class $baseClassName
276
{
277
278
    /**
279
     * @var TDBMService
280
     */
281
    protected \$tdbmService;
282
283
    /**
284
     * The default sort column.
285
     *
286
     * @var string
287
     */
288
    private \$defaultSort = ".($defaultSort ? "'$defaultSort'" : 'null').';
289
290
    /**
291
     * The default sort direction.
292
     *
293
     * @var string
294
     */
295
    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...
296
297
    /**
298
     * Sets the TDBM service used by this DAO.
299
     *
300
     * @param TDBMService \$tdbmService
301
     */
302
    public function __construct(TDBMService \$tdbmService)
303
    {
304
        \$this->tdbmService = \$tdbmService;
305
    }
306
307
    /**
308
     * Persist the $beanClassWithoutNameSpace instance.
309
     *
310
     * @param $beanClassWithoutNameSpace \$obj The bean to save.
311
     */
312
    public function save($beanClassWithoutNameSpace \$obj)
313
    {
314
        \$this->tdbmService->save(\$obj);
315
    }
316
317
    /**
318
     * Get all $tableCamel records.
319
     *
320
     * @return {$beanClassWithoutNameSpace}[]|ResultIterator|ResultArray
321
     */
322
    public function findAll() : iterable
323
    {
324
        if (\$this->defaultSort) {
325
            \$orderBy = '$tableName.'.\$this->defaultSort.' '.\$this->defaultDirection;
326
        } else {
327
            \$orderBy = null;
328
        }
329
        return \$this->tdbmService->findObjects('$tableName', null, [], \$orderBy);
330
    }
331
    ";
332
333
        if (count($primaryKeyColumns) === 1) {
334
            $primaryKeyColumn = $primaryKeyColumns[0];
335
            $primaryKeyPhpType = self::dbalTypeToPhpType($table->getColumn($primaryKeyColumn)->getType());
336
            $str .= "
337
    /**
338
     * Get $beanClassWithoutNameSpace specified by its ID (its primary key)
339
     * If the primary key does not exist, an exception is thrown.
340
     *
341
     * @param string|int \$id
342
     * @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.
343
     * @return $beanClassWithoutNameSpace
344
     * @throws TDBMException
345
     */
346
    public function getById($primaryKeyPhpType \$id, \$lazyLoading = false) : $beanClassWithoutNameSpace
347
    {
348
        return \$this->tdbmService->findObjectByPk('$tableName', ['$primaryKeyColumn' => \$id], [], \$lazyLoading);
349
    }
350
    ";
351
        }
352
        $str .= "
353
    /**
354
     * Deletes the $beanClassWithoutNameSpace passed in parameter.
355
     *
356
     * @param $beanClassWithoutNameSpace \$obj object to delete
357
     * @param bool \$cascade if true, it will delete all object linked to \$obj
358
     */
359
    public function delete($beanClassWithoutNameSpace \$obj, \$cascade = false) : void
360
    {
361
        if (\$cascade === true) {
362
            \$this->tdbmService->deleteCascade(\$obj);
363
        } else {
364
            \$this->tdbmService->delete(\$obj);
365
        }
366
    }
367
368
369
    /**
370
     * Get a list of $beanClassWithoutNameSpace specified by its filters.
371
     *
372
     * @param mixed \$filter The filter bag (see TDBMService::findObjects for complete description)
373
     * @param array \$parameters The parameters associated with the filter
374
     * @param mixed \$orderBy The order string
375
     * @param array \$additionalTablesFetch A list of additional tables to fetch (for performance improvement)
376
     * @param int \$mode Either TDBMService::MODE_ARRAY or TDBMService::MODE_CURSOR (for large datasets). Defaults to TDBMService::MODE_ARRAY.
377
     * @return {$beanClassWithoutNameSpace}[]|ResultIterator|ResultArray
378
     */
379
    protected function find(\$filter = null, array \$parameters = [], \$orderBy=null, array \$additionalTablesFetch = [], \$mode = null) : iterable
380
    {
381
        if (\$this->defaultSort && \$orderBy == null) {
382
            \$orderBy = '$tableName.'.\$this->defaultSort.' '.\$this->defaultDirection;
383
        }
384
        return \$this->tdbmService->findObjects('$tableName', \$filter, \$parameters, \$orderBy, \$additionalTablesFetch, \$mode);
385
    }
386
387
    /**
388
     * Get a list of $beanClassWithoutNameSpace specified by its filters.
389
     * Unlike the `find` method that guesses the FROM part of the statement, here you can pass the \$from part.
390
     *
391
     * You should not put an alias on the main table name. So your \$from variable should look like:
392
     *
393
     *   \"$tableName JOIN ... ON ...\"
394
     *
395
     * @param string \$from The sql from statement
396
     * @param mixed \$filter The filter bag (see TDBMService::findObjects for complete description)
397
     * @param array \$parameters The parameters associated with the filter
398
     * @param mixed \$orderBy The order string
399
     * @param int \$mode Either TDBMService::MODE_ARRAY or TDBMService::MODE_CURSOR (for large datasets). Defaults to TDBMService::MODE_ARRAY.
400
     * @return {$beanClassWithoutNameSpace}[]|ResultIterator|ResultArray
401
     */
402
    protected function findFromSql(\$from, \$filter = null, array \$parameters = [], \$orderBy = null, \$mode = null) : iterable
403
    {
404
        if (\$this->defaultSort && \$orderBy == null) {
405
            \$orderBy = '$tableName.'.\$this->defaultSort.' '.\$this->defaultDirection;
406
        }
407
        return \$this->tdbmService->findObjectsFromSql('$tableName', \$from, \$filter, \$parameters, \$orderBy, \$mode);
408
    }
409
410
    /**
411
     * Get a single $beanClassWithoutNameSpace specified by its filters.
412
     *
413
     * @param mixed \$filter The filter bag (see TDBMService::findObjects for complete description)
414
     * @param array \$parameters The parameters associated with the filter
415
     * @param array \$additionalTablesFetch A list of additional tables to fetch (for performance improvement)
416
     * @return $beanClassWithoutNameSpace|null
417
     */
418
    protected function findOne(\$filter = null, array \$parameters = [], array \$additionalTablesFetch = []) : ?$beanClassWithoutNameSpace
419
    {
420
        return \$this->tdbmService->findObject('$tableName', \$filter, \$parameters, \$additionalTablesFetch);
421
    }
422
423
    /**
424
     * Get a single $beanClassWithoutNameSpace specified by its filters.
425
     * Unlike the `find` method that guesses the FROM part of the statement, here you can pass the \$from part.
426
     *
427
     * You should not put an alias on the main table name. So your \$from variable should look like:
428
     *
429
     *   \"$tableName JOIN ... ON ...\"
430
     *
431
     * @param string \$from The sql from statement
432
     * @param mixed \$filter The filter bag (see TDBMService::findObjects for complete description)
433
     * @param array \$parameters The parameters associated with the filter
434
     * @return $beanClassWithoutNameSpace|null
435
     */
436
    protected function findOneFromSql(\$from, \$filter = null, array \$parameters = []) : ?$beanClassWithoutNameSpace
437
    {
438
        return \$this->tdbmService->findObjectFromSql('$tableName', \$from, \$filter, \$parameters);
439
    }
440
441
    /**
442
     * Sets the default column for default sorting.
443
     *
444
     * @param string \$defaultSort
445
     */
446
    public function setDefaultSort(string \$defaultSort) : void
447
    {
448
        \$this->defaultSort = \$defaultSort;
449
    }
450
";
451
452
        $str .= $findByDaoCode;
453
        $str .= '}
454
';
455
456
        $possibleBaseFileNames = $classNameMapper->getPossibleFileNames($daonamespace.'\\Generated\\'.$baseClassName);
457
        if (empty($possibleBaseFileNames)) {
458
            // @codeCoverageIgnoreStart
459
            throw new TDBMException('Sorry, autoload namespace issue. The class "'.$daonamespace.'\\Generated\\'.$baseClassName.'" is not autoloadable.');
460
            // @codeCoverageIgnoreEnd
461
        }
462
        $possibleBaseFileName = $this->rootPath.$possibleBaseFileNames[0];
463
464
        $this->ensureDirectoryExist($possibleBaseFileName);
465
        file_put_contents($possibleBaseFileName, $str);
466
        @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...
467
468
        $possibleFileNames = $classNameMapper->getPossibleFileNames($daonamespace.'\\'.$className);
469
        if (empty($possibleFileNames)) {
470
            // @codeCoverageIgnoreStart
471
            throw new TDBMException('Sorry, autoload namespace issue. The class "'.$daonamespace.'\\'.$className.'" is not autoloadable.');
472
            // @codeCoverageIgnoreEnd
473
        }
474
        $possibleFileName = $this->rootPath.$possibleFileNames[0];
475
476
        // Now, let's generate the "editable" class
477 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...
478
            $str = "<?php
479
480
/*
481
 * This file has been automatically generated by TDBM.
482
 * You can edit this file as it will not be overwritten.
483
 */
484
485
namespace {$daonamespace};
486
487
use {$daonamespace}\\Generated\\{$baseClassName};
488
489
/**
490
 * The $className class will maintain the persistence of $beanClassWithoutNameSpace class into the $tableName table.
491
 */
492
class $className extends $baseClassName
493
{
494
}
495
";
496
            $this->ensureDirectoryExist($possibleFileName);
497
            file_put_contents($possibleFileName, $str);
498
            @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...
499
        }
500
    }
501
502
    /**
503
     * Generates the factory bean.
504
     *
505
     * @param Table[] $tableList
506
     * @param ClassNameMapper $classNameMapper
507
     * @throws TDBMException
508
     */
509
    private function generateFactory(array $tableList, ClassNameMapper $classNameMapper) : 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
518
/*
519
 * This file has been automatically generated by TDBM.
520
 * DO NOT edit this file, as it might be overwritten.
521
 */
522
523
namespace {$daoNamespace}\\Generated;
524
525
";
526
        foreach ($tableList as $table) {
527
            $tableName = $table->getName();
528
            $daoClassName = $this->namingStrategy->getDaoClassName($tableName);
529
            $str .= "use {$daoNamespace}\\".$daoClassName.";\n";
530
        }
531
532
        $str .= "
533
/**
534
 * The $daoFactoryClassName provides an easy access to all DAOs generated by TDBM.
535
 *
536
 */
537
class $daoFactoryClassName
538
{
539
";
540
541
        foreach ($tableList as $table) {
542
            $tableName = $table->getName();
543
            $daoClassName = $this->namingStrategy->getDaoClassName($tableName);
544
            $daoInstanceName = self::toVariableName($daoClassName);
545
546
            $str .= '    /**
547
     * @var '.$daoClassName.'
548
     */
549
    private $'.$daoInstanceName.';
550
551
    /**
552
     * Returns an instance of the '.$daoClassName.' class.
553
     *
554
     * @return '.$daoClassName.'
555
     */
556
    public function get'.$daoClassName.'() : '.$daoClassName.'
557
    {
558
        return $this->'.$daoInstanceName.';
559
    }
560
561
    /**
562
     * Sets the instance of the '.$daoClassName.' class that will be returned by the factory getter.
563
     *
564
     * @param '.$daoClassName.' $'.$daoInstanceName.'
565
     */
566
    public function set'.$daoClassName.'('.$daoClassName.' $'.$daoInstanceName.') : void
567
    {
568
        $this->'.$daoInstanceName.' = $'.$daoInstanceName.';
569
    }';
570
        }
571
572
        $str .= '
573
}
574
';
575
576
        $possibleFileNames = $classNameMapper->getPossibleFileNames($daoNamespace.'\\Generated\\'.$daoFactoryClassName);
577
        if (empty($possibleFileNames)) {
578
            throw new TDBMException('Sorry, autoload namespace issue. The class "'.$daoNamespace.'\\'.$daoFactoryClassName.'" is not autoloadable.');
579
        }
580
        $possibleFileName = $this->rootPath.$possibleFileNames[0];
581
582
        $this->ensureDirectoryExist($possibleFileName);
583
        file_put_contents($possibleFileName, $str);
584
        @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...
585
    }
586
587
    /**
588
     * Transforms a string to camelCase (except the first letter will be uppercase too).
589
     * Underscores and spaces are removed and the first letter after the underscore is uppercased.
590
     *
591
     * @param $str string
592
     *
593
     * @return string
594
     */
595
    public static function toCamelCase($str)
596
    {
597
        $str = strtoupper(substr($str, 0, 1)).substr($str, 1);
598
        while (true) {
599
            if (strpos($str, '_') === false && strpos($str, ' ') === false) {
600
                break;
601
            }
602
603
            $pos = strpos($str, '_');
604
            if ($pos === false) {
605
                $pos = strpos($str, ' ');
606
            }
607
            $before = substr($str, 0, $pos);
608
            $after = substr($str, $pos + 1);
609
            $str = $before.strtoupper(substr($after, 0, 1)).substr($after, 1);
610
        }
611
612
        return $str;
613
    }
614
615
    /**
616
     * Tries to put string to the singular form (if it is plural).
617
     * We assume the table names are in english.
618
     *
619
     * @param $str string
620
     *
621
     * @return string
622
     */
623
    public static function toSingular($str)
624
    {
625
        return Inflector::singularize($str);
626
    }
627
628
    /**
629
     * Put the first letter of the string in lower case.
630
     * Very useful to transform a class name into a variable name.
631
     *
632
     * @param $str string
633
     *
634
     * @return string
635
     */
636
    public static function toVariableName($str)
637
    {
638
        return strtolower(substr($str, 0, 1)).substr($str, 1);
639
    }
640
641
    /**
642
     * Ensures the file passed in parameter can be written in its directory.
643
     *
644
     * @param string $fileName
645
     *
646
     * @throws TDBMException
647
     */
648
    private function ensureDirectoryExist($fileName)
649
    {
650
        $dirName = dirname($fileName);
651
        if (!file_exists($dirName)) {
652
            $old = umask(0);
653
            $result = mkdir($dirName, 0775, true);
654
            umask($old);
655
            if ($result === false) {
656
                throw new TDBMException("Unable to create directory: '".$dirName."'.");
657
            }
658
        }
659
    }
660
661
    /**
662
     * Absolute path to composer json file.
663
     *
664
     * @param string $composerFile
665
     */
666
    public function setComposerFile($composerFile)
667
    {
668
        $this->rootPath = dirname($composerFile).'/';
669
        $this->composerFile = basename($composerFile);
670
    }
671
672
    /**
673
     * Transforms a DBAL type into a PHP type (for PHPDoc purpose).
674
     *
675
     * @param Type $type The DBAL type
676
     *
677
     * @return string The PHP type
678
     */
679
    public static function dbalTypeToPhpType(Type $type)
680
    {
681
        $map = [
682
            Type::TARRAY => 'array',
683
            Type::SIMPLE_ARRAY => 'array',
684
            Type::JSON_ARRAY => 'array',
685
            Type::BIGINT => 'string',
686
            Type::BOOLEAN => 'bool',
687
            Type::DATETIME => '\DateTimeInterface',
688
            Type::DATETIMETZ => '\DateTimeInterface',
689
            Type::DATE => '\DateTimeInterface',
690
            Type::TIME => '\DateTimeInterface',
691
            Type::DECIMAL => 'float',
692
            Type::INTEGER => 'int',
693
            Type::OBJECT => 'string',
694
            Type::SMALLINT => 'int',
695
            Type::STRING => 'string',
696
            Type::TEXT => 'string',
697
            Type::BINARY => 'string',
698
            Type::BLOB => 'string',
699
            Type::FLOAT => 'float',
700
            Type::GUID => 'string',
701
        ];
702
703
        return isset($map[$type->getName()]) ? $map[$type->getName()] : $type->getName();
704
    }
705
}
706