Skip to content

Commit

Permalink
Merge branch 'cake3_next' into cake3
Browse files Browse the repository at this point in the history
  • Loading branch information
pabloelcolombiano committed Jun 9, 2021
2 parents 0afa9ab + 475b4c2 commit 2c1c80f
Show file tree
Hide file tree
Showing 18 changed files with 607 additions and 6 deletions.
38 changes: 37 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,45 @@ Here is a quick example, detailed in this section:
$article = ArticleFactory::make(5)->with('Authors[3].Address.City.Country', ['name' => 'Kenya'])->persist();
```

#### On the command line:
Factories can also conveniently populate your database in order to test your application on the browser.
The following command will persist 5 articles, each with 3 irish authors, considering that the `ArticleFactory` class features
a `withThreeIrishAuthors()` method:
```$xslt
bin/cake fixture_factories_persist Authors -n 5 -m withThreeIrishAuthors
```
The option `--dry-run` or `-d` will display the output without persisting.
The option `-c` will persist in the connection provided (default is `test`).
The option `-w` will create associated fixtures.

The `fixture_factories_persist` command is featured on CakePHP 4 only (open to contribution for CakePHP 3).

#### Scenarios:

You can create scenarios that will persist a multitude of test fixtures. 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.

Scenarios should implement the `CakephpFixtureFactories\Scenario\FixtureScenarioInterface` class.
[This test](tests/TestCase/Scenario/FixtureScenarioTest.php) provides an example on how to use scenarios.

## Querying the database

Because the fixture factories are closely related to the database, the package provide two methods to conveniently
query the database. Note that both methods will by-pass the `beforeFind` event, facilitating the inspection of your
test database.

#### ArticleFactory::find()
This method will return a query on the table related to the given factory.

#### ArticleFactory::count()
This method will return the number of entries in the table of the given factory.

## [Test Lifecycle](docs/lifecycle.md)

The only step performed by the package's test suite is to truncate *dirty* tables before each test.
The only step performed by the package's test suite is to truncate *dirty* tables before each test. More documentation
on the management of the test database may be found
[in the cakephp test suite light documentation](https://github.com/vierge-noire/cakephp-test-suite-light).

## License

Expand Down
20 changes: 20 additions & 0 deletions src/Error/FixtureScenarioException.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 FixtureScenarioException extends Exception
{
}
23 changes: 23 additions & 0 deletions src/Factory/BaseFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

use Cake\Datasource\EntityInterface;
use Cake\I18n\I18n;
use Cake\ORM\Query;
use Cake\ORM\Table;
use Cake\ORM\TableRegistry;
use CakephpFixtureFactories\Error\PersistenceException;
Expand Down Expand Up @@ -557,4 +558,26 @@ public function mergeAssociated(array $data): self

return $this;
}

/**
* Query the factory's related table without before find.
*
* @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
*/
public static function find(string $type = 'all', array $options = []): Query
{
return self::make()->getTable()->find($type, $options);
}

/**
* Count the factory's related table entries without before find.
*
* @return int
*/
public static function count(): int
{
return self::find()->count();
}
}
5 changes: 2 additions & 3 deletions src/Factory/DataCompiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ public function getCompiledTemplateData()
* @param bool $setPrimaryKey Set the primary key if this entity is alone or the first of an array.
* @return \Cake\Datasource\EntityInterface
*/
public function compileEntity($injectedData, $setPrimaryKey = false): EntityInterface
public function compileEntity($injectedData = [], bool $setPrimaryKey = false): EntityInterface
{
if ($injectedData instanceof EntityInterface) {
$entity = $injectedData;
Expand Down Expand Up @@ -280,7 +280,6 @@ private function mergeWithAssociatedData(EntityInterface $entity): self
private function mergeWithToOne(EntityInterface $entity, string $associationName, array $data)
{
$count = count($data);
$associationName = Inflector::singularize($associationName);
/** @var BaseFactory $factory */
$factory = $data[$count - 1];

Expand Down Expand Up @@ -348,7 +347,7 @@ public function getMarshallerAssociationName(string $associationName): string
$result = [];
$cast = explode('.', $associationName);
$table = $this->getFactory()->getRootTableRegistry();
foreach ($cast as $i => $ass) {
foreach ($cast as $ass) {
$association = $table->getAssociation($ass);
$result[] = $association->getProperty();
$table = $association->getTarget();
Expand Down
100 changes: 100 additions & 0 deletions src/Factory/FactoryAwareTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?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.0
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/

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 Additional arguments for `BaseFactory::make`
* @return \CakephpFixtureFactories\Factory\BaseFactory
* @throws \CakephpFixtureFactories\Error\FactoryNotFoundException if the factory could not be found
* @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::check('TestFixtureNamespace')) {
return Configure::read('TestFixtureNamespace');
} else {
return (
$plugin ?
str_replace('/', '\\', $plugin) :
Configure::read('App.namespace', 'App')
) . '\Test\Factory';
}
}
}
25 changes: 25 additions & 0 deletions src/Scenario/FixtureScenarioInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?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.0
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/

namespace CakephpFixtureFactories\Scenario;

interface FixtureScenarioInterface
{
/**
* Create your bunch of test fixtures in this method.
*
* @return void
*/
public function load();
}
49 changes: 49 additions & 0 deletions src/Scenario/ScenarioAwareTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?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.0
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/

namespace CakephpFixtureFactories\Scenario;

use CakephpFixtureFactories\Error\FixtureScenarioException;
use CakephpFixtureFactories\Factory\FactoryAwareTrait;

trait ScenarioAwareTrait
{
use FactoryAwareTrait;

/**
* Load a given fixture scenario
*
* @param string $scenario Name of the scenario or fully qualified class.
* @return void
*/
public function loadFixtureScenario(string $scenario): void
{
if (!class_exists($scenario)) {
// phpcs:disable
@[$scenarioName, $plugin] = array_reverse(explode('.', $scenario));
// phpcs:enable
$scenarioNamespace = trim($this->getFactoryNamespace($plugin), 'Factory') . 'Scenario';
$scenario = $scenarioNamespace . '\\' . $scenarioName . 'Scenario';
}

if (!is_subclass_of($scenario, FixtureScenarioInterface::class)) {
$msg = "The class {$scenario} must implement " . FixtureScenarioInterface::class;
throw new FixtureScenarioException($msg);
}

/** @var \CakephpFixtureFactories\Scenario\FixtureScenarioInterface $scenario */
$scenario = new $scenario();
$scenario->load();
}
}
10 changes: 10 additions & 0 deletions tests/Factory/ArticleFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,14 @@ public function withHiddenBiography(string $text)
Article::HIDDEN_PARAGRAPH_PROPERTY_NAME => $text
]);
}

public function published()
{
return $this->patchData(['published' => true]);
}

public function unpublished()
{
return $this->patchData(['published' => false]);
}
}
45 changes: 45 additions & 0 deletions tests/Factory/ArticlesAuthorFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?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\Test\Factory;

use CakephpFixtureFactories\Factory\BaseFactory;
use Faker\Generator;

class ArticlesAuthorFactory extends BaseFactory
{
/**
* Defines the Table Registry used to generate entities with
*
* @return string
*/
protected function getRootTableRegistryName(): string
{
return 'ArticlesAuthors';
}

/**
* Defines the default values of you factory. Useful for
* not nullable fields.
* Use the patchData method to set the field values.
* You may use methods of the factory here
*
* @return void
*/
protected function setDefaultTemplate(): void
{
$this->setDefaultData(function (Generator $faker) {
return [];
});
}
}
5 changes: 5 additions & 0 deletions tests/Factory/AuthorFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,9 @@ public function withAddress($parameter = null)
{
return $this->with('Address', AddressFactory::make($parameter));
}

public function fromCountry(string $name)
{
return $this->with('Address.City.Country', CountryFactory::make(compact('name')));
}
}
Loading

0 comments on commit 2c1c80f

Please sign in to comment.