src/Builder.php
<?php
namespace Bravesheep\Dogmatist;
use Bravesheep\Dogmatist\Exception\BuilderException;
/**
* Builds up the structure for generating samples.
*/
class Builder
{
const DEFAULT_MIN = 0;
const DEFAULT_MAX = 10;
/**
* @var string
*/
private $type;
/**
* @var Dogmatist
*/
protected $dogmatist;
/**
* @var Builder|null
*/
protected $parent;
/**
* @var Field[]
*/
private $fields = [];
/**
* @var callback[]
*/
private $listeners = [];
/**
* @var bool
*/
private $strict;
/**
* @var ConstructorBuilder|null
*/
private $constr;
/**
* @var string|int
*/
private $last_field;
/**
* @var mixed[]
*/
private $parent_links;
/**
* @param string $type
* @param Dogmatist $dogmatist
* @param Builder $parent
* @param bool $strict
*/
public function __construct($type, Dogmatist $dogmatist, Builder $parent = null, $strict = true)
{
$this->type = $type;
$this->dogmatist = $dogmatist;
$this->parent = $parent;
$this->strict = $strict;
$this->parent_links = [];
}
/**
* @param bool $strict
* @return $this
*/
public function setStrict($strict)
{
$this->strict = $strict;
if (null !== $this->constr) {
$this->constr->setStrict($strict);
}
foreach ($this->fields as $field) {
if ($field->isType(Field::TYPE_RELATION)) {
$field->getRelated()->setStrict($strict);
}
}
return $this;
}
/**
* @return bool
*/
public function isStrict()
{
return $this->strict;
}
/**
* @param bool $new If true and a constructor already exists, it will be discarded and a new one will be created.
* @return ConstructorBuilder
*/
public function constructor($new = false)
{
if (null === $this->constr || $new === true) {
$this->constr = new ConstructorBuilder($this->dogmatist, $this);
}
return $this->constr;
}
/**
* @return bool
*/
public function hasConstructor()
{
return null !== $this->constr;
}
/**
* @param Builder $builder
* @return $this
*/
public function setParent(Builder $builder = null)
{
$this->parent = $builder;
return $this;
}
/**
* @param string|int $field
*/
protected function checkState($field)
{
if (!is_string($field) && !is_int($field)) {
$type = gettype($field);
throw new BuilderException("Invalid field key type, must use integer or string, got {$type}");
}
}
/**
* @param string|int $field
* @return Field
*/
public function get($field)
{
$this->checkState($field);
if (!$this->has($field)) {
$this->fields[$field] = new Field($field);
}
$this->last_field = $field;
return $this->fields[$field];
}
/**
* @param string|int $field
* @return bool
*/
public function has($field)
{
return isset($this->fields[$field]);
}
/**
* @param string|int $field
* @return $this
*/
public function single($field)
{
$field = $this->get($field);
$field->setSingular();
return $this;
}
/**
* @param string|int $field
* @param int $min
* @param int $max
* @return $this
*/
public function multiple($field, $min = self::DEFAULT_MIN, $max = self::DEFAULT_MAX)
{
$field = $this->get($field);
$field->setMultiple($min, $max);
return $this;
}
/**
* Fake the contents of a field.
* @param string|int $field
* @param string|callback $type
* @param array $options
* @return $this
*/
public function fake($field, $type, array $options = [])
{
$field = $this->get($field);
$field->setFake($type, $options);
return $this;
}
/**
* @param string|int $field
* @param string $type
* @return Builder
*/
public function relation($field, $type)
{
$child = $this->dogmatist->create($type);
$child->setStrict($this->isStrict());
$child->setParent($this);
$field = $this->get($field);
$field->setRelation($child);
return $child;
}
/**
* @param string|int $field
* @param string|Builder $builder
* @return Builder
*/
public function relationFromCopy($field, $builder)
{
$copy = $this->dogmatist->copy($builder);
$copy->setStrict($this->isStrict());
$copy->setParent($this);
$field = $this->get($field);
$field->setRelation($copy);
return $copy;
}
/**
* @param string|int $relation
* @return Builder
*/
public function in($relation)
{
if ($this->get($relation)->isType(Field::TYPE_RELATION)) {
return $this->get($relation)->getRelated();
}
throw new BuilderException("The field {$relation} is not of type relation");
}
/**
* @param string|int $field
* @return $this
*/
public function none($field)
{
$field = $this->get($field);
$field->setNone();
return $this;
}
/**
* @param string|int $field
* @param mixed $value
* @return $this
*/
public function value($field, $value)
{
$field = $this->get($field);
$field->setValue($value);
return $this;
}
/**
* @param string|int $field
* @param array $values
* @return $this
*/
public function select($field, array $values)
{
$field = $this->get($field);
$field->setSelect($values);
return $this;
}
/**
* @param string|int $field
* @param string|array $target
* @return $this
*/
public function link($field, $target)
{
$field = $this->get($field);
$field->setLink($target);
return $this;
}
/**
* @param string $field
* @param callback $callback
* @return $this
*/
public function callback($field, $callback)
{
$field = $this->get($field);
$field->setCallback($callback);
return $this;
}
/**
* @param string $field
* @param bool $unique
* @return $this
*/
public function unique($field, $unique = true)
{
$field = $this->get($field);
$field->setUnique($unique);
return $this;
}
/**
* @param string|int $field
* @return $this
*/
public function linkParent($field)
{
if ($this->parent instanceof ConstructorBuilder) {
throw new BuilderException("Cannot link to parent when the parent is a constructor");
}
if ($this->parent === null) {
throw new BuilderException("There is no parent to link back to");
}
if ($this instanceof ConstructorBuilder) {
throw new BuilderException("Cannot link back to the parent inside the constructor");
}
$this->parent_links[] = $field;
return $this;
}
/**
* @param string|int $field
* @return $this
*/
public function unlinkParent($field)
{
$this->parent_links = array_filter(
$this->parent_links,
function ($curr) use ($field) { return $curr !== $field; }
);
return $this;
}
/**
* @return mixed[]
*/
public function getParentLinks()
{
return $this->parent_links;
}
/**
* @return bool
*/
public function hasParentLinks()
{
return count($this->parent_links) > 0;
}
/**
* @param bool $unique
* @return $this
*/
public function withUnique($unique = true)
{
if ($this->has($this->last_field)) {
$this->unique($this->last_field, $unique);
}
return $this;
}
/**
* @return $this
*/
public function withSingle()
{
if ($this->has($this->last_field)) {
$this->single($this->last_field);
}
return $this;
}
/**
* @param int $min
* @param int $max
* @return $this
*/
public function withMultiple($min = self::DEFAULT_MIN, $max = self::DEFAULT_MAX)
{
if ($this->has($this->last_field)) {
$this->multiple($this->last_field, $min, $max);
}
return $this;
}
/**
* Return the parent Builder instance, or if there is no parent return the
* attached Dogmatist instance.
* @return Builder|Dogmatist
*/
public function done()
{
if (null !== $this->parent) {
return $this->parent;
}
return $this->dogmatist;
}
/**
* Save this builder as a named builder in the attached Dogmatist instance.
* If no name is given, the typename is used.
* If a builder already exists with the given name, then this method fails. Either
* use `save()` on your Dogmatist instance directly, or choose a different name.
* @param string $name The name under which the Builder should be stored.
* @param int $generate The number of samples to generate, if less than
* or equal to zero, unlimited samples will be generated.
* @return $this
*/
public function save($name = null, $generate = -1)
{
if (null === $name) {
$name = $this->getType();
}
if ($this->dogmatist->getLinkManager()->has($name)) {
throw new BuilderException("A builder with the name '{$name}' already exists");
}
$this->dogmatist->save($this, $name, $generate);
return $this;
}
/**
* Retrieve the fields that are described by this builder.
* @return Field[]
*/
public function getFields()
{
return $this->fields;
}
/**
* Retrieve the type of objects this builder should generate samples for.
* @return string
* @deprecated
*/
public function getClass()
{
return $this->type;
}
/**
* Retrieve the type of objects this builder should generate samples for.
* @return string
*/
public function getType()
{
return $this->type;
}
/**
* Set the type of objects this builder should generate samples for.
* @param string $type
* @return $this
*/
public function setType($type)
{
$this->type = $type;
return $this;
}
/**
* Provide a callback function which should be called whenever a new sample
* is created using this Builder.
* @param callback $callback
* @return $this
*/
public function onCreate($callback)
{
$this->listeners[] = $callback;
return $this;
}
/**
* Retrieve the list of listeners which should be called when a new sample is
* generated.
* @return callback[]
*/
public function getListeners()
{
return $this->listeners;
}
/**
* Returns a clone for the current builder
* @param string $type The new type of the cloned builder.
* @return $this
*/
public function copy($type = null)
{
$builder = new Builder($this->type, $this->dogmatist, $this->parent, $this->strict);
$this->copyData($builder);
if ($type !== null) {
$builder->setType($type);
}
return $builder;
}
protected function copyData(Builder $copy)
{
foreach ($this->fields as $name => $field) {
$new = $field->copy();
if ($new->isType(Field::TYPE_RELATION)) {
$new->getRelated()->setParent($copy);
}
$copy->fields[$name] = $new;
}
$copy->listeners = $this->listeners;
if ($this->constr !== null) {
$copy->constr = $this->constr->copy();
$copy->constr->setParent($copy);
}
$copy->parent_links = $this->parent_links;
}
/**
* @return array|object
*/
public function sample()
{
return $this->dogmatist->sample($this);
}
/**
* @param int $count
* @return array|object[]
* @throws Exception\SampleException
*/
public function samples($count)
{
return $this->dogmatist->samples($this, $count);
}
}