From 1972a916539d0bb877df9b16d373c272edd20cfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=B6nthal?= Date: Thu, 5 Sep 2013 13:39:15 +0200 Subject: [PATCH] [Form] Added form debug collector --- .../FrameworkExtension.php | 1 + .../Resources/config/collectors.xml | 6 ++ .../Resources/config/form_debug.xml | 22 ++++++ .../Resources/views/Collector/form.html.twig | 56 +++++++++++++ .../DataCollector/Collector/FormCollector.php | 79 +++++++++++++++++++ .../DataCollector/DataCollectorExtension.php | 43 ++++++++++ .../EventListener/DataCollectorSubscriber.php | 75 ++++++++++++++++++ .../Type/DataCollectorTypeExtension.php | 50 ++++++++++++ .../Collector/FormCollectorTest.php | 46 +++++++++++ .../DataCollectorExtensionTest.php | 47 +++++++++++ .../DataCollectorSubscriberTest.php | 71 +++++++++++++++++ .../Type/DataCollectorTypeExtensionTest.php | 46 +++++++++++ 12 files changed, 542 insertions(+) create mode 100644 src/Symfony/Bundle/FrameworkBundle/Resources/config/form_debug.xml create mode 100644 src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig create mode 100644 src/Symfony/Component/Form/Extension/DataCollector/Collector/FormCollector.php create mode 100644 src/Symfony/Component/Form/Extension/DataCollector/DataCollectorExtension.php create mode 100644 src/Symfony/Component/Form/Extension/DataCollector/EventListener/DataCollectorSubscriber.php create mode 100644 src/Symfony/Component/Form/Extension/DataCollector/Type/DataCollectorTypeExtension.php create mode 100644 src/Symfony/Component/Form/Tests/Extension/DataCollector/Collector/FormCollectorTest.php create mode 100644 src/Symfony/Component/Form/Tests/Extension/DataCollector/DataCollectorExtensionTest.php create mode 100644 src/Symfony/Component/Form/Tests/Extension/DataCollector/EventListener/DataCollectorSubscriberTest.php create mode 100644 src/Symfony/Component/Form/Tests/Extension/DataCollector/Type/DataCollectorTypeExtensionTest.php diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 32487952c8..f00c240d2a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -218,6 +218,7 @@ class FrameworkExtension extends Extension return; } + $loader->load('form_debug.xml'); $loader->load('profiling.xml'); $loader->load('collectors.xml'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.xml index 0e07cdb5d9..ee956f0fa0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.xml @@ -13,6 +13,7 @@ Symfony\Component\HttpKernel\DataCollector\TimeDataCollector Symfony\Component\HttpKernel\DataCollector\MemoryDataCollector Symfony\Bundle\FrameworkBundle\DataCollector\RouterDataCollector + Symfony\Component\Form\Extension\DataCollector\Collector\FormCollector @@ -53,5 +54,10 @@ + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_debug.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_debug.xml new file mode 100644 index 0000000000..a03ea495b5 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_debug.xml @@ -0,0 +1,22 @@ + + + + + + Symfony\Component\Form\Extension\DataCollector\Type\DataCollectorTypeExtension + Symfony\Component\Form\Extension\DataCollector\EventListener\DataCollectorSubscriber + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig new file mode 100644 index 0000000000..7ee053d189 --- /dev/null +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig @@ -0,0 +1,56 @@ +{% extends app.request.isXmlHttpRequest ? 'WebProfilerBundle:Profiler:ajax_layout.html.twig' : 'WebProfilerBundle:Profiler:layout.html.twig' %} + +{% block toolbar %} + {% if collector.dataCount %} + {% set icon %} + Forms + {{ collector.dataCount }} + {% endset %} + + {% include 'WebProfilerBundle:Profiler:toolbar_item.html.twig' with { 'link': profiler_url } %} + {% endif %} +{% endblock %} + +{% block menu %} + + + Forms + {% if collector.dataCount %} + {{ collector.dataCount }} + {% endif %} + +{% endblock %} + +{% block panel %} +

Invalid Forms

+ + {% for formName, fields in collector.data %} +

{{ formName }}

+ + + + + + + + {% for fieldName, field in fields %} + + + + + + + {% endfor %} +
FieldTypeValueMessages
{{ fieldName }}{{ field.type }}{{ field.value }} +
    + {% for errorMessage in field.errors %} +
  • + {{ errorMessage.message }} +
  • + {% endfor %} +
+
+ {% else %} + No invalid form{% if collector.dataCount > 1 %}s{% endif %} detected for this request. + {% endfor %} +{% endblock %} diff --git a/src/Symfony/Component/Form/Extension/DataCollector/Collector/FormCollector.php b/src/Symfony/Component/Form/Extension/DataCollector/Collector/FormCollector.php new file mode 100644 index 0000000000..5cb0080df7 --- /dev/null +++ b/src/Symfony/Component/Form/Extension/DataCollector/Collector/FormCollector.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\DataCollector\Collector; + +use Symfony\Component\Form\FormInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\DataCollector\DataCollector as BaseCollector; + +/** + * DataCollector for Form Validation Failures. + * + * @author Robert Schönthal + */ +class FormCollector extends BaseCollector +{ + /** + * {@inheritDoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + //nothing to do, everything is added with addError() + } + + /** + * Adds a Form-Error to the Collector. + * + * @param FormInterface $form + */ + public function addError(FormInterface $form) + { + $storeData = array( + 'root' => $form->getRoot()->getName(), + 'name' => (string)$form->getPropertyPath(), + 'type' => $form->getConfig()->getType()->getName(), + 'errors' => $form->getErrors(), + 'value' => $this->varToString($form->getViewData()) + ); + + $this->data[$storeData['root']][$storeData['name']] = $storeData; + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return 'form'; + } + + /** + * Returns all collected Data. + * + * @return array + */ + public function getData() + { + return $this->data; + } + + /** + * Returns the number of invalid Forms. + * + * @return integer + */ + public function getDataCount() + { + return count($this->data); + } +} diff --git a/src/Symfony/Component/Form/Extension/DataCollector/DataCollectorExtension.php b/src/Symfony/Component/Form/Extension/DataCollector/DataCollectorExtension.php new file mode 100644 index 0000000000..c35a1a0b74 --- /dev/null +++ b/src/Symfony/Component/Form/Extension/DataCollector/DataCollectorExtension.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\DataCollector; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Form\AbstractExtension; + +/** + * DataCollectorExtension for collecting Form Validation Failures. + * + * @author Robert Schönthal + */ +class DataCollectorExtension extends AbstractExtension +{ + /** + * @var EventSubscriberInterface + */ + private $eventSubscriber; + + public function __construct(EventSubscriberInterface $eventSubscriber) + { + $this->eventSubscriber = $eventSubscriber; + } + + /** + * {@inheritDoc} + */ + protected function loadTypeExtensions() + { + return array( + new Type\DataCollectorTypeExtension($this->eventSubscriber) + ); + } +} diff --git a/src/Symfony/Component/Form/Extension/DataCollector/EventListener/DataCollectorSubscriber.php b/src/Symfony/Component/Form/Extension/DataCollector/EventListener/DataCollectorSubscriber.php new file mode 100644 index 0000000000..be343eed1f --- /dev/null +++ b/src/Symfony/Component/Form/Extension/DataCollector/EventListener/DataCollectorSubscriber.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\DataCollector\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Form\Extension\DataCollector\Collector\FormCollector; +use Symfony\Component\Form\FormEvent; +use Symfony\Component\Form\FormEvents; +use Symfony\Component\Form\FormInterface; + +/** + * EventSubscriber for adding Form Validation Failures to the DataCollector. + * + * @author Robert Schönthal + */ +class DataCollectorSubscriber implements EventSubscriberInterface +{ + /** + * @var FormCollector + */ + private $collector; + + public function __construct(FormCollector $collector) + { + $this->collector = $collector; + } + + /** + * {@inheritDoc} + */ + public static function getSubscribedEvents() + { + return array(FormEvents::POST_SUBMIT => array('addToProfiler', -255)); + } + + /** + * Searches for invalid Form-Data and adds them to the Collector. + * + * @param FormEvent $event The event object + */ + public function addToProfiler(FormEvent $event) + { + $form = $event->getForm(); + + if ($form->isRoot()) { + $this->addErrors($form); + } + } + + /** + * Adds an invalid Form-Element to the Collector. + * + * @param FormInterface $form + */ + private function addErrors(FormInterface $form) + { + if ($form->getErrors()) { + $this->collector->addError($form); + } + + //recursively add all child errors + foreach ($form->all() as $field) { + $this->addErrors($field); + } + } +} diff --git a/src/Symfony/Component/Form/Extension/DataCollector/Type/DataCollectorTypeExtension.php b/src/Symfony/Component/Form/Extension/DataCollector/Type/DataCollectorTypeExtension.php new file mode 100644 index 0000000000..8bc849b74b --- /dev/null +++ b/src/Symfony/Component/Form/Extension/DataCollector/Type/DataCollectorTypeExtension.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\DataCollector\Type; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Form\AbstractTypeExtension; +use Symfony\Component\Form\FormBuilderInterface; + +/** + * DataCollector Type Extension for collecting invalid Forms. + * + * @author Robert Schönthal + */ +class DataCollectorTypeExtension extends AbstractTypeExtension +{ + /** + * @var EventSubscriberInterface + */ + private $eventSubscriber; + + public function __construct(EventSubscriberInterface $eventSubscriber) + { + $this->eventSubscriber = $eventSubscriber; + } + + /** + * {@inheritDoc} + */ + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder->addEventSubscriber($this->eventSubscriber); + } + + /** + * {@inheritDoc} + */ + public function getExtendedType() + { + return 'form'; + } +} diff --git a/src/Symfony/Component/Form/Tests/Extension/DataCollector/Collector/FormCollectorTest.php b/src/Symfony/Component/Form/Tests/Extension/DataCollector/Collector/FormCollectorTest.php new file mode 100644 index 0000000000..99b585d888 --- /dev/null +++ b/src/Symfony/Component/Form/Tests/Extension/DataCollector/Collector/FormCollectorTest.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Tests\Extension\DataCollector\Collector; + +use Symfony\Component\Form\Extension\DataCollector\Collector\FormCollector; + +/** + * @covers Symfony\Component\Form\Extension\DataCollector\Collector\FormCollector + */ +class FormCollectorTest extends \PHPUnit_Framework_TestCase +{ + public function testAddError() + { + $form = $this->getMock('Symfony\Component\Form\Test\FormInterface'); + + $type = $this->getMock('Symfony\Component\Form\FormTypeInterface'); + $type->expects($this->atLeastOnce())->method('getName')->will($this->returnValue('fizz')); + + $config = $this->getMock('Symfony\Component\Form\FormConfigInterface'); + $config->expects($this->atLeastOnce())->method('getType')->will($this->returnValue($type)); + + $form->expects($this->atLeastOnce())->method('getRoot')->will($this->returnSelf()); + $form->expects($this->atLeastOnce())->method('getPropertyPath')->will($this->returnValue('bar')); + $form->expects($this->atLeastOnce())->method('getName')->will($this->returnValue('foo')); + $form->expects($this->atLeastOnce())->method('getViewData')->will($this->returnValue('bazz')); + $form->expects($this->atLeastOnce())->method('getErrors')->will($this->returnValue(array('foo'))); + $form->expects($this->atLeastOnce())->method('getConfig')->will($this->returnValue($config)); + + + $c = new FormCollector(); + $c->addError($form); + + $this->assertInternalType('array', $c->getData()); + $this->assertEquals(1, $c->getDataCount()); + $this->assertEquals(array('foo' => array('bar' => array('value' => 'bazz', 'root' => 'foo', 'type' => 'fizz', 'name' => 'bar', 'errors' => array('foo')))), $c->getData()); + } +} + \ No newline at end of file diff --git a/src/Symfony/Component/Form/Tests/Extension/DataCollector/DataCollectorExtensionTest.php b/src/Symfony/Component/Form/Tests/Extension/DataCollector/DataCollectorExtensionTest.php new file mode 100644 index 0000000000..14ab0eac1b --- /dev/null +++ b/src/Symfony/Component/Form/Tests/Extension/DataCollector/DataCollectorExtensionTest.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Tests\Extension\DataCollector; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Form\Extension\DataCollector\DataCollectorExtension; + +/** + * @covers Symfony\Component\Form\Extension\DataCollector\DataCollectorExtension + */ +class DataCollectorExtensionTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var DataCollectorExtension + */ + private $extension; + + /** + * @var EventSubscriberInterface + */ + private $eventSubscriber; + + public function setUp() + { + $this->eventSubscriber = $this->getMock('Symfony\Component\EventDispatcher\EventSubscriberInterface'); + $this->extension = new DataCollectorExtension($this->eventSubscriber); + } + + public function testLoadTypeExtensions() + { + $typeExtensions = $this->extension->getTypeExtensions('form'); + + $this->assertInternalType('array', $typeExtensions); + $this->assertCount(1, $typeExtensions); + $this->assertInstanceOf('Symfony\Component\Form\Extension\DataCollector\Type\DataCollectorTypeExtension', array_shift($typeExtensions)); + } +} + \ No newline at end of file diff --git a/src/Symfony/Component/Form/Tests/Extension/DataCollector/EventListener/DataCollectorSubscriberTest.php b/src/Symfony/Component/Form/Tests/Extension/DataCollector/EventListener/DataCollectorSubscriberTest.php new file mode 100644 index 0000000000..9bd8cdf6da --- /dev/null +++ b/src/Symfony/Component/Form/Tests/Extension/DataCollector/EventListener/DataCollectorSubscriberTest.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Tests\Extension\DataCollector\EventListener; + +use Symfony\Component\Form\Extension\DataCollector\Collector\FormCollector; +use Symfony\Component\Form\Extension\DataCollector\EventListener\DataCollectorSubscriber; +use Symfony\Component\Form\FormEvent; +use Symfony\Component\Form\FormEvents; + +/** + * @covers Symfony\Component\Form\Extension\DataCollector\EventListener\DataCollectorSubscriber + */ +class DataCollectorSubscriberTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var DataCollectorSubscriber + */ + private $eventSubscriber; + + /** + * @var FormCollector + */ + private $collector; + + public function setUp() + { + $this->collector = $this->getMockBuilder('Symfony\Component\Form\Extension\DataCollector\Collector\FormCollector')->setMethods(array('addError'))->getMock(); + $this->eventSubscriber = new DataCollectorSubscriber($this->collector); + } + + public function testSubscribedEvents() + { + $events = DataCollectorSubscriber::getSubscribedEvents(); + + $this->assertInternalType('array', $events); + $this->assertEquals(array(FormEvents::POST_SUBMIT => array('addToProfiler', -255)), $events); + } + + public function testAddToProfilerWithSubForm() + { + $form = $this->getMock('Symfony\Component\Form\Test\FormInterface'); + $form->expects($this->atLeastOnce())->method('isRoot')->will($this->returnValue(false)); + + $formEvent = new FormEvent($form, array()); + + $this->collector->expects($this->never())->method('addError'); + $this->eventSubscriber->addToProfiler($formEvent); + } + + public function testAddToProfilerWithInValidForm() + { + $form = $this->getMock('Symfony\Component\Form\Test\FormInterface'); + $form->expects($this->atLeastOnce())->method('isRoot')->will($this->returnValue(true)); + $form->expects($this->atLeastOnce())->method('getErrors')->will($this->returnValue(array('foo'))); + $form->expects($this->once())->method('all')->will($this->returnValue(array())); + + $formEvent = new FormEvent($form, array()); + + $this->collector->expects($this->atLeastOnce())->method('addError')->with($form); + $this->eventSubscriber->addToProfiler($formEvent); + } +} + \ No newline at end of file diff --git a/src/Symfony/Component/Form/Tests/Extension/DataCollector/Type/DataCollectorTypeExtensionTest.php b/src/Symfony/Component/Form/Tests/Extension/DataCollector/Type/DataCollectorTypeExtensionTest.php new file mode 100644 index 0000000000..4039bf6ee6 --- /dev/null +++ b/src/Symfony/Component/Form/Tests/Extension/DataCollector/Type/DataCollectorTypeExtensionTest.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Tests\Extension\DataCollector\Type; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Form\Extension\DataCollector\Type\DataCollectorTypeExtension; + +class DataCollectorTypeExtensionTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var DataCollectorTypeExtension + */ + private $extension; + + /** + * @var EventSubscriberInterface + */ + private $eventSubscriber; + + public function setUp() + { + $this->eventSubscriber = $this->getMock('Symfony\Component\EventDispatcher\EventSubscriberInterface'); + $this->extension = new DataCollectorTypeExtension($this->eventSubscriber); + } + + public function testGetExtendedType() + { + $this->assertEquals('form', $this->extension->getExtendedType()); + } + + public function testBuildForm() + { + $builder = $this->getMock('Symfony\Component\Form\Test\FormBuilderInterface'); + $builder->expects($this->atLeastOnce())->method('addEventSubscriber')->with($this->eventSubscriber); + + $this->extension->buildForm($builder, array()); + } +}