Skip to content

Commit

Permalink
Add support for data binding in collection types (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
andyexeter authored Jan 29, 2024
1 parent d94af85 commit 73ee2a7
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 5 deletions.
10 changes: 10 additions & 0 deletions src/Type/AbstractType.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
use Palmtree\ArgParser\ArgParser;
use Palmtree\Form\Constraint\ConstraintInterface;
use Palmtree\Form\Constraint\NotBlank;
use Palmtree\Form\Exception\InvalidTypeException;
use Palmtree\Form\Exception\OutOfBoundsException;
use Palmtree\Form\Form;
use Palmtree\Form\TypeLocator;
use Palmtree\Html\Element;
use Palmtree\NameConverter\SnakeCaseToCamelCaseNameConverter;
use Palmtree\NameConverter\SnakeCaseToHumanNameConverter;
Expand Down Expand Up @@ -45,6 +47,8 @@ abstract class AbstractType implements TypeInterface
protected $constraints = [];
/** @var SnakeCaseToHumanNameConverter */
protected $nameConverter;
/** @var TypeLocator */
protected $typeLocator;
/** @var bool */
protected $mapped = true;
/** @var array */
Expand All @@ -56,6 +60,7 @@ abstract class AbstractType implements TypeInterface
public function __construct(array $args = [])
{
$this->nameConverter = new SnakeCaseToHumanNameConverter();
$this->typeLocator = new TypeLocator();

$this->args = $this->parseArgs($args);

Expand Down Expand Up @@ -384,8 +389,13 @@ public function addChild(TypeInterface $child): TypeInterface

public function add(string $name, string $class, array $options = []): TypeInterface
{
if (!$class = $this->typeLocator->getTypeClass($class)) {
throw new InvalidTypeException('Type could not be found');
}

/** @var TypeInterface $type */
$type = new $class($options);

$type->setName($name);

if ($type->getLabel() === null) {
Expand Down
17 changes: 15 additions & 2 deletions src/Type/CollectionType.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Palmtree\Form\Type;

use Palmtree\Form\Exception\InvalidTypeException;
use Palmtree\Html\Element;

class CollectionType extends AbstractType
Expand Down Expand Up @@ -61,11 +62,15 @@ public function addChild(TypeInterface $child): TypeInterface
}

/**
* @psalm-param class-string<TypeInterface> $entryType
* @psalm-param class-string<TypeInterface>|string $entryType
*/
public function setEntryType(string $entryType): void
{
$this->entryType = $entryType;
if (!$class = $this->typeLocator->getTypeClass($entryType)) {
throw new InvalidTypeException('Type could not be found');
}

$this->entryType = $class;
}

/**
Expand Down Expand Up @@ -141,6 +146,14 @@ private function buildEntry(int $position = 0, $data = null): TypeInterface

if (\func_num_args() > 0) {
$entry->setData($data);

if (\is_array($data)) {
foreach ($entry->all() as $child) {
if (isset($data[$child->getName()])) {
$child->setData($data[$child->getName()]);
}
}
}
}

return $entry;
Expand Down
12 changes: 10 additions & 2 deletions src/TypeLocator.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

class TypeLocator
{
/** @var array<string, string> Map of types where key is the shorthand name e.g 'text' and value is the FCQN. */
/** @var array<string, class-string<TypeInterface>> Map of types where key is the shorthand name e.g 'text' and value is the FCQN. */
private static $types = [];

private const TYPE_KEYS = [
Expand All @@ -21,18 +21,26 @@ public function __construct()
{
if (empty(self::$types)) {
foreach (glob(__DIR__ . '/Type/*Type.php', \GLOB_NOSORT) ?: [] as $file) {
self::$types[strtolower(basename($file, 'Type.php'))] = __NAMESPACE__ . '\\Type\\' . basename($file, '.php');
/** @var class-string<TypeInterface> $class */
$class = __NAMESPACE__ . '\\Type\\' . basename($file, '.php');
self::$types[strtolower(basename($file, 'Type.php'))] = $class;
}
}
}

/**
* @param string|class-string<TypeInterface> $type
*
* @return class-string<TypeInterface>|null
*/
public function getTypeClass(string $type): ?string
{
if (isset(self::$types[$type])) {
return self::$types[$type];
}

if (class_exists($type)) {
/** @var class-string<TypeInterface> */
return $type;
}

Expand Down
82 changes: 81 additions & 1 deletion tests/DataBindingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
use Palmtree\Form\Form;
use Palmtree\Form\FormBuilder;
use Palmtree\Form\Test\Fixtures\Person;
use Palmtree\Form\Test\Fixtures\PersonType;
use Palmtree\Form\Type\CollectionType;
use PHPUnit\Framework\TestCase;

class DataBindingTest extends TestCase
Expand Down Expand Up @@ -168,6 +170,83 @@ public function testArrayAccessDataMapperThrowsOutOfBoundsException(): void
]);
}

public function testCollectionOneWayDataBinding(): void
{
$data = [
'people' => [
[
'name' => 'John Smith',
'emailAddress' => 'john.smith@example.org',
'age' => 42,
'favouriteConsole' => 'PlayStation',
'interests' => ['football', 'gaming'],
'signup' => false,
'pets' => [],
],
],
];

$builder = new FormBuilder('test', $data);

$builder->add('people', CollectionType::class, [
'entry_type' => PersonType::class,
]);

$builder->add('send_message', 'submit');

$form = $builder->getForm();
$form->render();

$formPeople = $form->get('people')->getData();

self::assertSame($data['people'], $formPeople);
}

public function testCollectionTwoWayDataBinding(): void
{
$data = new \ArrayObject([
'people' => [
[
'name' => 'John Smith',
'emailAddress' => 'john.smith@example.org',
'age' => 42,
'favouriteConsole' => 'PlayStation',
'interests' => ['football', 'gaming'],
'signup' => false,
'pets' => [],
],
],
]);

$builder = new FormBuilder('test', $data);

$builder->add('people', CollectionType::class, [
'entry_type' => PersonType::class,
]);

$builder->add('send_message', 'submit');

$form = $builder->getForm();

$form->submit([
'people' => [
[
'name' => 'Bob Smith',
'emailAddress' => 'bob.smith@example.org',
'age' => 45,
'favouriteConsole' => 'Xbox',
'interests' => ['guitar'],
],
],
]);

self::assertSame($data['people'][0]['name'], 'Bob Smith');
self::assertSame($data['people'][0]['emailAddress'], 'bob.smith@example.org');
self::assertSame($data['people'][0]['age'], 45);
self::assertSame($data['people'][0]['favouriteConsole'], 'Xbox');
self::assertSame($data['people'][0]['interests'], ['guitar']);
}

/**
* @param Person|array|\ArrayAccess|\stdClass $person
*/
Expand Down Expand Up @@ -201,7 +280,8 @@ private function buildForm($person): Form
])
->add('pets', 'collection', [
'entry_type' => 'text',
]);
])
;

return $builder->getForm();
}
Expand Down

0 comments on commit 73ee2a7

Please sign in to comment.