I have created a form that appears to be correct, it has a few text fields and a select box with a list of countries pulled from a table of countries I have. The select box displays correctly using the the correct values for it's 'value' and display text. When I submit the form however I get an exception:
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'countryid' cannot be null
If I set the database table (in PHPMyAdmin) to allow a null value for the countryid field it enters the record with no exception but the entry for the countryid is null.
my controller has the following code:
$duck = new \Wfuk\DuckBundle\Entity\Ducks();
$form = $this->createFormBuilder($duck)
->add('city', 'text')
->add('countryid', 'entity', array('class' => 'WfukDuckBundle:Country', 'property' => 'country'))
// cut other fields
->getForm();
if ($request->getMethod() == 'POST') {
$form->bindRequest($request);
$errors = $this->get('validator')->validate( $form );
echo $duck->getCountryid();
if ($form->isValid()) {
$em = $this->getDoctrine()->getEntityManager();
$em->persist($duck);
$em->flush();
return $this->redirect($this->generateUrl('upload_duck_success'));
}
the echo in there returns the __toString function of the country object which seems a bit odd - but it is the full country info for the country chosen in the form.
in the Ducks.php class:
/**
* #var string $countryid
*
* #ORM\Column(name="countryid", type="string", length=2, nullable=false)
*/
private $countryid;
/**
* Set countryid
*
* #param string $countryId
*/
public function setCountryid($countryid)
{
$this->countryid = $countryid;
}
/**
* Get countryid
*
* #return string
*/
public function getCountryid()
{
return $this->countryid;
}
This is my first symfony project, but I've been over the docs several times and think I have everything set up ok...
edit:
I have a join set up as follows:
Ducks.php
/**
* #ORM\ManyToOne(targetEntity="Country", inversedBy="ducks")
* #ORM\JoinColumn(name="countryid", referencedColumnName="id")
*/
private $country;
/**
* Set country
*
* #param string $country
*/
public function setCountry($country)
{
$this->country = $country;
}
/**
* Get country
*
* #return string
*/
public function getCountry()
{
return $this->country;
}
and on the Country.php side:
/**
* #ORM\OneToMany(targetEntity="Ducks", mappedBy="country")
*/
protected $ducks;
public function __construct()
{
$this->ducks = new ArrayCollection();
}
/**
* Get ducks
*
* #return Doctrine\Common\Collections\Collection
*/
public function getDucks()
{
return $this->ducks;
}
What's happening is that the form is sending an actual Country object to ducks. You can confirm this with:
public function setCountryid($countryid)
{
if (is_object($countryid)) die('Yep, got a country object.');
$this->countryid = $countryid;
}
It sounds like you only want to store a 2 char country code? You don't want an actual relation? If so then this might do the trick:
public function setCountryid($countryid)
{
if (is_object($countryid)) $countryid = $countryid->getId();
$this->countryid = $countryid;
}
If you want an actual normal Doctrine managed relation between duck and country then something like:
/**
* #ORM\ManyToOne(targetEntity="Country")
* #ORM\JoinColumn(name="country_id", referencedColumnName="id")
*/
*/
private $country;
And adjust your getter/setters accordingly.
It's a bit strange that you seem to have both yml and annotations. From what I understood, you could use one or the other in a given bundle.
Related
I am trying to pass id from one country table to user table but I can't' pass this error..
Expected value of type "ProjectBundle\Base\Entity\Country" for association field "ProjectBundle\Base\Entity\User#$country", got "string" instead.
My User entity class
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
* #Groups({"user_data"})
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Country")
* #JoinColumn(name="country_id", referencedColumnName="id")
*/
private $country;
/**
* #return mixed
*/
public function getCountry()
{
return $this->country;
}
/**
* #param mixed $country
*/
public function setCountry($country)
{
$this->country = $country;
}
My User service
public function registerUser($country)
{
$user = new User();
$user->setCountry($country);
$this->em->persist($user);
$this->em->flush();
return $user;
}
My User controller
public function registerUserAction()
{
$this->requirePostParams(['country_id']);
$country = $this->data['country_id'];
$user = $this->get('member')->registerUser($country);
return $this->success($user);
}
So I am passing country_id value via postman and I get this error.
The Problem is that
$user->setCountry()
expects an instance of your Country Entity. But you try to call it with the ID of an country. Doctrine does not fetch the Entity from the given ID.
To fix this you have two options:
fetch the country with the ID
in your user Service:
public function registerUser($countryId)
{
$country = $this->countryRepository->findById($countryId);
$user = new User();
$user->setCountry($country);
$this->em->persist($user);
$this->em->flush();
return $user;
}
You have to add the CountryRepository as a dependency in your service.
add the countryID column as a attribute to your UserEntity
in your User entity:
/**
* #var int
*
* #ORM\Column(name="country_id", type="integer", nullable=true)
*/
protected $countryId;
/**
* #return int
*/
public function getCountryId()
{
return $this->countryId;
}
/**
* #param int $countryId
*/
public function setCountryId($countryId)
{
$this->countryId = $countryId;
}
in your user Service:
public function registerUser($countryId)
{
$user = new User();
$user->setCountryId($countryId);
$this->em->persist($user);
$this->em->flush();
return $user;
}
$user->setCountry($country);
You have defined country as class in your USER entity so you need to assign country as object instead of string. You are passing string instead of country object.
Pass the country object in registerUser($country) instead of string, it won't give this exception.
i have two entities Survey.php and Choice.php I create a form to add new survey and multi choices, I used a many-to-one relation between the two entities, the problem is when I submit for a new survey, the foreign key of choice entity return null
here's my code
Survey.PHP
/**
* Survey
*
* #ORM\Table(name="survey")
* #ORM\Entity(repositoryClass="AppBundle\Repository\SurveyRepository")
*/
class Survey
{
/....
/**
* #var ArrayCollection
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Choice", mappedBy="survey_id",cascade="persist")
* #ORM\JoinColumn(nullable=false, referencedColumnName="id")
*/
private $choice;
public function __construct()
{
$this->choice = new ArrayCollection();
}
/**
* Add choice
*
* #param \AppBundle\Entity\Choice $choice
*
* #return Survey
*/
public function addChoice(\AppBundle\Entity\Choice $choice)
{
$this->choice[] = $choice;
$choice->setSurvey($this);
return $this;
}
/**
* Remove choice
*
* #param \AppBundle\Entity\Choice $choice
*/
public function removeChoice(\AppBundle\Entity\Choice $choice)
{
$this->choice->removeElement($choice);
}
/**
* Get choice
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getChoice()
{
return $this->choice;
}
}
Choice.php
/**
* Choice
* #ORM\Table(name="choice")
* #ORM\Entity(repositoryClass="AppBundle\Repository\ChoiceRepository")
*/
class Choice
{
/**
* #var int
* #ORM\ManyToOne(targetEntity="Survey",inversedBy="choice")
*/
private $survey;
/**
* Set survey
*
* #param \AppBundle\Entity\Survey $survey
*
* #return Choice
*/
public function setSurveyId(\AppBundle\Entity\Survey $survey)
{
$this->survey = $survey;
return $this;
}
/**
* Get surveyId
*
* #return \AppBundle\Entity\Survey
*/
public function getSurveyId()
{
return $this->survey_id;
}
}
SurveyController.php
<?php
namespace AppBundle\Controller;
use AppBundle\Entity\Survey;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
/**
* Survey controller.
*
*/
class SurveyController extends Controller
{
/**
* Creates a new survey entity.
* #param Request $request
* #return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
*/
public function newAction(Request $request)
{
$survey = new Survey();
//
$form = $this->createForm('AppBundle\Form\SurveyType', $survey);
$form->handleRequest($request);
$survey->setCreateDate(new \DateTime('NOW'));
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($survey);
$em->flush();
return $this->redirectToRoute('survey_show', ['id' => $survey->getId()]);
}
return $this->render('survey/new.html.twig', [
'survey' => $survey,
'form' => $form->createView(),
]);
}
any suggestion, btw I think the problem is in the getters and setters )
Link the choice to the survey:
// Survey
public function addChoice(\AppBundle\Entity\Choice $choice)
{
$this->choice[] = $choice;
$choice->setSurvey($this);
return $this;
}
And change the survey_id stuff to survey. Dealing with objects not ids. And of course Survey::choice should be Survey::choices. The name changes might seem minor but will make your easier to maintain.
I fixed the problem by adding a for each loop inside the SurveyController.php
and it works just fine
SurveyController.php
if ($form->isSubmitted() && $form->isValid())
{
foreach ($survey->getChoices() as $choice){
$choice->setSurvey($survey);
}
$em = $this->getDoctrine()->getManager();
$em->persist($survey);
$em->flush();
not "THE best solution" but it gets the job done
It worked with me, but I had to remove the explicit foreign key mapping with the "inversedBy" setting from the class definition. I use a composite foreign key (using two columns), which maybe makes things harder though...
I'm working on a members import batch (with insertions and updates) for a big project with a lot of entities such as Member, Client, Group, ....
After reading the chapter related to bulk imports in Doctrine doc, I've implemented this code :
$batchSize = 20;
$i = 0;
foreach ($entities as $entity)
{
$this->getEntityManager()->persist($entity);
if (($i % $batchSize) === 0)
{
$this->getEntityManager()->flush();
$this->getEntityManager()->clear();
}
}
$this->getEntityManager()->flush();
$this->getEntityManager()->clear();
Now, when I want to bulk handle an array of Member entities, Doctrine try to insert null data into a completely other table related to the Group entity and an exception is thrown An exception occurred while executing 'INSERT INTO groups ...
There are not any relations between Member and Group ...
Any idea about this weird behavior ?
EDIT
Short mapping details :
/**
* #ORM\Entity
* #ORM\Table(name="members")
*/
class Member
{
// some properties ...
/**
* #ORM\ManyToOne(targetEntity="Client", inversedBy="members", cascade={"persist", "merge"})
* #ORM\JoinColumn(name="client_id", referencedColumnName="id", onDelete="CASCADE")
*/
protected $client;
/**
* #return Client
*/
public function getClient()
{
return $this->client;
}
/**
* #param Client $client
*
* #return $this
*/
public function setClient(Client $client)
{
$this->client = $client;
return $this;
}
}
/**
* #ORM\Entity
* #ORM\Table(name="clients")
*/
class Client
{
/**
* #ORM\OneToMany(targetEntity="Member", mappedBy="client", cascade={"persist", "remove", "merge"}, fetch="EXTRA_LAZY")
*/
protected $members;
/**
* #ORM\ManyToOne(targetEntity="Group", inversedBy="clients", cascade={"persist", "merge"})
* #ORM\JoinColumn(name="clients_id", referencedColumnName="id", onDelete="SET NULL")
*/
protected $group;
public function __construct()
{
$this->members = new ArrayCollection();
}
/**
* #return ArrayCollection
*/
public function getMembers()
{
return $this->members;
}
/**
* #param $members
*
* #return $this
*/
public function setMembers($members)
{
$this->members = new ArrayCollection();
return $this->addMembers($members);
}
/**
* #param $members
*
* #return $this
*/
public function addMembers($members)
{
foreach ($members as $member)
{
$this->addMember($member);
}
return $this;
}
/**
* #param Member $member
*
* #return $this
*/
public function addMember(Member $member)
{
$this->members->add($member);
$member->setClient($this);
return $this;
}
/**
* #param Member $member
*
* #return $this
*/
public function removeMember(Member $member)
{
if ($this->members->contains($member))
{
$this->members->removeElement($member);
}
return $this;
}
/**
* #param $members
*
* #return $this
*/
public function removeMembers($members)
{
foreach ($members as $member)
{
$this->removeMember($member);
}
return $this;
}
/**
* #param Group $group
*
* #return $this
*/
public function setGroup(Group $group = null)
{
$this->group = $group;
return $this;
}
/**
* #return Group
*/
public function getGroup()
{
return $this->group;
}
}
/**
* #ORM\Entity
* #ORM\Table(name="groups")
*/
class Group
{
/**
* #ORM\OneToMany(targetEntity="Client", mappedBy="group")
*/
protected $clients;
public function __construct()
{
$this->clients = new ArrayCollection();
}
/**
* #return ArrayCollection
*/
public function getClients()
{
return $this->clients;
}
/**
* #param $clients
*
* #return $this
*/
public function setClients($clients)
{
$this->clients = new ArrayCollection();
return $this->addClients($clients);
}
/**
* #param $clients
*
* #return $this
*/
public function addClients($clients)
{
foreach ($clients as $client)
{
$this->addClient($client);
}
return $this;
}
/**
* #param Client $client
*
* #return $this
*/
public function addClient(Client $client)
{
if (!$this->clients->contains($client))
{
$this->clients->add($client);
$client->setGroup($this);
}
return $this;
}
/**
* #param $clients
*
* #return $this
*/
public function removeClients($clients)
{
foreach ($clients as $client)
{
$this->removeClient($client);
}
return $this;
}
/**
* #param Client $client
*
* #return $this
*/
public function removeClient(Client $client)
{
if ($this->clients->contains($client))
{
$this->clients->removeElement($client);
$client->setGroup(null);
}
return $this;
}
}
And the error is type of :
An exception occurred while executing 'INSERT INTO groups ... SQLSTATE[23502]: Not null violation: 7 ERROR: null value in column "label" violates not-null constraint
DETAIL: Failing row contains (60, null, f, null, f, null, null).
EDIT2
This is the table creation description (using postgresql) :
CREATE TABLE groups (
id integer NOT NULL,
tempref character varying(255) DEFAULT NULL::character varying,
prorated_basis boolean NOT NULL,
fixed_price_amount double precision,
is_indexed boolean,
pricing_grid pricing[],
label character varying(255) NOT NULL
);
CREATE SEQUENCE groups
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE groups_id_seq OWNED BY groups.id;
ALTER TABLE ONLY pricing_groups ALTER COLUMN id SET DEFAULT nextval('groups_id_seq'::regclass);
ALTER TABLE ONLY groups
ADD CONSTRAINT groups_pkey PRIMARY KEY (id);
I can describe what is causing the error, but only guess why it is caused and give some hints on what to look for when debuging this.
As you described, you are updating members, that are part of a client, that in turn is part of a group. As you specified on the relations by cascade=persist, clients and groups are saved as well when persisting a member. That means, groups are either updated or created when inserting members. In your case, you are creating a new group by this mechanism. Yet this group does not have the label property set, resulting in a NULL value in the database, which is not allowed by the scheme.
As you said, this error is already occuring during the best batch. One of the first 20 members you update implicity creates a new group with no label. To find out which one it is I'd suggest using a debugger and inspecet each member before persistence to see what the group of this member is part of, and if it exists in the database. If it does not exist (by ID), you should investigate why this group does not the required label set.
If all groups actually do exist in the database already, things do get a bit more tricky and this depends on how the members you are updating are loaded. Are they fetched from the EntityManager (managed state) or are they loaded from some different source (e.g. serialized) and hence in a unmanaged state? If they are unmanaged, they will become manage upon peristence, and by specification of the relation cascade=merge, client, and group, will become managed as well. There is an important thing to know here though, merge will return a new (managed) entity which is then persisted (see the accepted answer here). As this is a new object, there might be the chance that this object is not fully initialized and can contain undefined values (which then would translate to NULL).
So when loading the member data from a different source than the EntityManager, you might have to connect them with the EntityManager first to avoid this problem.
Debugging the last one is quite difficult and you'd need to step into the UnitOfWork->doPersist method to see how each individual entity is actual persisted.
A simple problem that has many answers on SO... Yet none of them work on my project... So I get this error:
ContextErrorException: Catchable Fatal Error: Argument 1 passed to Doctrine\Common\Collections\ArrayCollection::__construct() must be of the type array, object given, called in C:\wamp\www\Dig\front\vendor\doctrine\orm\lib\Doctrine\ORM\UnitOfWork.php on line 528 and defined in C:\wamp\www\Digidis\front\vendor\doctrine\collections\lib\Doctrine\Common\Collections\ArrayCollection.php line 48
This happens everytime I create a new Email and try to save it in the database. The email is in a relationship with skin..
This is how I try to save it:
/**
* #Route("/{skin_id}/new", name="cms_email_new")
* #Method({"GET"})
* #Template()
*/
public function newAction($skin_id) {
$skin = $this->getRepository('ProjectSkinBundle:Skin')->find($skin_id);
$item = new Email();
$form = $this->createForm(new EmailType($this->container->getParameter("langs")), $item);
return array('form' => $form->createView(), 'item' => $item, 'skin' => $skin_id);
}
/**
* #Route("/{skin_id}/save", name="cms_email_save")
* #Template("ProjectUserBundle:EmailAdmin:new.html.twig")
* #Method({"POST"})
*/
public function saveAction(Request $request, $skin_id) {
$skin = $this->getRepository('ProjectSkinBundle:Skin')->find($skin_id);
$item = new Email();
$type = new EmailType($this->container->getParameter("langs"));
$form = $this->createForm($type, $item);
$form->handleRequest($request);
$em = $this->getEntityManager();
if ($form->isValid()) {
$this->upload($form, $item);
$skin->setEmailId($item);
$item->setSkin($skin); /// the error is here
$em->persist($skin);
$em->persist($item);
$em->flush();
return $this->redirect($this->generateUrl('cms_skin_email_edit', array('skin_id' => $skin_id)));
}
return array('form' => $form->createView(), 'item' => $item);
}
So by doing some testing I found out that this line is causing the problem:
$item->setSkin($skin);
Without this line everything works like a charm. However I need this line to work.
So this is the Entity with the setSkin method:
/**
*
* #ORM\OneToMany(targetEntity="Project\SkinBundle\Entity\Skin", mappedBy="email_id")
* #ORM\JoinColumn(name="skin", referencedColumnName="id")
*/
protected $skin;
/**
* Set skin
*
* #param \Project\SkinBundle\Entity\Skin $skin
* #return Email
*/
public function setSkin(\Project\SkinBundle\Entity\Skin $skin = null)
{
$this->skin = $skin;
return $this;
}
/**
* Get skin
*
* #return \Project\SkinBundle\Entity\Skin
*/
public function getSkin()
{
return $this->skin;
}
So what can I do to make his object become an array?
I have this little line but id doesnt help me :
public function __construct()
{
$this->skin = new ArrayCollection();
}
The form for creating a new email is this:
public function buildForm(FormBuilderInterface $builder, array $option) {
$builder->add('title', 'text', array('label' => 'cms.Title'));
}
public function getDefaultOptions(array $options) {
return array(
'data_class' => 'Project\UserBundle\Entity\Email',
);
}
public function getName()
{
return 'my_email';
}
}
The $skin property is a One to Many relationship in your doctrine mapping. Doctrine is expecting an ArrayCollection object or array.
This is causing your exception:
/**
*
* #ORM\OneToMany(targetEntity="Project\SkinBundle\Entity\Skin", mappedBy="email_id")
* #ORM\JoinColumn(name="skin", referencedColumnName="id")
*/
protected $skin;
If you need a one to many relationship you should pass an array instead of a single object because you can have multiple skins. If you want a one to one relationship (a single skin per entity) you should change you doctrine mapping.
Possible solution 1:
public function __construct()
{
$this->skin = new ArrayCollection();
}
/**
* Set skin
*
* #param \Project\SkinBundle\Entity\Skin $skin
* #return Email
*/
public function setSkin(array $skin)
{
$this->skin = $skin;
return $this;
}
/**
* Get skin
*
* #return \Project\SkinBundle\Entity\Skin[]|ArrayCollection
*/
public function getSkin()
{
return $this->skin;
}
Possible solution 2 (OneToOne, but this could be a ManyToOne, that's up to you):
/**
*
* #ORM\OneToOne(targetEntity="Project\SkinBundle\Entity\Skin", mappedBy="email_id")
* #ORM\JoinColumn(name="skin", referencedColumnName="id")
*/
protected $skin;
You could prevent the error by simply wrapping the object (which you should confirm is an "Email" object) in an array:
$item->setSkin(array($skin));
However something else is going wrong here and the error is coming from when Doctrine compiles a unit-of-work to save to the database.
The skin relationship declartion of the Email entity is incorrect. The Join column declaration should be on the manyToOne side, so Email should be:
Email entity:
/*
* #ORM\OneToMany(targetEntity="Project\SkinBundle\Entity\Skin", mappedBy="email")
*/
protected $skins;
Skin entity:
/*
* #ORM\ManyToOne(targetEntity="Project\SkinBundle\Entity\Email", inversedBy="emails")
* #ORM\JoinColumn(name="email_id", referencedColumnName="id")
*/
protected $email
Running app/console doctrine:generate:entities SkinBundle:Email (or however the entity is referenced) will then generate a methods like addSkin(Skin $skin) which are used to add objects to the relationship.
More info can be found on Doctrine associations.
For a one to many relationship you should have and be using methods addSkin() and removeSkin() in place of setSkin(). Also, as a convention I recommend pluralising collection properties i.e. $skin -> $skins. It makes the code clearer and errors in declaring and using entities become more obvious.
So for your entity that has many $skins I would recommend:
/**
* #var \Doctrine\Common\Collections\Collection
*/
private $skins;
/**
* Constructor
*/
public function __construct()
{
$this->skins = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add skin
*
* #param Skin $skin
* #return Email
*/
public function addSkin(Skin $skin)
{
$this->skins[] = $skin;
return $this;
}
/**
* Remove skin
*
* #param Skin $skin
*/
public function removeSkin(Skin $skin)
{
$this->skins->removeElement($skin);
}
/**
* Get skins
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getSkins()
{
return $this->skins;
}
Then where you have:
$item->setSkin($skin);
You should instead use:
$item->addSkin($skin);
I have User entity which has country field as many to one reference:
/**
* #ORM\ManyToOne(targetEntity="Country")
*/
private $country;
/**
* Set country
*
* #param string $country
* #return User
*/
public function setCountry($country)
{
$this->country = $country;
return $this;
}
/**
* Get country
*
* #return string
*/
public function getCountry()
{
return $this->country;
}
I need current user to be represented as array, so in controller I get current user as $user = $this->getUser(); and use JMSSerializer and json_decode to convert object to array:
$userJSON = $serializer->serialize($user, 'json');
$user = json_decode($userJSON, true);
Now I have user object as an array, but instead of having country just as ID I get entire country object. What is the correct way to get country as ID in user object?
You have to use getter annotation for that purpose:
/** #Accessor(getter="getCountryName") */
private $country;
public function getCountryName()
{
return $this->country->getName(); // or which property for country entity is used to take its name
}
And do not forget to add JMS Annotations in use:
use JMS\Serializer\Annotation\Accessor;
Good luck.