Skip to content

Commit

Permalink
Merge pull request #124 from vierge-noire/next
Browse files Browse the repository at this point in the history
v2.4
  • Loading branch information
pabloelcolombiano authored Sep 8, 2021
2 parents 20a607c + a50672f commit 87d8eb4
Show file tree
Hide file tree
Showing 40 changed files with 778 additions and 433 deletions.
6 changes: 4 additions & 2 deletions .github/workflows/tests_composer2.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ jobs:
db-type: [sqlite, mysql, pgsql]
composer-type: [lowest, stable, dev]
exclude:
# excludes composer lowest on mysql
# excludes composer lowest on 7.2
- composer-type: lowest
php-version: '7.2'
# Mysql CI triggers error on 7.2
- db-type: mysql
composer-type: lowest
php-version: '7.2'


Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ test database with a reusable set of data.
Use the `CakephpFixtureFactories\Scenario\ScenarioAwareTrait`
in your test and load your scenario with the `loadFixtureScenario()` method. You can either provide the
fully qualified name of the scenario class, or place your scenarios under the `App\Test\Scenario` namespace.
Example:
```$xslt
$authors = $this->loadFixtureScenario('NAustralianAuthors', 3);
```
will persist 3 authors associated to the country Australia, as defined [in this example scenario](tests/Scenario/NAustralianAuthorsScenario.php).

Scenarios should implement the `CakephpFixtureFactories\Scenario\FixtureScenarioInterface` class.
[This test](tests/TestCase/Scenario/FixtureScenarioTest.php) provides an example on how to use scenarios.
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@
"pgsql": "bash run_tests.sh Postgres",
"sqlite": "bash run_tests.sh Sqlite",
"phpstan": "vendor/bin/phpstan analyse --memory-limit=-1",
"cs-check": "vendor/bin/phpcs --colors -p -s --extensions=php src/ tests/TestApp/tests/Factory tests/TestApp/plugins/TestPlugin/tests/Factory"
"cs-check": "vendor/bin/phpcs --colors -p -s --extensions=php src/ tests/TestApp/tests/Factory tests/TestApp/plugins/TestPlugin/tests/Factory",
"cs-fix": "vendor/bin/phpcbf --colors -p -s --extensions=php src/ tests/TestApp/tests/Factory"
},
"config": {
"sort-packages": true
Expand Down
15 changes: 14 additions & 1 deletion docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ $articles = ArticleFactory::make(3)->patchData(['title' => 'Foo'])->getEntities(
```
or
```php
$articles = ArticleFactory::make()->patchData(['title' => 'Foo'])->setTimes(3)->getEntities();
$articles = ArticleFactory::make(3)->setField('title', 'Foo')->getEntities();
```
or
```php
$articles = ArticleFactory::make()->setField('title', 'Foo')->setTimes(3)->getEntities();
```
or
```php
Expand Down Expand Up @@ -163,6 +167,15 @@ $threeCitiesAndFiveVillages = CityFactory::make()->threeCitiesAndFiveVillages()-
$country = CountryFactory::make()->with('Cities', $threeCitiesAndFiveVillages)->persist();
```

You may also pass an array of factories:
```php
$threeCitiesAndFiveVillages = CityFactory::make()->threeCitiesAndFiveVillages()->getEntities();
$country = CountryFactory::make()->with('Cities', [
CityFactory::make()->threeCitiesAndFiveVillages(),
CityFactory::make()->capitalCity()
])->persist();
```

### With a callable

In case a given field has not been specified with `faker` in the `setDefaultTemplate` method, all the generated fields of a given factory
Expand Down
4 changes: 1 addition & 3 deletions docs/factories.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,7 @@ class ArticleFactory extends BaseFactory
*/
public function setJobTitle()
{
return $this->patchData([
'title' => $this->getFaker()->jobTitle,
]);
return $this->setField('title', $this->getFaker()->jobTitle());
}
}
```
Expand Down
6 changes: 4 additions & 2 deletions src/Command/BakeFixtureFactoryCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -286,14 +286,16 @@ public function bakeFixtureFactory(string $modelName, Arguments $args, ConsoleIo
*/
public function templateData(Arguments $arg): array
{
$rootTableRegistryName = $this->plugin ? $this->plugin . '.' . $this->modelName : $this->modelName;
$entityClass = '\\' . TableRegistry::getTableLocator()->get($rootTableRegistryName)->getEntityClass();
$data = [
'rootTableRegistryName' => $this->plugin ? $this->plugin . '.' . $this->modelName : $this->modelName,
'rootTableRegistryName' => $rootTableRegistryName,
'entityClass' => $entityClass,
'modelNameSingular' => Inflector::singularize($this->modelName),
'modelName' => $this->modelName,
'factory' => Inflector::singularize($this->modelName) . 'Factory',
'namespace' => $this->getFactoryNamespace($this->plugin),
];
$methods = [];
if ($arg->getOption('methods')) {
$associations = $this->getAssociations();

Expand Down
244 changes: 244 additions & 0 deletions src/Command/PersistCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
<?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 2.3
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace CakephpFixtureFactories\Command;

use Cake\Command\Command;
use Cake\Console\Arguments;
use Cake\Console\ConsoleIo;
use Cake\Console\ConsoleOptionParser;
use Cake\Datasource\ConnectionManager;
use Cake\Datasource\EntityInterface;
use CakephpFixtureFactories\Error\FactoryNotFoundException;
use CakephpFixtureFactories\Error\PersistenceException;
use CakephpFixtureFactories\Factory\BaseFactory;
use CakephpFixtureFactories\Factory\FactoryAwareTrait;

class PersistCommand extends Command
{
use FactoryAwareTrait;

public const ARG_NAME = 'model';

/**
* @inheritDoc
*/
public static function defaultName(): string
{
return 'fixture_factories_persist';
}

/**
* @inheritDoc
*/
protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser
{
$parser
->setDescription('Helper to persist test fixtures on the command line')
->addArgument(self::ARG_NAME, [
'help' => 'The model to persist, accepts plugin notation. Or provide a fully qualified factory class.',
'required' => true,
])
->addOption('plugin', [
'help' => 'Fetch the factory in a plugin.',
'short' => 'p',
])
->addOption('connection', [
'help' => 'Persist into this connection.',
'short' => 'c',
'default' => 'test',
])
->addOption('method', [
'help' => 'Call this method defined in the factory class concerned.',
'short' => 'm',
])
->addOption('number', [
'help' => 'Number of entities to persist.',
'short' => 'n',
'default' => 1,
])
->addOption('with', [
'help' => 'With associated entity/entities.',
'short' => 'w',
])
->addOption('dry-run', [
'help' => 'Display the entities created without persisting.',
'short' => 'd',
'boolean' => true,
]);

return $parser;
}

/**
* @inheritDoc
*/
public function execute(Arguments $args, ConsoleIo $io): int
{
$factory = null;
try {
$factory = $this->parseFactory($args);
// The following order is important, as methods may overwrite $times
$this->setTimes($args, $factory);
$this->with($args, $factory);
$this->attachMethod($args, $factory, $io);
} catch (FactoryNotFoundException $e) {
$io->error($e->getMessage());
$this->abort();
}
if ($args->getOption('dry-run')) {
$this->dryRun($factory, $io);
} else {
$this->persist($factory, $args, $io);
}

return self::CODE_SUCCESS;
}

/**
* @param \Cake\Console\Arguments $args The command arguments
* @return \CakephpFixtureFactories\Factory\BaseFactory
* @throws \CakephpFixtureFactories\Error\FactoryNotFoundException if the factory could not be found
*/
public function parseFactory(Arguments $args): BaseFactory
{
$factoryString = $args->getArgument(self::ARG_NAME);

if (is_subclass_of($factoryString, BaseFactory::class)) {
return $factoryString::make();
}

$plugin = $args->getOption('plugin');
if (is_string($plugin)) {
$factoryString = $plugin . '.' . $factoryString;
}

return $this->getFactory($factoryString);
}

/**
* @param \Cake\Console\Arguments $args Arguments
* @param \CakephpFixtureFactories\Factory\BaseFactory $factory Factory
* @return \CakephpFixtureFactories\Factory\BaseFactory
*/
public function setTimes(Arguments $args, BaseFactory $factory): BaseFactory
{
if (!empty($args->getOption('number'))) {
$times = (int)$args->getOption('number');
} else {
$times = 1;
}

return $factory->setTimes($times);
}

/**
* @param \Cake\Console\Arguments $args Arguments
* @param \CakephpFixtureFactories\Factory\BaseFactory $factory Factory
* @param \Cake\Console\ConsoleIo $io Console
* @return \CakephpFixtureFactories\Factory\BaseFactory
* @throws \CakephpFixtureFactories\Error\FactoryNotFoundException if the method is not found in the factory
*/
public function attachMethod(Arguments $args, BaseFactory $factory, ConsoleIo $io): BaseFactory
{
$method = $args->getOption('method');

if ($method === null) {
return $factory;
}
if (!method_exists($factory, $method)) {
$className = get_class($factory);
$io->error("The method {$method} was not found in {$className}.");
throw new FactoryNotFoundException();
}

return $factory->{$method}();
}

/**
* @param \Cake\Console\Arguments $args Arguments
* @param \CakephpFixtureFactories\Factory\BaseFactory $factory Factory
* @return \CakephpFixtureFactories\Factory\BaseFactory
*/
public function with(Arguments $args, BaseFactory $factory)
{
$with = $args->getOption('with');

if ($with === null) {
return $factory;
}

return $factory->with($with);
}

/**
* Sets the connection passed in argument as the target connection,
* overwriting the table's default connection.
*
* @param string $connection Connection name
* @param \CakephpFixtureFactories\Factory\BaseFactory $factory Factory
* @return void
*/
public function aliasConnection(string $connection, BaseFactory $factory): void
{
ConnectionManager::alias(
$connection,
$factory->getRootTableRegistry()->getConnection()->configName()
);
}

/**
* @param \CakephpFixtureFactories\Factory\BaseFactory $factory Factory
* @param \Cake\Console\Arguments $args Arguments
* @param \Cake\Console\ConsoleIo $io Console
* @return void
*/
public function persist(BaseFactory $factory, Arguments $args, ConsoleIo $io): void
{
$connection = $args->getOption('connection') ?? 'test';
$this->aliasConnection($connection, $factory);

$entities = [];
try {
$entities = $factory->persist();
} catch (PersistenceException $e) {
$io->error($e->getMessage());
$this->abort();
}

$times = is_subclass_of($entities, EntityInterface::class) ? 1 : count($entities);
$factory = get_class($factory);
$io->success("{$times} {$factory} persisted on '{$connection}' connection.");
}

/**
* @param \CakephpFixtureFactories\Factory\BaseFactory $factory Factory
* @param \Cake\Console\ConsoleIo $io Console
* @return void
*/
public function dryRun(BaseFactory $factory, ConsoleIo $io): void
{
$entities = $factory->getEntities();
$times = count($entities);
$factory = get_class($factory);

$io->success("{$times} {$factory} generated on dry run.");
$eol = PHP_EOL;
foreach ($entities as $i => $entity) {
$io->hr();
$io->info("[$i]");
$output = json_encode($entity->toArray(), JSON_PRETTY_PRINT);
$io->info($output);
}
}
}
27 changes: 27 additions & 0 deletions src/Factory/BaseFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,18 @@ public function patchData(array $data)
return $this;
}

/**
* Sets the value for a single field
*
* @param string $field to set
* @param mixed $value to assign
* @return $this
*/
public function setField(string $field, $value)
{
return $this->patchData([$field => $value]);
}

/**
* A protected class dedicated to generating / collecting data for this factory
*
Expand Down Expand Up @@ -588,16 +600,31 @@ public function mergeAssociated(array $data)
* @param string $type the type of query to perform
* @param array $options An array that will be passed to Query::applyOptions()
* @return \Cake\ORM\Query The query builder
* @see Query::find()
*/
public static function find(string $type = 'all', array $options = []): Query
{
return self::make()->getTable()->find($type, $options);
}

/**
* Get from primary key the factory's related table entries, without before find.
*
* @param mixed $primaryKey primary key value to find
* @param array $options options accepted by `Table::find()`
* @return \Cake\Datasource\EntityInterface
* @see Table::get()
*/
public static function get($primaryKey, array $options = []): EntityInterface
{
return self::make()->getTable()->get($primaryKey, $options);
}

/**
* Count the factory's related table entries without before find.
*
* @return int
* @see Query::count()
*/
public static function count(): int
{
Expand Down
Loading

0 comments on commit 87d8eb4

Please sign in to comment.