Symfony 2实体字段类型与选择和/或添加新的 [英] Symfony 2 Entity field type with select and/or add new
问题描述
让有两个实体(正确映射为Doctrine)。
Let there be two entities (correctly mapped for Doctrine).
-
发布
属性{$ id
(integer,autoinc),$ name
(string),$ tags
(集合标签
)} -
标签
与属性{$ id
(integer,autoinc),$ name
(string),$ posts
(集合Post
)}
Post
with properties {$id
(integer, autoinc),$name
(string),$tags
(collection ofTag
)}Tag
with properties {$id
(integer, autoinc),$name
(string),$posts
(collection ofPost
)}
这两者之间的关系是多对多
。
创建新的 Post
时,我想立即添加标签给它
When creating a new Post
, I want to immediately add tags to it.
如果我想添加已经被隐藏的标签
,我将创建实体字段类型,没有问题。
If I wanted to add Tags
that already are peristed, I would create entity field type, no problem with that.
但是如果我想添加全新的标签
,该怎么办? (检查一些已经存在的标签,填写新标签的名称,也许添加另一个新标签,然后在提交后将其分配给 Post
实体)
But what would I do, if I wanted to add completely new Tags
too? (Check some of already existing tags, fill name for new tag, maybe add some another new tag, then after submit assign everyting properly to Post
entity)
Create new Post:
Name: [__________]
Add tags
|
|[x] alpha
|[ ] beta
|[x] gamma
|
|My tag doesnt exist, create new:
|
|Name: [__________]
|
|+Add another new tag
有没有办法做到这一点?我知道Symfony 2的基础知识,但不知道如何处理这个问题。也惊讶我,我没有找到我的答案在任何地方,似乎是一个常见的问题给我。我缺少什么?
Is there any way to do this? I know the basics of Symfony 2, but have no idea how to deal with this. Also surprised me I havent found my answer anywhere, seems like a common problem to me. What am I missing?
推荐答案
我的标签实体具有标签名称的唯一字段。对于添加标签,我使用新的表单类型和变体。
My Tag entity has a unique field for the tag name. For add Tags I use a new form type and a transformer.
表单类型:
namespace Sg\RecipeBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Bridge\Doctrine\RegistryInterface;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Sg\RecipeBundle\Form\DataTransformer\TagsDataTransformer;
class TagType extends AbstractType
{
/**
* @var RegistryInterface
*/
private $registry;
/**
* @var SecurityContextInterface
*/
private $securityContext;
/**
* Ctor.
*
* @param RegistryInterface $registry A RegistryInterface instance
* @param SecurityContextInterface $securityContext A SecurityContextInterface instance
*/
public function __construct(RegistryInterface $registry, SecurityContextInterface $securityContext)
{
$this->registry = $registry;
$this->securityContext = $securityContext;
}
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addViewTransformer(
new TagsDataTransformer(
$this->registry,
$this->securityContext
),
true
);
}
/**
* {@inheritdoc}
*/
public function getParent()
{
return 'text';
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'tag';
}
}
变形金刚:
<?php
/*
* Stepan Tanasiychuk is the author of the original implementation
* see: https://github.com/stfalcon/BlogBundle/blob/master/Bridge/Doctrine/Form/DataTransformer/EntitiesToStringTransformer.php
*/
namespace Sg\RecipeBundle\Form\DataTransformer;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Symfony\Bridge\Doctrine\RegistryInterface;
use Doctrine\ORM\EntityManager;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
use Sg\RecipeBundle\Entity\Tag;
/**
* Tags DataTransformer.
*/
class TagsDataTransformer implements DataTransformerInterface
{
/**
* @var EntityManager
*/
private $em;
/**
* @var SecurityContextInterface
*/
private $securityContext;
/**
* Ctor.
*
* @param RegistryInterface $registry A RegistryInterface instance
* @param SecurityContextInterface $securityContext A SecurityContextInterface instance
*/
public function __construct(RegistryInterface $registry, SecurityContextInterface $securityContext)
{
$this->em = $registry->getEntityManager();
$this->securityContext = $securityContext;
}
/**
* Convert string of tags to array.
*
* @param string $string
*
* @return array
*/
private function stringToArray($string)
{
$tags = explode(',', $string);
// strip whitespaces from beginning and end of a tag text
foreach ($tags as &$text) {
$text = trim($text);
}
// removes duplicates
return array_unique($tags);
}
/**
* Transforms tags entities into string (separated by comma).
*
* @param Collection | null $tagCollection A collection of entities or NULL
*
* @return string | null An string of tags or NULL
* @throws UnexpectedTypeException
*/
public function transform($tagCollection)
{
if (null === $tagCollection) {
return null;
}
if (!($tagCollection instanceof Collection)) {
throw new UnexpectedTypeException($tagCollection, 'Doctrine\Common\Collections\Collection');
}
$tags = array();
/**
* @var \Sg\RecipeBundle\Entity\Tag $tag
*/
foreach ($tagCollection as $tag) {
array_push($tags, $tag->getName());
}
return implode(', ', $tags);
}
/**
* Transforms string into tags entities.
*
* @param string | null $data Input string data
*
* @return Collection | null
* @throws UnexpectedTypeException
* @throws AccessDeniedException
*/
public function reverseTransform($data)
{
if (!$this->securityContext->isGranted('ROLE_AUTHOR')) {
throw new AccessDeniedException('Für das Speichern von Tags ist die Autorenrolle notwendig.');
}
$tagCollection = new ArrayCollection();
if ('' === $data || null === $data) {
return $tagCollection;
}
if (!is_string($data)) {
throw new UnexpectedTypeException($data, 'string');
}
foreach ($this->stringToArray($data) as $name) {
$tag = $this->em->getRepository('SgRecipeBundle:Tag')
->findOneBy(array('name' => $name));
if (null === $tag) {
$tag = new Tag();
$tag->setName($name);
$this->em->persist($tag);
}
$tagCollection->add($tag);
}
return $tagCollection;
}
}
config.yml
The config.yml
recipe.tags.type:
class: Sg\RecipeBundle\Form\Type\TagType
arguments: [@doctrine, @security.context]
tags:
- { name: form.type, alias: tag }
使用新的类型:
->add('tags', 'tag', array(
'label' => 'Tags',
'required' => false
))
可以通过自动完成功能阻止类似symfony和smfony的相似性:
Similarities, like "symfony" and "smfony" can be prevented with an autocomplete function:
TagController:
TagController:
<?php
namespace Sg\RecipeBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
/**
* Tag controller.
*
* @Route("/tag")
*/
class TagController extends Controller
{
/**
* Get all Tag entities.
*
* @Route("/tags", name="tag_tags")
* @Method("GET")
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function getTagsAction()
{
$request = $this->getRequest();
$isAjax = $request->isXmlHttpRequest();
if ($isAjax) {
$em = $this->getDoctrine()->getManager();
$search = $request->query->get('term');
/**
* @var \Sg\RecipeBundle\Entity\Repositories\TagRepository $repository
*/
$repository = $em->getRepository('SgRecipeBundle:Tag');
$qb = $repository->createQueryBuilder('t');
$qb->select('t.name');
$qb->add('where', $qb->expr()->like('t.name', ':search'));
$qb->setMaxResults(5);
$qb->orderBy('t.name', 'ASC');
$qb->setParameter('search', '%' . $search . '%');
$results = $qb->getQuery()->getScalarResult();
$json = array();
foreach ($results as $member) {
$json[] = $member['name'];
};
return new Response(json_encode($json));
}
return new Response('This is not ajax.', 400);
}
}
form.html.twig:
form.html.twig:
<script type="text/javascript">
$(document).ready(function() {
function split(val) {
return val.split( /,\s*/ );
}
function extractLast(term) {
return split(term).pop();
}
$("#sg_recipebundle_recipetype_tags").autocomplete({
source: function( request, response ) {
$.getJSON( "{{ path('tag_tags') }}", {
term: extractLast( request.term )
}, response );
},
search: function() {
// custom minLength
var term = extractLast( this.value );
if ( term.length < 2 ) {
return false;
}
},
focus: function() {
// prevent value inserted on focus
return false;
},
select: function( event, ui ) {
var terms = split( this.value );
// remove the current input
terms.pop();
// add the selected item
terms.push( ui.item.value );
// add placeholder to get the comma-and-space at the end
terms.push( "" );
this.value = terms.join( ", " );
return false;
}
});
});
</script>
这篇关于Symfony 2实体字段类型与选择和/或添加新的的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!