How to use group - symfony

I use FOSUserBundle and 'group' function
At first in my User.php
There were such getter and setter
/**
* Add groups
*
* #param \Acme\UserBundle\Entity\Group $groups
* #return User
*/
public function addGroup(\Acme\UserBundle\Entity\Group $groups)
{
$this->groups[] = $groups;
return $this;
}
/**
* Remove groups
*
* #param \Acme\UserBundle\Entity\Group $groups
*/
public function removeGroup(\Acme\UserBundle\Entity\Group $groups)
{
$this->groups->removeElement($groups);
}
/**
* Get groups
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getGroups()
{
return $this->groups;
}
but,this error happens
FatalErrorException: Compile Error: Declaration of Acme\UserBundle\Entity\User::addGroup() must be compatible with that of FOS\UserBundle\Model\GroupableInterface::addGroup()
in FOS\UserBundle\Model\GroupableInterface
public function addGroup(GroupInterface $group);
in my Acme\UserBundle\Entity\User
public function addGroup(\Acme\UserBundle\Entity\Group $groups)
{
$this->groups[] = $groups;
return $this;
}
How can I adjust the argument type or correct this error?
For now,I have commented out these three function.
at that time it looked works well.
But now ,I use Sonataadmin bundle and in
#SonataAdminBundle/Admin/UserAdmin.php
protected function configureFormFields(FormMapper $formMapper){
$formMapper
->with('General')
->add('groups','entity',array('property' => 'name',
'multiple' => true,
'class' => 'UserBundle:Group',
))
}
it shows this form correctly but when I push submit button to regist,
it shows
Error: Call to a member function contains() on a non-object in ~~~/FOS/UserBundle/Model/User.php line 572
in /FOS/UserBundle/Model/user.php
there is function likt this
public function addGroup(GroupInterface $group)
{
var_dump($group);# I added to check
if (!$this->getGroups()->contains($group)) {
$this->getGroups()->add($group);
}
return $this;
}
var_dump($group) shows
object(Acme\UserBundle\Entity\Group)#923 (3) { ["id":protected]=> int(2) ["name":protected]=> string(12) "TeacherGroup" ["roles":protected]=> array(0) { } }
I guess it has group information correctly..
How can I fix this problem?
my whole user.php
// src/Acme/UserBundle/Entity/User.php
namespace Acme\UserBundle\Entity;
use FOS\UserBundle\Entity\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use FOS\UserBundle\Model\GroupableInterface;
/**
* #ORM\Entity
* #ORM\Table(name="fos_user")
* #ORM\HasLifecycleCallbacks
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*
*/
protected $id;
/**
*
* #ORM\OneToOne(targetEntity="Acme\UserBundle\Entity\Lesson", mappedBy="teacher")
*/
private $LessonAsTeacher;
/**
*
* #ORM\OneToMany(targetEntity="Acme\UserBundle\Entity\Lesson", mappedBy="student*removethis : name of the variable in Lesson.php*")
*/
private $LessonAsStudent;
/**
*
* #ORM\ManyToOne(targetEntity="Acme\UserBundle\Entity\Sex", inversedBy="sex*removethis : name of the variable in user.php*")
* #ORM\JoinColumn(name="sex", referencedColumnName="id",nullable=false)
*/
private $sex;
/**
* #ORM\ManyToMany(targetEntity="Acme\UserBundle\Entity\Group")
* #ORM\JoinTable(name="fos_user_user_group",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="group_id", referencedColumnName="id")}
* )
*/
protected $groups;
/**
* #ORM\Column(type="string", length=255)
*
* #Assert\NotBlank(message="Please enter your first name.", groups={"Registration", "Profile"})
* #Assert\MinLength(limit="0", message="The name is too short.", groups={"Registration", "Profile"})
* #Assert\MaxLength(limit="255", message="The name is too long.", groups={"Registration", "Profile"})
*/
protected $firstname;
/**
* #ORM\Column(type="string", length=255)
*
* #Assert\NotBlank(message="Please enter your last name.", groups={"Registration", "Profile"})
* #Assert\MinLength(limit="0", message="The name is too short.", groups={"Registration", "Profile"})
* #Assert\MaxLength(limit="255", message="The name is too long.", groups={"Registration", "Profile"})
*/
protected $lastname;
/**
* #ORM\Column(type="date")
*/
protected $birthday;
/**
* #var \DateTime
*
* #ORM\Column(name="createdAt", type="datetime")
*/
private $createdAt;
/**
* #var \DateTime
*
* #ORM\Column(name="updatedAt", type="datetime")
*/
private $updatedAt;
public function __construct()
{
parent::__construct();
// your own logic
}
public function getFirstname()
{
return $this->firstname;
}
public function setFirstname($name)
{
$this->firstname = $name;
return $this;
}
public function getLastname()
{
return $this->lastname;
}
public function setLastname($name)
{
$this->lastname = $name;
return $this;
}
public function getSex()
{
return $this->sex;
}
public function setSex($sex)
{
$this->sex = $sex;
return $this;
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set age
*
* #param integer $age
* #return User
*/
public function setAge($age)
{
$this->age = $age;
return $this;
}
/**
* Get age
*
* #return integer
*/
public function getAge()
{
return $this->age;
}
/**
* Set birthday
*
* #param \DateTime $birthday
* #return User
*/
public function setBirthday($birthday)
{
$this->birthday = $birthday;
return $this;
}
/**
* Get birthday
*
* #return \DateTime
*/
public function getBirthday()
{
return $this->birthday;
}
/**
* #ORM\PrePersist
*/
public function prePersist()
{
$this->createdAt = new \DateTime;
$this->updatedAt = new \DateTime;
}
/**
* #ORM\PreUpdate
*/
public function preUpdate()
{
$this->updatedAt = new \DateTime;
}
/**
* Set createdAt
*
* #param \DateTime $createdAt
* #return User
*/
public function setCreatedAt($createdAt)
{
$this->createdAt = $createdAt;
return $this;
}
/**
* Get createdAt
*
* #return \DateTime
*/
public function getCreatedAt()
{
return $this->createdAt;
}
/**
* Set updatedAt
*
* #param \DateTime $updatedAt
* #return User
*/
public function setUpdatedAt($updatedAt)
{
$this->updatedAt = $updatedAt;
return $this;
}
/**
* Get updatedAt
*
* #return \DateTime
*/
public function getUpdatedAt()
{
return $this->updatedAt;
}
/**
* Get groups
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getGroups()
{
return $this->groups;
}
/**
* Add groups
*
* #param \Acme\UserBundle\Entity\Group $groups
* #return User
*/
// public function addGroup(\Acme\UserBundle\Entity\Group $groups)
// {
// $this->groups[] = $groups;
// return $this;
// }
/**
* Remove groups
*
* #param \Acme\UserBundle\Entity\Group $groups
*/
// public function removeGroup(\Acme\UserBundle\Entity\Group $groups)
// {
// $this->groups->removeElement($groups);
// }
}
my whole Group.php
namespace Acme\UserBundle\Entity;
use FOS\UserBundle\Entity\Group as BaseGroup;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="fos_group")
*/
class Group extends BaseGroup
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
}

The problem is that you override getGroups() like this:
/**
* Get groups
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getGroups()
{
return $this->groups;
}
But you never initialized $this->groups, so when you call $this->getGroups()->contains(), it says that $this->getGroups() is a non-object, which is true!
To solve this you have 2 solutions.
You can initialize groups in the constructor of User:
public function __construct()
{
parent::__construct();
&this->groups = new ArrayCollection();
}
Or you can change the getGroups() function to return a new object if groups hasn't been initialized:
public function getGroups()
{
return $this->groups ?: $this->groups = new ArrayCollection();
}
In both cases, don't forget to add use Doctrine\Common\Collections\ArrayCollection; at the beginning of User.php.

I got the same error:
FatalErrorException: Compile Error: Declaration of Acme\UserBundle\Entity\User::addGroup() must be compatible with that of FOS\UserBundle\Model\GroupableInterface::addGroup()
After searching I found out (FOS Git Issue 988) that this special error is a Doctrine 'Bug' or sth.
Doctrine should not generate the method at all because there is an implementation of addGroup() in the base class.
I worked around it by modifying the signature from
public function addGroup(\MyBundleNamespace\Entity\Group $groups)
public function removeGroup(\MyBundleNamespace\Entity\Group $groups)
to
public function addGroup(\FOS\UserBundle\Model\GroupInterface $groups)
public function removeGroup(\FOS\UserBundle\Model\GroupInterface $groups)
This way you prevent Doctrine from generating these two functions with the wrong signature.
Version Infos:
Symfony 2.2.1
DoctrineORM 2.2.3
FOSUserBundle 1.3.1
PHP 5.4.4-14
Maybe this can help someone else!

I came across this problem and the simple solution was to change the declaration of the groups variable from public to protected:
/**
* #var \Doctrine\Common\Collections\Collection
*/
protected $groups;

solution is simply: remove all setters and getters from your own entity in your own userBundle and always run doctrine:generate:entities for specyfic bundle, not for all.

Related

2.nd Translation Entity with KnpLabs/DoctrineBehaviors not working

I am using symfony 3 and trying to get the categories translated with mysql db. That's why I am using KnpLabs/DoctrineBehaviors, which should be the best for symfony.
I have done all like it is discribed in the documentation.
Category:
/**
* MdCategories
*
* #ORM\Table(name="md_category")
* #ORM\Entity(repositoryClass="AppBundle\Repository\CategoryRepository")
*/
class Category implements ORMBehaviors\Tree\NodeInterface, \ArrayAccess
{
use ORMBehaviors\Translatable\Translatable,
ORMBehaviors\Sortable\Sortable,
ORMBehaviors\Tree\Node;
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="NONE")
*/
protected $id;
CategoryTranslation:
/**
* #ORM\Table(name="md_category_translation")
* #ORM\Entity
*/
class CategoryTranslation
{
use ORMBehaviors\Translatable\Translation;
/**
* #var name
* #ORM\Column(type="string", length=120, nullable=false)
*/
protected $name;
/**
* #var route
*
* #ORM\Column(type="string", length=150)
*/
protected $route;
/**
* #var metaKey
*
* #ORM\Column(type="string", length=255)
*/
protected $metaKey;
/**
* #var metaTitle
*
* #ORM\Column(type="string", length=100)
*/
protected $metaTitle;
/**
* #var metaDescription
*
* #ORM\Column(type="string", length=120)
*/
protected $metaDescription;
/**
* #return name
*/
public function getName()
{
return $this->name;
}
/**
* #param name $name
* #return Categories
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* #return route
*/
public function getRoute()
{
return $this->route;
}
/**
* #param route $route
* #return Categories
*/
public function setRoute($route)
{
$this->route = $route;
return $this;
}
/**
* #return metaKey
*/
public function getMetaKey()
{
return $this->metaKey;
}
/**
* #param metaKey $metaKey
* #return Categories
*/
public function setMetaKey($metaKey)
{
$this->metaKey = $metaKey;
return $this;
}
/**
* #return metaTitle
*/
public function getMetaTitle()
{
return $this->metaTitle;
}
/**
* #param metaTitle $metaTitle
* #return Categories
*/
public function setMetaTitle($metaTitle)
{
$this->metaTitle = $metaTitle;
return $this;
}
/**
* #return metaDescription
*/
public function getMetaDescription()
{
return $this->metaDescription;
}
public function setMetaDescription($metaDescription)
{
$this->metaDescription = $metaDescription;
return $this;
}
public function __call($method, $arguments)
{
return $this->proxyCurrentLocaleTranslation($method, $arguments);
}
}
CategoryRepository:
class CategoryRepository extends EntityRepository
{
use ORMBehaviors\Tree\Tree;
}
config.yml:
knp_doctrine_behaviors:
translatable: true
tree: true
sortable: true
# All others behaviors are disabled
and registering the bundle
new Knp\DoctrineBehaviors\Bundle\DoctrineBehaviorsBundle(),
The databases are generated correctly
But how do I fill the categoryTranslation Data with the right local ?
$category = new Category;
$category->setCurrentLocale('de');
$category->setId(1); // tree nodes need an id to construct path.
$category->setName('Foobar');
$em->persist($category);
$em->flush();
This did not work !
I am surprised, that there is no KNP Translationsubscriber is listened in the subcribed events of symfony
Here is the solution.
$category = new Category;
$category->translate('de')->setName('Foobar');
$em->persist($category);
$category->mergeNewTranslations();
$em->flush();

Authentication Symfony is not working

I am having a problem with symfony Authentication, the point is, when i create a user, i send a email notification with a token, then when the user click on the link is when his account is activate and i do some verification like the token is correct, the user exist the account is active etc, and after that is when i am having my problem, everything is correctly until the user click on his link i do every verification but when i logged the user automatically and redirect the user to homepage the browser say that is redirecting a lot return $this->redirect($this->generateUrl('homepage'));
well there is when i am having a problem, here is my code on the securityController in activateUserAction is when i am having the problem, i hope someone can helpme
User entity
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\AdvancedUserInterface;
use Symfony\Component\Validator\Constraints as Assert;
/**
* User
*
* #ORM\Table(name="user")
* #ORM\Entity(repositoryClass="AppBundle\Repository\UserRepository")
*/
class User implements AdvancedUserInterface,\Serializable
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="email", type="string", length=255, unique=true)
* #Assert\Email(
* message = "The email '{{ value }}' is not a valid email.",
* checkMX = true
* )
*
*/
private $email;
/**
* #var string
*
* #ORM\Column(name="password", type="string", length=255)
*/
private $password;
/**
* #Assert\NotBlank(groups={"register"})
* #Assert\Length(min = 6)
*/
private $passwordClear;
/**
* #var bool
*
* #ORM\Column(name="active", type="boolean")
*/
private $active;
/**
* #ORM\Column(type="json_array")
*/
private $roles = array();
/**
* #ORM\Column(name="expired", type="boolean")
*/
private $expired;
/**
* #ORM\OneToOne(targetEntity="AppBundle\Entity\Person",cascade={"persist"})
*/
private $person;
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set email
*
* #param string $email
*
* #return User
*/
public function setEmail($email)
{
$this->email = $email;
return $this;
}
/**
* Get email
*
* #return string
*/
public function getEmail()
{
return $this->email;
}
/**
* Set password
*
* #param string $password
*
* #return User
*/
public function setPassword($password)
{
$this->password = $password;
return $this;
}
/**
* Get password
*
* #return string
*/
public function getPassword()
{
return $this->password;
}
/**
* get PasswordClear
*/
public function getPasswordClear(){
return $this->passwordClear;
}
/**
* set passwordClear
*/
public function setPasswordClear($password){
$this->passwordClear=$password;
}
/**
* Set active
*
* #param boolean $active
*
* #return User
*/
public function setActive($active=0)
{
$this->active = $active;
return $this;
}
/**
* Get active
*
* #return bool
*/
public function getActive()
{
return $this->active;
}
/**
* Set roles
*
* #param array $roles
*
* #return User
*/
public function setRoles($roles)
{
$this->roles = $roles;
return $this;
}
/**
* Get roles
*
* #return array
*/
public function getRoles()
{
return array($this->roles);
}
/**
* Set person
*
* #param \AppBundle\Entity\Person $person
*
* #return User
*/
public function setPerson(\AppBundle\Entity\Person $person = null)
{
$this->person = $person;
return $this;
}
/**
* Get person
*
* #return \AppBundle\Entity\Person
*/
public function getPerson()
{
return $this->person;
}
public function getUsername()
{
return $this->getEmail();
}
public function eraseCredentials()
{
$this->passwordClear = null;
}
public function getSalt()
{
return null;
}
public function isAccountNonExpired(){
return true;
}
public function isAccountNonLocked(){
return true;
}
public function isCredentialsNonExpired(){
return true;
}
public function isEnabled(){
return $this->active;
}
/**
* Set expired
*
* #param boolean $expired
*
* #return User
*/
public function setExpired($expired)
{
$this->expired = $expired;
return $this;
}
/**
* Get expired
*
* #return boolean
*/
public function getExpired()
{
return $this->expired;
}
/** #see \Serializable::serialize() */
public function serialize()
{
return serialize(array(
$this->id,
$this->email,
$this->password,
));
}
/** #see \Serializable::unserialize() */
public function unserialize($serialized)
{
list (
$this->id,
$this->email,
$this->password,
) = unserialize($serialized);
}
}
Entity Person
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpKernel\Kernel;
/**
* Person
*
* #ORM\Table(name="person")
* #ORM\Entity(repositoryClass="AppBundle\Repository\PersonRepository")
*/
class Person
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* #var string
*
* #ORM\Column(name="last_name", type="string", length=255)
*/
private $lastName;
/**
* #var string
* #ORM\Column(name="desccription",type="string", length=255,nullable=true)
*/
private $description;
/**
* #var string
*
* #ORM\Column(name="token", type="string", length=255)
*/
private $token;
/**
* #var string
*
* #ORM\Column(name="url_validate", type="string", length=255)
*/
private $urlValidate;
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Person
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set lastName
*
* #param string $lastName
*
* #return Person
*/
public function setLastName($lastName)
{
$this->lastName = $lastName;
return $this;
}
/**
* Get lastName
*
* #return string
*/
public function getLastName()
{
return $this->lastName;
}
/**
* Set token
*
* #param string $token
*
* #return Person
*/
public function setToken($token)
{
$this->token = $token;
return $this;
}
/**
* Get token
*
* #return string
*/
public function getToken()
{
return $this->token;
}
/**
* Set urlValidate
*
* #param string $urlValidate
*
* #return Person
*/
public function setUrlValidate($urlValidate)
{
$this->urlValidate = $urlValidate;
return $this;
}
/**
* Get urlValidate
*
* #return string
*/
public function getUrlValidate()
{
return $this->urlValidate;
}
}
My securityController
<?php
namespace AppBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security
\Core\Authentication\Token \UsernamePasswordToken;
use Symfony\Component\Security
\Http\Firewall\ListenerInterface;
use Symfony\Component\Security
\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security
\Core\Authentication\Token\Storage\TokenStorageInterface;
use AppBundle\Form\RegisterType;
use AppBundle\Entity\Register;
class SecurityController extends Controller
{
/**
* #Route("/login", name="user_login")
*/
public function loginAction()
{
if($this->getUser()){
return $this->redirectToRoute('homepage');
}
$authUtils = $this->get('security.authentication_utils');
return $this->render('front/homepage/_singin.html.twig', array(
'last_username' => $authUtils->getLastUsername(),
'error' => $authUtils->getLastAuthenticationError(),
));
}
/**
* #Route("/login_check", name="user_login_check")
*/
public function loginCheckAction()
{
}
/**
* #Route("/logout", name="user_logout")
*/
public function logoutAction()
{
}
/**
*
* #Route("/token/{token}", name="activate")
*/
public function activateUserAction($token){
$em = $this->getDoctrine()->getManager();
$person=$em->getRepository('AppBundle:Person')->findByToken($token);
if($person){
$user=$em->getRepository('AppBundle:User')->findByPerson($person);
$user->setActive(1);
$person->setToken("");
$person->setActivatedDate(new \DateTime());
$em->persist($person);
$em->flush();
$em->persist($user);
$em->flush();
$token= new UsernamePasswordToken(
$user,
$user->getPassword(),
'user',
$user->getRoles()
);
$this->get('security.token_storage')->setToken($token);
return $this->redirect($this->generateUrl('homepage'));
}else{
return $this->redirect($this->generateUrl('caducada'));
}
}

Symfony create User with language from entity

I try to setup a User creation form with FOS. There, the user specific language can be choosed from a databasetable (language).
This seems to be a bad solution for the reason that there must be an other way to give a choice between four langueges on user creation form. I'm stucking, and as far as I understand the documentation, theyre talking about website translation (this is my second step but first I need to setup the users).
Is there a nice way to get hteese languages into the creation form: actually, my form looks like
$form = $this->createFormBuilder($user)
->add('firstname', 'text')
->add('usergroups', 'entity', array('class' => 'PrUserBundle:Group','property' => 'name'))
->add('language', 'entity', array('class' => 'PrUserBundle:Language','property' => 'name'))
->add('lastname', 'text')
->add('username', 'text')
->add('email', 'text')
->add('phone', 'text')
->add('password', 'password')
->add('save', 'submit')
->getForm();
It works but with
->add('language', 'entity', array('class' => 'PrUserBundle:Language','property' => 'name'))
Cause an error for the reason that there are no getters and setters for it in my user class.
My User Class also don't know about the language so adding getters will bring no result :(
My User Entity:
<?php
// src/Pr/UserBundle/Entity/User.php
namespace Pr\UserBundle\Entity;
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="sys_user")
*/
class User extends BaseUser
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="integer", nullable=true)
*/
protected $client;
/**
* #ORM\Column(type="integer", nullable=true)
*/
private $client_id;
/**
* #ORM\Column(type="string", length=16383, nullable=true) //16383 = max varchar utf8
*/
private $imageurl;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
protected $firstname;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
protected $lastname;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $phone;
/**
* #ORM\Column(type="integer", nullable = true)
*/
protected $lock_state;
/**
* #ORM\Column(type="array", nullable=true)
*/
private $locations;
/**
* #ORM\Column(type="array", nullable=true)
*/
private $usergroups;
/**
* #ORM\Column(type="string", length=5, options={"fixed" = true, "default" = "de_DE"})
*/
private $locale = 'de_DE';
/**
* #ORM\Column(type="string", length=32)
*/
private $timezone = 'UTC';
/**
* #ORM\Column(type="array", nullable=true)
*/
private $created='1';
//do not persist!
protected $plain_password;
public function __construct()
{
parent::__construct();
// your own logic
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set client
*
* #param integer $client
* #return User
*/
public function setClient($client)
{
$this->client = $client;
return $this;
}
/**
* Get client
*
* #return integer
*/
public function getClient()
{
return $this->client;
}
/**
* Set client_id
*
* #param integer $clientId
* #return User
*/
public function setClientId($clientId)
{
$this->client_id = $clientId;
return $this;
}
/**
* Get client_id
*
* #return integer
*/
public function getClientId()
{
return $this->client_id;
}
/**
* Set imageurl
*
* #param string $imageurl
* #return User
*/
public function setImageurl($imageurl)
{
$this->imageurl = $imageurl;
return $this;
}
/**
* Get imageurl
*
* #return string
*/
public function getImageurl()
{
return $this->imageurl;
}
/**
* Set firstname
*
* #param string $firstname
* #return User
*/
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 User
*/
public function setLastname($lastname)
{
$this->lastname = $lastname;
return $this;
}
/**
* Get lastname
*
* #return string
*/
public function getLastname()
{
return $this->lastname;
}
/**
* Set phone
*
* #param string $phone
* #return User
*/
public function setPhone($phone)
{
$this->phone = $phone;
return $this;
}
/**
* Get phone
*
* #return string
*/
public function getPhone()
{
return $this->phone;
}
/**
* Set lock_state
*
* #param integer $lockState
* #return User
*/
public function setLockState($lockState)
{
$this->lock_state = $lockState;
return $this;
}
/**
* Get lock_state
*
* #return integer
*/
public function getLockState()
{
return $this->lock_state;
}
/**
* Set locations
*
* #param array $locations
* #return User
*/
public function setLocations($locations)
{
$this->locations = $locations;
return $this;
}
/**
* Get locations
*
* #return array
*/
public function getLanguage()
{
return $this->language;
}
/**
* Get locations
*
* #return array
*/
public function getLocations()
{
return $this->locations;
}
/**
* Set usergroups
*
* #param array $usergroups
* #return User
*/
public function setUsergroups($usergroups)
{
$this->usergroups = $usergroups;
return $this;
}
/**
* Get usergroups
*
* #return array
*/
public function getUsergroups()
{
return $this->usergroups;
}
/**
* Set locale
*
* #param string $locale
* #return User
*/
public function setLocale($locale)
{
$this->locale = $locale;
return $this;
}
/**
* Get locale
*
* #return string
*/
public function getLocale()
{
return $this->locale;
}
/**
* Set timezone
*
* #param string $timezone
* #return User
*/
public function setTimezone($timezone)
{
$this->timezone = $timezone;
return $this;
}
/**
* Get timezone
*
* #return string
*/
public function getTimezone()
{
return $this->timezone;
}
/**
* Set created
*
* #param array $created
* #return User
*/
public function setCreated($created)
{
$this->created = $created;
return $this;
}
/**
* Get created
*
* #return array
*/
public function getCreated()
{
return $this->created;
}
}

Knp DoctrineBehaviors throws ContextErrorException: Warning: call_user_func_array() expects parameter 1 to be a valid callback

I use symfony2.4 and KNP doctrine behaviors translatable.
I have entity Site (for ID, host, enabled) and entity SiteTranslation (for translated fields: name, descriptions, …).
I use query to get results
$qb = $this->createQueryBuilder('s')
->addSelect('translation') // to eager fetch translations (optional)
->leftJoin('s.translations', 'translation') // or innerJoin ?
->orderBy('s.root', 'ASC')
->addOrderBy('s.lft', 'ASC');
I would like to print result in Twig. For ID, host and enabled fields from Site entity it's easy:
{{ item.id }}
But I can't print translated fields (name, description, …)
{{ item.name }}
It doesn't work.
Error message:
ContextErrorException: Warning: call_user_func_array() expects parameter 1 to be a valid >callback, class 'Net\ConBundle\Entity\SiteTranslation' does not have a method 'name' in >D:\Users...\vendor\knplabs\doctrine->behaviors\src\Knp\DoctrineBehaviors\Model\Translatable\TranslatableMethods.php line 140
Getters and setters for translatable fields are in SiteTranslation entity.
UPDATE:
I still didn't find a solution for an error.
Here is Site entity:
<?php
namespace Pnet\ConlocoBundle\Entity;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Gedmo\Mapping\Annotation as Gedmo;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Knp\DoctrineBehaviors\Model as ORMBehaviors;
/**
* #UniqueEntity("host", message="site.host.unique", groups={"edit"})
* #Gedmo\Tree(type="nested")
* #ORM\Entity(repositoryClass="Pnet\ConlocoBundle\Entity\Repository\SiteRepository")
* #ORM\Table(name="site")
*/
class Site
{
use ORMBehaviors\Translatable\Translatable; // knp translatable strategy
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", length=40, unique=true)
* #Assert\NotBlank(message="site.host.notBlank", groups={"edit"})
* #Assert\Length(max = "40", maxMessage = "site.host.maxLength", groups={"edit"})
*/
protected $host;
/**
* #ORM\Column(type="string", length=255, nullable=true)
* #Assert\Image()
*/
protected $image;
/**
* #ORM\Column(type="boolean")
* #Assert\Choice(choices = {"1", "0"}, message = "site.isDefault.choice", groups={"edit"})
*/
protected $isDefault;
/**
* #ORM\Column(type="boolean")
* #Assert\Choice(choices = {"1", "0"}, message = "site.enabled.choice", groups={"edit"})
*/
protected $enabled;
/**
* #ORM\Column(type="string", length=64, nullable=true)
*/
protected $analytics;
/**
* #Gedmo\TreeLeft
* #ORM\Column(name="lft", type="integer")
*/
private $lft;
/**
* #Gedmo\TreeLevel
* #ORM\Column(name="lvl", type="integer")
*/
private $lvl;
/**
* #Gedmo\TreeRight
* #ORM\Column(name="rgt", type="integer")
*/
private $rgt;
/**
* #Gedmo\TreeRoot
* #ORM\Column(name="root", type="integer", nullable=true)
*/
private $root;
/**
* #Gedmo\TreeParent
* #ORM\ManyToOne(targetEntity="Site", inversedBy="children")
* #ORM\JoinColumn(name="parent_id", referencedColumnName="id", onDelete="CASCADE")
*/
private $parent;
/**
* #ORM\OneToMany(targetEntity="Site", mappedBy="parent")
* #ORM\OrderBy({"lft" = "ASC"})
*/
private $children;
private $file;
public $idByFilter;
public $nameByFilter;
/**
* Proxy translations (Knp/Doctrine Behaviors)
* An extra feature allows you to proxy translated fields of a translatable entity.
* You can use it in the magic __call method of you translatable entity so that when
* you try to call getName (for example) it will return you the translated value
* of the name for current locale:
*/
public function __call($method, $arguments)
{
return $this->proxyCurrentLocaleTranslation($method, $arguments);
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set host
*
* #param string $host
* #return Site
*/
public function setHost($host)
{
$this->host = $host;
return $this;
}
/**
* Get host
*
* #return string
*/
public function getHost()
{
return $this->host;
}
/**
* Set isDefault
*
* #param boolean $isDefault
* #return Site
*/
public function setIsDefault($isDefault)
{
$this->isDefault = $isDefault;
return $this;
}
/**
* Get isDefault
*
* #return boolean
*/
public function getIsDefault()
{
return $this->isDefault;
}
/**
* Set enabled
*
* #param boolean $enabled
* #return Site
*/
public function setEnabled($enabled)
{
$this->enabled = $enabled;
return $this;
}
/**
* Get enabled
*
* #return boolean
*/
public function getEnabled()
{
return $this->enabled;
}
/**
* Set analytics
*
* #param string $analytics
* #return Site
*/
public function setAnalytics($analytics)
{
$this->analytics = $analytics;
return $this;
}
/**
* Get analytics
*
* #return string
*/
public function getAnalytics()
{
return $this->analytics;
}
/**
* Get ID from Filter
*
* #return string
*/
public function getIdByFilter()
{
return $this->idByFilter;
}
/**
* Get name from Filter
*
* #return string
*/
public function getNameByFilter()
{
return $this->nameByFilter;
}
/**
* Set image
*
* #param string $image
* #return Site
*/
public function setImage($image)
{
$this->image = $image;
return $this;
}
/**
* Get image
*
* #return string
*/
public function getImage()
{
return $this->image;
}
/**
* Set file
*
* #param string $file
* #return Site
*/
public function setFile($file)
{
$this->file = $file;
return $this;
}
/**
* Get file
*
* #return string
*/
public function getFile()
{
return $this->file;
}
/**
*
* Tree functions
*/
public function setParent(Site $parent = null)
{
$this->parent = $parent;
}
public function getParent()
{
return $this->parent;
}
public function getRoot()
{
return $this->root;
}
public function getLvl()
{
return $this->lvl;
}
public function getChildren()
{
return $this->children;
}
public function getLft()
{
return $this->lft;
}
public function getRgt()
{
return $this->rgt;
}
/**
* Add a method to the entity class that shows the name indented by nesting level
*/
public function getLeveledName()
{
return str_repeat(
html_entity_decode(' ', ENT_QUOTES, 'UTF-8'),
($this->getLvl()) * 3
) . $this->getName();
}
public function getLeveledPosition()
{
return str_repeat(
html_entity_decode(' ', ENT_QUOTES, 'UTF-8'),
($this->getLvl()) * 3
);
}
}
And here is SiteTranslation entity:
namespace Pnet\ConlocoBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\ORM\Mapping as ORM;
use Knp\DoctrineBehaviors\Model as ORMBehaviors;
/**
* #ORM\Entity
*/
class SiteTranslation
{
use ORMBehaviors\Translatable\Translation;
/**
* #ORM\Column(type="string", length=60)
* #Assert\NotBlank(message="site.name.notBlank", groups={"edit"})
* #Assert\Length(max = "60", maxMessage = "site.name.maxLength", groups={"edit"})
*/
protected $name;
/**
* #ORM\Column(type="string", length=100)
* #Assert\NotBlank(message="site.title.notBlank", groups={"edit"})
* #Assert\Length(max = "100", maxMessage = "site.title.maxLength", groups={"edit"})
*/
protected $title;
/**
* #ORM\Column(type="string", length=200)
* #Assert\NotBlank(message="site.longTitle.notBlank", groups={"edit"})
* #Assert\Length(max = "200", maxMessage = "site.longTitle.maxLength", groups={"edit"})
*/
protected $longTitle;
/**
* #ORM\Column(type="string", length=250, nullable=true)
* #Assert\Length(max = "250", maxMessage = "site.keywords.maxLength", groups={"edit"})
*/
protected $keywords;
/**
* #ORM\Column(type="string", length=500, nullable=true)
* #Assert\Length(max = "500", maxMessage = "site.description.maxLength", groups={"edit"})
*/
protected $description;
/**
* Set name
*
* #param string $name
* #return Site
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set title
*
* #param string $title
* #return Site
*/
public function setTitle($title)
{
$this->title = $title;
return $this;
}
/**
* Get title
*
* #return string
*/
public function getTitle()
{
return $this->title;
}
/**
* Set longTitle
*
* #param string $longTitle
* #return Site
*/
public function setLongTitle($longTitle)
{
$this->longTitle = $longTitle;
return $this;
}
/**
* Get longTitle
*
* #return string
*/
public function getLongTitle()
{
return $this->longTitle;
}
/**
* Set keywords
*
* #param string $keywords
* #return Site
*/
public function setKeywords($keywords)
{
$this->keywords = $keywords;
return $this;
}
/**
* Get keywords
*
* #return string
*/
public function getKeywords()
{
return $this->keywords;
}
/**
* Set description
*
* #param string $description
* #return Site
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Get description
*
* #return string
*/
public function getDescription()
{
return $this->description;
}
}
TL;DR:
As a (probably dirty) workaround, use
{{ item.getName }}
in your Twig template instead of
{{ item.name }}
Explanation:
I came across the same issue and i think that this should be considered a bug in the Knp DoctrineBehaviors documentation when used with Twig. When you call this in your Twig template :
{{ item.name }}
This is what Twig does behind the scenes to get the name property :
try to get the name public property of the item object
if not found, checks for the name public method of the item object
if not found, checks for the getName() public method of the "item" object
if not found, checks for the __call() magic method (and calls it with the name parameter)
The problem here is step 4. The magic __call() method that you defined (as recommended by the official DoctrineBehaviors documentation) is called with the name parameter instead of getName. It then calls the proxyCurrentLocaleTranslation() method who tries to call the name public method of your translation class. Of course, it doesn't exist because you only have a getName() method.
See this issue in Twig : https://github.com/twigphp/Twig/issues/342
By using directly the {{ item.getName }} code in Twig, the proper method name will be called.
This work for me:
public function __call($method, $arguments)
{
try {
return $this->proxyCurrentLocaleTranslation($method, $arguments);
} catch (\Symfony\Component\Debug\Exception\ContextErrorException $e) {
return $this->proxyCurrentLocaleTranslation('get' . ucfirst($method), $arguments);
}
}
You haven't moved all the translatable properties/methods to your <Name>Translation class.
The exception clearly states that there is no name/getName method in your SiteTranslation class.
Please read my answer over here to see how Knp\DoctrineBehaviors's magic translation proxy is used correctly.

Form nulling a value when user doesn't supply it

Reading this page I've setup a form to handle PATCH requests.
I've a Player entity:
<?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;
}
}
and a Team entity:
<?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;
}
}
Now I've created a player form class with the app/console and I've edited the team field to be an instance of the Team entity, this way:
<?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';
}
}
And this is the relevant part of my controller:
/**
* 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');
}
The player creation works fine, the player edit doesn't, when the user makes the api request, passing the ID in the url and the name of the player I get:
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
Seems that form is trying to set the Team to null, why?
I've tried both sending and not the team as the form parameters but it doesn't work.
Any clue?
Figured it out that null unsent fields in the form is the default behaviour in symfony.
There is an open request for partial form binding in symfony. For now a guy created a form event subscriber that adds the missing fields with the actual values:
https://gist.github.com/3720535
This is his code:
<?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',
);
}
}
to add to the form you have to edit the buildForm method in the form class:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$subscriber = new PatchSubscriber();
$builder->addEventSubscriber($subscriber);
$builder->add('name');
....
}
In this case you're ok to use the PATCH REST requests to edit an entity just by the sent fields

Resources