9

我正在使用带有 PropelBundle 的 Symfony 2.1 表单,并且我正在尝试重构具有对象下拉列表(可供选择)的表单,以使用 jquery 自动完成字段(使用 AJAX)。对于下拉列表,我在表单类型中使用了以下代码(非常适合下拉列表):

$builder->add('books', 'collection', array(
    'type'          => 'model',
    'options'       => array(
        'class'     => 'MyVendor\MyBundle\Model\Book',
        'property'  => 'title',
    ),
    'allow_add'     => true,
    'allow_delete'  => true,
    'by_reference'  => false,
    'required'      => false,
));

为了提供一点上下文,假设我们正在创建一个新的“读者”对象,并且我们想从可用的“书籍”对象列表中选择读者最喜欢的书籍。使用集合类型,以便可以在新的“读者”表单中选择许多“最喜欢的书籍”。现在,我想更改以上内容以使用自动完成功能。为此,我尝试实现一个数据转换器,以便能够从一个简单的文本字段中获取一个 Book 对象,该文本字段可用于 Autocomplete 函数以传递 Book ID,如本问题的答案所示. 但是,我无法弄清楚如何使 Data Transformer 与集合类型和 Propel 类一起工作。我按照 Symfony Cookbook 中的说明创建了一个 BookToIdTransformer 类,并在“ReaderType”文件中尝试了以下操作:

$transformer = new BookToIdTransformer();
$builder->add(
        $builder->create('books', 'collection', array(
            'type'          => 'text',
            'allow_add'     => true,
            'allow_delete'  => true,
            'by_reference'  => false,
            'required'      => false,
        ))->addModelTransformer($transformer)
);

有了上面,我得到一个“调用未定义的方法:getId”异常(显然,Transformer 需要书籍的 PropelCollection,而不是单个 Book 对象..)。有谁知道该怎么做?或者让我知道是否有其他方法可以使用 Propel 在 Symfony 中实现自动完成功能并允许选择多个对象(例如书籍集合)?

4

1 回答 1

14

我最终寻求的解决方案与我之前的答案略有不同。我最终在“ReaderType”表单中使用“文本”字段类型而不是“集合”字段类型,因为我最终使用了Loopj Tokeninput jQuery 插件,该插件允许选择多个对象(例如“收藏夹”)进行关联到表单中的主要对象(例如“Reader”对象)。考虑到这一点,我创建了一个“数据转换器”,用于将传递的对象 id(在文本字段中的逗号分隔列表中)转换为 Propel 对象集合。代码共享如下,包括一个示例 ajax 对象控制器。

“ReaderType”表单的关键部分如下所示:

$transformer = new BooksToIdsTransformer();
$builder->add(
    $builder->create('books', 'text', array(
        'required' => false,
    ))->addModelTransformer($transformer)
);

“数据转换器”文件如下所示:

// src/MyVendor/MyBundle/Form/DataTransformer/BooksToIdsTransformer.php
namespace MyVendor\MyBundle\Form\DataTransformer;

use \PropelObjectCollection;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
use MyVendor\MyBundle\Model\BookQuery;

class BooksToIdsTransformer implements DataTransformerInterface
{
    public function transform($books)
    {
        if (null === $books) {
            return "";
        }

        if (!$books instanceof PropelObjectCollection) {
            throw new UnexpectedTypeException($books, '\PropelObjectCollection');
        }
        $idsArray = array();
        foreach ($books as $book) {
            $idsArray[] = $book->getId();
        }
        $ids = implode(",", $idsArray);
        return $ids;
    }

    public function reverseTransform($ids)
    {
        $books = new PropelObjectCollection();

        if ('' === $ids || null === $ids) {
            return $books;
        }

        if (!is_string($ids)) {
            throw new UnexpectedTypeException($ids, 'string');
        }
        $idsArray = explode(",", $ids);
        $idsArray = array_filter ($idsArray, 'is_numeric');
        foreach ($idsArray as $id) {
            $books->append(BookQuery::create()->findOneById($id));
        }
        return $books;
    }
}

将“书籍”的 json 集合返回给 Tokeninput 自动完成函数的 ajax 控制器如下:

namespace MyVendor\MyBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use MyVendor\MyBundle\Model\BookQuery;


class ClassAjaxController extends Controller
{
    public function bookAction(Request $request)
    {
        $value = $request->get('q');

        $books = BookQuery::create()->findByName('%'.$value.'%');

        $json = array();
        foreach ($books as $book) {
            $json[] = array(
                'id' => $book->getId(),
                'name' => $book->getName()
            );
        }

        $response = new Response();
        $response->setContent(json_encode($json));

        return $response;
    }
}

最后,“routing.yml”文件中的路由器:

ajax_book:
    pattern:  /ajax_book
    defaults: { _controller: MySiteBundle:ClassAjax:book }
于 2012-08-20T14:46:41.040 回答