Skip to content

Commit

Permalink
Merge pull request #68 from vierge-noire/next
Browse files Browse the repository at this point in the history
Postgres update
  • Loading branch information
pabloelcolombiano authored Apr 15, 2021
2 parents 271abd2 + 0a94895 commit aff688a
Show file tree
Hide file tree
Showing 15 changed files with 306 additions and 186 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ Or move that logic in your `UserFactory` by creating your own `withPermission` m

Creating or persisting test data is made ridiculously simple, and your tests get readable.

Given your preferred style, you can either use static factory instance getter as above or embeds `FactoryAwareTrait` in your tests :

`$users = $this->getFactory('User', 3)->withPermission('some-permission')->getEntity()`.

## Installation
For CakePHP 4.x:
```
Expand All @@ -50,7 +54,7 @@ protected function bootstrapCli(): void
}
```

and set up the test listener in `phpunit.xml.dist` by running:
and set up the test listener in `phpunit.xml.dist` by running:
```
bin/cake fixture_factories_setup
```
Expand Down
50 changes: 40 additions & 10 deletions docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ Here are some examples of how to use the fixture factories.
One article with a random title, as defined in the factory [on the previous page](factories.md):
```php
$article = ArticleFactory::make()->getEntity();
```
```
Two articles with different random titles:
```php
$articles = ArticleFactory::make(2)->getEntities();
```
```
One article with title set to 'Foo':
```php
$article = ArticleFactory::make(['title' => 'Foo'])->getEntity();
```
```
Three articles with the title set to 'Foo':
```php
$articles = ArticleFactory::make(['title' => 'Foo'], 3)->getEntities();
Expand All @@ -42,24 +42,54 @@ In order to persist the data generated, use the method `persist` instead of `get
$articles = ArticleFactory::make(3)->persist();
```

### Using `FactoryAwareTrait`
All examples above are using static getter to fetch a factory instance. As convenience and kinda syntactic sugar, you can use the `FactoryAwareTrait::getFactory` instead.

`getFactory` is more tolerant on provided name, as you can use plurals or lowercased names. All arguments passed after factory name will be cast to `BaseFactory::make`.

```php
use App\Test\Factory\ArticleFactory;
use CakephpFixtureFactories\Factory\FactoryAwareTrait;

class MyTest extends TestCase
{
use FactoryAwareTrait;

public function myTest(): void
{
// Static getter style
$article = ArticleFactory::make()->getEntity();
$article = ArticleFactory::make(['title' => 'Foo'])->getEntity();
$articles = ArticleFactory::make(3)->getEntities();
$articles = ArticleFactory::make(['title' => 'Foo'], 3)->getEntities();

// Exactly the same in FactoryAwareTrait style
$article = $this->getFactory('Article')->getEntity();
$article = $this->getFactory('Article', ['title' => 'Foo'])->getEntity();
$articles = $this->getFactory('Article', 3)->getEntities();
$articles = $this->getFactory('Article', ['title' => 'Foo'], 3)->getEntities();
}
}
```

### Chaining methods
The aim of the test fixture factories is to bring business coherence in your test fixtures.
This can be simply achieved using the chainable methods of your factories. As long as those return `$this`, you may chain as much methods as you require.
In the following example, we make use of a method in the Article factory in order to easily create articles with a job title.
It is a simple study case, but this could be any pattern of your business logic.
It is a simple study case, but this could be any pattern of your business logic.
```php
$articleFactory = ArticleFactory::make(['title' => 'Foo']);
$articleFoo1 = $articleFactory->persist();
$articleFoo2 = $articleFactory->persist();
$articleJobOffer = $articleFactory->setJobTitle()->persist();
```

The two first articles have a title set two 'Foo'. The third one has a job title, which is randomly generated by fake, as defined in the
`ArticleFactory`.
`ArticleFactory`.

### Populating associations: the _with_ method
If you have baked your factories with the option `-m` or `--methods`, you will have noticed that a method for each association
has been inserted in the factories. This will assist you creating fixtures for the associated models. For example, we can
has been inserted in the factories. This will assist you creating fixtures for the associated models. For example, we can
create an article with 10 authors as follows:
```php
use App\Test\Factory\ArticleFactory;
Expand Down Expand Up @@ -98,7 +128,7 @@ data created:
$article = ArticleFactory::make(5)->with('Authors[3].Address.City.Country', ['name' => 'Kenya'])->persist();
```
will create 5 articles, having themselves each 3 different associated authors, all located in Kenya.

It is also possible to specify the fields of a toMany associated model.
For example, if we wish to create a random country with two cities having known names:

Expand Down Expand Up @@ -147,4 +177,4 @@ $articles = ArticleFactory::make(function(ArticleFactory $factory, Generator $fa
'title' => $faker->text,
];
}, 3)->persist();
```
```
2 changes: 1 addition & 1 deletion run_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ DRIVER=$1;
echo "Starting PHPUNIT tests"
export DB_DRIVER=$DRIVER

./vendor/bin/phpunit
./vendor/bin/phpunit --stop-on-failure
19 changes: 6 additions & 13 deletions src/Command/BakeFixtureFactoryCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@
use Cake\ORM\Table;
use Cake\ORM\TableRegistry;
use Cake\Utility\Inflector;
use CakephpFixtureFactories\Util;
use CakephpFixtureFactories\Factory\FactoryAwareTrait;
use ReflectionClass;

class BakeFixtureFactoryCommand extends BakeCommand
{
use FactoryAwareTrait;

/**
* path to Factory directory
*
Expand Down Expand Up @@ -55,15 +57,6 @@ public function name(): string
return 'fixture_factory';
}

/**
* @param string $modelName Name of the model
* @return string Name of the factory file
*/
public function fileName(string $modelName): string
{
return Util::getFactoryNameFromModelName($modelName) . '.php';
}

/**
* @return string Name of the template
*/
Expand Down Expand Up @@ -283,7 +276,7 @@ public function bakeFixtureFactory(string $modelName, Arguments $args, ConsoleIo
$contents = $renderer->generate($this->template());

$path = $this->getPath($args);
$filename = $path . $this->fileName($modelName);
$filename = $path . $this->getFactoryFileName($modelName);

return $io->createFile($filename, $contents, $args->getOption('force') ?? false);
}
Expand All @@ -298,7 +291,7 @@ public function templateData(Arguments $arg): array
'modelNameSingular' => Inflector::singularize($this->modelName),
'modelName' => $this->modelName,
'factory' => Inflector::singularize($this->modelName) . 'Factory',
'namespace' => Util::getFactoryNamespace($this->plugin),
'namespace' => $this->getFactoryNamespace($this->plugin),
];
$methods = [];
if ($arg->getOption('methods')) {
Expand Down Expand Up @@ -337,7 +330,7 @@ public function getAssociations(): array

foreach ($this->getTable()->associations() as $association) {
$modelName = $association->getClassName() ?? $association->getName();
$factory = Util::getFactoryClassFromModelName($modelName);
$factory = $this->getFactoryClassName($modelName);
switch ($association->type()) {
case 'oneToOne':
case 'manyToOne':
Expand Down
20 changes: 20 additions & 0 deletions src/Error/FactoryNotFoundException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);

/**
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) 2020 Juan Pablo Ramirez and Nicolas Masson
* @link https://webrider.de/
* @since 1.0.0
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace CakephpFixtureFactories\Error;

use Cake\Core\Exception\Exception;

class FactoryNotFoundException extends Exception
{
}
10 changes: 6 additions & 4 deletions src/Factory/AssociationBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@
use Cake\Utility\Hash;
use Cake\Utility\Inflector;
use CakephpFixtureFactories\Error\AssociationBuilderException;
use CakephpFixtureFactories\Util;

class AssociationBuilder
{
use FactoryAwareTrait {
getFactory as getFactoryInstance;
}

private $associated = [];

/**
Expand Down Expand Up @@ -179,10 +182,9 @@ public function getAssociatedFactory(string $associationName, $data = []): BaseF
*/
public function getFactoryFromTableName(string $modelName, $data = []): BaseFactory
{
$factoryName = Util::getFactoryClassFromModelName($modelName);
try {
return $factoryName::make($data);
} catch (\Error $e) {
return $this->getFactoryInstance($modelName, $data);
} catch (\Throwable $e) {
throw new AssociationBuilderException($e->getMessage());
}
}
Expand Down
9 changes: 9 additions & 0 deletions src/Factory/BaseFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
*/
namespace CakephpFixtureFactories\Factory;

use Cake\Database\Driver\Postgres;
use Cake\Datasource\EntityInterface;
use Cake\ORM\Table;
use Cake\ORM\TableRegistry;
Expand Down Expand Up @@ -158,6 +159,14 @@ protected function setUp(BaseFactory $factory, int $times): void
$factory->getDataCompiler()->collectAssociationsFromDefaultTemplate();
}

/**
* @return bool
*/
public function isRunningOnPostgresql(): bool
{
return $this->getRootTableRegistry()->getConnection()->config()['driver'] === Postgres::class;
}

/**
* Method to apply all model event listeners, both in the
* related TableRegistry as well as in the Behaviors
Expand Down
14 changes: 9 additions & 5 deletions src/Factory/DataCompiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
use Cake\Utility\Inflector;
use CakephpFixtureFactories\Error\FixtureFactoryException;
use CakephpFixtureFactories\Error\PersistenceException;
use CakephpFixtureFactories\Util;
use InvalidArgumentException;

class DataCompiler
Expand Down Expand Up @@ -445,6 +444,7 @@ public function generateRandomPrimaryKey(string $columnType)
{
switch ($columnType) {
case 'uuid':
case 'string':
$res = $this->getFactory()->getFaker()->uuid;
break;
case 'biginteger':
Expand Down Expand Up @@ -492,13 +492,17 @@ public function setPrimaryKeyOffset($primaryKeyOffset): void
*/
private function updatePostgresSequence(array $primaryKeys): void
{
if (Util::isRunningOnPostgresql($this->getFactory())) {
if ($this->getFactory()->isRunningOnPostgresql()) {
$tableName = $this->getFactory()->getRootTableRegistry()->getTable();

foreach ($primaryKeys as $pk => $offset) {
$this->getFactory()->getRootTableRegistry()->getConnection()->execute(
"SELECT setval('$tableName" . "_$pk" . "_seq', $offset);"
);
$seq = $this->getFactory()->getRootTableRegistry()->getConnection()->execute("
SELECT pg_get_serial_sequence('$tableName','$pk')")->fetchAll()[0][0];
if ($seq !== null) {
$this->getFactory()->getRootTableRegistry()->getConnection()->execute(
"SELECT setval('$seq', $offset);"
);
}
}
}
}
Expand Down
88 changes: 88 additions & 0 deletions src/Factory/FactoryAwareTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php
declare(strict_types=1);

namespace CakephpFixtureFactories\Factory;

use Cake\Core\Configure;
use Cake\Utility\Inflector;
use CakephpFixtureFactories\Error\FactoryNotFoundException;

trait FactoryAwareTrait
{
/**
* Returns a factory instance from factory or model name
*
* Additionnal arguments are passed *as is* to `BaseFactory::make`
*
* @param string $name Factory or model name
* @param string|array[] ...$arguments Additionnal arguments for `BaseFactory::make`
* @return \CakephpFixtureFactories\Factory\BaseFactory
* @see CakephpFixtureFactories\Factory\BaseFactory::make
*/
public function getFactory(string $name, ...$arguments): BaseFactory
{
$factoryClassName = $this->getFactoryClassName($name);

if (class_exists($factoryClassName)) {
return $factoryClassName::make(...$arguments);
}

throw new FactoryNotFoundException("Unable to locate factory class $factoryClassName");
}

/**
* Converts factory or model name to a fully qualified factory class name
*
* @param string $name Factory or model name
* @return string Fully qualified class name
*/
public function getFactoryClassName(string $name): string
{
// phpcs:disable
@[$modelName, $plugin] = array_reverse(explode('.', $name));
// phpcs:enable

return $this->getFactoryNamespace($plugin) . '\\' . $this->getFactoryNameFromModelName($modelName);
}

/**
* Returns the factory file name
*
* @param string $name [description]
* @return string [description]
*/
public function getFactoryFileName(string $name): string
{
return $this->getFactoryNameFromModelName($name) . '.php';
}

/**
* Return the name of the factory from a model name
*
* @param string $modelName Name of the model
* @return string
*/
public static function getFactoryNameFromModelName(string $modelName): string
{
return Inflector::singularize(ucfirst($modelName)) . 'Factory';
}

/**
* Namespace where the factory belongs
*
* @param string|null $plugin name of the plugin, or null if no plugin
* @return string
*/
public function getFactoryNamespace(?string $plugin = null): string
{
if (Configure::read('TestFixtureNamespace')) {
return Configure::read('TestFixtureNamespace');
} else {
return (
$plugin ?
str_replace('/', '\\', $plugin) :
Configure::read('App.namespace', 'App')
) . '\Test\Factory';
}
}
}
Loading

0 comments on commit aff688a

Please sign in to comment.