Save Object in table related to another - symfony

How can I create a new object when a cell is related to another table? In my case there exist a table with states, like id=1,state=active;id=2,state=inactive.
My Entity/States.php
class States
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
....
Entity/User.php
....
/**
* Set state
*
* #param \Frontend\AccountBundle\Entity\States $state
* #return User
*/
public function setState(\Frontend\AccountBundle\Entity\States $state = null)
{
$this->state = $state;
return $this;
}
My AccountController:
....
$user = new User();
$em = $this->get('doctrine')->getEntityManager();
$state = $this->getDoctrine()->getRepository('FrontendAccountBundle:States')->find(1);
$user->setEmail($formData->getEmail());
$user->setStateId(1);
$em->persist($user);
$em->flush();
This is not working and way to complicated: http://symfony.com/doc/current/book/doctrine.html#relationship-mapping-metadata. It was so freaking easy in symfony1.4.

Your User entity has a method setState(), which takes a single parameter of $state. That parameter must be of type \Frontend\AccountBundle\Entity\States.
In your controller, you obtain that object through this call:
$state = $this->getDoctrine()->getRepository('FrontendAccountBundle:States')->find(1);
So when you go to set the State of the User, you don't need to bother with IDs. Rather, just set the State directly, and Doctrine will take care of the rest:
$user->setState($state);

This solution works for me:
https://stackoverflow.com/a/14131067/2400373
But in Symfony 4 change the line of getRepository:
$role = $this->getDoctrine()
->getRepository(Role::class)
->find(1);
$usuario->setRole($role);

Related

Doctrine Extension Slug : avoid duplication

I'm using doctrine and sf2 and i have a question about the slug extension :
Is there any way to generate it before the flush ?
Let say i have a Brand Entity:
/**
* Brand
*
* #ORM\Table(indexes={#ORM\Index(name="name_idx", columns={"name"})})
* #ORM\Entity(repositoryClass="Shoesalley\Bundle\CoreBundle\Entity\BrandRepository")
*/
class Brand
{
/**
* #var integer
*
* #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
*
* #Gedmo\Slug(fields={"name"})
* #ORM\Column(length=128, unique=true)
*/
private $slug;
}
// getters and setters ...
if i do this i get 2 differents slugs : test and test_1
$brand=new Brand();
$brand->setName('test');
$em->persist($brand);
$brand2=new Brand();
$brand2->setName('Test');
$em->persist($brand2);
The goal would be to find that the target slug allready exist and only have 1 DB Entry.
I can't use find() without a generated slug, so does anyone have an idea ?
The main idea is something like that, but i don't know how to implement it :
$brand=new Brand();
$brand->setName('test');
$slug = $brand->getSlug();
if( $oBrand = $em->getRepository("DemoBundle:Brand")->findOneBySlug($slug)){
$brand = $oBrand;
}
$em->persist($brand);
Thanks a lot for your help.
You are using the right logic in your solution i think; there are probalbly a few glitches you need to take care of though :
$brand = new Brand();
$brand->setName('test');
$slug = $brand->getSlug();
// Is getSlug() going to work before persist ?
// If not, you'll have to "simulate" the generation of a slug
// to obtain a string equivalent to that slug
$obrand = $em->getRepository("DemoBundle:Brand")->findOneBySlug($slug);
if(empty($obrand) { // empty, or is_null, depends on what your orm returns when it finds nothing
$em->persist(brand);
} else {
echo('Slug ' . $brand->getSlug() . ' is already in use !');
}

Symfony 2 - ManyToOne Bidirectional relationship behaviour

I had a big time trying to figure out how to setup a ManyToOne -> OneToMany relationship with Doctrine 2 and it still not working...
Here is the application behaviour:
A site has Pages
A User can write Comment on a Page
Here are my Entities (simplified):
Comment Entity:
**
* #ORM\Entity
* #ORM\Table(name="comment")
*/
class Comment {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* Many Comments have One User
*
* #ORM\ManyToOne(targetEntity="\Acme\UserBundle\Entity\User", inversedBy="comments")
*/
protected $user;
/**
* Many Comments have One Page
*
* #ORM\ManyToOne(targetEntity="\Acme\PageBundle\Entity\Page", inversedBy="comments")
*/
protected $page;
...
/**
* Set user
*
* #param \Acme\UserBundle\Entity\User $user
* #return Comment
*/
public function setUser(\Acme\UserBundle\Entity\User $user)
{
$this->user = $user;
return $this;
}
/**
* Set page
*
* #param \Acme\PageBundle\Entity\Page $page
* #return Comment
*/
public function setPage(\Acme\PageBundle\Entity\Page $page)
{
$this->page = $page;
return $this;
}
User Entity:
/**
* #ORM\Entity
* #ORM\Table(name="fos_user")
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* The User create the Comment so he's supposed to be the owner of this relationship
* However, Doctrine doc says: "The many side of OneToMany/ManyToOne bidirectional relationships must be the owning
* side", so Comment is the owner
*
* One User can write Many Comments
*
* #ORM\OneToMany(targetEntity="Acme\CommentBundle\Entity\Comment", mappedBy="user")
*/
protected $comments;
...
/**
* Get Comments
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getComments() {
return $this->comments ?: $this->comments = new ArrayCollection();
}
Page Entity:
/**
* #ORM\Entity
* #ORM\Table(name="page")
*/
class Page
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* One Page can have Many Comments
* Owner is Comment
*
* #ORM\OneToMany(targetEntity="\Acme\CommentBundle\Entity\Comment", mappedBy="page")
*/
protected $comments;
...
/**
* #return \Doctrine\Common\Collections\Collection
*/
public function getComments(){
return $this->comments ?: $this->comments = new ArrayCollection();
}
I want a bidirectional relationship to be able to get the collection of Comments from the Page or from the User (using getComments()).
My problem is that when I try to save a new Comment, I get an error saying that doctrine is not able to create a Page entity. I guess this is happening because it's not finding the Page (but it should) so it's trying to create a new Page entity to later link it to the Comment entity that I'm trying to create.
Here is the method from my controller to create a Comment:
public function createAction()
{
$user = $this->getUser();
$page = $this->getPage();
$comment = new EntityComment();
$form = $this->createForm(new CommentType(), $comment);
if ($this->getRequest()->getMethod() === 'POST') {
$form->bind($this->getRequest());
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$comment->setPage($page);
$comment->setUser($user);
$em->persist($comment);
$em->flush();
return $this->redirect($this->generateUrl('acme_comment_listing'));
}
}
return $this->render('AcmeCommentBundle:Default:create.html.twig', array(
'form' => $form->createView()
));
}
I don't understand why this is happening. I've checked my Page object in this controller (returned by $this->getPage() - which return the object stored in session) and it's a valid Page entity that exists (I've checked in the DB too).
I don't know what to do now and I can't find anyone having the same problem :(
This is the exact error message I have:
A new entity was found through the relationship
'Acme\CommentBundle\Entity\Comment#page' that was not configured to
cascade persist operations for entity:
Acme\PageBundle\Entity\Page#000000005d8a1f2000000000753399d4. To solve
this issue: Either explicitly call EntityManager#persist() on this
unknown entity or configure cascade persist this association in the
mapping for example #ManyToOne(..,cascade={"persist"}). If you cannot
find out which entity causes the problem implement
'Acme\PageBundle\Entity\Page#__toString()' to get a clue.
But I don't want to add cascade={"persist"} because I don't want to create the page on cascade, but just link the existing one.
UPDATE1:
If I fetch the page before to set it, it's working. But I still don't know why I should.
public function createAction()
{
$user = $this->getUser();
$page = $this->getPage();
// Fetch the page from the repository
$page = $this->getDoctrine()->getRepository('AcmePageBundle:page')->findOneBy(array(
'id' => $page->getId()
));
$comment = new EntityComment();
// Set the relation ManyToOne
$comment->setPage($page);
$comment->setUser($user);
$form = $this->createForm(new CommentType(), $comment);
if ($this->getRequest()->getMethod() === 'POST') {
$form->bind($this->getRequest());
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($comment);
$em->flush();
return $this->redirect($this->generateUrl('acme_comment_listing'));
}
}
return $this->render('AcmeCommentBundle:Default:create.html.twig', array(
'form' => $form->createView()
));
}
UPDATE2:
I've ended up storing the page_id in the session (instead of the full object) which I think is a better idea considering the fact that I won't have a use session to store but just the id. I'm also expecting Doctrine to cache the query when retrieving the Page Entity.
But can someone explain why I could not use the Page entity from the session? This is how I was setting the session:
$pages = $site->getPages(); // return doctrine collection
if (!$pages->isEmpty()) {
// Set the first page of the collection in session
$session = $request->getSession();
$session->set('page', $pages->first());
}
Actually, your Page object is not known by the entity manager, the object come from the session. (The correct term is "detached" from the entity manager.)
That's why it tries to create a new one.
When you get an object from different source, you have to use merge function. (from the session, from an unserialize function, etc...)
Instead of
// Fetch the page from the repository
$page = $this->getDoctrine()->getRepository('AcmePageBundle:page')->findOneBy(array(
'id' => $page->getId()
));
You can simply use :
$page = $em->merge($page);
It will help you if you want to work with object in your session.
More information on merging entities here

Symfony2 setting other rows to 0 after/before flush

Here's what I'm having trouble with.
I've a Table which contains a column called shown_on_homepage and only one row should be set to 1, the rest should all be set to 0. I'm trying to add a new row to the database and this one should be set to 1, setting the one that previously had a 1 to 0.
In MySQL I know this can be achieved by issuing an update before the insert:
UPDATE table_name SET shown_on_homepage = 0
Here's my Entity:
class FeaturedPerson {
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="content", type="string", length=2500, nullable=false)
*/
private $content;
/**
* #var \DateTime
*
* #ORM\Column(name="date_updated", type="datetime")
*/
private $dateUpdated;
/**
* #var bool
*
* #ORM\Column(name="shown_on_homepage", type="boolean", nullable=false)
*/
private $isShownOnHomepage;
//...
public function getIsShownOnHomepage() {
return $this->isShownOnHomepage;
}
public function setIsShownOnHomepage($isShownOnHomepage) {
$this->isShownOnHomepage = $isShownOnHomepage;
return $this;
}
}
And for the Controller I've:
$featured = new FeaturedPerson();
$featured->setContent('Test content.');
$featured->setDateUpdated('01/02/2013.');
$featured->setIsShownOnHomepage(TRUE);
$em = $this->getDoctrine()->getManager();
$em->persist($featured);
$em->flush();
It does add the new row, but the one that had a shown_on_homepage set to 1 still has it. I've researched but I couldn't find a way to achieve this, I hope you can help me.
You could execute a query prior to your existing code in your controller:
$queryBuilder = $this->getDoctrine()->getRepository('YourBundleName:FeaturedPerson')->createQueryBuilder('qb');
$result = $queryBuilder->update('YourBundleName:FeaturedPerson', 'd')
->set('d.isShownOnHomepage', $queryBuilder->expr()->literal(0))
->where('d.isShownOnHomepage = :shown')
->setParameter('shown', 1)
->getQuery()
->execute();
Change 'YourBundleName' to your bundle name.

Removing an object with em->remove($user) removes all objects in table

I have a User entity that implements UserInterface to use with a RBAC system. I have not implemented the whole system yet. However, when I try to remove a user with the following code, the action removes all the users and other associated objects in other tables and then throws me an error. I am able to remove objects from other entities without issues.
User entity
class User implements UserInterface
{
**
* #var integer $id
*
* #ORM\Column(name="id", type="smallint")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*
protected $id;
**
* #var string $username
*
* #ORM\Column(name="username", type="string", length=20, unique=TRUE)
*
protected $username;
**
* #var string $password
*
* #ORM\Column(name="password", type="string", length=255)
*
protected $password;
**
* #var string $salt
*
* #ORM\Column(name="salt", type="string", length=255)
*
protected $salt;
**
* #var string $fullName
*
* #ORM\Column(name="full_name", type="string", length=60, unique=TRUE)
*
protected $fullName;
**
* #ORM\ManyToMany(targetEntity="Role", inversedBy="users", cascade={"persist", "remove"})
* #ORM\JoinTable(name="users_roles")
*
* #var ArrayCollection $userRoles
*
protected $userRoles;
public function __construct()
{
$this->userRoles = new ArrayCollection();
}
}
Delete action
public function deleteUserAction($id) {
$user = $em->getRepository('ACMECompanyBundle:User')->find($id);
$currentUser = $this->get('security.context')->getToken()->getUser();
if ($id == $currentUser->getId()) {
return new Response("You cannot delete the current user");
}
if (!$user) {
throw $this->createNotFoundException('No user found for id '.$id);
}
try {
$em->remove($user);
$em->flush();
$msg = "User deleted!";
$code = "OK";
} catch (DBALException $e) {
return new Response($e);
$msg = "User cannot be deleted!";
$code = "ERR";
}
$response = new Response(json_encode(array('code' => $code, 'msg' => $msg)));
$response->headers->set('Content-Type', 'application/json');
return $response;
}
The error returned after all users are removed is
InvalidArgumentException: You cannot refresh a user from the EntityUserProvider that does not contain an identifier. The user object has to be serialized with its own identifier mapped by Doctrine.
You left out the definition for em in your action... define it with
$em = $this->getDoctrine()->getEntityManager();
and it should work. Unless you set it on the class itself, then you would need $this->...
When doctrine removes the user, it also removes all rolles assigned to this user and all users assigned to these roles. So according your annotation schema this is the correct behavior because of cascade={"remove"} in $userRoles annotation and cascade={"remove"} in $users annotation in Role entity.
If you want to prevent cascade removing and want to keep cascade persistent remove "remove" argument from both user and role relations

Doctrine2: skip not set columns

I have following entity:
class Employee {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $employeeId;
/**
* #ORM\Column(type="string", length=45, unique=true)
*/
protected $username;
/**
* #ORM\Column(type="string", length=255, nullable=false)
*/
protected $email;
and I'm running following code:
$employee = new Employee();
$employee->setUsername('test');
$em = $this->getDoctrine()->getManager();
$em->persist($employee);
$em->flush();
As you can see I didn't set value for email column.
But on persists I get:
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'email' cannot be null
because Doctrine adds all entity columns to the INSERT query and set null value for email column.
Is there a way to skip not set columns on insert? Or to make Doctrine insert '' (empty string) as default value for non null, string columns?
You may either allow your column to be null, setting nullable=true:
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
protected $email;
This won't raise the SQL error. But if you want to be consistent, use validation so you can deal with empty fields before persistance:
use Symfony\Component\Validator\Constraints as Assert;
...
/**
* #Assert\NotBlank()
* #ORM\Column(type="string", length=255)
*/
protected $email;
This way you can handle validation errors in a more specific way, like stated in docs for example:
$author = new Author();
// ... do something to the $author object
$validator = $this->get('validator');
$errors = $validator->validate($author);
if (count($errors) > 0) {
return new Response(print_r($errors, true));
} else {
return new Response('The author is valid! Yes!');
}
If you want just plain default values for columns, have a look at this question.
I seems that I just have to use entity __construct to set default values:
__construct() {
$this->email = '';
}
It's rather problem with your data model not with doctrine. You explicit declare that every record should have some value in email column. So either you remove NOT NULL constrain from entity or you just set some value on email column. In this case doctrine is only doing what you've told it to do.

Resources