TDBMDaoGenerator::generateBean()   B
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 45
Code Lines 23

Duplication

Lines 23
Ratio 51.11 %

Importance

Changes 0
Metric Value
dl 23
loc 45
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 23
nc 4
nop 7
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\TDBMException;
13
use Mouf\Database\TDBM\TDBMSchemaAnalyzer;
14
15
/**
16
 * This class generates automatically DAOs and Beans for TDBM.
17
 */
18
class TDBMDaoGenerator
19
{
20
    /**
21
     * @var SchemaAnalyzer
22
     */
23
    private $schemaAnalyzer;
24
25
    /**
26
     * @var Schema
27
     */
28
    private $schema;
29
30
    /**
31
     * The root directory of the project.
32
     *
33
     * @var string
34
     */
35
    private $rootPath;
36
37
    /**
38
     * Name of composer file.
39
     *
40
     * @var string
41
     */
42
    private $composerFile;
43
44
    /**
45
     * @var TDBMSchemaAnalyzer
46
     */
47
    private $tdbmSchemaAnalyzer;
48
49
    /**
50
     * Constructor.
51
     *
52
     * @param SchemaAnalyzer     $schemaAnalyzer
53
     * @param Schema             $schema
54
     * @param TDBMSchemaAnalyzer $tdbmSchemaAnalyzer
55
     */
56
    public function __construct(SchemaAnalyzer $schemaAnalyzer, Schema $schema, TDBMSchemaAnalyzer $tdbmSchemaAnalyzer)
57
    {
58
        $this->schemaAnalyzer = $schemaAnalyzer;
59
        $this->schema = $schema;
60
        $this->tdbmSchemaAnalyzer = $tdbmSchemaAnalyzer;
61
        $this->rootPath = __DIR__.'/../../../../../../../../';
62
        $this->composerFile = 'composer.json';
63
    }
64
65
    /**
66
     * Generates all the daos and beans.
67
     *
68
     * @param string $daoFactoryClassName The classe name of the DAO factory
69
     * @param string $daonamespace        The namespace for the DAOs, without trailing \
70
     * @param string $beannamespace       The Namespace for the beans, without trailing \
71
     * @param bool   $storeInUtc          If the generated daos should store the date in UTC timezone instead of user's timezone
72
     *
73
     * @return \string[] the list of tables
74
     *
75
     * @throws TDBMException
76
     */
77
    public function generateAllDaosAndBeans($daoFactoryClassName, $daonamespace, $beannamespace, $storeInUtc)
78
    {
79
        $classNameMapper = ClassNameMapper::createFromComposerFile($this->rootPath.$this->composerFile);
80
        // TODO: check that no class name ends with "Base". Otherwise, there will be name clash.
81
82
        $tableList = $this->schema->getTables();
83
84
        // Remove all beans and daos from junction tables
85
        $junctionTables = $this->schemaAnalyzer->detectJunctionTables(true);
86
        $junctionTableNames = array_map(function (Table $table) {
87
            return $table->getName();
88
        }, $junctionTables);
89
90
        $tableList = array_filter($tableList, function (Table $table) use ($junctionTableNames) {
91
            return !in_array($table->getName(), $junctionTableNames);
92
        });
93
94
        foreach ($tableList as $table) {
95
            $this->generateDaoAndBean($table, $daonamespace, $beannamespace, $classNameMapper, $storeInUtc);
96
        }
97
98
        $this->generateFactory($tableList, $daoFactoryClassName, $daonamespace, $classNameMapper);
99
100
        // Ok, let's return the list of all tables.
101
        // These will be used by the calling script to create Mouf instances.
102
103
        return array_map(function (Table $table) {
104
            return $table->getName();
105
        }, $tableList);
106
    }
107
108
    /**
109
     * Generates in one method call the daos and the beans for one table.
110
     *
111
     * @param Table           $table
112
     * @param string          $daonamespace
113
     * @param string          $beannamespace
114
     * @param ClassNameMapper $classNameMapper
115
     * @param bool            $storeInUtc
116
     *
117
     * @throws TDBMException
118
     */
119
    public function generateDaoAndBean(Table $table, $daonamespace, $beannamespace, ClassNameMapper $classNameMapper, $storeInUtc)
120
    {
121
        $tableName = $table->getName();
122
        $daoName = $this->getDaoNameFromTableName($tableName);
123
        $beanName = $this->getBeanNameFromTableName($tableName);
124
        $baseBeanName = $this->getBaseBeanNameFromTableName($tableName);
125
        $baseDaoName = $this->getBaseDaoNameFromTableName($tableName);
126
127
        $beanDescriptor = new BeanDescriptor($table, $this->schemaAnalyzer, $this->schema, $this->tdbmSchemaAnalyzer);
128
        $this->generateBean($beanDescriptor, $beanName, $baseBeanName, $table, $beannamespace, $classNameMapper, $storeInUtc);
129
        $this->generateDao($beanDescriptor, $daoName, $baseDaoName, $beanName, $table, $daonamespace, $beannamespace, $classNameMapper);
130
    }
131
132
    /**
133
     * Returns the name of the bean class from the table name.
134
     *
135
     * @param $tableName
136
     *
137
     * @return string
138
     */
139
    public static function getBeanNameFromTableName($tableName)
140
    {
141
        return self::toSingular(self::toCamelCase($tableName)).'Bean';
142
    }
143
144
    /**
145
     * Returns the name of the DAO class from the table name.
146
     *
147
     * @param $tableName
148
     *
149
     * @return string
150
     */
151
    public static function getDaoNameFromTableName($tableName)
152
    {
153
        return self::toSingular(self::toCamelCase($tableName)).'Dao';
154
    }
155
156
    /**
157
     * Returns the name of the base bean class from the table name.
158
     *
159
     * @param $tableName
160
     *
161
     * @return string
162
     */
163
    public static function getBaseBeanNameFromTableName($tableName)
164
    {
165
        return self::toSingular(self::toCamelCase($tableName)).'BaseBean';
166
    }
167
168
    /**
169
     * Returns the name of the base DAO class from the table name.
170
     *
171
     * @param $tableName
172
     *
173
     * @return string
174
     */
175
    public static function getBaseDaoNameFromTableName($tableName)
176
    {
177
        return self::toSingular(self::toCamelCase($tableName)).'BaseDao';
178
    }
179
180
    /**
181
     * Writes the PHP bean file with all getters and setters from the table passed in parameter.
182
     *
183
     * @param BeanDescriptor  $beanDescriptor
184
     * @param string          $className       The name of the class
185
     * @param string          $baseClassName   The name of the base class which will be extended (name only, no directory)
186
     * @param Table           $table           The table
187
     * @param string          $beannamespace   The namespace of the bean
188
     * @param ClassNameMapper $classNameMapper
189
     *
190
     * @throws TDBMException
191
     */
192
    public function generateBean(BeanDescriptor $beanDescriptor, $className, $baseClassName, Table $table, $beannamespace, ClassNameMapper $classNameMapper, $storeInUtc)
0 ignored issues
show
Unused Code introduced by
The parameter $storeInUtc is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

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