I am using Doctrine 2 MongoDB ODM, all working fine except 1 specific relation. What's obscure is that I have seemingly identical relations in the project and they all work just fine
namespace Project\Entities\World; // same NS as class, but this is after
// splitting functionality from Entity to MappedSuperclass, didn't work either
/**
* #ReferenceOne(targetDocument="Project\Entities\World")
* #var IWorld
*/
protected $world;
used in Project\Entities\PlayerCharacter (extends Project\Entities\World\Object mentioned above)
=>
namespace Project\Entities;
/**
* #Document(collection="worlds")
* #HasLifecycleCallbacks
*/
class World {
/**
* #ReferenceMany(targetDocument="PlayerCharacter")
* #var ArrayCollection
*/
protected $playerCharacters;
}
When I set the $world variable, it's fine in the PHP script. Even $objectManager->refresh($character), $character->getWorld() === $world turns out fine. But it does never appear in the database itself (die right after setting it and flush to make sure it is never changed by accident somewhere) on the PlayerCharacter's end, only on the World side
On the other hand
class PlayerCharacter {
/**
* #ReferenceOne(targetDocument="User")
* #var User
*/
protected $user;
}
=>
/**
* #Document(collection="users")
* #HasLifecycleCallbacks
*/
class User {
/**
* #ReferenceMany(targetDocument="PlayerCharacter")
* #var ArrayCollection
*/
protected $characters;
}
works
In simplified version:
- PlayerCharacter::$user <==(1:N)==> User::$characters
(and all others) are fine, while only
- PlayerCharacter::$world <==(1:N)==> World::$playerCharacters
works only on the World side
Looking at it many days, can't find anything different.
Tried renaming property names, no change
Hydrator entry for character--->world looks identically to others
When I add the entry semi-manually (via RockMongo), it works fine
Creating the "world" field as NULL makes no difference, with {} it says "Undefined index: $id", but I guess that's an expected behaviour
Entities separately work very fine too, it really just is this one relation
Is there anything I am missing/overlooked or what can I do to discover why is it not getting persisted
(will edit the post if there's need for more info)
Thank you!
Ok, the thing was there were many entries in the UoW some containing World => null and some World => ...WorldProxy, so the latter probably got overwritten
Using flush() before the assignment solved this
Related
I am under impression that ORM uses some kind of sanitation technique, but I am not sure. I looked at http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/security.html and was not clear on the issue.
Question
Will it be safe to use
$product = new Product();
$product->setModel($_POST['model']);
where POST is NOT sanitized previously, or must I always sanitize/validate my values first before sending them to Doctrine?
For reference
/**
* #Entity
*/
class Product
{
/**
* #var integer #Column(name="id", type="integer", nullable=false)
* #Id #GeneratedValue
*/
private $id;
/**
* #var string #Column(type="string")
*/
private $model;
}
You should always validate/sanitize user input. Even though Doctrine is using a prepared queries (which prevents SQL injections) you are not safe against other attacks.
Check this page, to see how to deal with inputs in Doctrine:
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/security.html#user-input-and-doctrine-orm
Your are right, Doctrine ORM is doing all the sanitization automatically. Therefore, as long as you are using ORM, you are perfectly safe.
So in your example no additional sanitization is required.
I would only say that instead of using raw $_POST array you are supposed to use the Request object that is automatically injected in your controller:
$product = new Product();
$product->setModel($request->get('model'));
I have this piece of code inside an Entity:
/**
* #ORM\ManyToOne(targetEntity="Centers")
* #ORM\JoinColumn(name="center_id", referencedColumnName="id")
* #ORM\Column(type="string", length=36, name="center_id")
*/
protected $centerId;
However, schema:update says that all is in sync. Even changing Centers to some other inexistent word, produces no error.
Please delete the last line of annotation doctrine like this and change $centerId by $center (there is object an object) :
/**
* #ORM\ManyToOne(targetEntity="Centers")
* #ORM\JoinColumn(name="center_id", referencedColumnName="id")
*/
protected $center;
Clear cache and run your command.
Tell me if this solved your problem.
I have a table "cms_objects" // Object.php - that stores all object info
I have another table "cms_media" // Media.php - that stores all media info
An object can have many media items (post with lots of different images)
In Object.php
/**
* #ORM\OneToMany(targetEntity="Media", mappedBy="Object")
*/
private $cms_media;
In Media.php
/**
* #ORM\ManyToOne(targetEntity="Object", inversedBy="cms_media")
* #ORM\JoinColumn(name="object_id", referencedColumnName="id")
*
* #Annotation\Exclude()
*/
private $object;
When I run: php public/index.php orm:validate-schema - I get:
[Mapping] FAIL - The entity-class 'Application\Entity\Cms\Media' mapping is invalid:
* The mappings Application\Entity\Cms\Media#object and Application\Entity\Cms\Object#cms_media are inconsistent with each other.
[Mapping] FAIL - The entity-class 'Application\Entity\Cms\Object' mapping is invalid:
* The association Application\Entity\Cms\Object#cms_media refers to the owning side field Application\Entity\Cms\Media#Object which does not exist.
Ideally, I need to be able to create a ZF2 form with element: 'media' or 'cms_media' but I haven't been able to validate it yet...
You can try to use FQCN inside the annotations. Instead of
/**
* #ORM\OneToMany(targetEntity="Media", mappedBy="Object")
*/
try
/**
* #ORM\OneToMany(targetEntity="Application\Entity\Cms\Media", mappedBy="Object")
*/
in both entities.
Also i would like to recommend using camelCased entity properties instead of underscored_ones. Hydration process of the entities with underscored properties using DoctrineObject hydrator is problematic. You can find more details here.
BEWARE - Using unnecessary bi-directional associations increases your object graph and domain model complexity. Best practice is avoiding bi-directional associations if possible.
For this case, you can rewrite the same mapping using uni-directional relation between Post (Object) and Media entities if you don't need reverse access from Media to Post like
$media->getPost()
For example Application/Entity/Cms/Post.php :
/** #ORM\Entity **/
class Post
{
/**
* One to many, unidirectional
* #ORM\ManyToMany(targetEntity="Application\Entity\Cms\Media")
* #ORM\JoinTable(name="post_to_media",
* joinColumns={#ORM\JoinColumn(name="post_id", referencedColumnName="id")},
* inverseJoinColumns={
* #ORM\JoinColumn(name="media_id", referencedColumnName="id",unique=true)
* })
**/
private $media;
public function __construct()
{
$this->media = new \Doctrine\Common\Collections\ArrayCollection();
}
// ...
}
and Application/Entity/Cms/Media.php :
/** #ORM\Entity **/
class Media
{
// No need to know about post
}
For eg I have entities like User,Item, Image. User has many items. Item has many images.
Which delete option should I choose, cascade={'remove'} or onDelete=Cascade ?
Also I have life cycle callbacks on Image. I know the difference between above mentioned cascade options. I was wondering if I used onDelete=cascade option, on deleting a User object, will the life cycle callback like PostRemove() be called ?
Here are my entities:
//User.php
class User {
/**
* #ORM\OneToMany(targetEntity="Item", mappedBy="user", onDelete="CASCADE")
*/
private $items;
}
//Item.php
class Item {
/**
* #var User
*
* #ORM\ManyToOne(targetEntity="User", inversedBy="items")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
* })
*/
private $user;
/**
* #ORM\OneToMany(targetEntity="ItemImage", mappedBy="item",onDelete="CASCADE")
*/
protected $images;
}
//ItemImage.php
class ItemImage {
/* Setters and getter **/
/**
* #var Items
*
* #ORM\ManyToOne(targetEntity="Item", inversedBy="images")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="item_id", referencedColumnName="id")
* })
*/
private $item;
/**
* #ORM\PostRemove()
*/
public function removeUpload() {
unlink($this->getUploadDir() . '/' . $this->imageName);
}
}
My question is when a user is deleted, will all the items associated with user and images related to the items be deleted ? I also want the PostRemove() callback of Image entity be called when User is deleted ? Which option should I use, onDelete="cascade" or cascade={'remove'} for such cases?
onDelete='CASCADE' will add an SQL level ON DELETE CASCADE. So yes, the images will be deleted from the table. This is very efficient because the database handles the deletes.
To have the listener called cascade={'remove'} is necessary. This makes Doctrine fetch the object's graph, which is slower.
Both options may be specified at the same time. In this case Doctrine will act as described in the last paragraph, but DELETEs not coming from Doctrine will cascade correctly, too; no listeners will be called in this case obviously.
I would tend to specify cascade={'remove'} only in this case to avoid accidental DELETEs without the listener being called (if there is no cleanup task for unreferenced files).
Details can be found in the Doctrine documentation.
Hello I have problem when trying to cascade remove entities in OneToMany relations.
After a few hours of debugging I tried to downgrade the doctrine from the latest 2.1.2 to 2.0.2 and It suddenly starts working.
Imagin two entities Company and Address in relation 1:N.
/**
* #Entity
*/
class Company extends Entity
{
/**
* #var integer
* #id #Column(type="integer")
* #generatedValue
*/
private $id;
/**
* #var Collection
* #OneToMany(targetEntity="Address",mappedBy="company", cascade={"persist","remove"})
*/
private $addresses;
}
/**
* #Entity
*/
class Address extends Entity
{
/**
* #var integer
* #id #Column(type="integer")
* #generatedValue
*/
private $id;
/**
* #var Company
* #ManyToOne(targetEntity="Company", inversedBy="addresses")
* #JoinColumn(name="company_id", referencedColumnName="id",nullable=false)
*/
private $company;
}
when I try to remove the entity Company, I would like the assigned addresses will be removed as well.
$em->remove($company);
$em->flush();
In doctrine 2.1.2 the deletion of addresses is not performed so the integrity constraint fails. In version 2.0.2 there it works perfectly. Wierd thing on it is, if I use EntityAudit extension https://github.com/simplethings/EntityAudit the LogRevisionListener is corretly versioning the addresses entities (set them revtype = DEL) in doctrine 2.1.2 (of course in 2.0.2 as well) but the UnitOfWork is not removing it.
Is there any difference how to handle cascade removing in 2.0.2 and in 2.1.2?
Thank you very much
Try using this on the addresses attribute of your Company Class
#OneToMany(targetEntity="Address",mappedBy="company",
cascade={"persist"}, orphanRemoval=true)
I had the same problem... Relations were added or updated, but not deleted, even if I had cascade: [persist, remove].
I found out that I didn't need the "remove" attribute in "cascade", but I had to add the orphanRemoval: true.
I was going crazy, you made my day!
I have met the same problem and i have solved him with that code :
$em->remove($object);
$em->flush();
$em->remove($user);
$em->flush();
Maybe you can use a findAll on your company for the addresses and remove this with a foreach like that :
// Return all the addresses of the company
$addresses = $em->getRepository(...)->findAllAddressesByCompany($company);
$em->remove($company);
foreach ($address in $addresses)
{
$em->remove($address);
}
That's not a very good method but for now, that's all I've found.