2

阅读页面,我设置了一个表单来处理 PATCH 请求。

我有一个 Player 实体:

<?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;
    }
}

现在,我使用 app/console 创建了一个玩家表单类,并将 team 字段编辑为 Team 实体的一个实例,这样:

<?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

似乎表单试图将 Team 设置为 null,为什么?

我已经尝试将发送而不是团队作为表单参数,但它不起作用。

有什么线索吗?

4

1 回答 1

4

发现表单中的空未发送字段是 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 请求仅通过发送的字段来编辑实体

于 2012-11-16T08:20:06.180 回答