I have a problem with Doctrine 2 and OneToOne relation when it's the time to insert new infos in the database.
Model: User.php
<?php
/**
* #Entity #Table(name="user")
**/
class User
{
/** #Id #Column(type="integer") #GeneratedValue **/
protected $id;
/** #Column(type="string") **/
protected $lastname;
/** #Column(type="string") **/
protected $firstname;
/** #Column(type="string") **/
protected $email;
/** #Column(type="integer") **/
protected $status;
/** #Column(type="string") **/
protected $created;
/** #Column(type="integer") **/
protected $created_by;
/**
* #OneToOne(targetEntity="UserAuth", mappedBy="user")
*/
protected $auth;
public function getAuth()
{
return $this->auth;
}
public function setAuth($auth)
{
$this->auth = $auth;
}
// + all setters and getters...
}
Model: UserAuth.php
<?php
/**
* #Entity #Table(name="user_auth")
**/
class UserAuth
{
/** #Id #Column(type="integer") #GeneratedValue **/
protected $id;
/** #Column(type="integer") **/
protected $user_id;
/** #Column(type="string") **/
protected $username;
/** #Column(type="string") **/
protected $password;
/** #Column(type="string") **/
protected $role;
/**
* #OneToOne(targetEntity="User", inversedBy="auth")
* #JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $user;
public function getUser()
{
return $this->user;
}
// + all setters and getters...
}
No problem to retrieve the data and update them:
$user = $orm->find('User', 2);
echo $user->getAuth()->getUsername();
$user->getAuth()->setUsername('jprlauzier');
$orm->persist($user);
$orm->flush();
But, impossible to create a new one:
$user = $orm->find('User', 3);
$auth = new UserAuth();
$auth->setUsername('newusername');
$auth->setPassword('51abb9636078defbf888d8457a7c76f85c8f114c');
$auth->setRole('user');
$orm->persist($auth);
$user->setAuth($auth);
$orm->persist($user);
$orm->flush();
The errors:
Fatal error: Uncaught exception 'PDOException' with message 'SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'user_id' cannot be null' in /Applications/MAMP/htdocs/test/library/Doctrine/DBAL/Statement.php:131 Stack trace: #0 /Applications/MAMP/htdocs/test/library/Doctrine/DBAL/Statement.php(131): PDOStatement- >execute(NULL) #1 /Applications/MAMP/htdocs/test/library/Doctrine/ORM/Persisters/BasicEntityPersister.php(239): Doctrine\DBAL\Statement->execute() #2 /Applications/MAMP/htdocs/test/library/Doctrine/ORM/UnitOfWork.php(870): Doctrine\ORM\Persisters\BasicEntityPersister->executeInserts() #3 /Applications/MAMP/htdocs/test/library/Doctrine/ORM/UnitOfWork.php(304): Doctrine\ORM\UnitOfWork->executeInserts(Object(Doctrine\ORM\Mapping\ClassMetadata)) #4 /Applications/MAMP/htdocs/test/library/Doctrine/ORM/EntityManager.php(355): Doctrine\ORM\UnitOfWork->commit(NULL) #5 /Applications/MAMP/htdocs/test/application/Bootstrap.php(106): Doctrine\ORM\EntityManager->flush() #6 /Applications/MAMP/htdocs/test/application/Bo in /Applications/MAMP/htdocs/test/library/Doctrine/DBAL/Statement.php on line 131
Doctrine doesn't find the user_id to create a new entry in the table user_auth. But, I indicated the #JoinColumn(name="user_id", referencedColumnName="id") in my model...for your help! I searched all day for this.
When you create the User instance, the instance of User::id is still null. It is why the doctrine return the constraint violation.
see here
http://docs.doctrine-project.org/en/2.0.x/reference/working-with-associations.html#transitive-persistence-cascade-operations
Remove:
/** #Column(type="integer") **/
protected $user_id;
And update the database structure.
Related
As PHP 7.4 supports typed class properties: https://www.php.net/manual/en/migration74.new-features.php#migration74.new-features.core.typed-properties. Looks like a lot of code could be eliminated, in particular getters and setters that in entities and DTOs that was responsible for controlling properties types. For example such snippet:
class Foo implements BarInterface
{
/**
* #var int
*/
protected $id;
/**
* #var int|null
*/
protected $type;
/**
* #return int
*/
public function getId(): int
{
return $this->id;
}
/**
* #param int $id
* #return $this
*/
public function setId(int $id)
{
$this->id = $id;
return $this;
}
/**
* #return int|null
*/
public function getType(): ?int
{
return $this->type;
}
/**
* #param int|null $type
* #return $this
*/
public function setType(?int $type)
{
$this->type = $type;
return $this;
}
}
Can be refactored to:
class Foo implements BarInterface
{
public int $id;
public ?int $type;
}
Am I right that this is good idea? What should I take into consideration while making such refactoring?
I load several pictures with the Vichuploaderbundle and the EntryImg Entity.
That is successful!
I have also an Entity named Entries.
Each Entry should be linked with one or more Images
I want to join both Entities in the Repository.
What is the error?
Here is my Entries Entity
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
//use Vich\UploaderBundle\Mapping\Annotation as Vich;
/**
* #ORM\Entity(repositoryClass="AppBundle\Repository\EntriesRepository")
* #ORM\Table(name="entries")
*/
class Entries
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
* #ORM\OneToMany(targetEntity="AppBundle\Entity\EntryImg", mappedBy="entries")
*/
private $id;
/**
* #ORM\Column(type="string")
*/
private $name;
/**
* #ORM\Column(type="text")
*/
private $description;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\User")
* #ORM\JoinColumn()
*/
private $user;
public function getId()
{
return $this->id;
}
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
public function getDescription()
{
return $this->description();
}
public function setDescription($description)
{
$this->description = $description;
return $this;
}
public function getUser()
{
return $this->user;
}
public function setUser(User $user)
{
$this->user = $user;
}
}
My Image Entity
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\HttpFoundation\File\File;
//use Symfony\Component\HttpFoundation\File\UploadedFile;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
/**
* #ORM\Entity
* #ORM\Table(name="`entry_img`")
* #Vich\Uploadable
*/
class EntryImg
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #ORM\Column(type="string", length=255)
* #var string
*/
private $image;
/**
* #Assert\File(maxSize="2000k",mimeTypes={"image/png", "image/jpeg", "image/pjpeg"})
* #Vich\UploadableField(mapping="entry_images", fileNameProperty="image")
* #var File
*/
private $imageFile;
/**
* #ORM\Column(type="datetime")
* #var \DateTime
*/
private $updatedAt;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Entries")
* #ORM\JoinColumn(name="entries_id",referencedColumnName="id",nullable=true)
*/
private $entry;
public function setImageFile(File $image = null)
{
$this->imageFile = $image;
// VERY IMPORTANT:
// It is required that at least one field changes if you are using Doctrine,
// otherwise the event listeners won't be called and the file is lost
if ($image) {
// if 'updatedAt' is not defined in your entity, use another property
$this->updatedAt = new \DateTime('now');
}
}
public function getImageFile()
{
return $this->imageFile;
}
public function setImage($image)
{
$this->image = $image;
}
public function getImage()
{
return $this->image;
}
public function getId()
{
return $this->id;
}
public function getEntry(){
return $this->entry();
}
public function setEntry(Entries $entry){
$this->entry = $entry;
}
}
and my repository
<?php
namespace AppBundle\Repository;
use AppBundle\Entity\Entries;
use AppBundle\Entity\EntryImg;
use Doctrine\ORM\EntityRepository;
class EntriesRepository extends EntityRepository
{
public function findAllEntries($e_ids)
{
$query = $this->createQueryBuilder('e')
->leftJoin('e.id','entry_img');
$i = 0;
foreach($e_ids as $e_id){
if($i==0){
//var_dump($e_id);
$query->where('e.id = :entries_'.$i)
->setParameter('entries_'.$i,$e_id['entry']);
}else if($i>0){
$query->orWhere('e.id = :entries_'.$i)
->setParameter('entries_'.$i,$e_id['entry']);
}
$i++;
}
$result = $query->getQuery()->execute();
return $result;
}
In your class Entries, you have this line:
#ORM\OneToMany(targetEntity="AppBundle\Entity\EntryImg", mappedBy="entries")
But in your EntryImg class, there isn't any $entries property.
Also, if 1 Entries can have many EntryImg, you need to use arraycollection.
I think you should rewrite your Entries class like this:
use Doctrine\Common\Collections\ArrayCollection; //don't forget this line for the constructor
class Entries
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
//look here I remove the relation and put it on $entriesImg property
*/
private $id;
/**
* #ORM\Column(type="array")
* #ORM\OneToMany(targetEntity="AppBundle\Entity\EntryImg", mappedBy="entry") //look here I changed "entries" by "entry" which is the actual property in your EntryImg class. (you did not have any $entries property)
*/
private $entriesImg;
.. the rest of your properties
public function __construct()
{
$this->entriesImg= new ArrayCollection();
}
public function addEntryImg(\AppBundle\Entity\EntryImg $entryImg)
{
$this->entriesImg[] = $entryImg;
return $this;
}
public function removeEntryImg(\AppBundle\Entity\EntryImg $entryImg)
{
$this->entriesImg->removeElement($entryImg);
}
// also don't forget to implement classic getters and setters for the $entriesImg property
Don't forget to implement a toString method for both your entities, you will need it for Crud operation.
Then, later in your repository, you forgot to use the ->addSelect(); method like this
class EntriesRepository extends EntityRepository
{
public function findAllEntries()
{
$query = $this->createQueryBuilder('e')
->leftJoin('e.entriesImg','i');
->addSelect('i')
// whatever from your logic ....
UPDATE
When I use:
public function setUrl_key($value) { $this->url_key = $value; }
public function getUrl_key() { return $this->url_key; }
Instead of:
public function setUrlKey($value) { $this->url_key = $value; }
public function getUrlKey() { return $this->url_key; }
Works fine. Why?
Using ZF2 with Doctrine 2. On edit action of my form only the fields title and email are displayed in their textbox. The other textboxes are empty as if there was no value in the database. But there is.
But if I put url_key for example into email setter/getter like below it works.
public function setEmail($value) { $this->url_key = $value; }
public function getEmail() { return $this->url_key; }
Works through email getter... I guess that something's wrong with my binding or doctrine 2 hydration?
Here is some of my code:
Controller
$link = $this->getObjectManager()->getRepository('Schema\Entity\Link')->find($this->params('id'));
$form = new AdminLinkForm($this->getObjectManager());
$form->setHydrator(new DoctrineEntity($this->getObjectManager(),'Schema\Entity\Link'));
$form->bind($link);
$request = $this->getRequest();
if ($request->isPost()) {
Entity (setters & getters)
.....
/** #ORM\Column(type="string", name="title", length=255, nullable=false) */
protected $title;
/** #ORM\Column(type="string", length=255, nullable=false) */
protected $short_description;
/** #ORM\Column(type="string", length=255, nullable=true) */
protected $image;
/** #ORM\Column(type="text", nullable=true) */
protected $sample_title;
/** #ORM\Column(type="text", nullable=true) */
protected $sample_description;
/** #ORM\Column(type="text", nullable=true) */
protected $sample_keys;
/** #ORM\Column(type="string", name="webpage_url", length=255, nullable=false) */
protected $webpage_url;
/** #ORM\Column(type="string", length=255, nullable=true) */
protected $email;
......
public function setId($value) { $this->link_id = (int)$value; }
public function getId() { return $this->link_id; }
public function setTitle($value) { $this->title = $value; }
public function getTitle() { return $this->title; }
public function setShortDesc($value) { $this->short_description = $value; }
public function getShortDesc() { return $this->short_description; }
public function setUrlKey($value) { $this->url_key = $value; }
public function getUrlKey() { return $this->url_key; }
public function setEmail($value) { $this->email = $value; }
public function getEmail() { return $this->email; }
It's your entity fields/setters mismatch as you noted in update.
Doctrine finds protected $short_description; and tries to find corresponding getter/setter, but setShortDesc() doesn't match.
You should use something like protected $shortDesc; getShortDesc(); setShortDesc(); as doctrine reads entity fields and then tries to find getters/setters matching the same name and prepending method before. It is not possible to match getShortDesc() with short_description when it is linked only by code inside getter.
In ZF2 you are adviced to use camelCase so even in entity it seems to be good practice to get rid of underscores. Otherwise getters would look out of place and it is not good to mix two styles in the same code.
If in your tables you want or need use underscores, you can tell doctrine like this:
/** #Column(name="field_name") */
private $fieldName;
Working with ZF2 and Doctrine 2. Trying to insert data using entity manager.
I have this Entity:
class Link
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
protected $link_id;
/** #ORM\Column(type="string", length=255, nullable=false) */
protected $title;
/** #ORM\Column(type="string", length=255, nullable=false) */
protected $short_description;
/** #ORM\Column(columnDefinition="LONGTEXT NOT NULL") */
protected $description;
/** #ORM\Column(type="string", length=255, nullable=false) */
protected $webpage_url;
/** #ORM\Column(type="string", length=255, nullable=false) */
protected $email;
/** #ORM\Column(type="string", length=255, nullable=false) */
protected $meta_keys;
/** #ORM\Column(type="datetime", columnDefinition="DATETIME NOT NULL") */
protected $date_created;
/**
* #ORM\ManyToOne(targetEntity="Schema\Entity\Category")
**/
private $category_id;
public function __get($property) {
if (property_exists($this, $property)) {
return $this->$property;
}
}
public function __set($property, $value) {
if (property_exists($this, $property)) {
$this->$property = $value;
}
return $this;
}
}
And this
class LinkType
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
protected $link_type_id;
/** #ORM\Column(type="string", length=255, nullable=false) */
protected $name;
public function __get($property) {
if (property_exists($this, $property)) {
return $this->$property;
}
}
public function __set($property, $value) {
if (property_exists($this, $property)) {
$this->$property = $value;
}
return $this;
}
}
When i try this:
$link = new Link();
$link->title = 'aa';
$link->category_id = array('1');
$link->link_type_id = array('1');
$link->description = 'adsfa';
$link->webpage_url = 'asdfad';
$link->short_description = 'aa';
$link->email = 'asdf';
$link->meta_keys = 'asdf';
$link->date_created ='2014-01-14 13:26:54';
$this->getObjectManager()->persist($link); // ?????
$this->getObjectManager()->flush();
Gives me Error: Found entity of type on association Schema\Entity\Link#category_id, but expecting Schema\Entity\Category
I tried also putting cascade={"persist"} in annontations but gives me error: Class '' does not exist
Why?
You have to set the category_id to a value of Schema\Entity\Category[] and not array()
I there,
I Want create a custom authentication that provides user roles, groups and roles groups. The login that i want to create is the next:
Users have groups and groups have roles;
Users have roles. This roles is for to revoke roles from groups.
ie:
Groups:
Group1: ROLE_WRITE, ROLE_READ
Group2: ROLE_CHECK, ROLE_NEW
Users:
Groups:
Group1
Group2
Roles
ROLE_CHECK
With above example the user can only use 3 roles ROLE_WRITE, ROLE_READ and ROLE_NEW
I made the following classes
Roles.php
namespace Test\UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\Role\RoleInterface;
/**
* Test\OverSkyBundle\Entity\Roles
*
* #ORM\Table(name="roles")
* #ORM\Entity()
*/
class Roles implements RoleInterface{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=70, unique=true)
*/
private $role;
public function __construct( $role )
{
$this->role = $role;
}
public function getId(){
return $this->id;
}
public function getRole(){
return $this->role;
}
public function setRole($role){
$this->role = $role;
}
public function __toString()
{
return (string) $this->role;
}
}
Groups.php
namespace Test\UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Test\OverSkyBundle\Entity\Groups
*
* #ORM\Table(name="groups")
* #ORM\Entity()
*/
class Groups {
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=70, unique=true)
*/
private $groupname;
/**
* #ORM\ManyToMany(targetEntity="Roles")
*
*/
private $roles;
public function __construct()
{
$this->roles = new ArrayCollection();
}
public function __toString()
{
return $this->groupname;
}
public function getId(){
return $this->id;
}
public function getRoles()
{
return $this->roles->toArray();
}
public function setRoles($roles)
{
$this->roles = $roles;
}
}
Users.php
<?php
namespace Test\UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Test\OverSkyBundle\Entity\Users
*
* #ORM\Table(name="users")
* #ORM\Entity(repositoryClass="Test\UserBundle\Entity\UsersRepository")
*/
class Users implements UserInterface, \Serializable{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=25, unique=true)
*/
private $username;
/**
* #ORM\ManyToMany(targetEntity="Roles")
*
*/
private $roles;
/**
* #ORM\ManyToMany(targetEntity="Groups")
*
*/
private $groups;
/**
* #ORM\Column(type="string", length=32)
*/
private $salt;
/**
* #ORM\Column(type="string", length=140)
*/
private $password;
/**
* #ORM\Column(type="string", length=140, unique=true)
*/
private $email;
/**
* #ORM\Column(name="is_active", type="boolean")
*/
private $isActive;
public function __toString()
{
return $this->name;
}
public function __construct() {
$this->roles = new ArrayCollection();
$this->groups = new ArrayCollection();
$this->isActive = true;
$this->salt = md5(uniqid(null, true));
}
public function getId()
{
return $this->id;
}
public function getRoles()
{
return $this->roles->toArray();
}
public function setRoles($roles)
{
$this->roles = $roles;
}
public function getGroups()
{
return $this->groups->toArray();
}
public function setGroups($groups)
{
$this->groups = $groups;
}
public function getUsername()
{
return $this->username;
}
public function setUsername($username)
{
$this->username = $username;
}
public function getSalt()
{
return $this->salt;
}
public function setSalt($salt)
{
$this->salt = $salt;
return $this;
}
public function getPassword()
{
return $this->password;
}
public function setPassword($password)
{
$this->password = $password;
return $this;
}
public function getEmail()
{
return $this->email;
}
public function setEmail($email)
{
$this->email = $email;
return $this;
}
public function getIsActive()
{
return $this->isActive;
}
public function setIsActive($isActive)
{
$this->isActive = $isActive;
return $this;
}
public function eraseCredentials() {
}
public function serialize() {
return serialize(array(
$this->id,
));
}
public function unserialize($serialized) {
list (
$this->id,
) = unserialize($serialized);
}
}
index.html.twig
{% if is_granted('ROLE_ZAP') %}
dasdsa
{% endif %}
{% for groups in app.user.groups %}
<li> groups </li>
{% endfor %}
In twig file i can access to user roles but not the groups roles. How can i merge both and revoke with the roles present in users?
If i try to execute for groups in app.user.groups.roles,I receive an error that roles is not found.
When execute if is_granted, i recieve the user roles.
is_granted() calls the user's getRoles() method and checks wether the given argument role is inside the returned array of roles. ( simplified - the security provider calls getRoles and adds them to the security-context then is_granted checks the security-context to be more precise )
Now if you want to return the roles inherited from the user's groups you will have to merge those.
Though i don't know why you're trying to revoke the user's roles from those provided by the groups ...
( normally user's roles extend their group-provided roles - if you don't want certain roles, don't add the user to the group )
... you can do something like this:
Users.php
public function getRoles()
{
$groupRoles = array();
// add all roles provided by groups
foreach ($this->getGroups() as $group) {
foreach ($group->getRoles() as $role) {
$groupRoles[] = $role;
}
}
// - remove dublicates
// - revoke user's roles
// - return remaining roles
return array_unique(array_diff($groupRoles, $this->getRoles()));
}