diff --git a/readme.md b/readme.md index e321ad6d80..3eada6cd2c 100644 --- a/readme.md +++ b/readme.md @@ -308,7 +308,7 @@ You can check available Faker locales in the source code, [under the `Provider` ## Populating Entities Using an ORM or an ODM -Faker provides adapters for Object-Relational and Object-Document Mappers (currently, [Propel](http://www.propelorm.org), [Doctrine2](http://www.doctrine-project.org/projects/orm/2.0/docs/en), and [Mandango](https://github.com/mandango/mandango) are supported). These adapters ease the population of databases through the Entity classes provided by an ORM library (or the population of document stores using Document classes provided by an ODM library). +Faker provides adapters for Object-Relational and Object-Document Mappers (currently, [Propel](http://www.propelorm.org), [Doctrine2](http://www.doctrine-project.org/projects/orm/2.0/docs/en), [CakePHP](http://cakephp.org) and [Mandango](https://github.com/mandango/mandango) are supported). These adapters ease the population of databases through the Entity classes provided by an ORM library (or the population of document stores using Document classes provided by an ODM library). To populate entities, create a new populator class (using a generator instance as parameter), then list the class and number of all the entities that must be generated. To launch the actual data population, call the `execute()` method. @@ -387,7 +387,7 @@ echo $faker->name; // 'Jess Mraz I'; > // make sure you fix the $max parameter > $faker->dateTime('2014-02-25 08:37:17'); // will return always the same date when seeded > ``` -> +> > **Tip**: Formatters won't reproduce the same fake data if you use the `rand()` php function. Use `$faker` or `mt_rand()` instead: > > ```php diff --git a/src/Faker/ORM/CakePHP/ColumnTypeGuesser.php b/src/Faker/ORM/CakePHP/ColumnTypeGuesser.php new file mode 100644 index 0000000000..0f36f60769 --- /dev/null +++ b/src/Faker/ORM/CakePHP/ColumnTypeGuesser.php @@ -0,0 +1,64 @@ +generator = $generator; + } + + public function guessFormat($column, $table) + { + $generator = $this->generator; + $schema = $table->schema(); + + switch ($schema->columnType($column)) { + case 'boolean': + return function () use ($generator) { + return $generator->boolean; + }; + case 'integer': + return function () use ($generator) { + return $generator->randomNumber(10); + }; + case 'biginteger': + return function () use ($generator) { + return $generator->randomNumber(20); + }; + case 'decimal': + case 'float': + return function () use ($generator) { + return $generator->randomFloat(); + }; + case 'uuid': + return function () use ($generator) { + return $generator->uuid(); + }; + case 'string': + $columnData = $schema->column($column); + $length = $columnData['length']; + return function () use ($generator, $length) { + return $generator->text($length); + }; + case 'text': + return function () use ($generator) { + return $generator->text(); + }; + case 'date': + case 'datetime': + case 'timestamp': + case 'time': + return function () use ($generator) { + return $generator->datetime(); + }; + + case 'binary': + default: + return null; + } + } +} diff --git a/src/Faker/ORM/CakePHP/EntityPopulator.php b/src/Faker/ORM/CakePHP/EntityPopulator.php new file mode 100644 index 0000000000..db1f705600 --- /dev/null +++ b/src/Faker/ORM/CakePHP/EntityPopulator.php @@ -0,0 +1,132 @@ +class = $class; + } + + public function __get($name) + { + return $this->{$name}; + } + + public function __set($name, $value) + { + $this->{$name} = $value; + } + + public function mergeColumnFormattersWith($columnFormatters) + { + $this->columnFormatters = array_merge($this->columnFormatters, $columnFormatters); + } + + public function mergeModifiersWith($modifiers) + { + $this->modifiers = array_merge($this->modifiers, $modifiers); + } + + public function guessColumnFormatters($populator) + { + $formatters = []; + $class = $this->class; + $table = $this->getTable($class); + $schema = $table->schema(); + $pk = $schema->primaryKey(); + $guessers = $populator->getGuessers() + ['ColumnTypeGuesser' => new ColumnTypeGuesser($populator->getGenerator())]; + $isForeignKey = function ($column) use ($table) { + foreach ($table->associations()->type('BelongsTo') as $assoc) { + if ($column == $assoc->foreignKey()) { + return true; + } + } + return false; + }; + + + foreach ($schema->columns() as $column) { + if ($column == $pk[0] || $isForeignKey($column)) { + continue; + } + + foreach ($guessers as $guesser) { + if ($formatter = $guesser->guessFormat($column, $table)) { + $formatters[$column] = $formatter; + break; + } + } + } + + return $formatters; + } + + public function guessModifiers($populator) + { + $modifiers = []; + $table = $this->getTable($this->class); + + $belongsTo = $table->associations()->type('BelongsTo'); + foreach ($belongsTo as $assoc) { + $modifiers['belongsTo' . $assoc->name()] = function ($data, $insertedEntities) use ($assoc) { + $table = $assoc->target(); + $foreignModel = $table->alias(); + $foreignKey = $insertedEntities[$foreignModel][array_rand($insertedEntities[$foreignModel])]; + $primaryKey = $table->primaryKey(); + $data[$assoc->foreignKey()] = $foreignKey; + return $data; + }; + } + + // TODO check if TreeBehavior attached to modify lft/rgt cols + + return $modifiers; + } + + public function execute($class, $insertedEntities, $options = []) + { + $table = $this->getTable($class); + $entity = $table->newEntity(); + + foreach ($this->columnFormatters as $column => $format) { + if (!is_null($format)) { + $entity->{$column} = is_callable($format) ? $format($insertedEntities, $table) : $format; + } + } + + foreach ($this->modifiers as $modifier) { + $entity = $modifier($entity, $insertedEntities); + } + + if (!$entity = $table->save($entity, $options)) { + throw new \RuntimeException("Failed saving $class record"); + } + + $pk = $table->primaryKey(); + return $entity->{$pk}; + } + + public function setConnection($name) + { + $this->connectionName = $name; + } + + protected function getTable($class) + { + $options = []; + if (!empty($this->connectionName)) { + $options['connection'] = $this->connectionName; + } + return TableRegistry::get($class, $options); + } +} diff --git a/src/Faker/ORM/CakePHP/Populator.php b/src/Faker/ORM/CakePHP/Populator.php new file mode 100644 index 0000000000..cd5aa47562 --- /dev/null +++ b/src/Faker/ORM/CakePHP/Populator.php @@ -0,0 +1,84 @@ +generator = $generator; + } + + public function getGenerator() + { + return $this->generator; + } + + public function getGuessers() + { + return $this->guessers; + } + + public function removeGuesser($name) + { + if ($this->guessers[$name]) { + unset($this->guessers[$name]); + } + return $this; + } + + public function addGuesser($class) + { + if (!is_object($class)) { + $class = new $class($this->generator); + } + + if (!method_exists($class, 'guessFormat')) { + throw new \Exception('Missing required custom guesser method: ' . get_class($class) . '::guessFormat()'); + } + + $this->guessers[get_class($class)] = $class; + return $this; + } + + public function addEntity($entity, $number, $customColumnFormatters = [], $customModifiers = []) + { + if (!$entity instanceof EntityPopulator) { + $entity = new EntityPopulator($entity); + } + + $entity->columnFormatters = $entity->guessColumnFormatters($this); + if ($customColumnFormatters) { + $entity->mergeColumnFormattersWith($customColumnFormatters); + } + + $entity->modifiers = $entity->guessModifiers($this); + if ($customModifiers) { + $entity->mergeModifiers($customModifiers); + } + + $class = $entity->class; + $this->entities[$class] = $entity; + $this->quantities[$class] = $number; + return $this; + } + + public function execute($options = []) + { + $insertedEntities = []; + + foreach ($this->quantities as $class => $number) { + for ($i = 0; $i < $number; $i++) { + $insertedEntities[$class][] = $this->entities[$class]->execute($class, $insertedEntities, $options); + } + } + + return $insertedEntities; + } +}