阅读this页面我已经设置了一个表单来处理 PATCH 请求。
我有一个玩家实体:
<?php
namespace Acme\PlayerBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\ORM\Mapping as ORM;
/**
* Acme\PlayerBundle\Entity\Player
*
* @ORM\Table()
* @ORM\Entity(repositoryClass="Acme\PlayerBundle\Entity\PlayerRepository")
*/
class Player
{
/**
* @var integer $id
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @var string $name
*
* @ORM\Column(name="name", type="string", length=255)
* @Assert\NotBlank()
*/
private $name;
/**
* @ORM\ManyToOne(targetEntity="Acme\UserBundle\Entity\User", inversedBy="players")
* @ORM\JoinColumn(nullable=false)
*/
private $owner;
/**
* @ORM\ManyToOne(targetEntity="Acme\TeamBundle\Entity\Team", inversedBy="players")
* @ORM\JoinColumn(nullable=false)
* @Assert\NotBlank()
*/
private $team;
/**
* @var integer $shirtNumber
*
* @ORM\Column(name="shirtNumber", type="smallint")
* @Assert\NotBlank()
*/
private $shirtNumber;
/**
* @var integer $vsid
*
* @ORM\Column(name="vsid", type="integer", nullable=true)
*/
private $vsid;
/**
* @var string $firstname
*
* @ORM\Column(name="firstname", type="string", length=255, nullable=true)
*/
private $firstname;
/**
* @var string $lastname
*
* @ORM\Column(name="lastname", type="string", length=255, nullable=true)
*/
private $lastname;
/**
* @var boolean $deleted
*
* @ORM\Column(name="deleted", type="boolean")
*/
private $deleted = false;
/**
* @var integer $role
*
* @ORM\Column(type="integer", nullable=true)
*/
private $role;
/**
* Create the user salt
*/
public function __construct()
{
//TODO: just for test
$this->uniqueId = substr(uniqid(), 0, 14);
}
/* MANAGED BY DOCTRINE, DON'T EDIT */
/**
* Get id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* @param string $name
* @return Player
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Set shirtNumber
*
* @param integer $shirtNumber
* @return Player
*/
public function setShirtNumber($shirtNumber)
{
$this->shirtNumber = $shirtNumber;
return $this;
}
/**
* Get shirtNumber
*
* @return integer
*/
public function getShirtNumber()
{
return $this->shirtNumber;
}
/**
* Set vsid
*
* @param integer $vsid
* @return Player
*/
public function setVsid($vsid)
{
$this->vsid = $vsid;
return $this;
}
/**
* Get vsid
*
* @return integer
*/
public function getVsid()
{
return $this->vsid;
}
/**
* Set firstname
*
* @param string $firstname
* @return Player
*/
public function setFirstname($firstname)
{
$this->firstname = $firstname;
return $this;
}
/**
* Get firstname
*
* @return string
*/
public function getFirstname()
{
return $this->firstname;
}
/**
* Set lastname
*
* @param string $lastname
* @return Player
*/
public function setLastname($lastname)
{
$this->lastname = $lastname;
return $this;
}
/**
* Get lastname
*
* @return string
*/
public function getLastname()
{
return $this->lastname;
}
/**
* Set deleted
*
* @param boolean $deleted
* @return Player
*/
public function setDeleted($deleted)
{
$this->deleted = $deleted;
return $this;
}
/**
* Get deleted
*
* @return boolean
*/
public function getDeleted()
{
return $this->deleted;
}
/**
* Set role
*
* @param integer $role
* @return Player
*/
public function setRole($role)
{
$this->role = $role;
return $this;
}
/**
* Get role
*
* @return integer
*/
public function getRole()
{
return $this->role;
}
/**
* Set owner
*
* @param Acme\UserBundle\Entity\User $owner
* @return Player
*/
public function setOwner(\Acme\UserBundle\Entity\User $owner)
{
$this->owner = $owner;
return $this;
}
/**
* Get owner
*
* @return Acme\UserBundle\Entity\User
*/
public function getOwner()
{
return $this->owner;
}
/**
* Set team
*
* @param Acme\TeamBundle\Entity\Team $team
* @return Player
*/
public function setTeam(\Acme\TeamBundle\Entity\Team $team)
{
$this->team = $team;
return $this;
}
/**
* Get team
*
* @return Acme\TeamBundle\Entity\Team
*/
public function getTeam()
{
return $this->team;
}
}
和团队实体:
<?php
namespace Acme\TeamBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* Acme\TeamBundle\Entity\Team
*
* @ORM\Table()
* @ORM\Entity(repositoryClass="Acme\TeamBundle\Entity\TeamRepository")
*/
class Team
{
/**
* @var integer $id
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @var string $uniqueId
*
* @ORM\Column(name="uniqueId", type="string", length=15)
*/
private $uniqueId;
/**
* @ORM\ManyToOne(targetEntity="Acme\UserBundle\Entity\User", inversedBy="teams")
* @ORM\JoinColumn(nullable=false)
*/
private $owner;
/**
* @var string $name
*
* @ORM\Column(name="name", type="string", length=50)
* @Assert\NotBlank()
*/
private $name;
/**
* @var string $homeColor
*
* @ORM\Column(name="homeColor", type="string", length=7, nullable=true)
*/
private $homeColor;
/**
* @var string $awayColor
*
* @ORM\Column(name="awayColor", type="string", length=7, nullable=true)
*/
private $awayColor;
/**
* @var string $homeShirt
*
* @ORM\Column(name="homeShirt", type="string", length=50, nullable=true)
*/
private $homeShirt;
/**
* @var string $awayShirt
*
* @ORM\Column(name="awayShirt", type="string", length=50, nullable=true)
*/
private $awayShirt;
/**
* @var string $teamLogo
*
* @ORM\Column(name="teamLogo", type="string", length=50, nullable=true)
*/
private $teamLogo;
/**
* @var boolean $deleted
*
* @ORM\Column(name="deleted", type="boolean")
*/
private $deleted = false;
/**
* @ORM\OneToMany(targetEntity="Acme\PlayerBundle\Entity\Player", mappedBy="team", cascade={"persist", "remove"})
*/
private $players;
/**
* @ORM\OneToMany(targetEntity="Acme\MatchBundle\Entity\Match", mappedBy="homeTeam")
*/
private $homeMatches;
/**
* @ORM\OneToMany(targetEntity="Acme\MatchBundle\Entity\Match", mappedBy="awayTeam")
*/
private $awayMatches;
/**
* Create the user salt
*/
public function __construct()
{
$this->players = new ArrayCollection();
$this->homeMatches = new ArrayCollection();
$this->awayMatches = new ArrayCollection();
//TODO: just for test
$this->uniqueId = substr(uniqid(), 0, 14);
}
public function getMatches()
{
return array_merge($this->awayMatches->toArray(), $this->homeMatches->toArray());
}
/* MANAGED BY DOCTRINE, DON'T EDIT */
/**
* Get id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set uniqueId
*
* @param string $uniqueId
* @return Team
*/
public function setUniqueId($uniqueId)
{
$this->uniqueId = $uniqueId;
return $this;
}
/**
* Get uniqueId
*
* @return string
*/
public function getUniqueId()
{
return $this->uniqueId;
}
/**
* Set name
*
* @param string $name
* @return Team
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Set homeColor
*
* @param string $homeColor
* @return Team
*/
public function setHomeColor($homeColor)
{
$this->homeColor = $homeColor;
return $this;
}
/**
* Get homeColor
*
* @return string
*/
public function getHomeColor()
{
return $this->homeColor;
}
/**
* Set awayColor
*
* @param string $awayColor
* @return Team
*/
public function setAwayColor($awayColor)
{
$this->awayColor = $awayColor;
return $this;
}
/**
* Get awayColor
*
* @return string
*/
public function getAwayColor()
{
return $this->awayColor;
}
/**
* Set homeShirt
*
* @param string $homeShirt
* @return Team
*/
public function setHomeShirt($homeShirt)
{
$this->homeShirt = $homeShirt;
return $this;
}
/**
* Get homeShirt
*
* @return string
*/
public function getHomeShirt()
{
return $this->homeShirt;
}
/**
* Set awayShirt
*
* @param string $awayShirt
* @return Team
*/
public function setAwayShirt($awayShirt)
{
$this->awayShirt = $awayShirt;
return $this;
}
/**
* Get awayShirt
*
* @return string
*/
public function getAwayShirt()
{
return $this->awayShirt;
}
/**
* Set teamLogo
*
* @param string $teamLogo
* @return Team
*/
public function setTeamLogo($teamLogo)
{
$this->teamLogo = $teamLogo;
return $this;
}
/**
* Get teamLogo
*
* @return string
*/
public function getTeamLogo()
{
return $this->teamLogo;
}
/**
* Set deleted
*
* @param boolean $deleted
* @return Team
*/
public function setDeleted($deleted)
{
$this->deleted = $deleted;
return $this;
}
/**
* Get deleted
*
* @return boolean
*/
public function getDeleted()
{
return $this->deleted;
}
/**
* Add players
*
* @param Acme\PlayerBundle\Entity\Player $players
* @return Team
*/
public function addPlayer(\Acme\PlayerBundle\Entity\Player $players)
{
$this->players[] = $players;
return $this;
}
/**
* Remove players
*
* @param Acme\PlayerBundle\Entity\Player $players
*/
public function removePlayer(\Acme\PlayerBundle\Entity\Player $players)
{
$this->players->removeElement($players);
}
/**
* Get players
*
* @return Doctrine\Common\Collections\Collection
*/
public function getPlayers()
{
return $this->players;
}
/**
* Add homeMatches
*
* @param Acme\MatchBundle\Entity\Match $homeMatches
* @return Team
*/
public function addHomeMatche(\Acme\MatchBundle\Entity\Match $homeMatches)
{
$this->homeMatches[] = $homeMatches;
return $this;
}
/**
* Remove homeMatches
*
* @param Acme\MatchBundle\Entity\Match $homeMatches
*/
public function removeHomeMatche(\Acme\MatchBundle\Entity\Match $homeMatches)
{
$this->homeMatches->removeElement($homeMatches);
}
/**
* Get homeMatches
*
* @return Doctrine\Common\Collections\Collection
*/
public function getHomeMatches()
{
return $this->homeMatches;
}
/**
* Add awayMatches
*
* @param Acme\MatchBundle\Entity\Match $awayMatches
* @return Team
*/
public function addAwayMatche(\Acme\MatchBundle\Entity\Match $awayMatches)
{
$this->awayMatches[] = $awayMatches;
return $this;
}
/**
* Remove awayMatches
*
* @param Acme\MatchBundle\Entity\Match $awayMatches
*/
public function removeAwayMatche(\Acme\MatchBundle\Entity\Match $awayMatches)
{
$this->awayMatches->removeElement($awayMatches);
}
/**
* Get awayMatches
*
* @return Doctrine\Common\Collections\Collection
*/
public function getAwayMatches()
{
return $this->awayMatches;
}
/**
* Set owner
*
* @param Acme\UserBundle\Entity\User $owner
* @return Team
*/
public function setOwner(\Acme\UserBundle\Entity\User $owner)
{
$this->owner = $owner;
return $this;
}
/**
* Get owner
*
* @return Acme\UserBundle\Entity\User
*/
public function getOwner()
{
return $this->owner;
}
}
现在我已经使用应用程序/控制台创建了一个玩家表单类,并且我已将团队字段编辑为团队实体的实例,如下所示:
<?php
namespace Acme\PlayerBundle\Form;
use Acme\TeamBundle\Entity\TeamRepository;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class PlayerType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('shirtNumber')
->add('firstname')
->add('lastname')
->add('role')
->add('team', 'entity', array(
'class' => 'AcmeTeamBundle:Team',
'query_builder' => function(TeamRepository $er) {
$query = $er->createQueryBuilder('t');
return $query;
}
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\PlayerBundle\Entity\Player',
'csrf_protection' => false
));
}
public function getName()
{
return 'player';
}
}
这是我的控制器的相关部分:
/**
* Create a new player
*
* @Route(".{_format}", name="api_player_create")
* @Method("POST")
* @ApiDoc(
* description="Create a new player",
* statusCodes={
* 201="Player created and informations are returned",
* 400="Missing informations",
* 403="The user isn't authorized"
* },
* input="Acme\PlayerBundle\Form\PlayerType",
* return="Acme\PlayerBundle\Entity\Player"
* )
*
* @return Renders the player just created
*/
public function createPlayerAction()
{
return $this->processForm(new Player());
}
/**
* Edit a player
*
* @param integer $id The id of the player to be created
*
* @Route("/{id}.{_format}", name="api_player_patch", requirements={ "id": "\d+" })
* @Method("PATCH")
* @ApiDoc(
* description="Edit a player",
* statusCodes={
* 200="Player is updated",
* 400="Missing informations",
* 403="The user isn't authorized"
* },
* input="Acme\PlayerBundle\Form\PlayerType",
* return="Acme\PlayerBundle\Entity\Player"
* )
*
* @return Renders the player just edited
*/
public function editPlayerAction(Player $player)
{
if ($player->getOwner() != $this->getUser()) {
throw new ApiException\PermissionDeniedException;
}
return $this->processForm($player);
}
/**
* Function to handle a form to create/edit a player
*
* @param Player $player The player to be created or edited
*
* @return Api Response
*/
private function processForm(Player $player)
{
/**
* Check if the player is new (to be created) or we're editing a player
*/
$statusCode = is_null($player->getId()) ? 201 : 200;
$form = $this->createForm(new PlayerType(), $player);
$form->bind($this->getRequest());
if ($form->isValid()) {
if($player->getTeam()->getOwner() != $this->getUser()) {
throw new ApiException\PermissionDeniedException;
}
$player->setOwner($this->getUser());
$this->entityManager->persist($player);
$this->entityManager->flush();
return $this->apiResponse->getResponse($player, $statusCode);
}
return $this->apiResponse->getResponse($form, 400, 'Missing arguments');
}
播放器创建工作正常,播放器编辑则不然,当用户发出 api 请求时,传递 url 中的 ID 和我得到的播放器名称:
Catchable Fatal Error: Argument 1 passed to Acme\PlayerBundle\Entity\Player::setTeam() must be an instance of Acme\TeamBundle\Entity\Team, null given, called in /Volumes/Dati/Users/alessandro/Sites/acme-api/vendor/symfony/symfony/src/Symfony/Component/Form/Util/PropertyPath.php on line 538 and defined in /Volumes/Dati/Users/alessandro/Sites/acme-api/src/Acme/PlayerBundle/Entity/Player.php line 278
似乎表单试图将团队设置为空,为什么?
我尝试过发送而不是团队作为表单参数,但它不起作用。
有什么线索吗?
发现表单中的 null 未发送字段是 symfony 中的默认行为。 在 symfony 中存在对部分表单绑定的开放请求。现在,一个人创建了一个表单事件订阅者,它用实际值添加了缺失的字段:
https://gist.github.com/3720535
这是他的代码:
<?php
namespace Foo;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormEvent;
/**
* Changes Form->bind() behavior so that it treats not set values as if they
* were sent unchanged.
*
* Use when you don't want fields to be set to NULL when they are not displayed
* on the page (or to implement PUT/PATCH requests).
*/
class PatchSubscriber implements EventSubscriberInterface
{
public function onPreBind(FormEvent $event)
{
$form = $event->getForm();
$clientData = $event->getData();
$clientData = array_replace($this->unbind($form), $clientData ?: array());
$event->setData($clientData);
}
/**
* Returns the form's data like $form->bind() expects it
*/
protected function unbind($form)
{
if ($form->hasChildren()) {
$ary = array();
foreach ($form->getChildren() as $name => $child) {
$ary[$name] = $this->unbind($child);
}
return $ary;
} else {
return $form->getClientData();
}
}
static public function getSubscribedEvents()
{
return array(
FormEvents::PRE_BIND => 'onPreBind',
);
}
}
要添加到表单,您必须编辑表单类中的 buildForm 方法:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$subscriber = new PatchSubscriber();
$builder->addEventSubscriber($subscriber);
$builder->add('name');
....
}
在这种情况下,您可以使用 PATCH REST 请求仅通过发送的字段来编辑实体