Skip to content
This repository has been archived by the owner on Jul 4, 2018. It is now read-only.

Commit

Permalink
feature #1330 Add the possibility to register forms as services (skalpa)
Browse files Browse the repository at this point in the history
This PR was squashed before being merged into the 2.0.x-dev branch (closes #1330).

Discussion
----------

Add the possibility to register forms as services

This allows you to register your form types / form types extensions / form types guessers as Silex services:
```php
    $app['your.type.service'] = function ($app) {
        return new YourServiceFormType();
    };
    $app->extend('form.types', function ($types) use ($app) {
        $types[] = 'your.type.service';
        return $types;
    }));
    ...
    $builder = $app['form.factory']->createBuilder('your.type.service');
```
This is particularly useful for users of Symfony 3.x, now that `FormFactory::createBuilder()` and friends don't allow you to pass an instance of `FormTypeInterface` anymore, preventing you from loading form types that require constructor arguments lazily (without the patch you have to add objects to the `form.types` / `form.type.extensions` / `form.type.guessers` parameters which means every single form of your application will have to get instantiated whenever you use the form component).

The patch contains a new `SilexFormExtension` extension class that handles the dereferencing of services names / lazy loading, new tests, and documentation fixes.

Other quick notes:
* No type checking is performed by the extension, but that's consistent with the way the `FormServiceProvider` works without the patch. Nevertheless, I believe this should be enhanced and will be ready to work on this after the merge if you reckon that would be a good idea.
* I had to change the `DummyFormType` declaration in the tests. You used a class with a `getName()` method with Symfony < 2.8 and one without the method with 2.8+. Now the problem is that the `FormRegistry` from 2.8.x still uses the `getName()` method to store form types, which means it is still needed in some cases. Thus for my tests to pass I had to make sure the class with `getName()` was used with any 2.x version while the one without it was only used with 3.x

Commits
-------

cfcfa14 Add the possibility to register forms as services
  • Loading branch information
fabpot committed May 16, 2016
2 parents e2f16da + cfcfa14 commit ff0d791
Show file tree
Hide file tree
Showing 4 changed files with 283 additions and 6 deletions.
14 changes: 13 additions & 1 deletion doc/providers/form.rst
Expand Up @@ -142,8 +142,12 @@ form by adding constraints on the fields::

You can register form types by extending ``form.types``::

$app['form.types'] = $app->share($app->extend('form.types', function ($types) use ($app) {
$app['your.type.service'] = function ($app) {
return new YourServiceFormType();
};
$app->extend('form.types', function ($types) use ($app) {
$types[] = new YourFormType();
$types[] = 'your.type.service';

return $types;
}));
Expand All @@ -159,16 +163,24 @@ You can register form extensions by extending ``form.extensions``::

You can register form type extensions by extending ``form.type.extensions``::

$app['your.type.extension.service'] = function ($app) {
return new YourServiceFormTypeExtension();
};
$app->extend('form.type.extensions', function ($extensions) use ($app) {
$extensions[] = new YourFormTypeExtension();
$extensions[] = 'your.type.extension.service';

return $extensions;
});

You can register form type guessers by extending ``form.type.guessers``::

$app['your.type.guesser.service'] = function ($app) {
return new YourServiceFormTypeGuesser();
};
$app->extend('form.type.guessers', function ($guessers) use ($app) {
$guessers[] = new YourFormTypeGuesser();
$guessers[] = 'your.type.guesser.service';

return $guessers;
});
Expand Down
122 changes: 122 additions & 0 deletions src/Silex/Provider/Form/SilexFormExtension.php
@@ -0,0 +1,122 @@
<?php

/*
* This file is part of the Silex framework.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Silex\Provider\Form;

use Pimple\Container;
use Symfony\Component\Form\Exception\InvalidArgumentException;
use Symfony\Component\Form\FormExtensionInterface;
use Symfony\Component\Form\FormTypeGuesserChain;

class SilexFormExtension implements FormExtensionInterface
{
private $app;
private $types;
private $typeExtensions;
private $guessers;
private $guesserLoaded = false;
private $guesser;

public function __construct(Container $app, array $types, array $typeExtensions, array $guessers)
{
$this->app = $app;
$this->setTypes($types);
$this->setTypeExtensions($typeExtensions);
$this->setGuessers($guessers);
}

public function getType($name)
{
if (!isset($this->types[$name])) {
throw new InvalidArgumentException(sprintf('The type "%s" is not the name of a registered form type.', $name));
}
if (!is_object($this->types[$name])) {
$this->types[$name] = $this->app[$this->types[$name]];
}

return $this->types[$name];
}

public function hasType($name)
{
return isset($this->types[$name]);
}

public function getTypeExtensions($name)
{
return isset($this->typeExtensions[$name]) ? $this->typeExtensions[$name] : [];
}

public function hasTypeExtensions($name)
{
return isset($this->typeExtensions[$name]);
}

public function getTypeGuesser()
{
if (!$this->guesserLoaded) {
$this->guesserLoaded = true;

if ($this->guessers) {
$guessers = [];
foreach ($this->guessers as $guesser) {
if (!is_object($guesser)) {
$guesser = $this->app[$guesser];
}
$guessers[] = $guesser;
}
$this->guesser = new FormTypeGuesserChain($guessers);
}
}

return $this->guesser;
}

private function setTypes(array $types)
{
$this->types = [];
foreach ($types as $type) {
if (!is_object($type)) {
if (!isset($this->app[$type])) {
throw new InvalidArgumentException(sprintf('Invalid form type. The silex service "%s" does not exist.', $type));
}
$this->types[$type] = $type;
} else {
$this->types[get_class($type)] = $type;
}
}
}

private function setTypeExtensions(array $typeExtensions)
{
$this->typeExtensions = [];
foreach ($typeExtensions as $extension) {
if (!is_object($extension)) {
if (!isset($this->app[$extension])) {
throw new InvalidArgumentException(sprintf('Invalid form type extension. The silex service "%s" does not exist.', $extension));
}
$extension = $this->app[$extension];
}
$this->typeExtensions[$extension->getExtendedType()][] = $extension;
}
}

private function setGuessers(array $guessers)
{
$this->guessers = [];
foreach ($guessers as $guesser) {
if (!is_object($guesser) && !isset($this->app[$guesser])) {
throw new InvalidArgumentException(sprintf('Invalid form type guesser. The silex service "%s" does not exist.', $guesser));
}
$this->guessers[] = $guesser;
}
}
}
8 changes: 5 additions & 3 deletions src/Silex/Provider/FormServiceProvider.php
Expand Up @@ -63,8 +63,13 @@ public function register(Container $app)
return new CsrfExtension($app['csrf.token_manager']);
};

$app['form.extension.silex'] = function ($app) {
return new Form\SilexFormExtension($app, $app['form.types'], $app['form.type.extensions'], $app['form.type.guessers']);
};

$app['form.extensions'] = function ($app) {
$extensions = array(
$app['form.extension.silex'],
new HttpFoundationExtension(),
);

Expand All @@ -82,9 +87,6 @@ public function register(Container $app)
$app['form.factory'] = function ($app) {
return Forms::createFormFactoryBuilder()
->addExtensions($app['form.extensions'])
->addTypes($app['form.types'])
->addTypeExtensions($app['form.type.extensions'])
->addTypeGuessers($app['form.type.guessers'])
->setResolvedTypeFactory($app['form.resolved_type_factory'])
->getFormFactory()
;
Expand Down
145 changes: 143 additions & 2 deletions tests/Silex/Tests/Provider/FormServiceProviderTest.php
Expand Up @@ -53,6 +53,51 @@ public function testFormServiceProviderWillLoadTypes()
$this->assertInstanceOf('Symfony\Component\Form\Form', $form);
}

public function testFormServiceProviderWillLoadTypesServices()
{
$app = new Application();

$app->register(new FormServiceProvider());

$app['dummy'] = function () {
return new DummyFormType();
};
$app->extend('form.types', function ($extensions) {
$extensions[] = 'dummy';

return $extensions;
});

$form = $app['form.factory']
->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', array())
->add('dummy', 'dummy')
->getForm();

$this->assertInstanceOf('Symfony\Component\Form\Form', $form);
}

/**
* @expectedException \Symfony\Component\Form\Exception\InvalidArgumentException
* @expectedExceptionMessage Invalid form type. The silex service "dummy" does not exist.
*/
public function testNonExistentTypeService()
{
$app = new Application();

$app->register(new FormServiceProvider());

$app->extend('form.types', function ($extensions) {
$extensions[] = 'dummy';

return $extensions;
});

$app['form.factory']
->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', array())
->add('dummy', 'dummy')
->getForm();
}

public function testFormServiceProviderWillLoadTypeExtensions()
{
$app = new Application();
Expand All @@ -72,6 +117,51 @@ public function testFormServiceProviderWillLoadTypeExtensions()
$this->assertInstanceOf('Symfony\Component\Form\Form', $form);
}

public function testFormServiceProviderWillLoadTypeExtensionsServices()
{
$app = new Application();

$app->register(new FormServiceProvider());

$app['dummy.form.type.extension'] = function () {
return new DummyFormTypeExtension();
};
$app->extend('form.type.extensions', function ($extensions) {
$extensions[] = 'dummy.form.type.extension';

return $extensions;
});

$form = $app['form.factory']
->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', array())
->add('file', 'Symfony\Component\Form\Extension\Core\Type\FileType', array('image_path' => 'webPath'))
->getForm();

$this->assertInstanceOf('Symfony\Component\Form\Form', $form);
}

/**
* @expectedException \Symfony\Component\Form\Exception\InvalidArgumentException
* @expectedExceptionMessage Invalid form type extension. The silex service "dummy.form.type.extension" does not exist.
*/
public function testNonExistentTypeExtensionService()
{
$app = new Application();

$app->register(new FormServiceProvider());

$app->extend('form.type.extensions', function ($extensions) {
$extensions[] = 'dummy.form.type.extension';

return $extensions;
});

$app['form.factory']
->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', array())
->add('dummy', 'dummy.form.type')
->getForm();
}

public function testFormServiceProviderWillLoadTypeGuessers()
{
$app = new Application();
Expand All @@ -87,6 +177,43 @@ public function testFormServiceProviderWillLoadTypeGuessers()
$this->assertInstanceOf('Symfony\Component\Form\FormFactory', $app['form.factory']);
}

public function testFormServiceProviderWillLoadTypeGuessersServices()
{
$app = new Application();

$app->register(new FormServiceProvider());

$app['dummy.form.type.guesser'] = function () {
return new FormTypeGuesserChain(array());
};
$app->extend('form.type.guessers', function ($guessers) {
$guessers[] = 'dummy.form.type.guesser';

return $guessers;
});

$this->assertInstanceOf('Symfony\Component\Form\FormFactory', $app['form.factory']);
}

/**
* @expectedException \Symfony\Component\Form\Exception\InvalidArgumentException
* @expectedExceptionMessage Invalid form type guesser. The silex service "dummy.form.type.guesser" does not exist.
*/
public function testNonExistentTypeGuesserService()
{
$app = new Application();

$app->register(new FormServiceProvider());

$app->extend('form.type.guessers', function ($extensions) {
$extensions[] = 'dummy.form.type.guesser';

return $extensions;
});

$factory = $app['form.factory'];
}

public function testFormServiceProviderWillUseTranslatorIfAvailable()
{
$app = new Application();
Expand Down Expand Up @@ -159,8 +286,22 @@ public function testFormCsrf()
}
}

class DummyFormType extends AbstractType
{
if (!class_exists('Symfony\Component\Form\Deprecated\FormEvents')) {
class DummyFormType extends AbstractType
{
}
} else {
// FormTypeInterface::getName() is needed by the form component 2.8.x
class DummyFormType extends AbstractType
{
/**
* @return string The name of this type
*/
public function getName()
{
return 'dummy';
}
}
}

if (method_exists('Symfony\Component\Form\AbstractType', 'configureOptions')) {
Expand Down

0 comments on commit ff0d791

Please sign in to comment.