From c012341345c48b323718a7b75996eedf58924c45 Mon Sep 17 00:00:00 2001 From: Dhaval Patel Date: Thu, 10 Mar 2016 15:55:25 -0500 Subject: [PATCH 1/3] Add Propel2 support for faker --- src/Faker/ORM/Propel2/ColumnTypeGuesser.php | 107 +++++++++++ src/Faker/ORM/Propel2/EntityPopulator.php | 192 ++++++++++++++++++++ src/Faker/ORM/Propel2/Populator.php | 92 ++++++++++ 3 files changed, 391 insertions(+) create mode 100644 src/Faker/ORM/Propel2/ColumnTypeGuesser.php create mode 100644 src/Faker/ORM/Propel2/EntityPopulator.php create mode 100644 src/Faker/ORM/Propel2/Populator.php diff --git a/src/Faker/ORM/Propel2/ColumnTypeGuesser.php b/src/Faker/ORM/Propel2/ColumnTypeGuesser.php new file mode 100644 index 0000000000..78cb8487c6 --- /dev/null +++ b/src/Faker/ORM/Propel2/ColumnTypeGuesser.php @@ -0,0 +1,107 @@ +generator = $generator; + } + + /** + * @param ColumnMap $column + * @return \Closure|null + */ + public function guessFormat(ColumnMap $column) + { + $generator = $this->generator; + if ($column->isTemporal()) { + if ($column->isEpochTemporal()) { + return function () use ($generator) { + return $generator->dateTime; + }; + } else { + return function () use ($generator) { + return $generator->dateTimeAD; + }; + } + } + $type = $column->getType(); + switch ($type) { + case PropelTypes::BOOLEAN: + case PropelTypes::BOOLEAN_EMU: + return function () use ($generator) { + return $generator->boolean; + }; + case PropelTypes::NUMERIC: + case PropelTypes::DECIMAL: + $size = $column->getSize(); + + return function () use ($generator, $size) { + return $generator->randomNumber($size + 2) / 100; + }; + case PropelTypes::TINYINT: + return function () { + return mt_rand(0, 127); + }; + case PropelTypes::SMALLINT: + return function () { + return mt_rand(0, 32767); + }; + case PropelTypes::INTEGER: + return function () { + return mt_rand(0, intval('2147483647')); + }; + case PropelTypes::BIGINT: + return function () { + return mt_rand(0, intval('9223372036854775807')); + }; + case PropelTypes::FLOAT: + return function () { + return mt_rand(0, intval('2147483647'))/mt_rand(1, intval('2147483647')); + }; + case PropelTypes::DOUBLE: + case PropelTypes::REAL: + return function () { + return mt_rand(0, intval('9223372036854775807'))/mt_rand(1, intval('9223372036854775807')); + }; + case PropelTypes::CHAR: + case PropelTypes::VARCHAR: + case PropelTypes::BINARY: + case PropelTypes::VARBINARY: + $size = $column->getSize(); + + return function () use ($generator, $size) { + return $generator->text($size); + }; + case PropelTypes::LONGVARCHAR: + case PropelTypes::LONGVARBINARY: + case PropelTypes::CLOB: + case PropelTypes::CLOB_EMU: + case PropelTypes::BLOB: + return function () use ($generator) { + return $generator->text; + }; + case PropelTypes::ENUM: + $valueSet = $column->getValueSet(); + + return function () use ($generator, $valueSet) { + return $generator->randomElement($valueSet); + }; + case PropelTypes::OBJECT: + case PropelTypes::PHP_ARRAY: + default: + // no smart way to guess what the user expects here + return null; + } + } +} diff --git a/src/Faker/ORM/Propel2/EntityPopulator.php b/src/Faker/ORM/Propel2/EntityPopulator.php new file mode 100644 index 0000000000..df5b6710b6 --- /dev/null +++ b/src/Faker/ORM/Propel2/EntityPopulator.php @@ -0,0 +1,192 @@ +class = $class; + } + + /** + * @return string + */ + public function getClass() + { + return $this->class; + } + + public function setColumnFormatters($columnFormatters) + { + $this->columnFormatters = $columnFormatters; + } + + /** + * @return array + */ + public function getColumnFormatters() + { + return $this->columnFormatters; + } + + public function mergeColumnFormattersWith($columnFormatters) + { + $this->columnFormatters = array_merge($this->columnFormatters, $columnFormatters); + } + + /** + * @param \Faker\Generator $generator + * @return array + */ + public function guessColumnFormatters(\Faker\Generator $generator) + { + $formatters = array(); + $class = $this->class; + $peerClass = $class::TABLE_MAP; + $tableMap = $peerClass::getTableMap(); + $nameGuesser = new \Faker\Guesser\Name($generator); + $columnTypeGuesser = new \Faker\ORM\Propel2\ColumnTypeGuesser($generator); + foreach ($tableMap->getColumns() as $columnMap) { + // skip behavior columns, handled by modifiers + if ($this->isColumnBehavior($columnMap)) { + continue; + } + if ($columnMap->isForeignKey()) { + $relatedClass = $columnMap->getRelation()->getForeignTable()->getClassname(); + $formatters[$columnMap->getPhpName()] = function ($inserted) use ($relatedClass) { + $relatedClass = trim($relatedClass, "\\"); + return isset($inserted[$relatedClass]) ? $inserted[$relatedClass][mt_rand(0, count($inserted[$relatedClass]) - 1)] : null; + }; + continue; + } + if ($columnMap->isPrimaryKey()) { + continue; + } + if ($formatter = $nameGuesser->guessFormat($columnMap->getPhpName(), $columnMap->getSize())) { + $formatters[$columnMap->getPhpName()] = $formatter; + continue; + } + if ($formatter = $columnTypeGuesser->guessFormat($columnMap)) { + $formatters[$columnMap->getPhpName()] = $formatter; + continue; + } + } + + return $formatters; + } + + /** + * @param ColumnMap $columnMap + * @return bool + */ + protected function isColumnBehavior(ColumnMap $columnMap) + { + foreach ($columnMap->getTable()->getBehaviors() as $name => $params) { + $columnName = Base::toLower($columnMap->getName()); + switch ($name) { + case 'nested_set': + $columnNames = array($params['left_column'], $params['right_column'], $params['level_column']); + if (in_array($columnName, $columnNames)) { + return true; + } + break; + case 'timestampable': + $columnNames = array($params['create_column'], $params['update_column']); + if (in_array($columnName, $columnNames)) { + return true; + } + break; + } + } + + return false; + } + + public function setModifiers($modifiers) + { + $this->modifiers = $modifiers; + } + + /** + * @return array + */ + public function getModifiers() + { + return $this->modifiers; + } + + public function mergeModifiersWith($modifiers) + { + $this->modifiers = array_merge($this->modifiers, $modifiers); + } + + /** + * @param \Faker\Generator $generator + * @return array + */ + public function guessModifiers(\Faker\Generator $generator) + { + $modifiers = array(); + $class = $this->class; + $peerClass = $class::TABLE_MAP; + $tableMap = $peerClass::getTableMap(); + foreach ($tableMap->getBehaviors() as $name => $params) { + switch ($name) { + case 'nested_set': + $modifiers['nested_set'] = function ($obj, $inserted) use ($class, $generator) { + if (isset($inserted[$class])) { + $queryClass = $class . 'Query'; + $parent = $queryClass::create()->findPk($generator->randomElement($inserted[$class])); + $obj->insertAsLastChildOf($parent); + } else { + $obj->makeRoot(); + } + }; + break; + case 'sortable': + $modifiers['sortable'] = function ($obj, $inserted) use ($class) { + $maxRank = isset($inserted[$class]) ? count($inserted[$class]) : 0; + $obj->insertAtRank(mt_rand(1, $maxRank + 1)); + }; + break; + } + } + + return $modifiers; + } + + /** + * Insert one new record using the Entity class. + */ + public function execute($con, $insertedEntities) + { + $obj = new $this->class(); + foreach ($this->getColumnFormatters() as $column => $format) { + if (null !== $format) { + $obj->setByName($column, is_callable($format) ? $format($insertedEntities, $obj) : $format); + } + } + foreach ($this->getModifiers() as $modifier) { + $modifier($obj, $insertedEntities); + } + $obj->save($con); + + return $obj->getPrimaryKey(); + } +} diff --git a/src/Faker/ORM/Propel2/Populator.php b/src/Faker/ORM/Propel2/Populator.php new file mode 100644 index 0000000000..47ea08c57b --- /dev/null +++ b/src/Faker/ORM/Propel2/Populator.php @@ -0,0 +1,92 @@ +generator = $generator; + } + + /** + * Add an order for the generation of $number records for $entity. + * + * @param mixed $entity A Propel ActiveRecord classname, or a \Faker\ORM\Propel2\EntityPopulator instance + * @param int $number The number of entities to populate + */ + public function addEntity($entity, $number, $customColumnFormatters = array(), $customModifiers = array()) + { + if (!$entity instanceof \Faker\ORM\Propel2\EntityPopulator) { + $entity = new \Faker\ORM\Propel2\EntityPopulator($entity); + } + $entity->setColumnFormatters($entity->guessColumnFormatters($this->generator)); + if ($customColumnFormatters) { + $entity->mergeColumnFormattersWith($customColumnFormatters); + } + $entity->setModifiers($entity->guessModifiers($this->generator)); + if ($customModifiers) { + $entity->mergeModifiersWith($customModifiers); + } + $class = $entity->getClass(); + $this->entities[$class] = $entity; + $this->quantities[$class] = $number; + } + + /** + * Populate the database using all the Entity classes previously added. + * + * @param PropelPDO $con A Propel connection object + * + * @return array A list of the inserted PKs + */ + public function execute($con = null) + { + if (null === $con) { + $con = $this->getConnection(); + } + $isInstancePoolingEnabled = Propel::isInstancePoolingEnabled(); + Propel::disableInstancePooling(); + $insertedEntities = array(); + $con->beginTransaction(); + foreach ($this->quantities as $class => $number) { + for ($i=0; $i < $number; $i++) { + $insertedEntities[$class][]= $this->entities[$class]->execute($con, $insertedEntities); + } + } + $con->commit(); + if ($isInstancePoolingEnabled) { + Propel::enableInstancePooling(); + } + + return $insertedEntities; + } + + protected function getConnection() + { + // use the first connection available + $class = key($this->entities); + + if (!$class) { + throw new \RuntimeException('No class found from entities. Did you add entities to the Populator ?'); + } + + $peer = $class::TABLE_MAP; + + return Propel::getConnection($peer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } +} From 872544150a004ba8286099c58b3d7d144fc22d58 Mon Sep 17 00:00:00 2001 From: Dhaval Patel Date: Thu, 10 Mar 2016 21:28:43 -0500 Subject: [PATCH 2/3] Update CONECTION_WRITE reference CONNECTION_WRITE has been moved under ServiceContainerInterface in propel2 --- src/Faker/ORM/Propel2/Populator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Faker/ORM/Propel2/Populator.php b/src/Faker/ORM/Propel2/Populator.php index 47ea08c57b..c0efe3b82c 100644 --- a/src/Faker/ORM/Propel2/Populator.php +++ b/src/Faker/ORM/Propel2/Populator.php @@ -87,6 +87,6 @@ protected function getConnection() $peer = $class::TABLE_MAP; - return Propel::getConnection($peer::DATABASE_NAME, Propel::CONNECTION_WRITE); + return Propel::getConnection($peer::DATABASE_NAME, ServiceContainerInterface::CONNECTION_WRITE); } } From 3b907d8973ee2c2f10e801201f9d82993876e8a3 Mon Sep 17 00:00:00 2001 From: Dhaval Patel Date: Fri, 11 Mar 2016 00:53:04 -0500 Subject: [PATCH 3/3] Remove deprecated method isEpochTemporal in Propel2 --- src/Faker/ORM/Propel2/ColumnTypeGuesser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Faker/ORM/Propel2/ColumnTypeGuesser.php b/src/Faker/ORM/Propel2/ColumnTypeGuesser.php index 78cb8487c6..13e4ee4036 100644 --- a/src/Faker/ORM/Propel2/ColumnTypeGuesser.php +++ b/src/Faker/ORM/Propel2/ColumnTypeGuesser.php @@ -25,7 +25,7 @@ public function guessFormat(ColumnMap $column) { $generator = $this->generator; if ($column->isTemporal()) { - if ($column->isEpochTemporal()) { + if ($column->getType() == PropelTypes::BU_DATE || $column->getType() == PropelTypes::BU_TIMESTAMP) { return function () use ($generator) { return $generator->dateTime; };