Last active
December 26, 2015 00:39
-
-
Save mattsnowboard/7065865 to your computer and use it in GitHub Desktop.
This demonstrates an error in a Symfony2 form collection with dynamically added fields that are not filled out.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"require": { | |
"php": ">=5.4.11", | |
"silex/silex": "~1.0", | |
"silex/web-profiler": "~1.0", | |
"symfony/browser-kit": "~2.3", | |
"symfony/class-loader": "~2.3", | |
"symfony/console": "~2.3", | |
"symfony/config": "~2.3", | |
"symfony/css-selector": "~2.3", | |
"symfony/dom-crawler": "~2.3", | |
"symfony/filesystem": "~2.3", | |
"symfony/finder": "~2.3", | |
"symfony/form": "~2.3", | |
"symfony/locale": "~2.3", | |
"symfony/process": "~2.3", | |
"symfony/serializer": "~2.3", | |
"symfony/translation": "~2.3", | |
"symfony/validator": "~2.3", | |
"symfony/twig-bridge": "~2.3", | |
"twig/twig": ">=1.8,<2.0-dev", | |
}, | |
"autoload": { | |
"psr-0": { "": "src/" } | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
use Symfony\Component\ClassLoader\DebugClassLoader; | |
use Symfony\Component\HttpKernel\Debug\ErrorHandler; | |
use Symfony\Component\HttpKernel\Debug\ExceptionHandler; | |
require_once __DIR__.'/../vendor/autoload.php'; | |
error_reporting(-1); | |
DebugClassLoader::enable(); | |
ErrorHandler::register(); | |
if ('cli' !== php_sapi_name()) { | |
ExceptionHandler::register(); | |
} | |
$env = 'dev'; | |
$app = require __DIR__.'/../src/testapp.php'; | |
$app->run(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<head> | |
<title>Test</title> | |
</head> | |
<body> | |
{{ form_start(form) }} | |
{{ form_row(form.description) }} | |
<h3>Tags</h3> | |
<ul class="tasks" data-prototype="{{ form_widget(form.tasks.vars.prototype)|e }}"> | |
{# iterate over each existing tag and render its only field: name #} | |
{% for task in form.tasks %} | |
<li>{{ form_row(task.name) }}</li> | |
{% endfor %} | |
</ul> | |
{{ form_widget(form.submit) }} | |
{{ form_end(form) }} | |
<script>window.jQuery || document.write('<script src="/js/vendor/jquery-1.10.1.min.js"><\/script>')</script> | |
<script> | |
// Get the ul that holds the collection of tags | |
var collectionHolder = $('ul.tasks'); | |
// setup an "add a tag" link | |
var $addTaskLink = $('<a href="#" class="add_task_link">Add a task</a>'); | |
var $newLinkLi = $('<li></li>').append($addTaskLink); | |
function addTaskForm(collectionHolder, $newLinkLi) { | |
// Get the data-prototype explained earlier | |
var prototype = collectionHolder.data('prototype'); | |
// get the new index | |
var index = collectionHolder.data('index'); | |
// Replace '__name__' in the prototype's HTML to | |
// instead be a number based on how many items we have | |
var newForm = prototype.replace(/__name__/g, index); | |
// increase the index with one for the next item | |
collectionHolder.data('index', index + 1); | |
// Display the form in the page in an li, before the "Add a tag" link li | |
var $newFormLi = $('<li></li>').append(newForm); | |
$newLinkLi.before($newFormLi); | |
} | |
jQuery(document).ready(function() { | |
// add the "add a tag" anchor and li to the tags ul | |
collectionHolder.append($newLinkLi); | |
// count the current form inputs we have (e.g. 2), use that as the new | |
// index when inserting a new item (e.g. 2) | |
collectionHolder.data('index', collectionHolder.find(':input').length); | |
$addTaskLink.on('click', function(e) { | |
// prevent the link from creating a "#" on the URL | |
e.preventDefault(); | |
// add a new tag form (see next code block) | |
addTaskForm(collectionHolder, $newLinkLi); | |
}); | |
}); | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
use Silex\Application; | |
use Silex\Provider\FormServiceProvider; | |
use Silex\Provider\ServiceControllerServiceProvider; | |
use Silex\Provider\SessionServiceProvider; | |
use Silex\Provider\SwiftmailerServiceProvider; | |
use Silex\Provider\TranslationServiceProvider; | |
use Silex\Provider\TwigServiceProvider; | |
use Silex\Provider\UrlGeneratorServiceProvider; | |
use Silex\Provider\ValidatorServiceProvider; | |
use Symfony\Component\HttpFoundation\Request; | |
use Symfony\Component\Form\AbstractType; | |
use Symfony\Component\Form\FormBuilderInterface; | |
use Symfony\Component\OptionsResolver\OptionsResolverInterface; | |
$app = new Application(); | |
$app['debug'] = true; | |
$app->register(new FormServiceProvider()); | |
$app->register(new ServiceControllerServiceProvider()); | |
$app->register(new SessionServiceProvider()); | |
$app->register(new SwiftmailerServiceProvider()); | |
$app->register(new TranslationServiceProvider(), array( | |
'locale_fallback' => 'en', | |
)); | |
$app->register(new UrlGeneratorServiceProvider()); | |
$app->register(new ValidatorServiceProvider()); | |
// TWIG | |
$app->register(new TwigServiceProvider(), array( | |
'twig.path' => array(__DIR__.'/views'), | |
)); | |
// | |
// MODEL | |
// | |
class Task | |
{ | |
protected $name; | |
public function getName() | |
{ | |
return $this->name; | |
} | |
public function setName($name) | |
{ | |
$this->name = $name; | |
return $this; | |
} | |
} | |
class Project | |
{ | |
protected $description; | |
protected $tasks; | |
public function __construct() | |
{ | |
$this->tasks = array(); | |
} | |
public function getDescription() | |
{ | |
return $this->description; | |
} | |
public function setDescription($description) | |
{ | |
$this->description = $description; | |
return $this; | |
} | |
public function getTasks() | |
{ | |
return $this->tasks; | |
} | |
public function addTask(Task $task) | |
{ | |
if (!is_null($task) && !in_array($task, $this->tasks)) { | |
$this->tasks[] = $task; | |
} | |
return $this; | |
} | |
public function removeTask(Task $task) | |
{ | |
if (!is_null($task)) { | |
$this->tasks = array_diff($this->tasks, array($task)); | |
} | |
return $this; | |
} | |
} | |
// | |
// FORM | |
// | |
class TaskType extends AbstractType | |
{ | |
public function buildForm(FormBuilderInterface $builder, array $options) | |
{ | |
$builder->add('name', 'text', array( | |
'required' => false | |
)); | |
} | |
public function setDefaultOptions(OptionsResolverInterface $resolver) | |
{ | |
$resolver->setDefaults(array( | |
'data_class' => '\Task', | |
)); | |
} | |
public function getName() | |
{ | |
return 'task'; | |
} | |
} | |
class ProjectType extends AbstractType | |
{ | |
public function buildForm(FormBuilderInterface $builder, array $options) | |
{ | |
$builder->add('description', 'text', array()) | |
->add('tasks', 'collection', array( | |
'type' => new TaskType(), | |
'allow_add' => true, | |
'by_reference' => false, | |
'allow_delete' => true, // This causes the issue | |
'required' => false | |
)) | |
->add('submit', 'submit') | |
; | |
} | |
public function setDefaultOptions(OptionsResolverInterface $resolver) | |
{ | |
$resolver->setDefaults(array( | |
'data_class' => '\Project', | |
)); | |
} | |
public function getName() | |
{ | |
return 'project'; | |
} | |
} | |
// | |
// Controller | |
// | |
$app->match('/', function (Request $request) use ($app) { | |
$project = new Project(); | |
// dummy code - this is here just so that the Task has some tags | |
// otherwise, this isn't an interesting example | |
$task1 = new Task(); | |
$task1->setName('A Task'); | |
$project->addTask($task1); | |
// end dummy code | |
$form = $app['form.factory']->create(new ProjectType(), $project); | |
$form->handleRequest($request); | |
if ($form->isValid()) { | |
$data = $form->getData(); | |
$debug = print_r($data, true); | |
echo $debug; | |
} | |
return $app['twig']->render('test.html.twig', array( | |
'form' => $form->createView(), | |
)); | |
}) | |
->method('GET|POST') | |
; | |
return $app; |
Nevermind, the problem was 'required' => false on the collection.
->add('tasks', 'collection', array(
'type' => new TaskType(),
'allow_add' => true,
'by_reference' => false,
'allow_delete' => true,
'required' => false // this causes the issue!
))
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
If you try to add a task with javascript, but don't fill in its name and submit the form, you get the following error:
ContextErrorException: Catchable Fatal Error: Argument 1 passed to Project::addTask() must be an instance of Task, null given in D:\web_workspace\wedding\src\testapp.php line 82