Made document editing possible

This commit is contained in:
Polonkai Gergely 2012-07-22 19:38:00 +02:00
parent 1b55b079f4
commit b82b4ffd34
136 changed files with 2734 additions and 1289 deletions

View File

@ -6,13 +6,17 @@ use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use KekRozsak\FrontBundle\Entity\Document;
use KekRozsak\FrontBundle\Form\Type\DocumentType;
use KekRozsak\FrontBundle\Extensions\Slugifier;
class DocumentController extends Controller class DocumentController extends Controller
{ {
/** /**
* @Route("/dokumentum/{documentSlug}", name="KekRozsakFrontBundle_documentView") * @Route("/dokumentum/{documentSlug}", name="KekRozsakFrontBundle_documentView")
* @Template() * @Template()
*/ */
public function documentViewAction($documentSlug) public function viewAction($documentSlug)
{ {
$docRepo = $this->getDoctrine()->getRepository('KekRozsakFrontBundle:Document'); $docRepo = $this->getDoctrine()->getRepository('KekRozsakFrontBundle:Document');
if (!($document = $docRepo->findOneBySlug($documentSlug))) if (!($document = $docRepo->findOneBySlug($documentSlug)))
@ -22,4 +26,86 @@ class DocumentController extends Controller
'document' => $document, 'document' => $document,
); );
} }
/**
* @Route("/dokumentumok/uj/{groupSlug}", name="KekRozsakFrontBundle_documentCreate", defaults={"groupslug"=""})
* @Template()
*/
public function createAction($groupSlug = '')
{
$groupRepo = $this->getDoctrine()->getRepository('KekRozsakFrontBundle:Group');
$group = $groupRepo->findOneBySlug($groupSlug);
/* TODO: make group fully optional */
$document = new Document();
$document->setSlug('n-a');
$form = $this->createForm(new DocumentType(), $document);
$request = $this->getRequest();
if ($request->getMethod() == 'POST')
{
$form->bindRequest($request);
if ($form->isValid())
{
$slugifier = new Slugifier();
$document->setSlug($slugifier->slugify($document->getTitle()));
$document->setCreatedAt(new \DateTime('now'));
$document->setCreatedBy($this->get('security.context')->getToken()->getUser());
if ($group)
{
$group->addDocument($document);
$document->addGroup($group);
}
$em = $this->getDoctrine()->getEntityManager();
$em->persist($document);
$em->flush();
/* TODO: return to group list if groupSlug is empty! */
return $this->redirect($this->generateUrl('KekRozsakFrontBundle_groupDocuments', array('groupSlug' => $group->getSlug())));
}
}
return array(
'defaultGroup' => $groupSlug,
'form' => $form->createView(),
);
}
/**
* @Route("/dokumentum/{documentSlug}/szerkesztes", name="KekRozsakFrontBundle_documentEdit")
* @Template()
*/
public function editAction($documentSlug)
{
$documentRepo = $this->getDoctrine()->getRepository('KekRozsakFrontBundle:Document');
if (!($document = $documentRepo->findOneBySlug($documentSlug)))
throw $this->createNotFoundException('A kért dokumentum nem létezik!');
$form = $this->createForm(new DocumentType(), $document);
$request = $this->getRequest();
if ($request->getMethod() == 'POST')
{
$form->bindRequest($request);
if ($form->isValid())
{
$slugifier = new Slugifier();
$document->setSlug($slugifier->slugify($document->getTitle()));
// TODO: add updatedAt, updatedBy, updateReason, etc.
$em = $this->getDoctrine()->getEntityManager();
$em->persist($document);
$em->flush();
return $this->redirect($this->generateUrl('KekRozsakFrontBundle_documentView', array('documentSlug' => $document->getSlug())));
}
}
return array(
'document' => $document,
'form' => $form->createView(),
);
}
} }

View File

@ -4,6 +4,8 @@ namespace KekRozsak\FrontBundle\Entity;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints as DoctrineAssert;
use KekRozsak\SecurityBundle\Entity\User; use KekRozsak\SecurityBundle\Entity\User;
use KekRozsak\FrontBundle\Entity\Group; use KekRozsak\FrontBundle\Entity\Group;
@ -12,6 +14,8 @@ use KekRozsak\FrontBundle\Entity\Group;
* KekRozsak\FrontBundle\Entity\Document * KekRozsak\FrontBundle\Entity\Document
* @ORM\Entity * @ORM\Entity
* @ORM\Table(name="documents") * @ORM\Table(name="documents")
* @DoctrineAssert\UniqueEntity(fields={"title"}, message="Ilyen című dokumentum már létezik. Kérlek válassz másikat!")
* @DoctrineAssert\UniqueEntity(fields={"slug"}, message="Ilyen című dokumentum már létezik. Kérlek válassz másikat!")
*/ */
class Document class Document
{ {
@ -41,6 +45,7 @@ class Document
/** /**
* @var string $title * @var string $title
* @ORM\Column(type="string", length=150, unique=true, nullable=false) * @ORM\Column(type="string", length=150, unique=true, nullable=false)
* @Assert\NotBlank()
*/ */
protected $title; protected $title;
@ -69,6 +74,7 @@ class Document
/** /**
* @var string $slug * @var string $slug
* @ORM\Column(type="string", length=150, unique=true, nullable=false) * @ORM\Column(type="string", length=150, unique=true, nullable=false)
* @Assert\NotBlank()
*/ */
protected $slug; protected $slug;
@ -207,4 +213,88 @@ class Document
{ {
return $this->group; return $this->group;
} }
/**
* @var KekRozsak\SecurityBundle\Entity\User $updatedBy
* @ORM\ManyToOne(targetEntity="KekRozsak\SecurityBundle\Entity\User")
*/
protected $updatedBy;
/**
* Set updatedBy
*
* @param KekRozsak\SecurityBundle\Entity\User $updatedBy
* @return Document
*/
public function setUpdatedBy(\KekRozsak\SecurityBundle\Entity\User $updatedBy = null)
{
$this->updatedBy = $updatedBy;
return $this;
}
/**
* Get updatedBy
*
* @return KekRozsak\SecurityBundle\Entity\User
*/
public function getUpdatedBy()
{
return $this->updatedBy;
}
/**
* @var DateTime $updatedAt
* @ORM\Column(type="datetime", nullable=true)
*/
protected $updatedAt;
/**
* Set updatedAt
*
* @param DateTime $updatedAt
* @return Document
*/
public function setUpdatedAt(\DateTime $updatedAt = null)
{
$this->updatedAt = $updatedAt;
return $this;
}
/**
* Get updatedAt
*
* @return DateTime
*/
public function getUpdatedAt()
{
return $this->updatedAt;
}
/**
* @var string updateReason
* @ORM\Column(type="text", nullable=true)
*/
protected $updateReason;
/**
* Set updateReason
*
* @param string $updateReason
* @return Document
*/
public function setUpdateReason($updateReason = null)
{
$this->updateReason = $updateReason;
return $this;
}
/**
* Get updateReason
*
* @return string
*/
public function getUpdateReason()
{
return $this->updateReason;
}
} }

View File

@ -0,0 +1,27 @@
<?php
namespace KekRozsak\FrontBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class DocumentType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('title', null, array(
'label' => 'A dokumentum címe',
));
$builder->add('content', 'ckeditor', array(
'label' => ' ',
));
/* TODO: possibility to add to other groups! */
}
public function getName()
{
return 'document';
}
}

View File

@ -0,0 +1,9 @@
{% extends '::main_template.html.twig' %}
{% block title %} - Dokumentum létrehozása{% endblock %}
{% block content %}
<h3>Új dokumentum létrehozása</h3>
<form method="post" action="{{ path('KekRozsakFrontBundle_documentCreate', { groupSlug: defaultGroup }) }}">
{{ form_widget(form) }}
<button type="submit">Mentés</button>
</form>
{% endblock content %}

View File

@ -1,7 +0,0 @@
{% extends '::main_template.html.twig' %}
{% block title %} - Dokumentum - {{ document.title }}{% endblock %}
{% block content %}
<h3>{{ document.title }}</h3>
{{ document.content }}
<div class="szerzo">{{ document.createdBy.displayName }}</div>
{% endblock content %}

View File

@ -0,0 +1,9 @@
{% extends '::main_template.html.twig' %}
{% block title %} - Dokumentum szerkesztése - {{ document.title }}{% endblock %}
{% block content %}
<h3>Dokumentum szerkesztése - {{ document.title }}</h3>
<form method="post" action="{{ path('KekRozsakFrontBundle_documentEdit', { documentSlug: document.slug }) }}">
{{ form_widget(form) }}
<button type="submit">Mentés</button>
</form>
{% endblock content %}

View File

@ -0,0 +1,9 @@
{% extends '::main_template.html.twig' %}
{% block title %} - Dokumentum - {{ document.title }}{% endblock %}
{% block content %}
<h3>
{{ document.title }}{% if document.createdBy == app.user %} [ <a href="{{ path('KekRozsakFrontBundle_documentEdit', { documentSlug: document.slug }) }}">Szerkesztés</a> ] {% endif %}
</h3>
{{ document.content|raw }}
<div class="szerzo">{{ document.createdBy.displayName }}</div>
{% endblock content %}

View File

@ -9,9 +9,25 @@
<li><a href="{{ path('KekRozsakFrontBundle_groupMembers', { groupSlug: group.slug }) }}">Tagok</a></li> <li><a href="{{ path('KekRozsakFrontBundle_groupMembers', { groupSlug: group.slug }) }}">Tagok</a></li>
</ul> </ul>
<h3>{{ group.name }} - Dokumentumok</h3> <h3>{{ group.name }} - Dokumentumok</h3>
<ul> <table>
<thead>
<tr>
<td colspan="2">Cím</td>
<td>Készítette</td>
</tr>
</thead>
<tbody>
{% for document in group.documents %} {% for document in group.documents %}
<li><a href="{{ path('KekRozsakFrontBundle_documentView', { documentSlug: document.slug }) }}">{{ document.title }}</a></li> <tr>
<td>[ikon]</td>
<td><a href="{{ path('KekRozsakFrontBundle_documentView', { documentSlug: document.slug }) }}">{{ document.title }}</a></td>
<td>
{{ document.createdBy.displayName }}<br />
{{ document.createdAt|date('Y-m-d H:i') }}
</td>
</tr>
{% endfor %} {% endfor %}
</ul> </tbody>
</table>
<a href="{{ path('KekRozsakFrontBundle_documentCreate', { groupSlug: group.slug }) }}">Új dokumentum</a>
{% endblock %} {% endblock %}

View File

@ -3,7 +3,7 @@
"name": "jms/metadata", "name": "jms/metadata",
"version": "1.1.1", "version": "1.1.1",
"version_normalized": "1.1.1.0", "version_normalized": "1.1.1.0",
"time": "2011-12-29 19:32:49", "time": "2011-12-29 14:32:49",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/schmittjoh/metadata", "url": "https://github.com/schmittjoh/metadata",
@ -47,7 +47,7 @@
"name": "jms/cg", "name": "jms/cg",
"version": "1.0.0", "version": "1.0.0",
"version_normalized": "1.0.0.0", "version_normalized": "1.0.0.0",
"time": "2011-12-29 18:40:52", "time": "2011-12-29 13:40:52",
"source": { "source": {
"type": "git", "type": "git",
"url": "git://github.com/schmittjoh/cg-library.git", "url": "git://github.com/schmittjoh/cg-library.git",
@ -89,7 +89,7 @@
"version": "1.0.0", "version": "1.0.0",
"version_normalized": "1.0.0.0", "version_normalized": "1.0.0.0",
"target-dir": "JMS/AopBundle", "target-dir": "JMS/AopBundle",
"time": "2011-12-29 18:50:26", "time": "2011-12-29 13:50:26",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/schmittjoh/JMSAopBundle", "url": "https://github.com/schmittjoh/JMSAopBundle",
@ -133,7 +133,7 @@
"version": "1.1.0", "version": "1.1.0",
"version_normalized": "1.1.0.0", "version_normalized": "1.1.0.0",
"target-dir": "JMS/SecurityExtraBundle", "target-dir": "JMS/SecurityExtraBundle",
"time": "2011-12-29 22:38:12", "time": "2011-12-29 17:38:12",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/schmittjoh/JMSSecurityExtraBundle", "url": "https://github.com/schmittjoh/JMSSecurityExtraBundle",
@ -178,7 +178,7 @@
"version": "1.0.1", "version": "1.0.1",
"version_normalized": "1.0.1.0", "version_normalized": "1.0.1.0",
"target-dir": "JMS/DiExtraBundle", "target-dir": "JMS/DiExtraBundle",
"time": "2012-02-24 14:01:54", "time": "2012-02-24 09:01:54",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/schmittjoh/JMSDiExtraBundle", "url": "https://github.com/schmittjoh/JMSDiExtraBundle",
@ -221,7 +221,7 @@
"name": "doctrine/common", "name": "doctrine/common",
"version": "2.2.2", "version": "2.2.2",
"version_normalized": "2.2.2.0", "version_normalized": "2.2.2.0",
"time": "2012-04-06 21:46:44", "time": "2012-04-06 11:46:44",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/doctrine/common", "url": "https://github.com/doctrine/common",
@ -287,7 +287,7 @@
"name": "monolog/monolog", "name": "monolog/monolog",
"version": "1.1.0", "version": "1.1.0",
"version_normalized": "1.1.0.0", "version_normalized": "1.1.0.0",
"time": "2012-04-17 08:27:40", "time": "2012-04-16 22:27:40",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/Seldaek/monolog.git", "url": "https://github.com/Seldaek/monolog.git",
@ -337,7 +337,7 @@
"name": "twig/extensions", "name": "twig/extensions",
"version": "dev-master", "version": "dev-master",
"version_normalized": "9999999-dev", "version_normalized": "9999999-dev",
"time": "2012-05-15 15:28:19", "time": "2012-05-15 05:28:19",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/fabpot/Twig-extensions", "url": "https://github.com/fabpot/Twig-extensions",
@ -378,71 +378,12 @@
} }
} }
}, },
{
"name": "kriswallsmith/assetic",
"version": "dev-master",
"version_normalized": "9999999-dev",
"time": "2012-06-06 19:41:54",
"source": {
"type": "git",
"url": "http://github.com/kriswallsmith/assetic.git",
"reference": "d6f89a3170c5280ad554347dc113eb25fdf00ad7"
},
"dist": {
"type": "zip",
"url": "https://github.com/kriswallsmith/assetic/zipball/d6f89a3170c5280ad554347dc113eb25fdf00ad7",
"reference": "d6f89a3170c5280ad554347dc113eb25fdf00ad7",
"shasum": ""
},
"require": {
"php": ">=5.3.0",
"symfony/process": "2.1.*"
},
"require-dev": {
"twig/twig": ">=1.6.0,<2.0",
"leafo/lessphp": "*"
},
"suggest": {
"twig/twig": "Assetic provides the integration with the Twig templating engine",
"leafo/lessphp": "Assetic provides the integration with the lessphp LESS compiler"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.1-dev"
}
},
"installation-source": "source",
"license": [
"MIT"
],
"authors": [
{
"name": "Kris Wallsmith",
"email": "kris.wallsmith@gmail.com",
"homepage": "http://kriswallsmith.net/",
"role": null
}
],
"description": "Asset Management for PHP",
"homepage": "https://github.com/kriswallsmith/assetic",
"keywords": [
"assets",
"compression",
"minification"
],
"autoload": {
"psr-0": {
"Assetic": "src/"
}
}
},
{ {
"name": "doctrine/doctrine-bundle", "name": "doctrine/doctrine-bundle",
"version": "dev-master", "version": "dev-master",
"version_normalized": "9999999-dev", "version_normalized": "9999999-dev",
"target-dir": "Doctrine/Bundle/DoctrineBundle", "target-dir": "Doctrine/Bundle/DoctrineBundle",
"time": "2012-07-02 04:42:17", "time": "2012-07-01 18:42:17",
"source": { "source": {
"type": "git", "type": "git",
"url": "git://github.com/doctrine/DoctrineBundle.git", "url": "git://github.com/doctrine/DoctrineBundle.git",
@ -516,7 +457,7 @@
"name": "doctrine/orm", "name": "doctrine/orm",
"version": "2.2.x-dev", "version": "2.2.x-dev",
"version_normalized": "2.2.9999999.9999999-dev", "version_normalized": "2.2.9999999.9999999-dev",
"time": "2012-07-06 01:48:00", "time": "2012-07-05 15:48:00",
"source": { "source": {
"type": "git", "type": "git",
"url": "git://github.com/doctrine/doctrine2.git", "url": "git://github.com/doctrine/doctrine2.git",
@ -581,7 +522,7 @@
"name": "doctrine/dbal", "name": "doctrine/dbal",
"version": "2.2.x-dev", "version": "2.2.x-dev",
"version_normalized": "2.2.9999999.9999999-dev", "version_normalized": "2.2.9999999.9999999-dev",
"time": "2012-07-07 12:30:35", "time": "2012-07-07 02:30:35",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/doctrine/dbal", "url": "https://github.com/doctrine/dbal",
@ -647,7 +588,7 @@
"version": "dev-master", "version": "dev-master",
"version_normalized": "9999999-dev", "version_normalized": "9999999-dev",
"target-dir": "Sensio/Bundle/GeneratorBundle", "target-dir": "Sensio/Bundle/GeneratorBundle",
"time": "2012-07-12 18:04:48", "time": "2012-07-12 08:04:48",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sensio/SensioGeneratorBundle", "url": "https://github.com/sensio/SensioGeneratorBundle",
@ -695,68 +636,12 @@
} }
} }
}, },
{
"name": "symfony/assetic-bundle",
"version": "dev-master",
"version_normalized": "9999999-dev",
"target-dir": "Symfony/Bundle/AsseticBundle",
"time": "2012-07-12 00:51:17",
"source": {
"type": "git",
"url": "https://github.com/symfony/AsseticBundle",
"reference": "ed933dcfa45f00b6bc6d7727007403f3ff429e5a"
},
"dist": {
"type": "zip",
"url": "https://github.com/symfony/AsseticBundle/zipball/ed933dcfa45f00b6bc6d7727007403f3ff429e5a",
"reference": "ed933dcfa45f00b6bc6d7727007403f3ff429e5a",
"shasum": ""
},
"require": {
"php": ">=5.3.0",
"symfony/framework-bundle": "2.1.*",
"kriswallsmith/assetic": "1.1.*"
},
"suggest": {
"symfony/twig-bundle": "2.1.*"
},
"type": "symfony-bundle",
"extra": {
"branch-alias": {
"dev-master": "2.1.x-dev"
}
},
"installation-source": "source",
"license": [
"MIT"
],
"authors": [
{
"name": "Kris Wallsmith",
"email": "kris.wallsmith@gmail.com",
"homepage": "http://kriswallsmith.net/",
"role": null
}
],
"description": "Integrates Assetic into Symfony2",
"homepage": "https://github.com/symfony/AsseticBundle",
"keywords": [
"assets",
"compression",
"minification"
],
"autoload": {
"psr-0": {
"Symfony\\Bundle\\AsseticBundle": ""
}
}
},
{ {
"name": "symfony/monolog-bundle", "name": "symfony/monolog-bundle",
"version": "dev-master", "version": "dev-master",
"version_normalized": "9999999-dev", "version_normalized": "9999999-dev",
"target-dir": "Symfony/Bundle/MonologBundle", "target-dir": "Symfony/Bundle/MonologBundle",
"time": "2012-06-29 23:48:07", "time": "2012-06-29 13:48:07",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/MonologBundle", "url": "https://github.com/symfony/MonologBundle",
@ -816,7 +701,7 @@
"version": "dev-master", "version": "dev-master",
"version_normalized": "9999999-dev", "version_normalized": "9999999-dev",
"target-dir": "Sensio/Bundle/FrameworkExtraBundle", "target-dir": "Sensio/Bundle/FrameworkExtraBundle",
"time": "2012-07-11 17:13:52", "time": "2012-07-11 07:13:52",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sensio/SensioFrameworkExtraBundle", "url": "https://github.com/sensio/SensioFrameworkExtraBundle",
@ -861,124 +746,11 @@
} }
} }
}, },
{
"name": "twig/twig",
"version": "dev-master",
"version_normalized": "9999999-dev",
"time": "2012-07-15 17:42:44",
"source": {
"type": "git",
"url": "git://github.com/fabpot/Twig.git",
"reference": "d45664045194ef62573c153c424508527105041e"
},
"dist": {
"type": "zip",
"url": "https://github.com/fabpot/Twig/zipball/d45664045194ef62573c153c424508527105041e",
"reference": "d45664045194ef62573c153c424508527105041e",
"shasum": ""
},
"require": {
"php": ">=5.2.4"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.9-dev"
}
},
"installation-source": "source",
"license": [
"BSD-3"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com",
"homepage": null,
"role": null
},
{
"name": "Armin Ronacher",
"email": "armin.ronacher@active-4.com",
"homepage": null,
"role": null
}
],
"description": "Twig, the flexible, fast, and secure template language for PHP",
"homepage": "http://twig.sensiolabs.org",
"keywords": [
"templating"
],
"autoload": {
"psr-0": {
"Twig_": "lib/"
}
}
},
{
"name": "symfony/swiftmailer-bundle",
"version": "dev-master",
"version_normalized": "9999999-dev",
"target-dir": "Symfony/Bundle/SwiftmailerBundle",
"time": "2012-07-05 05:33:34",
"source": {
"type": "git",
"url": "https://github.com/symfony/SwiftmailerBundle",
"reference": "v2.1.0-BETA3"
},
"dist": {
"type": "zip",
"url": "https://github.com/symfony/SwiftmailerBundle/zipball/v2.1.0-BETA3",
"reference": "v2.1.0-BETA3",
"shasum": ""
},
"require": {
"php": ">=5.3.2",
"symfony/swiftmailer-bridge": "2.1.*",
"swiftmailer/swiftmailer": ">=4.2.0,<4.3-dev"
},
"require-dev": {
"symfony/dependency-injection": "2.1.*",
"symfony/http-kernel": "2.1.*",
"symfony/config": "2.1.*"
},
"type": "symfony-bundle",
"extra": {
"branch-alias": {
"dev-master": "2.1-dev"
}
},
"installation-source": "source",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com",
"homepage": null,
"role": null
},
{
"name": "Symfony Community",
"email": null,
"homepage": "http://symfony.com/contributors",
"role": null
}
],
"description": "Symfony SwiftmailerBundle",
"homepage": "http://symfony.com",
"autoload": {
"psr-0": {
"Symfony\\Bundle\\SwiftmailerBundle": ""
}
}
},
{ {
"name": "swiftmailer/swiftmailer", "name": "swiftmailer/swiftmailer",
"version": "dev-master", "version": "dev-master",
"version_normalized": "9999999-dev", "version_normalized": "9999999-dev",
"time": "2012-07-13 06:47:17", "time": "2012-07-12 20:47:17",
"source": { "source": {
"type": "git", "type": "git",
"url": "git://github.com/swiftmailer/swiftmailer.git", "url": "git://github.com/swiftmailer/swiftmailer.git",
@ -1034,7 +806,7 @@
"version": "dev-master", "version": "dev-master",
"version_normalized": "9999999-dev", "version_normalized": "9999999-dev",
"target-dir": "Sensio/Bundle/DistributionBundle", "target-dir": "Sensio/Bundle/DistributionBundle",
"time": "2012-07-15 04:24:10", "time": "2012-07-14 18:24:10",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sensio/SensioDistributionBundle", "url": "https://github.com/sensio/SensioDistributionBundle",
@ -1073,12 +845,71 @@
} }
} }
}, },
{
"name": "symfony/swiftmailer-bundle",
"version": "dev-master",
"version_normalized": "9999999-dev",
"target-dir": "Symfony/Bundle/SwiftmailerBundle",
"time": "2012-07-19 01:55:12",
"source": {
"type": "git",
"url": "https://github.com/symfony/SwiftmailerBundle",
"reference": "65e6079443c3cd413012e146aa74307f18671f42"
},
"dist": {
"type": "zip",
"url": "https://github.com/symfony/SwiftmailerBundle/zipball/65e6079443c3cd413012e146aa74307f18671f42",
"reference": "65e6079443c3cd413012e146aa74307f18671f42",
"shasum": ""
},
"require": {
"php": ">=5.3.2",
"symfony/swiftmailer-bridge": "2.1.*",
"swiftmailer/swiftmailer": ">=4.2.0,<4.3-dev"
},
"require-dev": {
"symfony/dependency-injection": "2.1.*",
"symfony/http-kernel": "2.1.*",
"symfony/config": "2.1.*"
},
"type": "symfony-bundle",
"extra": {
"branch-alias": {
"dev-master": "2.1-dev"
}
},
"installation-source": "source",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com",
"homepage": null,
"role": null
},
{
"name": "Symfony Community",
"email": null,
"homepage": "http://symfony.com/contributors",
"role": null
}
],
"description": "Symfony SwiftmailerBundle",
"homepage": "http://symfony.com",
"autoload": {
"psr-0": {
"Symfony\\Bundle\\SwiftmailerBundle": ""
}
}
},
{ {
"name": "egeloen/ckeditor-bundle", "name": "egeloen/ckeditor-bundle",
"version": "dev-master", "version": "dev-master",
"version_normalized": "9999999-dev", "version_normalized": "9999999-dev",
"target-dir": "Ivory/CKEditorBundle", "target-dir": "Ivory/CKEditorBundle",
"time": "2012-05-28 13:16:47", "time": "2012-05-28 11:16:47",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/egeloen/IvoryCKEditorBundle", "url": "https://github.com/egeloen/IvoryCKEditorBundle",
@ -1118,19 +949,188 @@
} }
}, },
{ {
"name": "symfony/symfony", "name": "kriswallsmith/assetic",
"version": "dev-master", "version": "dev-master",
"version_normalized": "9999999-dev", "version_normalized": "9999999-dev",
"time": "2012-07-17 09:07:53", "time": "2012-07-20 10:33:33",
"source": { "source": {
"type": "git", "type": "git",
"url": "git://github.com/symfony/symfony.git", "url": "http://github.com/kriswallsmith/assetic.git",
"reference": "f52ce6178243e4b11aa09bde147f684d596fb120" "reference": "5c1c9b658b732a980312e8f29cec4e175c2bb6b2"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://github.com/symfony/symfony/zipball/f52ce6178243e4b11aa09bde147f684d596fb120", "url": "https://github.com/kriswallsmith/assetic/zipball/5c1c9b658b732a980312e8f29cec4e175c2bb6b2",
"reference": "f52ce6178243e4b11aa09bde147f684d596fb120", "reference": "5c1c9b658b732a980312e8f29cec4e175c2bb6b2",
"shasum": ""
},
"require": {
"php": ">=5.3.0",
"symfony/process": "2.1.*"
},
"require-dev": {
"twig/twig": ">=1.6.0,<2.0",
"leafo/lessphp": "*"
},
"suggest": {
"twig/twig": "Assetic provides the integration with the Twig templating engine",
"leafo/lessphp": "Assetic provides the integration with the lessphp LESS compiler"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.1-dev"
}
},
"installation-source": "source",
"license": [
"MIT"
],
"authors": [
{
"name": "Kris Wallsmith",
"email": "kris.wallsmith@gmail.com",
"homepage": "http://kriswallsmith.net/",
"role": null
}
],
"description": "Asset Management for PHP",
"homepage": "https://github.com/kriswallsmith/assetic",
"keywords": [
"assets",
"compression",
"minification"
],
"autoload": {
"psr-0": {
"Assetic": "src/"
}
}
},
{
"name": "symfony/assetic-bundle",
"version": "dev-master",
"version_normalized": "9999999-dev",
"target-dir": "Symfony/Bundle/AsseticBundle",
"time": "2012-07-20 19:34:07",
"source": {
"type": "git",
"url": "https://github.com/symfony/AsseticBundle",
"reference": "e5e9a56a872d9e1f305d14cd8296bf77888388af"
},
"dist": {
"type": "zip",
"url": "https://github.com/symfony/AsseticBundle/zipball/e5e9a56a872d9e1f305d14cd8296bf77888388af",
"reference": "e5e9a56a872d9e1f305d14cd8296bf77888388af",
"shasum": ""
},
"require": {
"php": ">=5.3.0",
"symfony/framework-bundle": "2.1.*",
"kriswallsmith/assetic": "1.1.*"
},
"suggest": {
"symfony/twig-bundle": "2.1.*"
},
"type": "symfony-bundle",
"extra": {
"branch-alias": {
"dev-master": "2.1.x-dev"
}
},
"installation-source": "source",
"license": [
"MIT"
],
"authors": [
{
"name": "Kris Wallsmith",
"email": "kris.wallsmith@gmail.com",
"homepage": "http://kriswallsmith.net/",
"role": null
}
],
"description": "Integrates Assetic into Symfony2",
"homepage": "https://github.com/symfony/AsseticBundle",
"keywords": [
"assets",
"compression",
"minification"
],
"autoload": {
"psr-0": {
"Symfony\\Bundle\\AsseticBundle": ""
}
}
},
{
"name": "twig/twig",
"version": "dev-master",
"version_normalized": "9999999-dev",
"time": "2012-07-20 12:41:38",
"source": {
"type": "git",
"url": "git://github.com/fabpot/Twig.git",
"reference": "8a84838798e45424c5fe2d87149db6855ae037bf"
},
"dist": {
"type": "zip",
"url": "https://github.com/fabpot/Twig/zipball/8a84838798e45424c5fe2d87149db6855ae037bf",
"reference": "8a84838798e45424c5fe2d87149db6855ae037bf",
"shasum": ""
},
"require": {
"php": ">=5.2.4"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.9-dev"
}
},
"installation-source": "source",
"license": [
"BSD-3"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com",
"homepage": null,
"role": null
},
{
"name": "Armin Ronacher",
"email": "armin.ronacher@active-4.com",
"homepage": null,
"role": null
}
],
"description": "Twig, the flexible, fast, and secure template language for PHP",
"homepage": "http://twig.sensiolabs.org",
"keywords": [
"templating"
],
"autoload": {
"psr-0": {
"Twig_": "lib/"
}
}
},
{
"name": "symfony/symfony",
"version": "dev-master",
"version_normalized": "9999999-dev",
"time": "2012-07-21 11:16:18",
"source": {
"type": "git",
"url": "git://github.com/symfony/symfony.git",
"reference": "6c256b01b087f94a4ec04487d875fe81375eb6c1"
},
"dist": {
"type": "zip",
"url": "https://github.com/symfony/symfony/zipball/6c256b01b087f94a4ec04487d875fe81375eb6c1",
"reference": "6c256b01b087f94a4ec04487d875fe81375eb6c1",
"shasum": "" "shasum": ""
}, },
"require": { "require": {

View File

@ -0,0 +1,4 @@
phpunit.xml
vendor/
composer.phar
composer.lock

View File

@ -1,5 +1,6 @@
{ {
"name": "kriswallsmith/assetic", "name": "kriswallsmith/assetic",
"minimum-stability": "dev",
"description": "Asset Management for PHP", "description": "Asset Management for PHP",
"keywords": ["assets", "compression", "minification"], "keywords": ["assets", "compression", "minification"],
"homepage": "https://github.com/kriswallsmith/assetic", "homepage": "https://github.com/kriswallsmith/assetic",
@ -20,6 +21,7 @@
"twig/twig": ">=1.6.0,<2.0", "twig/twig": ">=1.6.0,<2.0",
"leafo/lessphp": "*" "leafo/lessphp": "*"
}, },
"minimum-stability": "dev",
"suggest": { "suggest": {
"twig/twig": "Assetic provides the integration with the Twig templating engine", "twig/twig": "Assetic provides the integration with the Twig templating engine",
"leafo/lessphp": "Assetic provides the integration with the lessphp LESS compiler" "leafo/lessphp": "Assetic provides the integration with the lessphp LESS compiler"

View File

@ -25,6 +25,7 @@ use Symfony\Component\Process\ProcessBuilder;
class CompassFilter implements FilterInterface class CompassFilter implements FilterInterface
{ {
private $compassPath; private $compassPath;
private $rubyPath;
private $scss; private $scss;
// sass options // sass options
@ -50,9 +51,10 @@ class CompassFilter implements FilterInterface
private $generatedImagesPath; private $generatedImagesPath;
private $httpJavascriptsPath; private $httpJavascriptsPath;
public function __construct($compassPath = '/usr/bin/compass') public function __construct($compassPath = '/usr/bin/compass', $rubyPath = null)
{ {
$this->compassPath = $compassPath; $this->compassPath = $compassPath;
$this->rubyPath = $rubyPath;
$this->cacheLocation = sys_get_temp_dir(); $this->cacheLocation = sys_get_temp_dir();
if ('cli' !== php_sapi_name()) { if ('cli' !== php_sapi_name()) {
@ -133,6 +135,11 @@ class CompassFilter implements FilterInterface
$this->plugins[] = $plugin; $this->plugins[] = $plugin;
} }
public function setLoadPaths(array $loadPaths)
{
$this->loadPaths = $loadPaths;
}
public function addLoadPath($loadPath) public function addLoadPath($loadPath)
{ {
$this->loadPaths[] = $loadPath; $this->loadPaths[] = $loadPath;
@ -171,11 +178,16 @@ class CompassFilter implements FilterInterface
// compass does not seems to handle symlink, so we use realpath() // compass does not seems to handle symlink, so we use realpath()
$tempDir = realpath(sys_get_temp_dir()); $tempDir = realpath(sys_get_temp_dir());
$pb = new ProcessBuilder(array( $compassProcessArgs = array(
$this->compassPath, $this->compassPath,
'compile', 'compile',
$tempDir, $tempDir,
)); );
if (null !== $this->rubyPath) {
array_unshift($compassProcessArgs, $this->rubyPath);
}
$pb = new ProcessBuilder($compassProcessArgs);
$pb->inheritEnvironmentVariables(); $pb->inheritEnvironmentVariables();
if ($this->force) { if ($this->force) {
@ -330,12 +342,12 @@ class CompassFilter implements FilterInterface
// does we have an associative array ? // does we have an associative array ?
if (count(array_filter(array_keys($array), "is_numeric")) != count($array)) { if (count(array_filter(array_keys($array), "is_numeric")) != count($array)) {
foreach($array as $name => $value) { foreach ($array as $name => $value) {
$output[] = sprintf(' :%s => "%s"', $name, addcslashes($value, '\\')); $output[] = sprintf(' :%s => "%s"', $name, addcslashes($value, '\\'));
} }
$output = "{\n".implode(",\n", $output)."\n}"; $output = "{\n".implode(",\n", $output)."\n}";
} else { } else {
foreach($array as $name => $value) { foreach ($array as $name => $value) {
$output[] = sprintf(' "%s"', addcslashes($value, '\\')); $output[] = sprintf(' "%s"', addcslashes($value, '\\'));
} }
$output = "[\n".implode(",\n", $output)."\n]"; $output = "[\n".implode(",\n", $output)."\n]";

View File

@ -30,6 +30,7 @@ class SassFilter implements FilterInterface
const STYLE_COMPRESSED = 'compressed'; const STYLE_COMPRESSED = 'compressed';
private $sassPath; private $sassPath;
private $rubyPath;
private $unixNewlines; private $unixNewlines;
private $scss; private $scss;
private $style; private $style;
@ -41,9 +42,10 @@ class SassFilter implements FilterInterface
private $noCache; private $noCache;
private $compass; private $compass;
public function __construct($sassPath = '/usr/bin/sass') public function __construct($sassPath = '/usr/bin/sass', $rubyPath = null)
{ {
$this->sassPath = $sassPath; $this->sassPath = $sassPath;
$this->rubyPath = $rubyPath;
$this->cacheLocation = realpath(sys_get_temp_dir()); $this->cacheLocation = realpath(sys_get_temp_dir());
} }
@ -99,7 +101,12 @@ class SassFilter implements FilterInterface
public function filterLoad(AssetInterface $asset) public function filterLoad(AssetInterface $asset)
{ {
$pb = new ProcessBuilder(array($this->sassPath)); $sassProcessArgs = array($this->sassPath);
if (null !== $this->rubyPath) {
array_unshift($sassProcessArgs, $this->rubyPath);
}
$pb = new ProcessBuilder($sassProcessArgs);
$root = $asset->getSourceRoot(); $root = $asset->getSourceRoot();
$path = $asset->getSourcePath(); $path = $asset->getSourcePath();

View File

@ -19,9 +19,9 @@ namespace Assetic\Filter\Sass;
*/ */
class ScssFilter extends SassFilter class ScssFilter extends SassFilter
{ {
public function __construct($sassPath = '/usr/bin/sass') public function __construct($sassPath = '/usr/bin/sass', $rubyPath = null)
{ {
parent::__construct($sassPath); parent::__construct($sassPath, $rubyPath);
$this->setScss(true); $this->setScss(true);
} }

View File

@ -80,11 +80,10 @@ abstract class BaseCompressorFilter implements FilterInterface
// input and output files // input and output files
$tempDir = realpath(sys_get_temp_dir()); $tempDir = realpath(sys_get_temp_dir());
$hash = substr(sha1(time().rand(11111, 99999)), 0, 7); $input = tempnam($tempDir, 'YUI-IN-');
$input = $tempDir.DIRECTORY_SEPARATOR.$hash.'.'.$type; $output = tempnam($tempDir, 'YUI-OUT-');
$output = $tempDir.DIRECTORY_SEPARATOR.$hash.'-min.'.$type;
file_put_contents($input, $content); file_put_contents($input, $content);
$pb->add('-o')->add($output)->add($input); $pb->add('-o')->add($output)->add('--type')->add($type)->add($input);
$proc = $pb->getProcess(); $proc = $pb->getProcess();
$code = $proc->run(); $code = $proc->run();

View File

@ -9,7 +9,7 @@
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
if (!$loader = @include __DIR__.'/../vendor/.composer/autoload.php') { if (!$loader = @include __DIR__.'/../vendor/autoload.php') {
die('You must set up the project dependencies, run the following commands:'.PHP_EOL. die('You must set up the project dependencies, run the following commands:'.PHP_EOL.
'curl -s http://getcomposer.org/installer | php'.PHP_EOL. 'curl -s http://getcomposer.org/installer | php'.PHP_EOL.
'php composer.phar install'.PHP_EOL); 'php composer.phar install'.PHP_EOL);
@ -43,4 +43,4 @@ if (isset($_SERVER['PACKAGER'])) {
if (isset($_SERVER['PACKER'])) { if (isset($_SERVER['PACKER'])) {
require_once $_SERVER['PACKER']; require_once $_SERVER['PACKER'];
} }

View File

@ -57,7 +57,7 @@ class DumpCommand extends ContainerAwareCommand
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output)
{ {
$output->writeln(sprintf('Dumping all <comment>%s</comment> assets.', $input->getOption('env'))); $output->writeln(sprintf('Dumping all <comment>%s</comment> assets.', $input->getOption('env')));
$output->writeln(sprintf('Debug mode is <comment>%s</comment>.', $input->getOption('no-debug') ? 'off' : 'on')); $output->writeln(sprintf('Debug mode is <comment>%s</comment>.', $this->am->isDebug() ? 'on' : 'off'));
$output->writeln(''); $output->writeln('');
if (!$input->getOption('watch')) { if (!$input->getOption('watch')) {

View File

@ -22,6 +22,7 @@
<service id="assetic.filter.compass" class="%assetic.filter.compass.class%"> <service id="assetic.filter.compass" class="%assetic.filter.compass.class%">
<tag name="assetic.filter" alias="compass" /> <tag name="assetic.filter" alias="compass" />
<argument>%assetic.filter.compass.bin%</argument> <argument>%assetic.filter.compass.bin%</argument>
<argument>%assetic.ruby.bin%</argument>
<call method="setDebugInfo"><argument>%assetic.filter.compass.debug%</argument></call> <call method="setDebugInfo"><argument>%assetic.filter.compass.debug%</argument></call>
<call method="setStyle"><argument>%assetic.filter.compass.style%</argument></call> <call method="setStyle"><argument>%assetic.filter.compass.style%</argument></call>
<call method="setImagesDir"><argument>%assetic.filter.compass.images_dir%</argument></call> <call method="setImagesDir"><argument>%assetic.filter.compass.images_dir%</argument></call>

View File

@ -15,6 +15,7 @@
<service id="assetic.filter.sass" class="%assetic.filter.sass.class%"> <service id="assetic.filter.sass" class="%assetic.filter.sass.class%">
<tag name="assetic.filter" alias="sass" /> <tag name="assetic.filter" alias="sass" />
<argument>%assetic.filter.sass.bin%</argument> <argument>%assetic.filter.sass.bin%</argument>
<argument>%assetic.ruby.bin%</argument>
<call method="setStyle"><argument>%assetic.filter.sass.style%</argument></call> <call method="setStyle"><argument>%assetic.filter.sass.style%</argument></call>
<call method="setCompass"><argument>%assetic.filter.sass.compass%</argument></call> <call method="setCompass"><argument>%assetic.filter.sass.compass%</argument></call>
</service> </service>

View File

@ -15,6 +15,7 @@
<service id="assetic.filter.scss" class="%assetic.filter.scss.class%"> <service id="assetic.filter.scss" class="%assetic.filter.scss.class%">
<tag name="assetic.filter" alias="scss" /> <tag name="assetic.filter" alias="scss" />
<argument>%assetic.filter.scss.sass%</argument> <argument>%assetic.filter.scss.sass%</argument>
<argument>%assetic.ruby.bin%</argument>
<call method="setStyle"><argument>%assetic.filter.scss.style%</argument></call> <call method="setStyle"><argument>%assetic.filter.scss.style%</argument></call>
<call method="setCompass"><argument>%assetic.filter.scss.compass%</argument></call> <call method="setCompass"><argument>%assetic.filter.scss.compass%</argument></call>
</service> </service>

View File

@ -46,7 +46,13 @@
{% endfor %} {% endfor %}
<p> <p>
<pre>{{ message.body|e('html', message.charset)|convert_encoding('UTF-8', message.charset) }}</pre> <pre>
{%- if message.charset %}
{{- message.body|e('html', message.charset)|convert_encoding('UTF-8', message.charset) }}
{%- else %}
{{- message.body|e('html') }}
{%- endif -%}
</pre>
</p> </p>
{% endfor %} {% endfor %}
{% endif %} {% endif %}

View File

@ -13,14 +13,15 @@
configuration (i.e. `config.yml`), merging could yield a set of base URL's configuration (i.e. `config.yml`), merging could yield a set of base URL's
for multiple environments. for multiple environments.
* The priorities for the built-in listeners have changed: * The priorities for the built-in listeners have changed.
2.0 2.1 ```
security.firewall request 64 8 2.0 2.1
locale listener early_request 253 255 security.firewall kernel.request 64 8
request -1 16 locale listener kernel.request 0 16
router listener early_request 255 128 router listener early_request 255 n/a
request 10 32 request 0 32
```
### Doctrine ### Doctrine
@ -106,7 +107,7 @@
if ($locale = $request->attributes->get('_locale')) { if ($locale = $request->attributes->get('_locale')) {
$request->getSession()->set('_locale', $locale); $request->getSession()->set('_locale', $locale);
} else { } else {
$request->setDefaultLocale($request->getSession()->get('_locale', $this->defaultLocale)); $request->setLocale($request->getSession()->get('_locale', $this->defaultLocale));
} }
} }
@ -1053,10 +1054,25 @@
$registry->addType($registry->resolveType(new MyFormType())); $registry->addType($registry->resolveType(new MyFormType()));
``` ```
* The method `renderBlock()` of the helper for the PHP Templating component was
deprecated and will be removed in Symfony 2.3. You should use `block()` instead.
Before:
```
<?php echo $view['form']->renderBlock('widget_attributes') ?>
```
After:
```
<?php echo $view['form']->block('widget_attributes') ?>
```
### Validator ### Validator
* The methods `setMessage()`, `getMessageTemplate()` and * The methods `setMessage()`, `getMessageTemplate()` and
`getMessageParameters()` in the Constraint class were deprecated and will `getMessageParameters()` in the `ConstraintValidator` class were deprecated and will
be removed in Symfony 2.3. be removed in Symfony 2.3.
If you have implemented custom validators, you should use the If you have implemented custom validators, you should use the

View File

@ -9,3 +9,4 @@ CHANGELOG
* DoctrineOrmTypeGuesser now guesses "collection" for array Doctrine type * DoctrineOrmTypeGuesser now guesses "collection" for array Doctrine type
* DoctrineType now caches its choice lists in order to improve performance * DoctrineType now caches its choice lists in order to improve performance
* DoctrineType now uses ManagerRegistry::getManagerForClass() if the option "em" is not set * DoctrineType now uses ManagerRegistry::getManagerForClass() if the option "em" is not set
* UniqueEntity validation constraint now accepts a "repositoryMethod" option that will be used to check for uniqueness instead of the default "findBy"

View File

@ -12,11 +12,12 @@
namespace Symfony\Bridge\Doctrine\Form; namespace Symfony\Bridge\Doctrine\Form;
use Doctrine\Common\Persistence\ManagerRegistry; use Doctrine\Common\Persistence\ManagerRegistry;
use Doctrine\Common\Persistence\Mapping\MappingException;
use Doctrine\ORM\Mapping\MappingException as LegacyMappingException;
use Symfony\Component\Form\FormTypeGuesserInterface; use Symfony\Component\Form\FormTypeGuesserInterface;
use Symfony\Component\Form\Guess\Guess; use Symfony\Component\Form\Guess\Guess;
use Symfony\Component\Form\Guess\TypeGuess; use Symfony\Component\Form\Guess\TypeGuess;
use Symfony\Component\Form\Guess\ValueGuess; use Symfony\Component\Form\Guess\ValueGuess;
use Doctrine\ORM\Mapping\MappingException;
class DoctrineOrmTypeGuesser implements FormTypeGuesserInterface class DoctrineOrmTypeGuesser implements FormTypeGuesserInterface
{ {
@ -145,6 +146,8 @@ class DoctrineOrmTypeGuesser implements FormTypeGuesserInterface
return $this->cache[$class] = array($em->getClassMetadata($class), $name); return $this->cache[$class] = array($em->getClassMetadata($class), $name);
} catch (MappingException $e) { } catch (MappingException $e) {
// not an entity or mapped super class // not an entity or mapped super class
} catch (LegacyMappingException $e) {
// not an entity or mapped super class, using Doctrine ORM 2.2
} }
} }
} }

View File

@ -114,7 +114,7 @@ abstract class DoctrineType extends AbstractType
return $choiceListCache[$hash]; return $choiceListCache[$hash];
}; };
$emFilter = function (Options $options, $em) use ($registry) { $emNormalizer = function (Options $options, $em) use ($registry) {
/* @var ManagerRegistry $registry */ /* @var ManagerRegistry $registry */
if (null !== $em) { if (null !== $em) {
return $registry->getManager($em); return $registry->getManager($em);
@ -134,8 +134,8 @@ abstract class DoctrineType extends AbstractType
'group_by' => null, 'group_by' => null,
)); ));
$resolver->setFilters(array( $resolver->setNormalizers(array(
'em' => $emFilter, 'em' => $emNormalizer,
)); ));
} }

View File

@ -47,6 +47,50 @@ class UniqueValidatorTest extends DoctrineOrmTestCase
return $registry; return $registry;
} }
protected function createRepositoryMock()
{
$repository = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectRepository')
->setMethods(array('findByCustom', 'find', 'findAll', 'findOneBy', 'findBy', 'getClassName'))
->getMock()
;
return $repository;
}
protected function createEntityManagerMock($repositoryMock)
{
$em = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')
->getMock()
;
$em->expects($this->any())
->method('getRepository')
->will($this->returnValue($repositoryMock))
;
$classMetadata = $this->getMock('Doctrine\Common\Persistence\Mapping\ClassMetadata');
$classMetadata
->expects($this->any())
->method('hasField')
->will($this->returnValue(true))
;
$refl = $this->getMockBuilder('Doctrine\Common\Reflection\StaticReflectionProperty')
->disableOriginalConstructor()
->getMock()
;
$refl
->expects($this->any())
->method('getValue')
->will($this->returnValue(true))
;
$classMetadata->reflFields = array('name' => $refl);
$em->expects($this->any())
->method('getClassMetadata')
->will($this->returnValue($classMetadata))
;
return $em;
}
protected function createMetadataFactoryMock($metadata) protected function createMetadataFactoryMock($metadata)
{ {
$metadataFactory = $this->getMock('Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface'); $metadataFactory = $this->getMock('Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface');
@ -69,7 +113,7 @@ class UniqueValidatorTest extends DoctrineOrmTestCase
return $validatorFactory; return $validatorFactory;
} }
public function createValidator($entityManagerName, $em, $validateClass = null, $uniqueFields = null, $errorPath = null) public function createValidator($entityManagerName, $em, $validateClass = null, $uniqueFields = null, $errorPath = null, $repositoryMethod = 'findBy')
{ {
if (!$validateClass) { if (!$validateClass) {
$validateClass = 'Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIdentEntity'; $validateClass = 'Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIdentEntity';
@ -83,7 +127,13 @@ class UniqueValidatorTest extends DoctrineOrmTestCase
$uniqueValidator = new UniqueEntityValidator($registry); $uniqueValidator = new UniqueEntityValidator($registry);
$metadata = new ClassMetadata($validateClass); $metadata = new ClassMetadata($validateClass);
$metadata->addConstraint(new UniqueEntity(array('fields' => $uniqueFields, 'em' => $entityManagerName, 'errorPath' => $errorPath))); $constraint = new UniqueEntity(array(
'fields' => $uniqueFields,
'em' => $entityManagerName,
'errorPath' => $errorPath,
'repositoryMethod' => $repositoryMethod
));
$metadata->addConstraint($constraint);
$metadataFactory = $this->createMetadataFactoryMock($metadata); $metadataFactory = $this->createMetadataFactoryMock($metadata);
$validatorFactory = $this->createValidatorFactory($uniqueValidator); $validatorFactory = $this->createValidatorFactory($uniqueValidator);
@ -194,6 +244,23 @@ class UniqueValidatorTest extends DoctrineOrmTestCase
$this->assertEquals(1, $violationsList->count(), 'Violation found on entity with conflicting entity existing in the database.'); $this->assertEquals(1, $violationsList->count(), 'Violation found on entity with conflicting entity existing in the database.');
} }
public function testValidateUniquenessUsingCustomRepositoryMethod()
{
$entityManagerName = 'foo';
$repository = $this->createRepositoryMock();
$repository->expects($this->once())
->method('findByCustom')
->will($this->returnValue(array()))
;
$em = $this->createEntityManagerMock($repository);
$validator = $this->createValidator($entityManagerName, $em, null, array(), null, 'findByCustom');
$entity1 = new SingleIdentEntity(1, 'foo');
$violationsList = $validator->validate($entity1);
$this->assertEquals(0, $violationsList->count(), 'Violation is using custom repository method.');
}
/** /**
* @group GH-1635 * @group GH-1635
*/ */

View File

@ -24,6 +24,7 @@ class UniqueEntity extends Constraint
public $message = 'This value is already used.'; public $message = 'This value is already used.';
public $service = 'doctrine.orm.validator.unique'; public $service = 'doctrine.orm.validator.unique';
public $em = null; public $em = null;
public $repositoryMethod = 'findBy';
public $fields = array(); public $fields = array();
public $errorPath = null; public $errorPath = null;

View File

@ -100,7 +100,7 @@ class UniqueEntityValidator extends ConstraintValidator
} }
$repository = $em->getRepository($className); $repository = $em->getRepository($className);
$result = $repository->findBy($criteria); $result = $repository->{$constraint->repositoryMethod}($criteria);
/* If the result is a MongoCursor, it must be advanced to the first /* If the result is a MongoCursor, it must be advanced to the first
* element. Rewinding should have no ill effect if $result is another * element. Rewinding should have no ill effect if $result is another

View File

@ -12,11 +12,9 @@
namespace Symfony\Bridge\Twig\Extension; namespace Symfony\Bridge\Twig\Extension;
use Symfony\Bridge\Twig\TokenParser\FormThemeTokenParser; use Symfony\Bridge\Twig\TokenParser\FormThemeTokenParser;
use Symfony\Component\Form\FormView; use Symfony\Bridge\Twig\Form\TwigRendererInterface;
use Symfony\Component\Form\Exception\FormException; use Symfony\Component\Form\FormViewInterface;
use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
use Symfony\Component\Form\Extension\Core\View\ChoiceView;
use Symfony\Component\Form\Util\FormUtil;
/** /**
* FormExtension extends Twig with form capabilities. * FormExtension extends Twig with form capabilities.
@ -26,21 +24,17 @@ use Symfony\Component\Form\Util\FormUtil;
*/ */
class FormExtension extends \Twig_Extension class FormExtension extends \Twig_Extension
{ {
protected $csrfProvider; /**
protected $resources; * This property is public so that it can be accessed directly from compiled
protected $blocks; * templates without having to call a getter, which slightly decreases performance.
protected $environment; *
protected $themes; * @var \Symfony\Component\Form\FormRendererInterface
protected $varStack; */
protected $template; public $renderer;
public function __construct(CsrfProviderInterface $csrfProvider = null, array $resources = array()) public function __construct(TwigRendererInterface $renderer)
{ {
$this->csrfProvider = $csrfProvider; $this->renderer = $renderer;
$this->themes = new \SplObjectStorage();
$this->varStack = array();
$this->blocks = new \SplObjectStorage();
$this->resources = $resources;
} }
/** /**
@ -48,25 +42,11 @@ class FormExtension extends \Twig_Extension
*/ */
public function initRuntime(\Twig_Environment $environment) public function initRuntime(\Twig_Environment $environment)
{ {
$this->environment = $environment; $this->renderer->setEnvironment($environment);
} }
/** /**
* Sets a theme for a given view. * {@inheritdoc}
*
* @param FormView $view A FormView instance
* @param array|string $resources An array of resource names|a resource name
*/
public function setTheme(FormView $view, $resources)
{
$this->themes->attach($view, (array) $resources);
$this->blocks = new \SplObjectStorage();
}
/**
* Returns the token parser instance to add to the existing list.
*
* @return array An array of Twig_TokenParser instances
*/ */
public function getTokenParsers() public function getTokenParsers()
{ {
@ -76,305 +56,39 @@ class FormExtension extends \Twig_Extension
); );
} }
/**
* {@inheritdoc}
*/
public function getFunctions() public function getFunctions()
{ {
return array( return array(
'form_enctype' => new \Twig_Function_Method($this, 'renderEnctype', array('is_safe' => array('html'))), 'form_enctype' => new \Twig_Function_Method($this, 'renderer->renderEnctype', array('is_safe' => array('html'))),
'form_widget' => new \Twig_Function_Method($this, 'renderWidget', array('is_safe' => array('html'))), 'form_widget' => new \Twig_Function_Method($this, 'renderer->renderWidget', array('is_safe' => array('html'))),
'form_errors' => new \Twig_Function_Method($this, 'renderErrors', array('is_safe' => array('html'))), 'form_errors' => new \Twig_Function_Method($this, 'renderer->renderErrors', array('is_safe' => array('html'))),
'form_label' => new \Twig_Function_Method($this, 'renderLabel', array('is_safe' => array('html'))), 'form_label' => new \Twig_Function_Method($this, 'renderer->renderLabel', array('is_safe' => array('html'))),
'form_row' => new \Twig_Function_Method($this, 'renderRow', array('is_safe' => array('html'))), 'form_row' => new \Twig_Function_Method($this, 'renderer->renderRow', array('is_safe' => array('html'))),
'form_rest' => new \Twig_Function_Method($this, 'renderRest', array('is_safe' => array('html'))), 'form_rest' => new \Twig_Function_Method($this, 'renderer->renderRest', array('is_safe' => array('html'))),
'csrf_token' => new \Twig_Function_Method($this, 'getCsrfToken'), 'csrf_token' => new \Twig_Function_Method($this, 'renderer->renderCsrfToken'),
'_form_is_choice_group' => new \Twig_Function_Method($this, 'isChoiceGroup', array('is_safe' => array('html'))), '_form_is_choice_group' => new \Twig_Function_Method($this, 'renderer->isChoiceGroup', array('is_safe' => array('html'))),
'_form_is_choice_selected' => new \Twig_Function_Method($this, 'isChoiceSelected', array('is_safe' => array('html'))), '_form_is_choice_selected' => new \Twig_Function_Method($this, 'renderer->isChoiceSelected', array('is_safe' => array('html'))),
); );
} }
/**
* {@inheritdoc}
*/
public function getFilters() public function getFilters()
{ {
return array( return array(
'humanize' => new \Twig_Filter_Function(__NAMESPACE__.'\humanize'), 'humanize' => new \Twig_Filter_Method($this, 'renderer->humanize'),
); );
} }
public function isChoiceGroup($label)
{
return FormUtil::isChoiceGroup($label);
}
public function isChoiceSelected(FormView $view, ChoiceView $choice)
{
return FormUtil::isChoiceSelected($choice->getValue(), $view->getVar('value'));
}
/** /**
* Renders the HTML enctype in the form tag, if necessary * {@inheritdoc}
*
* Example usage in Twig templates:
*
* <form action="..." method="post" {{ form_enctype(form) }}>
*
* @param FormView $view The view for which to render the encoding type
*
* @return string The html markup
*/
public function renderEnctype(FormView $view)
{
return $this->render($view, 'enctype');
}
/**
* Renders a row for the view.
*
* @param FormView $view The view to render as a row
* @param array $variables An array of variables
*
* @return string The html markup
*/
public function renderRow(FormView $view, array $variables = array())
{
return $this->render($view, 'row', $variables);
}
/**
* Renders views which have not already been rendered.
*
* @param FormView $view The parent view
* @param array $variables An array of variables
*
* @return string The html markup
*/
public function renderRest(FormView $view, array $variables = array())
{
return $this->render($view, 'rest', $variables);
}
/**
* Renders the HTML for a given view
*
* Example usage in Twig:
*
* {{ form_widget(view) }}
*
* You can pass options during the call:
*
* {{ form_widget(view, {'attr': {'class': 'foo'}}) }}
*
* {{ form_widget(view, {'separator': '+++++'}) }}
*
* @param FormView $view The view to render
* @param array $variables Additional variables passed to the template
*
* @return string The html markup
*/
public function renderWidget(FormView $view, array $variables = array())
{
return $this->render($view, 'widget', $variables);
}
/**
* Renders the errors of the given view
*
* @param FormView $view The view to render the errors for
*
* @return string The html markup
*/
public function renderErrors(FormView $view)
{
return $this->render($view, 'errors');
}
/**
* Renders the label of the given view
*
* @param FormView $view The view to render the label for
* @param string $label Label name
* @param array $variables Additional variables passed to the template
*
* @return string The html markup
*/
public function renderLabel(FormView $view, $label = null, array $variables = array())
{
if ($label !== null) {
$variables += array('label' => $label);
}
return $this->render($view, 'label', $variables);
}
/**
* Renders a template.
*
* 1. This function first looks for a block named "_<view id>_<section>",
* 2. if such a block is not found the function will look for a block named
* "<type name>_<section>",
* 3. the type name is recursively replaced by the parent type name until a
* corresponding block is found
*
* @param FormView $view The form view
* @param string $section The section to render (i.e. 'row', 'widget', 'label', ...)
* @param array $variables Additional variables
*
* @return string The html markup
*
* @throws FormException if no template block exists to render the given section of the view
*/
protected function render(FormView $view, $section, array $variables = array())
{
$mainTemplate = in_array($section, array('widget', 'row'));
if ($mainTemplate && $view->isRendered()) {
return '';
}
if (null === $this->template) {
$this->template = reset($this->resources);
if (!$this->template instanceof \Twig_Template) {
$this->template = $this->environment->loadTemplate($this->template);
}
}
$custom = '_'.$view->getVar('id');
$rendering = $custom.$section;
$blocks = $this->getBlocks($view);
if (isset($this->varStack[$rendering])) {
$typeIndex = $this->varStack[$rendering]['typeIndex'] - 1;
$types = $this->varStack[$rendering]['types'];
$this->varStack[$rendering]['variables'] = array_replace_recursive($this->varStack[$rendering]['variables'], $variables);
} else {
$types = $view->getVar('types');
$types[] = $view->getVar('full_block_name');
$typeIndex = count($types) - 1;
$this->varStack[$rendering] = array(
'variables' => array_replace_recursive($view->getVars(), $variables),
'types' => $types,
);
}
do {
$types[$typeIndex] .= '_'.$section;
if (isset($blocks[$types[$typeIndex]])) {
$this->varStack[$rendering]['typeIndex'] = $typeIndex;
$context = $this->environment->mergeGlobals($this->varStack[$rendering]['variables']);
// we do not call renderBlock here to avoid too many nested level calls (XDebug limits the level to 100 by default)
ob_start();
$this->template->displayBlock($types[$typeIndex], $context, $blocks);
$html = ob_get_clean();
if ($mainTemplate) {
$view->setRendered();
}
unset($this->varStack[$rendering]);
return $html;
}
} while (--$typeIndex >= 0);
throw new FormException(sprintf(
'Unable to render the form as none of the following blocks exist: "%s".',
implode('", "', array_reverse($types))
));
}
/**
* Returns a CSRF token.
*
* Use this helper for CSRF protection without the overhead of creating a
* form.
*
* <code>
* <input type="hidden" name="token" value="{{ csrf_token('rm_user_' ~ user.id) }}">
* </code>
*
* Check the token in your action using the same intention.
*
* <code>
* $csrfProvider = $this->get('form.csrf_provider');
* if (!$csrfProvider->isCsrfTokenValid('rm_user_'.$user->getId(), $token)) {
* throw new \RuntimeException('CSRF attack detected.');
* }
* </code>
*
* @param string $intention The intention of the protected action
*
* @return string A CSRF token
*/
public function getCsrfToken($intention)
{
if (!$this->csrfProvider instanceof CsrfProviderInterface) {
throw new \BadMethodCallException('CSRF token can only be generated if a CsrfProviderInterface is injected in the constructor.');
}
return $this->csrfProvider->generateCsrfToken($intention);
}
/**
* Returns the name of the extension.
*
* @return string The extension name
*/ */
public function getName() public function getName()
{ {
return 'form'; return 'form';
} }
/**
* Returns the blocks used to render the view.
*
* Templates are looked for in the resources in the following order:
* * resources from the themes (and its parents)
* * resources from the themes of parent views (up to the root view)
* * default resources
*
* @param FormView $view The view
*
* @return array An array of Twig_TemplateInterface instances
*/
protected function getBlocks(FormView $view)
{
if (!$this->blocks->contains($view)) {
$rootView = !$view->hasParent();
$templates = $rootView ? $this->resources : array();
if (isset($this->themes[$view])) {
$templates = array_merge($templates, $this->themes[$view]);
}
$blocks = array();
foreach ($templates as $template) {
if (!$template instanceof \Twig_Template) {
$template = $this->environment->loadTemplate($template);
}
$templateBlocks = array();
do {
$templateBlocks = array_merge($template->getBlocks(), $templateBlocks);
} while (false !== $template = $template->getParent(array()));
$blocks = array_merge($blocks, $templateBlocks);
}
if (!$rootView) {
$blocks = array_merge($this->getBlocks($view->getParent()), $blocks);
}
$this->blocks->attach($view, $blocks);
} else {
$blocks = $this->blocks[$view];
}
return $blocks;
}
}
function humanize($text)
{
return ucfirst(trim(strtolower(preg_replace('/[_\s]+/', ' ', $text))));
} }

View File

@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Symfony package.
*
* (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 Symfony\Bridge\Twig\Form;
use Symfony\Component\Form\FormRenderer;
use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class TwigRenderer extends FormRenderer implements TwigRendererInterface
{
/**
* @var TwigRendererEngineInterface
*/
private $engine;
public function __construct(TwigRendererEngineInterface $engine, CsrfProviderInterface $csrfProvider = null)
{
parent::__construct($engine, $csrfProvider);
$this->engine = $engine;
}
/**
* {@inheritdoc}
*/
public function setEnvironment(\Twig_Environment $environment)
{
$this->engine->setEnvironment($environment);
}
}

View File

@ -0,0 +1,183 @@
<?php
/*
* This file is part of the Symfony package.
*
* (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 Symfony\Bridge\Twig\Form;
use Symfony\Component\Form\AbstractRendererEngine;
use Symfony\Component\Form\FormViewInterface;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class TwigRendererEngine extends AbstractRendererEngine implements TwigRendererEngineInterface
{
/**
* @var \Twig_Environment
*/
private $environment;
/**
* @var \Twig_Template
*/
private $template;
/**
* {@inheritdoc}
*/
public function setEnvironment(\Twig_Environment $environment)
{
$this->environment = $environment;
}
/**
* {@inheritdoc}
*/
public function renderBlock(FormViewInterface $view, $resource, $block, array $variables = array())
{
$cacheKey = $view->getVar(self::CACHE_KEY_VAR);
$context = $this->environment->mergeGlobals($variables);
ob_start();
// By contract,This method can only be called after getting the resource
// (which is passed to the method). Getting a resource for the first time
// (with an empty cache) is guaranteed to invoke loadResourcesFromTheme(),
// where the property $template is initialized.
// We do not call renderBlock here to avoid too many nested level calls
// (XDebug limits the level to 100 by default)
$this->template->displayBlock($block, $context, $this->resources[$cacheKey]);
return ob_get_clean();
}
/**
* Loads the cache with the resource for a given block name.
*
* This implementation eagerly loads all blocks of the themes assigned to the given view
* and all of its ancestors views. This is necessary, because Twig receives the
* list of blocks later. At that point, all blocks must already be loaded, for the
* case that the function "block()" is used in the Twig template.
*
* @see getResourceForBlock()
*
* @param string $cacheKey The cache key of the form view.
* @param FormViewInterface $view The form view for finding the applying themes.
* @param string $block The name of the block to load.
*
* @return Boolean True if the resource could be loaded, false otherwise.
*/
protected function loadResourceForBlock($cacheKey, FormViewInterface $view, $block)
{
// The caller guarantees that $this->resources[$cacheKey][$block] is
// not set, but it doesn't have to check whether $this->resources[$cacheKey]
// is set. If $this->resources[$cacheKey] is set, all themes for this
// $cacheKey are already loaded (due to the eager population, see doc comment).
if (isset($this->resources[$cacheKey])) {
// As said in the previous, the caller guarantees that
// $this->resources[$cacheKey][$block] is not set. Since the themes are
// already loaded, it can only be a non-existing block.
$this->resources[$cacheKey][$block] = false;
return false;
}
// Recursively try to find the block in the themes assigned to $view,
// then of its parent view, then of the parent view of the parent and so on.
// When the root view is reached in this recursion, also the default
// themes are taken into account.
// Check each theme whether it contains the searched block
if (isset($this->themes[$cacheKey])) {
for ($i = count($this->themes[$cacheKey]) - 1; $i >= 0; --$i) {
$this->loadResourcesFromTheme($cacheKey, $this->themes[$cacheKey][$i]);
// CONTINUE LOADING (see doc comment)
}
}
// Check the default themes once we reach the root view without success
if (!$view->hasParent()) {
for ($i = count($this->defaultThemes) - 1; $i >= 0; --$i) {
$this->loadResourcesFromTheme($cacheKey, $this->defaultThemes[$i]);
// CONTINUE LOADING (see doc comment)
}
}
// Proceed with the themes of the parent view
if ($view->hasParent()) {
$parentCacheKey = $view->getParent()->getVar(self::CACHE_KEY_VAR);
if (!isset($this->resources[$parentCacheKey])) {
$this->loadResourceForBlock($parentCacheKey, $view->getParent(), $block);
}
// EAGER CACHE POPULATION (see doc comment)
foreach ($this->resources[$parentCacheKey] as $blockName => $resource) {
if (!isset($this->resources[$cacheKey][$blockName])) {
$this->resources[$cacheKey][$blockName] = $resource;
}
}
}
// Even though we loaded the themes, it can happen that none of them
// contains the searched block
if (!isset($this->resources[$cacheKey][$block])) {
// Cache that we didn't find anything to speed up further accesses
$this->resources[$cacheKey][$block] = false;
}
return false !== $this->resources[$cacheKey][$block];
}
/**
* Loads the resources for all blocks in a theme.
*
* @param string $cacheKey The cache key for storing the resource.
* @param mixed $theme The theme to load the block from. This parameter
* is passed by reference, because it might be necessary
* to initialize the theme first. Any changes made to
* this variable will be kept and be available upon
* further calls to this method using the same theme.
*/
protected function loadResourcesFromTheme($cacheKey, &$theme)
{
if (!$theme instanceof \Twig_Template) {
/* @var \Twig_Template $theme */
$theme = $this->environment->loadTemplate($theme);
}
if (null === $this->template) {
// Store the first \Twig_Template instance that we find so that
// we can call displayBlock() later on. It doesn't matter *which*
// template we use for that, since we pass the used blocks manually
// anyway.
$this->template = $theme;
}
// Use a separate variable for the inheritance traversal, because
// theme is a reference and we don't want to change it.
$currentTheme = $theme;
// The do loop takes care of template inheritance.
// Add blocks from all templates in the inheritance tree, but avoid
// overriding blocks already set.
do {
foreach ($currentTheme->getBlocks() as $block => $blockData) {
if (!isset($this->resources[$cacheKey][$block])) {
// The resource given back is the key to the bucket that
// contains this block.
$this->resources[$cacheKey][$block] = $blockData;
}
}
} while (false !== $currentTheme = $currentTheme->getParent(array()));
}
}

View File

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (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 Symfony\Bridge\Twig\Form;
use Symfony\Component\Form\FormRendererEngineInterface;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface TwigRendererEngineInterface extends FormRendererEngineInterface
{
/**
* Sets Twig's environment.
*
* @param \Twig_Environment $environment
*/
public function setEnvironment(\Twig_Environment $environment);
}

View File

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (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 Symfony\Bridge\Twig\Form;
use Symfony\Component\Form\FormRendererInterface;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface TwigRendererInterface extends FormRendererInterface
{
/**
* Sets Twig's environment.
*
* @param \Twig_Environment $environment
*/
public function setEnvironment(\Twig_Environment $environment);
}

View File

@ -30,7 +30,7 @@ class FormThemeNode extends \Twig_Node
{ {
$compiler $compiler
->addDebugInfo($this) ->addDebugInfo($this)
->write('echo $this->env->getExtension(\'form\')->setTheme(') ->write('$this->env->getExtension(\'form\')->renderer->setTheme(')
->subcompile($this->getNode('form')) ->subcompile($this->getNode('form'))
->raw(', ') ->raw(', ')
->subcompile($this->getNode('resources')) ->subcompile($this->getNode('resources'))

View File

@ -12,6 +12,8 @@
namespace Symfony\Bridge\Twig\Tests\Extension; namespace Symfony\Bridge\Twig\Tests\Extension;
use Symfony\Bridge\Twig\Extension\FormExtension; use Symfony\Bridge\Twig\Extension\FormExtension;
use Symfony\Bridge\Twig\Form\TwigRenderer;
use Symfony\Bridge\Twig\Form\TwigRendererEngine;
use Symfony\Bridge\Twig\Extension\TranslationExtension; use Symfony\Bridge\Twig\Extension\TranslationExtension;
use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubTranslator; use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubTranslator;
use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubFilesystemLoader; use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubFilesystemLoader;
@ -20,6 +22,9 @@ use Symfony\Component\Form\Tests\AbstractDivLayoutTest;
class FormExtensionDivLayoutTest extends AbstractDivLayoutTest class FormExtensionDivLayoutTest extends AbstractDivLayoutTest
{ {
/**
* @var FormExtension
*/
protected $extension; protected $extension;
protected function setUp() protected function setUp()
@ -42,20 +47,23 @@ class FormExtensionDivLayoutTest extends AbstractDivLayoutTest
parent::setUp(); parent::setUp();
$loader = new StubFilesystemLoader(array( $rendererEngine = new TwigRendererEngine(array(
__DIR__.'/../../../../../../src/Symfony/Bridge/Twig/Resources/views/Form',
__DIR__,
));
$this->extension = new FormExtension($this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface'), array(
'form_div_layout.html.twig', 'form_div_layout.html.twig',
'custom_widgets.html.twig', 'custom_widgets.html.twig',
)); ));
$renderer = new TwigRenderer($rendererEngine, $this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface'));
$this->extension = new FormExtension($renderer);
$loader = new StubFilesystemLoader(array(
__DIR__.'/../../Resources/views/Form',
__DIR__,
));
$environment = new \Twig_Environment($loader, array('strict_variables' => true)); $environment = new \Twig_Environment($loader, array('strict_variables' => true));
$environment->addExtension($this->extension);
$environment->addExtension(new TranslationExtension(new StubTranslator())); $environment->addExtension(new TranslationExtension(new StubTranslator()));
$environment->addGlobal('global', ''); $environment->addGlobal('global', '');
$environment->addExtension($this->extension);
$this->extension->initRuntime($environment); $this->extension->initRuntime($environment);
} }
@ -99,37 +107,37 @@ class FormExtensionDivLayoutTest extends AbstractDivLayoutTest
protected function renderEnctype(FormView $view) protected function renderEnctype(FormView $view)
{ {
return (string) $this->extension->renderEnctype($view); return (string) $this->extension->renderer->renderEnctype($view);
} }
protected function renderLabel(FormView $view, $label = null, array $vars = array()) protected function renderLabel(FormView $view, $label = null, array $vars = array())
{ {
return (string) $this->extension->renderLabel($view, $label, $vars); return (string) $this->extension->renderer->renderLabel($view, $label, $vars);
} }
protected function renderErrors(FormView $view) protected function renderErrors(FormView $view)
{ {
return (string) $this->extension->renderErrors($view); return (string) $this->extension->renderer->renderErrors($view);
} }
protected function renderWidget(FormView $view, array $vars = array()) protected function renderWidget(FormView $view, array $vars = array())
{ {
return (string) $this->extension->renderWidget($view, $vars); return (string) $this->extension->renderer->renderWidget($view, $vars);
} }
protected function renderRow(FormView $view, array $vars = array()) protected function renderRow(FormView $view, array $vars = array())
{ {
return (string) $this->extension->renderRow($view, $vars); return (string) $this->extension->renderer->renderRow($view, $vars);
} }
protected function renderRest(FormView $view, array $vars = array()) protected function renderRest(FormView $view, array $vars = array())
{ {
return (string) $this->extension->renderRest($view, $vars); return (string) $this->extension->renderer->renderRest($view, $vars);
} }
protected function setTheme(FormView $view, array $themes) protected function setTheme(FormView $view, array $themes)
{ {
$this->extension->setTheme($view, $themes); $this->extension->renderer->setTheme($view, $themes);
} }
public static function themeBlockInheritanceProvider() public static function themeBlockInheritanceProvider()

View File

@ -12,6 +12,8 @@
namespace Symfony\Bridge\Twig\Tests\Extension; namespace Symfony\Bridge\Twig\Tests\Extension;
use Symfony\Component\Form\FormView; use Symfony\Component\Form\FormView;
use Symfony\Bridge\Twig\Form\TwigRenderer;
use Symfony\Bridge\Twig\Form\TwigRendererEngine;
use Symfony\Bridge\Twig\Extension\FormExtension; use Symfony\Bridge\Twig\Extension\FormExtension;
use Symfony\Bridge\Twig\Extension\TranslationExtension; use Symfony\Bridge\Twig\Extension\TranslationExtension;
use Symfony\Component\Form\Tests\AbstractTableLayoutTest; use Symfony\Component\Form\Tests\AbstractTableLayoutTest;
@ -20,6 +22,9 @@ use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubFilesystemLoader;
class FormExtensionTableLayoutTest extends AbstractTableLayoutTest class FormExtensionTableLayoutTest extends AbstractTableLayoutTest
{ {
/**
* @var FormExtension
*/
protected $extension; protected $extension;
protected function setUp() protected function setUp()
@ -42,20 +47,23 @@ class FormExtensionTableLayoutTest extends AbstractTableLayoutTest
parent::setUp(); parent::setUp();
$loader = new StubFilesystemLoader(array( $rendererEngine = new TwigRendererEngine(array(
__DIR__.'/../../../../../../src/Symfony/Bridge/Twig/Resources/views/Form',
__DIR__,
));
$this->extension = new FormExtension($this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface'), array(
'form_table_layout.html.twig', 'form_table_layout.html.twig',
'custom_widgets.html.twig', 'custom_widgets.html.twig',
)); ));
$renderer = new TwigRenderer($rendererEngine, $this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface'));
$this->extension = new FormExtension($renderer);
$loader = new StubFilesystemLoader(array(
__DIR__.'/../../Resources/views/Form',
__DIR__,
));
$environment = new \Twig_Environment($loader, array('strict_variables' => true)); $environment = new \Twig_Environment($loader, array('strict_variables' => true));
$environment->addExtension($this->extension);
$environment->addExtension(new TranslationExtension(new StubTranslator())); $environment->addExtension(new TranslationExtension(new StubTranslator()));
$environment->addGlobal('global', ''); $environment->addGlobal('global', '');
$environment->addExtension($this->extension);
$this->extension->initRuntime($environment); $this->extension->initRuntime($environment);
} }
@ -69,36 +77,36 @@ class FormExtensionTableLayoutTest extends AbstractTableLayoutTest
protected function renderEnctype(FormView $view) protected function renderEnctype(FormView $view)
{ {
return (string) $this->extension->renderEnctype($view); return (string) $this->extension->renderer->renderEnctype($view);
} }
protected function renderLabel(FormView $view, $label = null, array $vars = array()) protected function renderLabel(FormView $view, $label = null, array $vars = array())
{ {
return (string) $this->extension->renderLabel($view, $label, $vars); return (string) $this->extension->renderer->renderLabel($view, $label, $vars);
} }
protected function renderErrors(FormView $view) protected function renderErrors(FormView $view)
{ {
return (string) $this->extension->renderErrors($view); return (string) $this->extension->renderer->renderErrors($view);
} }
protected function renderWidget(FormView $view, array $vars = array()) protected function renderWidget(FormView $view, array $vars = array())
{ {
return (string) $this->extension->renderWidget($view, $vars); return (string) $this->extension->renderer->renderWidget($view, $vars);
} }
protected function renderRow(FormView $view, array $vars = array()) protected function renderRow(FormView $view, array $vars = array())
{ {
return (string) $this->extension->renderRow($view, $vars); return (string) $this->extension->renderer->renderRow($view, $vars);
} }
protected function renderRest(FormView $view, array $vars = array()) protected function renderRest(FormView $view, array $vars = array())
{ {
return (string) $this->extension->renderRest($view, $vars); return (string) $this->extension->renderer->renderRest($view, $vars);
} }
protected function setTheme(FormView $view, array $themes) protected function setTheme(FormView $view, array $themes)
{ {
$this->extension->setTheme($view, $themes); $this->extension->renderer->setTheme($view, $themes);
} }
} }

View File

@ -55,7 +55,7 @@ class FormThemeTest extends TestCase
$this->assertEquals( $this->assertEquals(
sprintf( sprintf(
'echo $this->env->getExtension(\'form\')->setTheme(%s, array(0 => "tpl1", 1 => "tpl2"));', '$this->env->getExtension(\'form\')->renderer->setTheme(%s, array(0 => "tpl1", 1 => "tpl2"));',
$this->getVariableGetter('form') $this->getVariableGetter('form')
), ),
trim($compiler->compile($node)->getSource()) trim($compiler->compile($node)->getSource())
@ -67,7 +67,7 @@ class FormThemeTest extends TestCase
$this->assertEquals( $this->assertEquals(
sprintf( sprintf(
'echo $this->env->getExtension(\'form\')->setTheme(%s, "tpl1");', '$this->env->getExtension(\'form\')->renderer->setTheme(%s, "tpl1");',
$this->getVariableGetter('form') $this->getVariableGetter('form')
), ),
trim($compiler->compile($node)->getSource()) trim($compiler->compile($node)->getSource())

View File

@ -1,89 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (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 Symfony\Bundle\FrameworkBundle\DataCollector;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\RequestDataCollector as BaseRequestDataCollector;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
/**
* RequestDataCollector.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class RequestDataCollector extends BaseRequestDataCollector
{
protected $controllers;
public function __construct()
{
$this->controllers = new \SplObjectStorage();
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = null)
{
parent::collect($request, $response, $exception);
$this->data['controller'] = 'n/a';
if (isset($this->controllers[$request])) {
$controller = $this->controllers[$request];
if (is_array($controller)) {
$r = new \ReflectionMethod($controller[0], $controller[1]);
$this->data['controller'] = array(
'class' => get_class($controller[0]),
'method' => $controller[1],
'file' => $r->getFilename(),
'line' => $r->getStartLine(),
);
} elseif ($controller instanceof \Closure) {
$this->data['controller'] = 'Closure';
} else {
$this->data['controller'] = (string) $controller ?: 'n/a';
}
unset($this->controllers[$request]);
}
}
public function onKernelController(FilterControllerEvent $event)
{
$this->controllers[$event->getRequest()] = $event->getController();
}
/**
* {@inheritdoc}
*/
public function getRoute()
{
return isset($this->data['request_attributes']['_route']) ? $this->data['request_attributes']['_route'] : '';
}
/**
* {@inheritdoc}
*/
public function getRouteParams()
{
return isset($this->data['request_attributes']['_route_params']) ? $this->data['request_attributes']['_route_params'] : array();
}
/**
* {@inheritdoc}
*/
public function getController()
{
return $this->data['controller'];
}
}

View File

@ -6,7 +6,7 @@
<parameters> <parameters>
<parameter key="data_collector.config.class">Symfony\Component\HttpKernel\DataCollector\ConfigDataCollector</parameter> <parameter key="data_collector.config.class">Symfony\Component\HttpKernel\DataCollector\ConfigDataCollector</parameter>
<parameter key="data_collector.request.class">Symfony\Bundle\FrameworkBundle\DataCollector\RequestDataCollector</parameter> <parameter key="data_collector.request.class">Symfony\Component\HttpKernel\DataCollector\RequestDataCollector</parameter>
<parameter key="data_collector.exception.class">Symfony\Component\HttpKernel\DataCollector\ExceptionDataCollector</parameter> <parameter key="data_collector.exception.class">Symfony\Component\HttpKernel\DataCollector\ExceptionDataCollector</parameter>
<parameter key="data_collector.events.class">Symfony\Component\HttpKernel\DataCollector\EventDataCollector</parameter> <parameter key="data_collector.events.class">Symfony\Component\HttpKernel\DataCollector\EventDataCollector</parameter>
<parameter key="data_collector.logger.class">Symfony\Component\HttpKernel\DataCollector\LoggerDataCollector</parameter> <parameter key="data_collector.logger.class">Symfony\Component\HttpKernel\DataCollector\LoggerDataCollector</parameter>
@ -22,7 +22,7 @@
</service> </service>
<service id="data_collector.request" class="%data_collector.request.class%"> <service id="data_collector.request" class="%data_collector.request.class%">
<tag name="kernel.event_listener" event="kernel.controller" method="onKernelController"/> <tag name="kernel.event_subscriber" />
<tag name="data_collector" template="WebProfilerBundle:Collector:request" id="request" priority="255" /> <tag name="data_collector" template="WebProfilerBundle:Collector:request" id="request" priority="255" />
</service> </service>

View File

@ -15,6 +15,8 @@
<parameter key="templating.helper.code.class">Symfony\Bundle\FrameworkBundle\Templating\Helper\CodeHelper</parameter> <parameter key="templating.helper.code.class">Symfony\Bundle\FrameworkBundle\Templating\Helper\CodeHelper</parameter>
<parameter key="templating.helper.translator.class">Symfony\Bundle\FrameworkBundle\Templating\Helper\TranslatorHelper</parameter> <parameter key="templating.helper.translator.class">Symfony\Bundle\FrameworkBundle\Templating\Helper\TranslatorHelper</parameter>
<parameter key="templating.helper.form.class">Symfony\Bundle\FrameworkBundle\Templating\Helper\FormHelper</parameter> <parameter key="templating.helper.form.class">Symfony\Bundle\FrameworkBundle\Templating\Helper\FormHelper</parameter>
<parameter key="templating.form.engine.class">Symfony\Component\Form\Extension\Templating\TemplatingRendererEngine</parameter>
<parameter key="templating.form.renderer.class">Symfony\Component\Form\FormRenderer</parameter>
<parameter key="templating.globals.class">Symfony\Bundle\FrameworkBundle\Templating\GlobalVariables</parameter> <parameter key="templating.globals.class">Symfony\Bundle\FrameworkBundle\Templating\GlobalVariables</parameter>
<parameter key="templating.asset.path_package.class">Symfony\Bundle\FrameworkBundle\Templating\Asset\PathPackage</parameter> <parameter key="templating.asset.path_package.class">Symfony\Bundle\FrameworkBundle\Templating\Asset\PathPackage</parameter>
<parameter key="templating.asset.url_package.class">Symfony\Component\Templating\Asset\UrlPackage</parameter> <parameter key="templating.asset.url_package.class">Symfony\Component\Templating\Asset\UrlPackage</parameter>
@ -96,11 +98,19 @@
<service id="templating.helper.form" class="%templating.helper.form.class%"> <service id="templating.helper.form" class="%templating.helper.form.class%">
<tag name="templating.helper" alias="form" /> <tag name="templating.helper" alias="form" />
<argument type="service" id="templating.form.renderer" />
</service>
<service id="templating.form.engine" class="%templating.form.engine.class%" public="false">
<argument type="service" id="templating.engine.php" /> <argument type="service" id="templating.engine.php" />
<argument type="service" id="form.csrf_provider" on-invalid="null" />
<argument>%templating.helper.form.resources%</argument> <argument>%templating.helper.form.resources%</argument>
</service> </service>
<service id="templating.form.renderer" class="%templating.form.renderer.class%" public="false">
<argument type="service" id="templating.form.engine" />
<argument type="service" id="form.csrf_provider" on-invalid="null" />
</service>
<service id="templating.globals" class="%templating.globals.class%"> <service id="templating.globals" class="%templating.globals.class%">
<argument type="service" id="service_container" /> <argument type="service" id="service_container" />
</service> </service>

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('widget_attributes') ?> <?php echo $view['form']->block('widget_attributes') ?>

View File

@ -1,5 +1,5 @@
<input type="checkbox" <input type="checkbox"
<?php echo $view['form']->renderBlock('widget_attributes') ?> <?php echo $view['form']->block('widget_attributes') ?>
<?php if ($value): ?> value="<?php echo $view->escape($value) ?>"<?php endif ?> <?php if ($value): ?> value="<?php echo $view->escape($value) ?>"<?php endif ?>
<?php if ($checked): ?> checked="checked"<?php endif ?> <?php if ($checked): ?> checked="checked"<?php endif ?>
/> />

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('choice_widget_options') ?> <?php echo $view['form']->block('choice_widget_options') ?>

View File

@ -1,5 +1,5 @@
<?php if ($expanded): ?> <?php if ($expanded): ?>
<?php echo $view['form']->renderBlock('choice_widget_expanded') ?> <?php echo $view['form']->block('choice_widget_expanded') ?>
<?php else: ?> <?php else: ?>
<?php echo $view['form']->renderBlock('choice_widget_collapsed') ?> <?php echo $view['form']->block('choice_widget_collapsed') ?>
<?php endif ?> <?php endif ?>

View File

@ -1,13 +1,13 @@
<select <select
<?php echo $view['form']->renderBlock('widget_attributes') ?> <?php echo $view['form']->block('widget_attributes') ?>
<?php if ($multiple): ?> multiple="multiple"<?php endif ?> <?php if ($multiple): ?> multiple="multiple"<?php endif ?>
> >
<?php if (null !== $empty_value): ?><option value=""><?php echo $view->escape($view['translator']->trans($empty_value, array(), $translation_domain)) ?></option><?php endif; ?> <?php if (null !== $empty_value): ?><option value=""><?php echo $view->escape($view['translator']->trans($empty_value, array(), $translation_domain)) ?></option><?php endif; ?>
<?php if (count($preferred_choices) > 0): ?> <?php if (count($preferred_choices) > 0): ?>
<?php echo $view['form']->renderBlock('choice_widget_options', array('options' => $preferred_choices)) ?> <?php echo $view['form']->block('choice_widget_options', array('options' => $preferred_choices)) ?>
<?php if (count($choices) > 0 && null !== $separator): ?> <?php if (count($choices) > 0 && null !== $separator): ?>
<option disabled="disabled"><?php echo $separator ?></option> <option disabled="disabled"><?php echo $separator ?></option>
<?php endif ?> <?php endif ?>
<?php endif ?> <?php endif ?>
<?php echo $view['form']->renderBlock('choice_widget_options', array('options' => $choices)) ?> <?php echo $view['form']->block('choice_widget_options', array('options' => $choices)) ?>
</select> </select>

View File

@ -1,4 +1,4 @@
<div <?php echo $view['form']->renderBlock('widget_container_attributes') ?>> <div <?php echo $view['form']->block('widget_container_attributes') ?>>
<?php foreach ($form as $child): ?> <?php foreach ($form as $child): ?>
<?php echo $view['form']->widget($child) ?> <?php echo $view['form']->widget($child) ?>
<?php echo $view['form']->label($child) ?> <?php echo $view['form']->label($child) ?>

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('widget_container_attributes') ?> <?php echo $view['form']->block('widget_container_attributes') ?>

View File

@ -1,7 +1,7 @@
<?php if ($widget == 'single_text'): ?> <?php if ($widget == 'single_text'): ?>
<?php echo $view['form']->renderBlock('form_widget_simple'); ?> <?php echo $view['form']->block('form_widget_simple'); ?>
<?php else: ?> <?php else: ?>
<div <?php echo $view['form']->renderBlock('widget_container_attributes') ?>> <div <?php echo $view['form']->block('widget_container_attributes') ?>>
<?php echo str_replace(array('{{ year }}', '{{ month }}', '{{ day }}'), array( <?php echo str_replace(array('{{ year }}', '{{ month }}', '{{ day }}'), array(
$view['form']->widget($form['year']), $view['form']->widget($form['year']),
$view['form']->widget($form['month']), $view['form']->widget($form['month']),

View File

@ -1,7 +1,7 @@
<?php if ($widget == 'single_text'): ?> <?php if ($widget == 'single_text'): ?>
<?php echo $view['form']->renderBlock('form_widget_simple'); ?> <?php echo $view['form']->block('form_widget_simple'); ?>
<?php else: ?> <?php else: ?>
<div <?php echo $view['form']->renderBlock('widget_container_attributes') ?>> <div <?php echo $view['form']->block('widget_container_attributes') ?>>
<?php echo $view['form']->widget($form['date']).' '.$view['form']->widget($form['time']) ?> <?php echo $view['form']->widget($form['date']).' '.$view['form']->widget($form['time']) ?>
</div> </div>
<?php endif ?> <?php endif ?>

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('form_widget_simple', array('type' => isset($type) ? $type : 'email')) ?> <?php echo $view['form']->block('form_widget_simple', array('type' => isset($type) ? $type : 'email')) ?>

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('form_enctype') ?> <?php echo $view['form']->block('form_enctype') ?>

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('form_errors') ?> <?php echo $view['form']->block('form_errors') ?>

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('form_label') ?> <?php echo $view['form']->block('form_label') ?>

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('form_rest') ?> <?php echo $view['form']->block('form_rest') ?>

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('form_row') ?> <?php echo $view['form']->block('form_row') ?>

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('form_rows') ?> <?php echo $view['form']->block('form_rows') ?>

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('form_widget_simple') ?> <?php echo $view['form']->block('form_widget_simple') ?>

View File

@ -1,5 +1,5 @@
<?php if ($compound): ?> <?php if ($compound): ?>
<?php echo $view['form']->renderBlock('form_widget_compound')?> <?php echo $view['form']->block('form_widget_compound')?>
<?php else: ?> <?php else: ?>
<?php echo $view['form']->renderBlock('form_widget_simple')?> <?php echo $view['form']->block('form_widget_simple')?>
<?php endif ?> <?php endif ?>

View File

@ -1,4 +1,4 @@
<div <?php echo $view['form']->renderBlock('widget_container_attributes') ?>> <div <?php echo $view['form']->block('widget_container_attributes') ?>>
<?php if (!$form->hasParent() && $errors): ?> <?php if (!$form->hasParent() && $errors): ?>
<tr> <tr>
<td colspan="2"> <td colspan="2">
@ -6,6 +6,6 @@
</td> </td>
</tr> </tr>
<?php endif ?> <?php endif ?>
<?php echo $view['form']->renderBlock('form_rows') ?> <?php echo $view['form']->block('form_rows') ?>
<?php echo $view['form']->rest($form) ?> <?php echo $view['form']->rest($form) ?>
</div> </div>

View File

@ -1,5 +1,5 @@
<input <input
type="<?php echo isset($type) ? $view->escape($type) : 'text' ?>" type="<?php echo isset($type) ? $view->escape($type) : 'text' ?>"
<?php if (!empty($value)): ?>value="<?php echo $view->escape($value) ?>"<?php endif ?> <?php if (!empty($value)): ?>value="<?php echo $view->escape($value) ?>"<?php endif ?>
<?php echo $view['form']->renderBlock('widget_attributes') ?> <?php echo $view['form']->block('widget_attributes') ?>
/> />

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('form_widget_simple', array('type' => isset($type) ? $type : "hidden")) ?> <?php echo $view['form']->block('form_widget_simple', array('type' => isset($type) ? $type : "hidden")) ?>

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('form_widget_simple', array('type' => isset($type) ? $type : "number")) ?> <?php echo $view['form']->block('form_widget_simple', array('type' => isset($type) ? $type : "number")) ?>

View File

@ -1 +1 @@
<?php echo str_replace('{{ widget }}', $view['form']->renderBlock('form_widget_simple'), $money_pattern) ?> <?php echo str_replace('{{ widget }}', $view['form']->block('form_widget_simple'), $money_pattern) ?>

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('form_widget_simple', array('type' => isset($type) ? $type : "text")) ?> <?php echo $view['form']->block('form_widget_simple', array('type' => isset($type) ? $type : "text")) ?>

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('form_widget_simple', array('type' => isset($type) ? $type : "password")) ?> <?php echo $view['form']->block('form_widget_simple', array('type' => isset($type) ? $type : "password")) ?>

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('form_widget_simple', array('type' => isset($type) ? $type : "text")) ?> % <?php echo $view['form']->block('form_widget_simple', array('type' => isset($type) ? $type : "text")) ?> %

View File

@ -1,5 +1,5 @@
<input type="radio" <input type="radio"
<?php echo $view['form']->renderBlock('widget_attributes') ?> <?php echo $view['form']->block('widget_attributes') ?>
value="<?php echo $view->escape($value) ?>" value="<?php echo $view->escape($value) ?>"
<?php if ($checked): ?> checked="checked"<?php endif ?> <?php if ($checked): ?> checked="checked"<?php endif ?>
/> />

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('form_rows') ?> <?php echo $view['form']->block('form_rows') ?>

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('form_widget_simple', array('type' => isset($type) ? $type : "search")) ?> <?php echo $view['form']->block('form_widget_simple', array('type' => isset($type) ? $type : "search")) ?>

View File

@ -1 +1 @@
<textarea <?php echo $view['form']->renderBlock('widget_attributes') ?>><?php echo $view->escape($value) ?></textarea> <textarea <?php echo $view['form']->block('widget_attributes') ?>><?php echo $view->escape($value) ?></textarea>

View File

@ -1,7 +1,7 @@
<?php if ($widget == 'single_text'): ?> <?php if ($widget == 'single_text'): ?>
<?php echo $view['form']->renderBlock('form_widget_simple'); ?> <?php echo $view['form']->block('form_widget_simple'); ?>
<?php else: ?> <?php else: ?>
<div <?php echo $view['form']->renderBlock('widget_container_attributes') ?>> <div <?php echo $view['form']->block('widget_container_attributes') ?>>
<?php <?php
// There should be no spaces between the colons and the widgets, that's why // There should be no spaces between the colons and the widgets, that's why
// this block is written in a single PHP tag // this block is written in a single PHP tag

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('form_widget_simple', array('type' => isset($type) ? $type : "url")) ?> <?php echo $view['form']->block('form_widget_simple', array('type' => isset($type) ? $type : "url")) ?>

View File

@ -1,7 +1,7 @@
<table <?php echo $view['form']->renderBlock('widget_container_attributes') ?>> <table <?php echo $view['form']->block('widget_container_attributes') ?>>
<?php if (!$form->hasParent()): ?> <?php if (!$form->hasParent()): ?>
<?php echo $view['form']->errors($form) ?> <?php echo $view['form']->errors($form) ?>
<?php endif ?> <?php endif ?>
<?php echo $view['form']->renderBlock('form_rows') ?> <?php echo $view['form']->block('form_rows') ?>
<?php echo $view['form']->rest($form) ?> <?php echo $view['form']->rest($form) ?>
</table> </table>

View File

@ -12,8 +12,9 @@
namespace Symfony\Bundle\FrameworkBundle\Templating\Helper; namespace Symfony\Bundle\FrameworkBundle\Templating\Helper;
use Symfony\Component\Templating\Helper\Helper; use Symfony\Component\Templating\Helper\Helper;
use Symfony\Component\Form\FormRendererInterface;
use Symfony\Component\Form\FormViewInterface;
use Symfony\Component\Templating\EngineInterface; use Symfony\Component\Templating\EngineInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\Exception\FormException; use Symfony\Component\Form\Exception\FormException;
use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
use Symfony\Component\Form\Extension\Core\View\ChoiceView; use Symfony\Component\Form\Extension\Core\View\ChoiceView;
@ -27,46 +28,35 @@ use Symfony\Component\Form\Util\FormUtil;
*/ */
class FormHelper extends Helper class FormHelper extends Helper
{ {
protected $engine; /**
* @var FormRendererInterface
protected $csrfProvider; */
private $renderer;
protected $varStack;
protected $context;
protected $resources;
protected $themes;
protected $templates;
/** /**
* Constructor. * @param FormRendererInterface $renderer
*
* @param EngineInterface $engine The templating engine
* @param CsrfProviderInterface $csrfProvider The CSRF provider
* @param array $resources An array of theme names
*/ */
public function __construct(EngineInterface $engine, CsrfProviderInterface $csrfProvider = null, array $resources = array()) public function __construct(FormRendererInterface $renderer)
{ {
$this->engine = $engine; $this->renderer = $renderer;
$this->csrfProvider = $csrfProvider; }
$this->resources = $resources;
$this->varStack = array(); /**
$this->context = array(); * {@inheritdoc}
$this->templates = array(); */
$this->themes = array(); public function getName()
{
return 'form';
} }
public function isChoiceGroup($label) public function isChoiceGroup($label)
{ {
return FormUtil::isChoiceGroup($label); return $this->renderer->isChoiceGroup($label);
} }
public function isChoiceSelected(FormView $view, ChoiceView $choice) public function isChoiceSelected(FormViewInterface $view, ChoiceView $choice)
{ {
return FormUtil::isChoiceSelected($choice->getValue(), $view->getVar('value')); return $this->renderer->isChoiceSelected($view, $choice);
} }
/** /**
@ -74,13 +64,12 @@ class FormHelper extends Helper
* *
* The theme format is "<Bundle>:<Controller>". * The theme format is "<Bundle>:<Controller>".
* *
* @param FormView $view A FormView instance * @param FormViewInterface $view A FormViewInterface instance
* @param string|array $themes A theme or an array of theme * @param string|array $themes A theme or an array of theme
*/ */
public function setTheme(FormView $view, $themes) public function setTheme(FormViewInterface $view, $themes)
{ {
$this->themes[$view->getVar('id')] = (array) $themes; $this->renderer->setTheme($view, $themes);
$this->templates = array();
} }
/** /**
@ -90,13 +79,13 @@ class FormHelper extends Helper
* *
* <form action="..." method="post" <?php echo $view['form']->enctype() ?>> * <form action="..." method="post" <?php echo $view['form']->enctype() ?>>
* *
* @param FormView $view The view for which to render the encoding type * @param FormViewInterface $view The view for which to render the encoding type
* *
* @return string The html markup * @return string The HTML markup
*/ */
public function enctype(FormView $view) public function enctype(FormViewInterface $view)
{ {
return $this->renderSection($view, 'enctype'); return $this->renderer->renderEnctype($view);
} }
/** /**
@ -112,70 +101,95 @@ class FormHelper extends Helper
* *
* <?php echo view['form']->widget(array('separator' => '+++++)) ?> * <?php echo view['form']->widget(array('separator' => '+++++)) ?>
* *
* @param FormView $view The view for which to render the widget * @param FormViewInterface $view The view for which to render the widget
* @param array $variables Additional variables passed to the template * @param array $variables Additional variables passed to the template
* *
* @return string The html markup * @return string The HTML markup
*/ */
public function widget(FormView $view, array $variables = array()) public function widget(FormViewInterface $view, array $variables = array())
{ {
return $this->renderSection($view, 'widget', $variables); return $this->renderer->renderWidget($view, $variables);
} }
/** /**
* Renders the entire form field "row". * Renders the entire form field "row".
* *
* @param FormView $view The view for which to render the row * @param FormViewInterface $view The view for which to render the row
* @param array $variables Additional variables passed to the template * @param array $variables Additional variables passed to the template
* *
* @return string The html markup * @return string The HTML markup
*/ */
public function row(FormView $view, array $variables = array()) public function row(FormViewInterface $view, array $variables = array())
{ {
return $this->renderSection($view, 'row', $variables); return $this->renderer->renderRow($view, $variables);
} }
/** /**
* Renders the label of the given view. * Renders the label of the given view.
* *
* @param FormView $view The view for which to render the label * @param FormViewInterface $view The view for which to render the label
* @param string $label The label * @param string $label The label
* @param array $variables Additional variables passed to the template * @param array $variables Additional variables passed to the template
* *
* @return string The html markup * @return string The HTML markup
*/ */
public function label(FormView $view, $label = null, array $variables = array()) public function label(FormViewInterface $view, $label = null, array $variables = array())
{ {
if ($label !== null) { return $this->renderer->renderLabel($view, $label, $variables);
$variables += array('label' => $label);
}
return $this->renderSection($view, 'label', $variables);
} }
/** /**
* Renders the errors of the given view. * Renders the errors of the given view.
* *
* @param FormView $view The view to render the errors for * @param FormViewInterface $view The view to render the errors for
* *
* @return string The html markup * @return string The HTML markup
*/ */
public function errors(FormView $view) public function errors(FormViewInterface $view)
{ {
return $this->renderSection($view, 'errors'); return $this->renderer->renderErrors($view);
} }
/** /**
* Renders views which have not already been rendered. * Renders views which have not already been rendered.
* *
* @param FormView $view The parent view * @param FormViewInterface $view The parent view
* @param array $variables An array of variables * @param array $variables An array of variables
* *
* @return string The html markup * @return string The HTML markup
*/ */
public function rest(FormView $view, array $variables = array()) public function rest(FormViewInterface $view, array $variables = array())
{ {
return $this->renderSection($view, 'rest', $variables); return $this->renderer->renderRest($view, $variables);
}
/**
* Alias of {@link block()}
*
* @param string $block The name of the block to render.
* @param array $variables The variable to pass to the template.
*
* @return string The HTML markup
*
* @deprecated Deprecated since version 2.1, to be removed in 2.3. Use
* {@link block()} instead.
*/
public function renderBlock($block, array $variables = array())
{
return $this->block($block, $variables);
}
/**
* Renders a block of the template.
*
* @param string $block The name of the block to render.
* @param array $variables The variable to pass to the template.
*
* @return string The HTML markup
*/
public function block($block, array $variables = array())
{
return $this->renderer->renderBlock($block, $variables);
} }
/** /**
@ -200,166 +214,16 @@ class FormHelper extends Helper
* @param string $intention The intention of the protected action * @param string $intention The intention of the protected action
* *
* @return string A CSRF token * @return string A CSRF token
*
* @throws \BadMethodCallException When no CSRF provider was injected in the constructor.
*/ */
public function csrfToken($intention) public function csrfToken($intention)
{ {
if (!$this->csrfProvider instanceof CsrfProviderInterface) { return $this->renderer->renderCsrfToken($intention);
throw new \BadMethodCallException('CSRF token can only be generated if a CsrfProviderInterface is injected in the constructor.');
}
return $this->csrfProvider->generateCsrfToken($intention);
}
/**
* Renders a template.
*
* 1. This function first looks for a block named "_<view id>_<section>",
* 2. if such a block is not found the function will look for a block named
* "<type name>_<section>",
* 3. the type name is recursively replaced by the parent type name until a
* corresponding block is found
*
* @param FormView $view The form view
* @param string $section The section to render (i.e. 'row', 'widget', 'label', ...)
* @param array $variables Additional variables
*
* @return string The html markup
*
* @throws FormException if no template block exists to render the given section of the view
*/
protected function renderSection(FormView $view, $section, array $variables = array())
{
$mainTemplate = in_array($section, array('row', 'widget'));
if ($mainTemplate && $view->isRendered()) {
return '';
}
$template = null;
$custom = '_'.$view->getVar('id');
$rendering = $custom.$section;
if (isset($this->varStack[$rendering])) {
$typeIndex = $this->varStack[$rendering]['typeIndex'] - 1;
$types = $this->varStack[$rendering]['types'];
$variables = array_replace_recursive($this->varStack[$rendering]['variables'], $variables);
} else {
$types = $view->getVar('types');
$types[] = $view->getVar('full_block_name');
$typeIndex = count($types) - 1;
$variables = array_replace_recursive($view->getVars(), $variables);
$this->varStack[$rendering]['types'] = $types;
}
$this->varStack[$rendering]['variables'] = $variables;
do {
$types[$typeIndex] .= '_'.$section;
$template = $this->lookupTemplate($view, $types[$typeIndex]);
if ($template) {
$this->varStack[$rendering]['typeIndex'] = $typeIndex;
$this->context[] = array(
'variables' => $variables,
'view' => $view,
);
$html = $this->engine->render($template, $variables);
array_pop($this->context);
unset($this->varStack[$rendering]);
if ($mainTemplate) {
$view->setRendered();
}
return trim($html);
}
} while (--$typeIndex >= 0);
throw new FormException(sprintf(
'Unable to render the form as none of the following blocks exist: "%s".',
implode('", "', array_reverse($types))
));
}
/**
* Render a block from a form element.
*
* @param string $name
* @param array $variables Additional variables (those would override the current context)
*
* @throws FormException if the block is not found
* @throws FormException if the method is called out of a form element (no context)
*/
public function renderBlock($name, $variables = array())
{
if (0 == count($this->context)) {
throw new FormException(sprintf('This method should only be called while rendering a form element.', $name));
}
$context = end($this->context);
$template = $this->lookupTemplate($context['view'], $name);
if (false === $template) {
throw new FormException(sprintf('No block "%s" found while rendering the form.', $name));
}
$variables = array_replace_recursive($context['variables'], $variables);
return trim($this->engine->render($template, $variables));
} }
public function humanize($text) public function humanize($text)
{ {
return ucfirst(trim(strtolower(preg_replace('/[_\s]+/', ' ', $text)))); return $this->renderer->humanize($text);
}
public function getName()
{
return 'form';
}
/**
* Returns the name of the template to use to render the block
*
* @param FormView $view The form view
* @param string $block The name of the block
*
* @return string|Boolean The template logical name or false when no template is found
*/
protected function lookupTemplate(FormView $view, $block)
{
$file = $block.'.html.php';
$id = $view->getVar('id');
if (!isset($this->templates[$id][$block])) {
$template = false;
$themes = $view->hasParent() ? array() : $this->resources;
if (isset($this->themes[$id])) {
$themes = array_merge($themes, $this->themes[$id]);
}
for ($i = count($themes) - 1; $i >= 0; --$i) {
if ($this->engine->exists($templateName = $themes[$i].':'.$file)) {
$template = $templateName;
break;
}
}
if (false === $template && $view->hasParent()) {
$template = $this->lookupTemplate($view->getParent(), $block);
}
$this->templates[$id][$block] = $template;
}
return $this->templates[$id][$block];
} }
} }

View File

@ -15,9 +15,11 @@ use Symfony\Bundle\FrameworkBundle\Templating\Helper\FormHelper;
use Symfony\Bundle\FrameworkBundle\Templating\Helper\TranslatorHelper; use Symfony\Bundle\FrameworkBundle\Templating\Helper\TranslatorHelper;
use Symfony\Bundle\FrameworkBundle\Tests\Templating\Helper\Fixtures\StubTemplateNameParser; use Symfony\Bundle\FrameworkBundle\Tests\Templating\Helper\Fixtures\StubTemplateNameParser;
use Symfony\Bundle\FrameworkBundle\Tests\Templating\Helper\Fixtures\StubTranslator; use Symfony\Bundle\FrameworkBundle\Tests\Templating\Helper\Fixtures\StubTranslator;
use Symfony\Component\Form\FormView;
use Symfony\Component\Templating\PhpEngine; use Symfony\Component\Templating\PhpEngine;
use Symfony\Component\Templating\Loader\FilesystemLoader; use Symfony\Component\Templating\Loader\FilesystemLoader;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormRenderer;
use Symfony\Component\Form\Extension\Templating\TemplatingRendererEngine;
use Symfony\Component\Form\Tests\AbstractDivLayoutTest; use Symfony\Component\Form\Tests\AbstractDivLayoutTest;
class FormHelperDivLayoutTest extends AbstractDivLayoutTest class FormHelperDivLayoutTest extends AbstractDivLayoutTest
@ -26,6 +28,8 @@ class FormHelperDivLayoutTest extends AbstractDivLayoutTest
protected function setUp() protected function setUp()
{ {
ini_set('xdebug.max_nesting_level', 120);
parent::setUp(); parent::setUp();
$root = realpath(__DIR__.'/../../../Resources/views'); $root = realpath(__DIR__.'/../../../Resources/views');
@ -34,8 +38,10 @@ class FormHelperDivLayoutTest extends AbstractDivLayoutTest
$loader = new FilesystemLoader(array()); $loader = new FilesystemLoader(array());
$engine = new PhpEngine($templateNameParser, $loader); $engine = new PhpEngine($templateNameParser, $loader);
$engine->addGlobal('global', ''); $engine->addGlobal('global', '');
$rendererEngine = new TemplatingRendererEngine($engine, array('FrameworkBundle:Form'));
$renderer = new FormRenderer($rendererEngine, $this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface'));
$this->helper = new FormHelper($engine, $this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface'), array('FrameworkBundle:Form')); $this->helper = new FormHelper($renderer);
$engine->setHelpers(array( $engine->setHelpers(array(
$this->helper, $this->helper,

View File

@ -16,9 +16,11 @@ use Symfony\Bundle\FrameworkBundle\Templating\Helper\TranslatorHelper;
use Symfony\Bundle\FrameworkBundle\Tests\Templating\Helper\Fixtures\StubTemplateNameParser; use Symfony\Bundle\FrameworkBundle\Tests\Templating\Helper\Fixtures\StubTemplateNameParser;
use Symfony\Bundle\FrameworkBundle\Tests\Templating\Helper\Fixtures\StubTranslator; use Symfony\Bundle\FrameworkBundle\Tests\Templating\Helper\Fixtures\StubTranslator;
use Symfony\Component\Form\FormView; use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormRenderer;
use Symfony\Component\Form\Extension\Templating\TemplatingRendererEngine;
use Symfony\Component\Form\Tests\AbstractTableLayoutTest;
use Symfony\Component\Templating\PhpEngine; use Symfony\Component\Templating\PhpEngine;
use Symfony\Component\Templating\Loader\FilesystemLoader; use Symfony\Component\Templating\Loader\FilesystemLoader;
use Symfony\Component\Form\Tests\AbstractTableLayoutTest;
class FormHelperTableLayoutTest extends AbstractTableLayoutTest class FormHelperTableLayoutTest extends AbstractTableLayoutTest
{ {
@ -34,11 +36,13 @@ class FormHelperTableLayoutTest extends AbstractTableLayoutTest
$loader = new FilesystemLoader(array()); $loader = new FilesystemLoader(array());
$engine = new PhpEngine($templateNameParser, $loader); $engine = new PhpEngine($templateNameParser, $loader);
$engine->addGlobal('global', ''); $engine->addGlobal('global', '');
$rendererEngine = new TemplatingRendererEngine($engine, array(
$this->helper = new FormHelper($engine, $this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface'), array(
'FrameworkBundle:Form', 'FrameworkBundle:Form',
'FrameworkBundle:FormTable' 'FrameworkBundle:FormTable'
)); ));
$renderer = new FormRenderer($rendererEngine, $this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface'));
$this->helper = new FormHelper($renderer);
$engine->setHelpers(array( $engine->setHelpers(array(
$this->helper, $this->helper,

View File

@ -172,7 +172,7 @@ abstract class AbstractFactory implements SecurityFactoryInterface
return $config['success_handler']; return $config['success_handler'];
} }
$successHandlerId = 'security.authentication.success_handler.'.$id; $successHandlerId = 'security.authentication.success_handler.'.$id.'.'.str_replace('-', '_', $this->getKey());
$successHandler = $container->setDefinition($successHandlerId, new DefinitionDecorator('security.authentication.success_handler')); $successHandler = $container->setDefinition($successHandlerId, new DefinitionDecorator('security.authentication.success_handler'));
$successHandler->replaceArgument(1, array_intersect_key($config, $this->defaultSuccessHandlerOptions)); $successHandler->replaceArgument(1, array_intersect_key($config, $this->defaultSuccessHandlerOptions));
@ -187,7 +187,7 @@ abstract class AbstractFactory implements SecurityFactoryInterface
return $config['failure_handler']; return $config['failure_handler'];
} }
$id = 'security.authentication.failure_handler.'.$id; $id = 'security.authentication.failure_handler.'.$id.'.'.str_replace('-', '_', $this->getKey());
$failureHandler = $container->setDefinition($id, new DefinitionDecorator('security.authentication.failure_handler')); $failureHandler = $container->setDefinition($id, new DefinitionDecorator('security.authentication.failure_handler'));
$failureHandler->replaceArgument(2, array_intersect_key($config, $this->defaultFailureHandlerOptions)); $failureHandler->replaceArgument(2, array_intersect_key($config, $this->defaultFailureHandlerOptions));

View File

@ -54,7 +54,7 @@ class AbstractFactoryTest extends \PHPUnit_Framework_TestCase
$definition = $container->getDefinition('abstract_listener.foo'); $definition = $container->getDefinition('abstract_listener.foo');
$arguments = $definition->getArguments(); $arguments = $definition->getArguments();
$this->assertEquals(new Reference('security.authentication.failure_handler.foo'), $arguments['index_6']); $this->assertEquals(new Reference('security.authentication.failure_handler.foo.abstract_factory'), $arguments['index_6']);
} }
public function testDefaultSuccessHandler() public function testDefaultSuccessHandler()
@ -67,7 +67,7 @@ class AbstractFactoryTest extends \PHPUnit_Framework_TestCase
$definition = $container->getDefinition('abstract_listener.foo'); $definition = $container->getDefinition('abstract_listener.foo');
$arguments = $definition->getArguments(); $arguments = $definition->getArguments();
$this->assertEquals(new Reference('security.authentication.success_handler.foo'), $arguments['index_5']); $this->assertEquals(new Reference('security.authentication.success_handler.foo.abstract_factory'), $arguments['index_5']);
} }
protected function callFactory($id, $config, $userProviderId, $defaultEntryPointId) protected function callFactory($id, $config, $userProviderId, $defaultEntryPointId)
@ -84,6 +84,11 @@ class AbstractFactoryTest extends \PHPUnit_Framework_TestCase
->method('getListenerId') ->method('getListenerId')
->will($this->returnValue('abstract_listener')) ->will($this->returnValue('abstract_listener'))
; ;
$factory
->expects($this->any())
->method('getKey')
->will($this->returnValue('abstract_factory'))
;
$container = new ContainerBuilder(); $container = new ContainerBuilder();
$container->register('auth_provider'); $container->register('auth_provider');

View File

@ -16,6 +16,8 @@
<parameter key="twig.extension.routing.class">Symfony\Bridge\Twig\Extension\RoutingExtension</parameter> <parameter key="twig.extension.routing.class">Symfony\Bridge\Twig\Extension\RoutingExtension</parameter>
<parameter key="twig.extension.yaml.class">Symfony\Bridge\Twig\Extension\YamlExtension</parameter> <parameter key="twig.extension.yaml.class">Symfony\Bridge\Twig\Extension\YamlExtension</parameter>
<parameter key="twig.extension.form.class">Symfony\Bridge\Twig\Extension\FormExtension</parameter> <parameter key="twig.extension.form.class">Symfony\Bridge\Twig\Extension\FormExtension</parameter>
<parameter key="twig.form.engine.class">Symfony\Bridge\Twig\Form\TwigRendererEngine</parameter>
<parameter key="twig.form.renderer.class">Symfony\Bridge\Twig\Form\TwigRenderer</parameter>
<parameter key="twig.translation.extractor.class">Symfony\Bridge\Twig\Translation\TwigExtractor</parameter> <parameter key="twig.translation.extractor.class">Symfony\Bridge\Twig\Translation\TwigExtractor</parameter>
<parameter key="twig.exception_listener.class">Symfony\Component\HttpKernel\EventListener\ExceptionListener</parameter> <parameter key="twig.exception_listener.class">Symfony\Component\HttpKernel\EventListener\ExceptionListener</parameter>
</parameters> </parameters>
@ -75,10 +77,18 @@
<service id="twig.extension.form" class="%twig.extension.form.class%" public="false"> <service id="twig.extension.form" class="%twig.extension.form.class%" public="false">
<tag name="twig.extension" /> <tag name="twig.extension" />
<argument type="service" id="form.csrf_provider" on-invalid="null" /> <argument type="service" id="twig.form.renderer" />
</service>
<service id="twig.form.engine" class="%twig.form.engine.class%" public="false">
<argument>%twig.form.resources%</argument> <argument>%twig.form.resources%</argument>
</service> </service>
<service id="twig.form.renderer" class="%twig.form.renderer.class%" public="false">
<argument type="service" id="twig.form.engine" />
<argument type="service" id="form.csrf_provider" on-invalid="null" />
</service>
<service id="twig.translation.extractor" class="%twig.translation.extractor.class%"> <service id="twig.translation.extractor" class="%twig.translation.extractor.class%">
<argument type="service" id="twig" /> <argument type="service" id="twig" />
<tag name="translation.extractor" alias="twig" /> <tag name="translation.extractor" alias="twig" />

View File

@ -86,7 +86,7 @@
margin-left: -1px; margin-left: -1px;
bottom: 38px; bottom: 38px;
border-bottom: 1px solid #fff; border-bottom-width: 0;
} }
.sf-toolbar-block .sf-toolbar-info:empty { .sf-toolbar-block .sf-toolbar-info:empty {
@ -216,9 +216,9 @@
} }
.sf-toolbar-block .sf-toolbar-info { .sf-toolbar-block .sf-toolbar-info {
top: 38px; top: 39px;
bottom: auto; bottom: auto;
border-top: 1px solid #fff; border-top-width: 0;
border-bottom: 1px solid #bbb; border-bottom: 1px solid #bbb;
} }
{% endif %} {% endif %}

View File

@ -13,4 +13,4 @@ CHANGELOG
* added GenericEvent event class * added GenericEvent event class
* added the possibility for subscribers to subscribe several times for the * added the possibility for subscribers to subscribe several times for the
same event same event
* added UnmodifiableEventDispatcher * added ImmutableEventDispatcher

View File

@ -16,7 +16,7 @@ namespace Symfony\Component\EventDispatcher;
* *
* @author Bernhard Schussek <bschussek@gmail.com> * @author Bernhard Schussek <bschussek@gmail.com>
*/ */
class UnmodifiableEventDispatcher implements EventDispatcherInterface class ImmutableEventDispatcher implements EventDispatcherInterface
{ {
/** /**
* The proxied dispatcher. * The proxied dispatcher.

View File

@ -12,13 +12,13 @@
namespace Symfony\Component\EventDispatcher\Tests; namespace Symfony\Component\EventDispatcher\Tests;
use Symfony\Component\EventDispatcher\Event; use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\UnmodifiableEventDispatcher; use Symfony\Component\EventDispatcher\ImmutableEventDispatcher;
use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/** /**
* @author Bernhard Schussek <bschussek@gmail.com> * @author Bernhard Schussek <bschussek@gmail.com>
*/ */
class UnmodifiableEventDispatcherTest extends \PHPUnit_Framework_TestCase class ImmutableEventDispatcherTest extends \PHPUnit_Framework_TestCase
{ {
/** /**
* @var \PHPUnit_Framework_MockObject_MockObject * @var \PHPUnit_Framework_MockObject_MockObject
@ -26,14 +26,14 @@ class UnmodifiableEventDispatcherTest extends \PHPUnit_Framework_TestCase
private $innerDispatcher; private $innerDispatcher;
/** /**
* @var UnmodifiableEventDispatcher * @var ImmutableEventDispatcher
*/ */
private $dispatcher; private $dispatcher;
protected function setUp() protected function setUp()
{ {
$this->innerDispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); $this->innerDispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
$this->dispatcher = new UnmodifiableEventDispatcher($this->innerDispatcher); $this->dispatcher = new ImmutableEventDispatcher($this->innerDispatcher);
} }
public function testDispatchDelegates() public function testDispatchDelegates()

View File

@ -66,7 +66,7 @@ class Finder implements \IteratorAggregate, \Countable
*/ */
public static function create() public static function create()
{ {
return new self(); return new static();
} }
/** /**

View File

@ -0,0 +1,206 @@
<?php
/*
* This file is part of the Symfony package.
*
* (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 Symfony\Component\Form;
/**
* Default implementation of {@link FormRendererEngineInterface}.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
abstract class AbstractRendererEngine implements FormRendererEngineInterface
{
/**
* The variable in {@link FormViewInterface} used as cache key.
*/
const CACHE_KEY_VAR = 'full_block_name';
/**
* @var array
*/
protected $defaultThemes;
/**
* @var array
*/
protected $themes = array();
/**
* @var array
*/
protected $resources = array();
/**
* @var array
*/
private $resourceHierarchyLevels = array();
/**
* Creates a new renderer engine.
*
* @param array $defaultThemes The default themes. The type of these
* themes is open to the implementation.
*/
public function __construct(array $defaultThemes = array())
{
$this->defaultThemes = $defaultThemes;
}
/**
* {@inheritdoc}
*/
public function setTheme(FormViewInterface $view, $themes)
{
$cacheKey = $view->getVar(self::CACHE_KEY_VAR);
// Do not cast, as casting turns objects into arrays of properties
$this->themes[$cacheKey] = is_array($themes) ? $themes : array($themes);
// Unset instead of resetting to an empty array, in order to allow
// implementations (like TwigRendererEngine) to check whether $cacheKey
// is set at all.
unset($this->resources[$cacheKey]);
unset($this->resourceHierarchyLevels[$cacheKey]);
}
/**
* {@inheritdoc}
*/
public function getResourceForBlock(FormViewInterface $view, $block)
{
$cacheKey = $view->getVar(self::CACHE_KEY_VAR);
if (!isset($this->resources[$cacheKey][$block])) {
$this->loadResourceForBlock($cacheKey, $view, $block);
}
return $this->resources[$cacheKey][$block];
}
/**
* {@inheritdoc}
*/
public function getResourceForBlockHierarchy(FormViewInterface $view, array $blockHierarchy, $hierarchyLevel)
{
$cacheKey = $view->getVar(self::CACHE_KEY_VAR);
$block = $blockHierarchy[$hierarchyLevel];
if (!isset($this->resources[$cacheKey][$block])) {
$this->loadResourceForBlockHierarchy($cacheKey, $view, $blockHierarchy, $hierarchyLevel);
}
return $this->resources[$cacheKey][$block];
}
/**
* {@inheritdoc}
*/
public function getResourceHierarchyLevel(FormViewInterface $view, array $blockHierarchy, $hierarchyLevel)
{
$cacheKey = $view->getVar(self::CACHE_KEY_VAR);
$block = $blockHierarchy[$hierarchyLevel];
if (!isset($this->resources[$cacheKey][$block])) {
$this->loadResourceForBlockHierarchy($cacheKey, $view, $blockHierarchy, $hierarchyLevel);
}
// If $block was previously rendered loaded with loadTemplateForBlock(), the template
// is cached but the hierarchy level is not. In this case, we know that the block
// exists at this very hierarchy level, so we can just set it.
if (!isset($this->resourceHierarchyLevels[$cacheKey][$block])) {
$this->resourceHierarchyLevels[$cacheKey][$block] = $hierarchyLevel;
}
return $this->resourceHierarchyLevels[$cacheKey][$block];
}
/**
* Loads the cache with the resource for a given block name.
*
* @see getResourceForBlock()
*
* @param string $cacheKey The cache key of the form view.
* @param FormViewInterface $view The form view for finding the applying themes.
* @param string $block The name of the block to load.
*
* @return Boolean True if the resource could be loaded, false otherwise.
*/
abstract protected function loadResourceForBlock($cacheKey, FormViewInterface $view, $block);
/**
* Loads the cache with the resource for a specific level of a block hierarchy.
*
* @see getResourceForBlockHierarchy()
*
* @param string $cacheKey The cache key used for storing the
* resource.
* @param FormViewInterface $view The form view for finding the applying
* themes.
* @param array $blockHierarchy The block hierarchy, with the most
* specific block name at the end.
* @param integer $hierarchyLevel The level in the block hierarchy that
* should be loaded.
*
* @return Boolean True if the resource could be loaded, false otherwise.
*/
private function loadResourceForBlockHierarchy($cacheKey, FormViewInterface $view, array $blockHierarchy, $hierarchyLevel)
{
$block = $blockHierarchy[$hierarchyLevel];
// Try to find a template for that block
if ($this->loadResourceForBlock($cacheKey, $view, $block)) {
// If loadTemplateForBlock() returns true, it was able to populate the
// cache. The only missing thing is to set the hierarchy level at which
// the template was found.
$this->resourceHierarchyLevels[$cacheKey][$block] = $hierarchyLevel;
return true;
}
if ($hierarchyLevel > 0) {
$parentLevel = $hierarchyLevel - 1;
$parentBlock = $blockHierarchy[$parentLevel];
// The next two if statements contain slightly duplicated code. This is by intention
// and tries to avoid execution of unnecessary checks in order to increase performance.
if (isset($this->resources[$cacheKey][$parentBlock])) {
// It may happen that the parent block is already loaded, but its level is not.
// In this case, the parent block must have been loaded by loadResourceForBlock(),
// which does not check the hierarchy of the block. Subsequently the block must have
// been found directly on the parent level.
if (!isset($this->resourceHierarchyLevels[$cacheKey][$parentBlock])) {
$this->resourceHierarchyLevels[$cacheKey][$parentBlock] = $parentLevel;
}
// Cache the shortcuts for further accesses
$this->resources[$cacheKey][$block] = $this->resources[$cacheKey][$parentBlock];
$this->resourceHierarchyLevels[$cacheKey][$block] = $this->resourceHierarchyLevels[$cacheKey][$parentBlock];
return true;
}
if ($this->loadResourceForBlockHierarchy($cacheKey, $view, $blockHierarchy, $parentLevel)) {
// Cache the shortcuts for further accesses
$this->resources[$cacheKey][$block] = $this->resources[$cacheKey][$parentBlock];
$this->resourceHierarchyLevels[$cacheKey][$block] = $this->resourceHierarchyLevels[$cacheKey][$parentBlock];
return true;
}
}
// Cache the result for further accesses
$this->resources[$cacheKey][$block] = false;
$this->resourceHierarchyLevels[$cacheKey][$block] = false;
return false;
}
}

View File

@ -31,7 +31,6 @@ CHANGELOG
* ArrayToChoicesTransformer to ChoicesToValuesTransformer * ArrayToChoicesTransformer to ChoicesToValuesTransformer
* ScalarToChoiceTransformer to ChoiceToValueTransformer * ScalarToChoiceTransformer to ChoiceToValueTransformer
to be consistent with the naming in ChoiceListInterface. to be consistent with the naming in ChoiceListInterface.
* [BC BREAK] removed FormUtil::toArrayKey() and FormUtil::toArrayKeys().
They were merged into ChoiceList and have no public equivalent anymore. They were merged into ChoiceList and have no public equivalent anymore.
* choice fields now throw a FormException if neither the "choices" nor the * choice fields now throw a FormException if neither the "choices" nor the
"choice_list" option is set "choice_list" option is set
@ -141,7 +140,7 @@ CHANGELOG
* deprecated `hasChildren` in Form and FormBuilder in favor of `count` * deprecated `hasChildren` in Form and FormBuilder in favor of `count`
* FormBuilder now implements \IteratorAggregate * FormBuilder now implements \IteratorAggregate
* [BC BREAK] compound forms now always need a data mapper * [BC BREAK] compound forms now always need a data mapper
* FormBuilder now maintains the order when explicitely adding form builders as children * FormBuilder now maintains the order when explicitly adding form builders as children
* ChoiceType now doesn't add the empty value anymore if the choices already contain an empty element * ChoiceType now doesn't add the empty value anymore if the choices already contain an empty element
* DateType, TimeType and DateTimeType now show empty values again if not required * DateType, TimeType and DateTimeType now show empty values again if not required
* [BC BREAK] fixed rendering of errors for DateType, BirthdayType and similar ones * [BC BREAK] fixed rendering of errors for DateType, BirthdayType and similar ones
@ -172,4 +171,11 @@ CHANGELOG
* ChoiceType now caches its created choice lists to improve performance * ChoiceType now caches its created choice lists to improve performance
* [BC BREAK] Rows of a collection field cannot be themed individually anymore. All rows in the collection * [BC BREAK] Rows of a collection field cannot be themed individually anymore. All rows in the collection
field now have the same block names, which contains "entry" where it previously contained the row index. field now have the same block names, which contains "entry" where it previously contained the row index.
* [BC BREAK] When registering a type through the DI extension, the tag alias has to match the actual type name. * [BC BREAK] When registering a type through the DI extension, the tag alias has to match the actual type name.
* added FormRendererInterface, FormRendererEngineInterface and implementations of these interfaces
* [BC BREAK] removed the following methods from FormUtil:
* `toArrayKey`
* `toArrayKeys`
* `isChoiceGroup`
* `isChoiceSelected`
* added method `block` to FormHelper and deprecated `renderBlock` instead

View File

@ -157,7 +157,7 @@ class ChoiceType extends AbstractType
return $options['required'] ? null : ''; return $options['required'] ? null : '';
}; };
$emptyValueFilter = function (Options $options, $emptyValue) { $emptyValueNormalizer = function (Options $options, $emptyValue) {
if ($options['multiple'] || $options['expanded']) { if ($options['multiple'] || $options['expanded']) {
// never use an empty value for these cases // never use an empty value for these cases
return null; return null;
@ -186,8 +186,8 @@ class ChoiceType extends AbstractType
'compound' => $compound, 'compound' => $compound,
)); ));
$resolver->setFilters(array( $resolver->setNormalizers(array(
'empty_value' => $emptyValueFilter, 'empty_value' => $emptyValueNormalizer,
)); ));
$resolver->setAllowedTypes(array( $resolver->setAllowedTypes(array(

View File

@ -74,7 +74,7 @@ class CollectionType extends AbstractType
*/ */
public function setDefaultOptions(OptionsResolverInterface $resolver) public function setDefaultOptions(OptionsResolverInterface $resolver)
{ {
$optionsFilter = function (Options $options, $value) { $optionsNormalizer = function (Options $options, $value) {
$value['block_name'] = 'entry'; $value['block_name'] = 'entry';
return $value; return $value;
@ -89,8 +89,8 @@ class CollectionType extends AbstractType
'options' => array(), 'options' => array(),
)); ));
$resolver->setFilters(array( $resolver->setNormalizers(array(
'options' => $optionsFilter, 'options' => $optionsNormalizer,
)); ));
} }

View File

@ -164,7 +164,7 @@ class DateType extends AbstractType
return $options['required'] ? null : ''; return $options['required'] ? null : '';
}; };
$emptyValueFilter = function (Options $options, $emptyValue) use ($emptyValueDefault) { $emptyValueNormalizer = function (Options $options, $emptyValue) use ($emptyValueDefault) {
if (is_array($emptyValue)) { if (is_array($emptyValue)) {
$default = $emptyValueDefault($options); $default = $emptyValueDefault($options);
@ -216,8 +216,8 @@ class DateType extends AbstractType
'compound' => $compound, 'compound' => $compound,
)); ));
$resolver->setFilters(array( $resolver->setNormalizers(array(
'empty_value' => $emptyValueFilter, 'empty_value' => $emptyValueNormalizer,
)); ));
$resolver->setAllowedValues(array( $resolver->setAllowedValues(array(

View File

@ -134,7 +134,7 @@ class TimeType extends AbstractType
return $options['required'] ? null : ''; return $options['required'] ? null : '';
}; };
$emptyValueFilter = function (Options $options, $emptyValue) use ($emptyValueDefault) { $emptyValueNormalizer = function (Options $options, $emptyValue) use ($emptyValueDefault) {
if (is_array($emptyValue)) { if (is_array($emptyValue)) {
$default = $emptyValueDefault($options); $default = $emptyValueDefault($options);
@ -186,8 +186,8 @@ class TimeType extends AbstractType
'compound' => $compound, 'compound' => $compound,
)); ));
$resolver->setFilters(array( $resolver->setNormalizers(array(
'empty_value' => $emptyValueFilter, 'empty_value' => $emptyValueNormalizer,
)); ));
$resolver->setAllowedValues(array( $resolver->setAllowedValues(array(

View File

@ -46,7 +46,12 @@ class DependencyInjectionExtension implements FormExtensionInterface
$type = $this->container->get($this->typeServiceIds[$name]); $type = $this->container->get($this->typeServiceIds[$name]);
if ($type->getName() !== $name) { if ($type->getName() !== $name) {
throw new \InvalidArgumentException(sprintf('The type name specified for the service %s does not match the actual name', $this->typeServiceIds[$name])); throw new \InvalidArgumentException(
sprintf('The type name specified for the service "%s" does not match the actual name. Expected "%s", given "%s"',
$this->typeServiceIds[$name],
$name,
$type->getName()
));
} }
return $type; return $type;

View File

@ -0,0 +1,125 @@
<?php
/*
* This file is part of the Symfony package.
*
* (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 Symfony\Component\Form\Extension\Templating;
use Symfony\Component\Form\AbstractRendererEngine;
use Symfony\Component\Form\FormViewInterface;
use Symfony\Component\Templating\EngineInterface;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class TemplatingRendererEngine extends AbstractRendererEngine
{
/**
* @var EngineInterface
*/
private $engine;
public function __construct(EngineInterface $engine, array $defaultThemes = array())
{
parent::__construct($defaultThemes);
$this->engine = $engine;
}
/**
* {@inheritdoc}
*/
public function renderBlock(FormViewInterface $view, $resource, $block, array $variables = array())
{
return trim($this->engine->render($resource, $variables));
}
/**
* Loads the cache with the resource for a given block name.
*
* This implementation tries to load as few blocks as possible, since each block
* is represented by a template on the file system.
*
* @see getResourceForBlock()
*
* @param string $cacheKey The cache key of the form view.
* @param FormViewInterface $view The form view for finding the applying themes.
* @param string $block The name of the block to load.
*
* @return Boolean True if the resource could be loaded, false otherwise.
*/
protected function loadResourceForBlock($cacheKey, FormViewInterface $view, $block)
{
// Recursively try to find the block in the themes assigned to $view,
// then of its parent form, then of the parent form of the parent and so on.
// When the root form is reached in this recursion, also the default
// themes are taken into account.
// Check each theme whether it contains the searched block
if (isset($this->themes[$cacheKey])) {
for ($i = count($this->themes[$cacheKey]) - 1; $i >= 0; --$i) {
if ($this->loadResourceFromTheme($cacheKey, $block, $this->themes[$cacheKey][$i])) {
return true;
}
}
}
// Check the default themes once we reach the root form without success
if (!$view->hasParent()) {
for ($i = count($this->defaultThemes) - 1; $i >= 0; --$i) {
if ($this->loadResourceFromTheme($cacheKey, $block, $this->defaultThemes[$i])) {
return true;
}
}
}
// If we did not find anything in the themes of the current view, proceed
// with the themes of the parent view
if ($view->hasParent()) {
$parentCacheKey = $view->getParent()->getVar(self::CACHE_KEY_VAR);
if (!isset($this->resources[$parentCacheKey][$block])) {
$this->loadResourceForBlock($parentCacheKey, $view->getParent(), $block);
}
// If a template exists in the parent themes, cache that template
// for the current theme as well to speed up further accesses
if ($this->resources[$parentCacheKey][$block]) {
$this->resources[$cacheKey][$block] = $this->resources[$parentCacheKey][$block];
return true;
}
}
// Cache that we didn't find anything to speed up further accesses
$this->resources[$cacheKey][$block] = false;
return false;
}
/**
* Tries to load the resource for a block from a theme.
*
* @param string $cacheKey The cache key for storing the resource.
* @param string $block The name of the block to load a resource for.
* @param mixed $theme The theme to load the block from.
*
* @return Boolean True if the resource could be loaded, false otherwise.
*/
protected function loadResourceFromTheme($cacheKey, $block, $theme)
{
if ($this->engine->exists($templateName = $theme . ':' . $block . '.html.php')) {
$this->resources[$cacheKey][$block] = $templateName;
return true;
}
return false;
}
}

View File

@ -59,7 +59,7 @@ class FormTypeValidatorExtension extends AbstractTypeExtension
}; };
// Make sure that validation groups end up as null, closure or array // Make sure that validation groups end up as null, closure or array
$validationGroupsFilter = function (Options $options, $groups) { $validationGroupsNormalizer = function (Options $options, $groups) {
if (empty($groups)) { if (empty($groups)) {
return null; return null;
} }
@ -72,7 +72,7 @@ class FormTypeValidatorExtension extends AbstractTypeExtension
}; };
// Constraint should always be converted to an array // Constraint should always be converted to an array
$constraintsFilter = function (Options $options, $constraints) { $constraintsNormalizer = function (Options $options, $constraints) {
return is_object($constraints) ? array($constraints) : (array) $constraints; return is_object($constraints) ? array($constraints) : (array) $constraints;
}; };
@ -88,9 +88,9 @@ class FormTypeValidatorExtension extends AbstractTypeExtension
'post_max_size_message' => 'The uploaded file was too large. Please try to upload a smaller file.', 'post_max_size_message' => 'The uploaded file was too large. Please try to upload a smaller file.',
)); ));
$resolver->setFilters(array( $resolver->setNormalizers(array(
'validation_groups' => $validationGroupsFilter, 'validation_groups' => $validationGroupsNormalizer,
'constraints' => $constraintsFilter, 'constraints' => $constraintsNormalizer,
)); ));
} }

View File

@ -119,6 +119,26 @@ class Form implements \IteratorAggregate, FormInterface
*/ */
private $synchronized = true; private $synchronized = true;
/**
* Whether the form's data has been initialized.
*
* When the data is initialized with its default value, that default value
* is passed through the transformer chain in order to synchronize the
* model, normalized and view format for the first time. This is done
* lazily in order to save performance when {@link setData()} is called
* manually, making the initialization with the configured default value
* superfluous.
*
* @var Boolean
*/
private $initialized = false;
/**
* Whether setData() is currently being called.
* @var Boolean
*/
private $lockSetData = false;
/** /**
* Creates a new form based on the given configuration. * Creates a new form based on the given configuration.
* *
@ -126,8 +146,8 @@ class Form implements \IteratorAggregate, FormInterface
*/ */
public function __construct(FormConfigInterface $config) public function __construct(FormConfigInterface $config)
{ {
if (!$config instanceof UnmodifiableFormConfig) { if (!$config instanceof ImmutableFormConfig) {
$config = new UnmodifiableFormConfig($config); $config = new ImmutableFormConfig($config);
} }
// Compound forms always need a data mapper, otherwise calls to // Compound forms always need a data mapper, otherwise calls to
@ -138,8 +158,6 @@ class Form implements \IteratorAggregate, FormInterface
} }
$this->config = $config; $this->config = $config;
$this->setData($config->getData());
} }
public function __clone() public function __clone()
@ -152,7 +170,7 @@ class Form implements \IteratorAggregate, FormInterface
/** /**
* Returns the configuration of the form. * Returns the configuration of the form.
* *
* @return UnmodifiableFormConfig The form's immutable configuration. * @return ImmutableFormConfig The form's immutable configuration.
*/ */
public function getConfig() public function getConfig()
{ {
@ -327,13 +345,16 @@ class Form implements \IteratorAggregate, FormInterface
/** /**
* Updates the form with default data. * Updates the form with default data.
* *
* @param array $modelData The data formatted as expected for the underlying object * @param mixed $modelData The data formatted as expected for the underlying object
* *
* @return Form The current form * @return Form The current form
*/ */
public function setData($modelData) public function setData($modelData)
{ {
if ($this->bound) { // If the form is bound while disabled, it is set to bound, but the data is not
// changed. In such cases (i.e. when the form is not initialized yet) don't
// abort this method.
if ($this->bound && $this->initialized) {
throw new AlreadyBoundException('You cannot change the data of a bound form'); throw new AlreadyBoundException('You cannot change the data of a bound form');
} }
@ -346,6 +367,12 @@ class Form implements \IteratorAggregate, FormInterface
$modelData = clone $modelData; $modelData = clone $modelData;
} }
if ($this->lockSetData) {
throw new FormException('A cycle was detected. Listeners to the PRE_SET_DATA event must not call setData(). You should call setData() on the FormEvent object instead.');
}
$this->lockSetData = true;
// Hook to change content of the data // Hook to change content of the data
$event = new FormEvent($this, $modelData); $event = new FormEvent($this, $modelData);
$this->config->getEventDispatcher()->dispatch(FormEvents::PRE_SET_DATA, $event); $this->config->getEventDispatcher()->dispatch(FormEvents::PRE_SET_DATA, $event);
@ -395,8 +422,12 @@ class Form implements \IteratorAggregate, FormInterface
$this->normData = $normData; $this->normData = $normData;
$this->viewData = $viewData; $this->viewData = $viewData;
$this->synchronized = true; $this->synchronized = true;
$this->initialized = true;
$this->lockSetData = false;
if ($this->config->getCompound()) { // It is not necessary to invoke this method if the form doesn't have children,
// even if the form is compound.
if (count($this->children) > 0) {
// Update child forms from the data // Update child forms from the data
$this->config->getDataMapper()->mapDataToForms($viewData, $this->children); $this->config->getDataMapper()->mapDataToForms($viewData, $this->children);
} }
@ -414,9 +445,29 @@ class Form implements \IteratorAggregate, FormInterface
*/ */
public function getData() public function getData()
{ {
if (!$this->initialized) {
$this->setData($this->config->getData());
}
return $this->modelData; return $this->modelData;
} }
/**
* Returns the normalized data of the form.
*
* @return mixed When the form is not bound, the default data is returned.
* When the form is bound, the normalized bound data is
* returned if the form is valid, null otherwise.
*/
public function getNormData()
{
if (!$this->initialized) {
$this->setData($this->config->getData());
}
return $this->normData;
}
/** /**
* Returns the data transformed by the value transformer. * Returns the data transformed by the value transformer.
* *
@ -424,6 +475,10 @@ class Form implements \IteratorAggregate, FormInterface
*/ */
public function getViewData() public function getViewData()
{ {
if (!$this->initialized) {
$this->setData($this->config->getData());
}
return $this->viewData; return $this->viewData;
} }
@ -543,7 +598,9 @@ class Form implements \IteratorAggregate, FormInterface
} }
// Merge form data from children into existing view data // Merge form data from children into existing view data
if ($this->config->getCompound()) { // It is not necessary to invoke this method if the form has no children,
// even if it is compound.
if (count($this->children) > 0) {
$this->config->getDataMapper()->mapFormsToData($this->children, $viewData); $this->config->getDataMapper()->mapFormsToData($this->children, $viewData);
} }
@ -562,7 +619,7 @@ class Form implements \IteratorAggregate, FormInterface
// Synchronize representations - must not change the content! // Synchronize representations - must not change the content!
$modelData = $this->normToModel($normData); $modelData = $this->normToModel($normData);
$viewData = $this->normToView($normData); $viewData = $this->normToView($normData);
$synchronized = true; $synchronized = true;
} catch (TransformationFailedException $e) { } catch (TransformationFailedException $e) {
} }
@ -573,6 +630,7 @@ class Form implements \IteratorAggregate, FormInterface
$this->viewData = $viewData; $this->viewData = $viewData;
$this->extraData = $extraData; $this->extraData = $extraData;
$this->synchronized = $synchronized; $this->synchronized = $synchronized;
$this->initialized = true;
$event = new FormEvent($this, $viewData); $event = new FormEvent($this, $viewData);
$this->config->getEventDispatcher()->dispatch(FormEvents::POST_BIND, $event); $this->config->getEventDispatcher()->dispatch(FormEvents::POST_BIND, $event);
@ -604,18 +662,6 @@ class Form implements \IteratorAggregate, FormInterface
return $this->bind($request); return $this->bind($request);
} }
/**
* Returns the normalized data of the form.
*
* @return mixed When the form is not bound, the default data is returned.
* When the form is bound, the normalized bound data is
* returned if the form is valid, null otherwise.
*/
public function getNormData()
{
return $this->normData;
}
/** /**
* Adds an error to this form. * Adds an error to this form.
* *
@ -833,11 +879,32 @@ class Form implements \IteratorAggregate, FormInterface
throw new FormException('You cannot add children to a simple form. Maybe you should set the option "compound" to true?'); throw new FormException('You cannot add children to a simple form. Maybe you should set the option "compound" to true?');
} }
// Obtain the view data
$viewData = null;
// If setData() is currently being called, there is no need to call
// mapDataToForms() here, as mapDataToForms() is called at the end
// of setData() anyway. Not doing this check leads to an endless
// recursion when initializing the form lazily and an event listener
// (such as ResizeFormListener) adds fields depending on the data:
//
// * setData() is called, the form is not initialized yet
// * add() is called by the listener (setData() is not complete, so
// the form is still not initialized)
// * getViewData() is called
// * setData() is called since the form is not initialized yet
// * ... endless recursion ...
if (!$this->lockSetData) {
$viewData = $this->getViewData();
}
$this->children[$child->getName()] = $child; $this->children[$child->getName()] = $child;
$child->setParent($this); $child->setParent($this);
$this->config->getDataMapper()->mapDataToForms($this->getViewData(), array($child)); if (!$this->lockSetData) {
$this->config->getDataMapper()->mapDataToForms($viewData, array($child));
}
return $this; return $this;
} }

View File

@ -0,0 +1,350 @@
<?php
/*
* This file is part of the Symfony package.
*
* (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 Symfony\Component\Form;
use Symfony\Component\Form\Extension\Core\View\ChoiceView;
use Symfony\Component\Form\Exception\FormException;
use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
/**
* Renders a form into HTML using a rendering engine.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class FormRenderer implements FormRendererInterface
{
/**
* @var FormRendererEngineInterface
*/
private $engine;
/**
* @var CsrfProviderInterface
*/
private $csrfProvider;
/**
* @var array
*/
private $blockHierarchyMap = array();
/**
* @var array
*/
private $hierarchyLevelMap = array();
/**
* @var array
*/
private $variableMap = array();
/**
* @var array
*/
private $stack = array();
public function __construct(FormRendererEngineInterface $engine, CsrfProviderInterface $csrfProvider = null)
{
$this->engine = $engine;
$this->csrfProvider = $csrfProvider;
}
/**
* {@inheritdoc}
*/
public function getEngine()
{
return $this->engine;
}
/**
* {@inheritdoc}
*/
public function setTheme(FormViewInterface $view, $themes)
{
$this->engine->setTheme($view, $themes);
}
/**
* {@inheritdoc}
*/
public function renderEnctype(FormViewInterface $view)
{
return $this->renderSection($view, 'enctype');
}
/**
* {@inheritdoc}
*/
public function renderRow(FormViewInterface $view, array $variables = array())
{
return $this->renderSection($view, 'row', $variables);
}
/**
* {@inheritdoc}
*/
public function renderRest(FormViewInterface $view, array $variables = array())
{
return $this->renderSection($view, 'rest', $variables);
}
/**
* {@inheritdoc}
*/
public function renderWidget(FormViewInterface $view, array $variables = array())
{
return $this->renderSection($view, 'widget', $variables);
}
/**
* {@inheritdoc}
*/
public function renderErrors(FormViewInterface $view)
{
return $this->renderSection($view, 'errors');
}
/**
* {@inheritdoc}
*/
public function renderLabel(FormViewInterface $view, $label = null, array $variables = array())
{
if ($label !== null) {
$variables += array('label' => $label);
}
return $this->renderSection($view, 'label', $variables);
}
/**
* {@inheritdoc}
*/
public function renderCsrfToken($intention)
{
if (null === $this->csrfProvider) {
throw new \BadMethodCallException('CSRF token can only be generated if a CsrfProviderInterface is injected in the constructor.');
}
return $this->csrfProvider->generateCsrfToken($intention);
}
/**
* {@inheritdoc}
*/
public function renderBlock($block, array $variables = array())
{
if (0 == count($this->stack)) {
throw new FormException('This method should only be called while rendering a form element.');
}
list($view, $scopeVariables) = end($this->stack);
$resource = $this->engine->getResourceForBlock($view, $block);
if (!$resource) {
throw new FormException(sprintf('No block "%s" found while rendering the form.', $block));
}
// Merge the passed with the existing attributes
if (isset($variables['attr']) && isset($scopeVariables['attr'])) {
$variables['attr'] = array_replace($scopeVariables['attr'], $variables['attr']);
}
// Merge the passed with the exist *label* attributes
if (isset($variables['label_attr']) && isset($scopeVariables['label_attr'])) {
$variables['label_attr'] = array_replace($scopeVariables['label_attr'], $variables['label_attr']);
}
// Do not use array_replace_recursive(), otherwise array variables
// cannot be overwritten
$variables = array_replace($scopeVariables, $variables);
return $this->engine->renderBlock($view, $resource, $block, $variables);
}
/**
* {@inheritdoc}
*/
public function isChoiceGroup($choice)
{
return is_array($choice) || $choice instanceof \Traversable;
}
/**
* {@inheritdoc}
*/
public function isChoiceSelected(FormViewInterface $view, ChoiceView $choice)
{
$value = $view->getVar('value');
$choiceValue = $choice->getValue();
if (is_array($value)) {
return false !== array_search($choiceValue, $value, true);
}
return $choiceValue === $value;
}
/**
* {@inheritdoc}
*/
public function humanize($text)
{
return ucfirst(trim(strtolower(preg_replace('/[_\s]+/', ' ', $text))));
}
/**
* Renders the given section of a form view.
*
* @param FormViewInterface $view The form view.
* @param string $section The name of the section to render.
* @param array $variables The variables to pass to the template.
*
* @return string The HTML markup.
*
* @throws Exception\FormException If no fitting template was found.
*/
protected function renderSection(FormViewInterface $view, $section, array $variables = array())
{
$renderOnlyOnce = in_array($section, array('row', 'widget'));
if ($renderOnlyOnce && $view->isRendered()) {
return '';
}
// The cache key for storing the variables and types
$mapKey = $uniqueBlockName = $view->getVar('full_block_name') . '_' . $section;
// In templates, we have to deal with two kinds of block hierarchies:
//
// +---------+ +---------+
// | Theme B | -------> | Theme A |
// +---------+ +---------+
//
// form_widget -------> form_widget
// ^
// |
// choice_widget -----> choice_widget
//
// The first kind of hierarchy is the theme hierarchy. This allows to
// override the block "choice_widget" from Theme A in the extending
// Theme B. This kind of inheritance needs to be supported by the
// template engine and, for example, offers "parent()" or similar
// functions to fall back from the custom to the parent implementation.
//
// The second kind of hierarchy is the form type hierarchy. This allows
// to implement a custom "choice_widget" block (no matter in which theme),
// or to fallback to the block of the parent type, which would be
// "form_widget" in this example (again, no matter in which theme).
// If the designer wants to explicitly fallback to "form_widget" in his
// custom "choice_widget", for example because he only wants to wrap
// a <div> around the original implementation, he can simply call the
// widget() function again to render the block for the parent type.
//
// The second kind is implemented in the following blocks.
if (!isset($this->blockHierarchyMap[$mapKey])) {
// INITIAL CALL
// Calculate the hierarchy of template blocks and start on
// the bottom level of the hierarchy (= "_<id>_<section>" block)
$blockHierarchy = array();
foreach ($view->getVar('types') as $type) {
$blockHierarchy[] = $type . '_' . $section;
}
$blockHierarchy[] = $uniqueBlockName;
$hierarchyLevel = count($blockHierarchy) - 1;
// The default variable scope contains all view variables, merged with
// the variables passed explicitly to the helper
$scopeVariables = $view->getVars();
} else {
// RECURSIVE CALL
// If a block recursively calls renderSection() again, resume rendering
// using the parent type in the hierarchy.
$blockHierarchy = $this->blockHierarchyMap[$mapKey];
$hierarchyLevel = $this->hierarchyLevelMap[$mapKey] - 1;
// Reuse the current scope and merge it with the explicitly passed variables
$scopeVariables = $this->variableMap[$mapKey];
}
// Load the resource where this block can be found
$resource = $this->engine->getResourceForBlockHierarchy($view, $blockHierarchy, $hierarchyLevel);
// Update the current hierarchy level to the one at which the resource was
// found. For example, if looking for "choice_widget", but only a resource
// is found for its parent "form_widget", then the level is updated here
// to the parent level.
$hierarchyLevel = $this->engine->getResourceHierarchyLevel($view, $blockHierarchy, $hierarchyLevel);
// The actually existing block name in $resource
$block = $blockHierarchy[$hierarchyLevel];
// Escape if no resource exists for this block
if (!$resource) {
throw new FormException(sprintf(
'Unable to render the form as none of the following blocks exist: "%s".',
implode('", "', array_reverse($blockHierarchy))
));
}
// Merge the passed with the existing attributes
if (isset($variables['attr']) && isset($scopeVariables['attr'])) {
$variables['attr'] = array_replace($scopeVariables['attr'], $variables['attr']);
}
// Merge the passed with the exist *label* attributes
if (isset($variables['label_attr']) && isset($scopeVariables['label_attr'])) {
$variables['label_attr'] = array_replace($scopeVariables['label_attr'], $variables['label_attr']);
}
// Do not use array_replace_recursive(), otherwise array variables
// cannot be overwritten
$variables = array_replace($scopeVariables, $variables);
// In order to make recursive calls possible, we need to store the block hierarchy,
// the current level of the hierarchy and the variables so that this method can
// resume rendering one level higher of the hierarchy when it is called recursively.
//
// We need to store these values in maps (associative arrays) because within a
// call to widget() another call to widget() can be made, but for a different view
// object. These nested calls should not override each other.
$this->blockHierarchyMap[$mapKey] = $blockHierarchy;
$this->hierarchyLevelMap[$mapKey] = $hierarchyLevel;
$this->variableMap[$mapKey] = $variables;
// We also need to store the view and the variables so that we can render custom
// blocks with renderBlock() using the same themes and variables as in the outer
// block.
//
// A stack is sufficient for this purpose, because renderBlock() always accesses
// the immediate next outer scope, which is always stored at the end of the stack.
$this->stack[] = array($view, $variables);
// Do the rendering
$html = $this->engine->renderBlock($view, $resource, $block, $variables);
// Clear the stack
array_pop($this->stack);
// Clear the maps
unset($this->blockHierarchyMap[$mapKey]);
unset($this->hierarchyLevelMap[$mapKey]);
unset($this->variableMap[$mapKey]);
if ($renderOnlyOnce) {
$view->setRendered();
}
return $html;
}
}

View File

@ -0,0 +1,146 @@
<?php
/*
* This file is part of the Symfony package.
*
* (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 Symfony\Component\Form;
/**
* Adapter for rendering form templates with a specific templating engine.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface FormRendererEngineInterface
{
/**
* Sets the theme(s) to be used for rendering a view and its children.
*
* @param FormViewInterface $view The view to assign the theme(s) to.
* @param mixed $themes The theme(s). The type of these themes
* is open to the implementation.
*/
public function setTheme(FormViewInterface $view, $themes);
/**
* Returns the resource for a block name.
*
* The resource is first searched in the themes attached to $view, then
* in the themes of its parent view and so on, until a resource was found.
*
* The type of the resource is decided by the implementation. The resource
* is later passed to {@link renderBlock()} by the rendering algorithm.
*
* @param FormViewInterface $view The view for determining the used themes.
* First the themes attached directly to the
* view with {@link setTheme()} are considered,
* then the ones of its parent etc.
* @param string $block The name of the block to render.
*
* @return mixed The renderer resource or false, if none was found.
*/
public function getResourceForBlock(FormViewInterface $view, $block);
/**
* Returns the resource for a block hierarchy.
*
* A block hierarchy is an array which starts with the root of the hierarchy
* and continues with the child of that root, the child of that child etc.
* The following is an example for a block hierarchy:
*
* <code>
* form_widget
* text_widget
* url_widget
* </code>
*
* In this example, "url_widget" is the most specific block, while the other
* blocks are its ancestors in the hierarchy.
*
* The second parameter $hierarchyLevel determines the level of the hierarchy
* that should be rendered. For example, if $hierarchyLevel is 2 for the
* above hierarchy, the engine will first look for the block "url_widget",
* then, if that does not exist, for the block "text_widget" etc.
*
* The type of the resource is decided by the implementation. The resource
* is later passed to {@link renderBlock()} by the rendering algorithm.
*
* @param FormViewInterface $view The view for determining the used
* themes. First the themes attached
* directly to the view with
* {@link setTheme()} are considered,
* then the ones of its parent etc.
* @param array $blockHierarchy The block name hierarchy, with
* the root block at the beginning.
* @param integer $hierarchyLevel The level in the hierarchy at
* which to start looking. Level 0
* indicates the root block, i.e.
* the first element of $blockHierarchy.
*
* @return mixed The renderer resource or false, if none was found.
*/
public function getResourceForBlockHierarchy(FormViewInterface $view, array $blockHierarchy, $hierarchyLevel);
/**
* Returns the hierarchy level at which a resource can be found.
*
* A block hierarchy is an array which starts with the root of the hierarchy
* and continues with the child of that root, the child of that child etc.
* The following is an example for a block hierarchy:
*
* <code>
* form_widget
* text_widget
* url_widget
* </code>
*
* The second parameter $hierarchyLevel determines the level of the hierarchy
* that should be rendered.
*
* If we call this method with the hierarchy level 2, the engine will first
* look for a resource for block "url_widget". If such a resource exists,
* the method returns 2. Otherwise it tries to find a resource for block
* "text_widget" (at level 1) and, again, returns 1 if a resource was found.
* The method continues to look for resources until the root level was
* reached and nothing was found. In this case false is returned.
*
* The type of the resource is decided by the implementation. The resource
* is later passed to {@link renderBlock()} by the rendering algorithm.
*
* @param FormViewInterface $view The view for determining the used
* themes. First the themes attached
* directly to the view with
* {@link setTheme()} are considered,
* then the ones of its parent etc.
* @param array $blockHierarchy The block name hierarchy, with
* the root block at the beginning.
* @param integer $hierarchyLevel The level in the hierarchy at
* which to start looking. Level 0
* indicates the root block, i.e.
* the first element of $blockHierarchy.
*
* @return integer|Boolean The hierarchy level or false, if no resource was found.
*/
public function getResourceHierarchyLevel(FormViewInterface $view, array $blockHierarchy, $hierarchyLevel);
/**
* Renders a block in the given renderer resource.
*
* The resource can be obtained by calling {@link getResourceForBlock()}
* or {@link getResourceForBlockHierarchy()}. The type of the resource is
* decided by the implementation.
*
* @param FormViewInterface $view The view to render.
* @param mixed $resource The renderer resource.
* @param string $block The name of the block to render.
* @param array $variables The variables to pass to the template.
*
* @return string The HTML markup.
*/
public function renderBlock(FormViewInterface $view, $resource, $block, array $variables = array());
}

View File

@ -0,0 +1,180 @@
<?php
/*
* This file is part of the Symfony package.
*
* (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 Symfony\Component\Form;
use Symfony\Component\Form\Extension\Core\View\ChoiceView;
/**
* Renders a form into HTML.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface FormRendererInterface
{
/**
* Returns the engine used by this renderer.
*
* @return FormRendererEngineInterface The renderer engine.
*/
public function getEngine();
/**
* Sets the theme(s) to be used for rendering a view and its children.
*
* @param FormViewInterface $view The view to assign the theme(s) to.
* @param mixed $themes The theme(s). The type of these themes
* is open to the implementation.
*/
public function setTheme(FormViewInterface $view, $themes);
/**
* Renders the HTML enctype in the form tag, if necessary.
*
* Example usage templates:
*
* <form action="..." method="post" <?php echo $renderer->renderEnctype($form) ?>>
*
* @param FormViewInterface $view The view for which to render the encoding type
*
* @return string The HTML markup
*/
public function renderEnctype(FormViewInterface $view);
/**
* Renders the entire row for a form field.
*
* A row typically contains the label, errors and widget of a field.
*
* @param FormViewInterface $view The view for which to render the row
* @param array $variables Additional variables passed to the template
*
* @return string The HTML markup
*/
public function renderRow(FormViewInterface $view, array $variables = array());
/**
* Renders views which have not already been rendered.
*
* @param FormViewInterface $view The parent view
* @param array $variables An array of variables
*
* @return string The HTML markup
*/
public function renderRest(FormViewInterface $view, array $variables = array());
/**
* Renders the HTML for a given view.
*
* Example usage:
*
* <?php echo $renderer->renderWidget($form) ?>
*
* You can pass options during the call:
*
* <?php echo $renderer->renderWidget($form, array('attr' => array('class' => 'foo'))) ?>
*
* <?php echo $renderer->renderWidget($form, array('separator' => '+++++)) ?>
*
* @param FormViewInterface $view The view for which to render the widget
* @param array $variables Additional variables passed to the template
*
* @return string The HTML markup
*/
public function renderWidget(FormViewInterface $view, array $variables = array());
/**
* Renders the errors of the given view.
*
* @param FormViewInterface $view The view to render the errors for
*
* @return string The HTML markup
*/
public function renderErrors(FormViewInterface $view);
/**
* Renders the label of the given view.
*
* @param FormViewInterface $view The view for which to render the label
* @param string $label The label
* @param array $variables Additional variables passed to the template
*
* @return string The HTML markup
*/
public function renderLabel(FormViewInterface $view, $label = null, array $variables = array());
/**
* Renders a named block of the form theme.
*
* @param string $block The name of the block.
* @param array $variables The variables to pass to the template.
*
* @return string The HTML markup
*/
public function renderBlock($block, array $variables = array());
/**
* Renders a CSRF token.
*
* Use this helper for CSRF protection without the overhead of creating a
* form.
*
* <code>
* <input type="hidden" name="token" value="<?php $renderer->renderCsrfToken('rm_user_'.$user->getId()) ?>">
* </code>
*
* Check the token in your action using the same intention.
*
* <code>
* $csrfProvider = $this->get('form.csrf_provider');
* if (!$csrfProvider->isCsrfTokenValid('rm_user_'.$user->getId(), $token)) {
* throw new \RuntimeException('CSRF attack detected.');
* }
* </code>
*
* @param string $intention The intention of the protected action
*
* @return string A CSRF token
*/
public function renderCsrfToken($intention);
/**
* Returns whether the given choice is a group.
*
* @param mixed $choice A choice
*
* @return Boolean Whether the choice is a group
*/
public function isChoiceGroup($choice);
/**
* Returns whether the given choice is selected.
*
* @param FormViewInterface $view The view of the choice field
* @param ChoiceView $choice The choice to check
*
* @return Boolean Whether the choice is selected
*/
public function isChoiceSelected(FormViewInterface $view, ChoiceView $choice);
/**
* Makes a technical name human readable.
*
* Sequences of underscores are replaced by single spaces. The first letter
* of the resulting string is capitalized, while all other letters are
* turned to lowercase.
*
* @param string $text The text to humanize.
*
* @return string The humanized text.
*/
public function humanize($text);
}

View File

@ -12,14 +12,14 @@
namespace Symfony\Component\Form; namespace Symfony\Component\Form;
use Symfony\Component\Form\Util\PropertyPath; use Symfony\Component\Form\Util\PropertyPath;
use Symfony\Component\EventDispatcher\UnmodifiableEventDispatcher; use Symfony\Component\EventDispatcher\ImmutableEventDispatcher;
/** /**
* A read-only form configuration. * A read-only form configuration.
* *
* @author Bernhard Schussek <bschussek@gmail.com> * @author Bernhard Schussek <bschussek@gmail.com>
*/ */
class UnmodifiableFormConfig implements FormConfigInterface class ImmutableFormConfig implements FormConfigInterface
{ {
/** /**
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
@ -134,8 +134,8 @@ class UnmodifiableFormConfig implements FormConfigInterface
public function __construct(FormConfigInterface $config) public function __construct(FormConfigInterface $config)
{ {
$dispatcher = $config->getEventDispatcher(); $dispatcher = $config->getEventDispatcher();
if (!$dispatcher instanceof UnmodifiableEventDispatcher) { if (!$dispatcher instanceof ImmutableEventDispatcher) {
$dispatcher = new UnmodifiableEventDispatcher($dispatcher); $dispatcher = new ImmutableEventDispatcher($dispatcher);
} }
$this->dispatcher = $dispatcher; $this->dispatcher = $dispatcher;

View File

@ -545,7 +545,7 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest
] ]
/following-sibling::div /following-sibling::div
[ [
./label ./label[.="child"]
/following-sibling::div /following-sibling::div
[ [
./div ./div

View File

@ -20,7 +20,7 @@ use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\Form\Tests\Fixtures\FixedDataTransformer; use Symfony\Component\Form\Tests\Fixtures\FixedDataTransformer;
class FormTest extends AbstractFormTest class CompoundFormTest extends AbstractFormTest
{ {
public function testValidIfAllChildrenAreValid() public function testValidIfAllChildrenAreValid()
{ {

Some files were not shown because too many files have changed in this diff Show More