I have created an entity A with OneToMany relation to B, which have relation OneToMany to C.
I have to clone this A entity and set it in database with new id. Also all deep relations should be cloned with new ids too.
What have I tried is to set A id to null:
$A = clone $A_original;
$A->setId(null);
$em->persist($A);
It creates new record in A table, but does not in B and C.
What should I do to make a full copy of A entity ?
You have to implement a __clone() method in your entities that sets the id to null and clones the relations if desired. Because if you keep the id in the related object it assumes that your new entity A has a relation to the existing entities B and C.
Clone-method for A:
public function __clone() {
if ($this->id) {
$this->setId(null);
$this->B = clone $this->B;
$this->C = clone $this->C;
}
}
Clone-method for B and C:
public function __clone() {
if ($this->id) {
$this->setId(null);
}
}
https://groups.google.com/forum/?fromgroups=#!topic/doctrine-user/Nu2rayrDkgQ
https://doctrine-orm.readthedocs.org/en/latest/cookbook/implementing-wakeup-or-clone.html
Based on the comment of coder4show a clone-method for a OneToMany relationship on A where $this->M is OneToMany and therefore an ArrayCollection:
public function __clone() {
if ($this->id) {
$this->setId(null);
// cloning the relation M which is a OneToMany
$mClone = new ArrayCollection();
foreach ($this->M as $item) {
$itemClone = clone $item;
$itemClone->setA($this);
$mClone->add($itemClone);
}
$this->M = $mClone;
}
}
There is also a module that will do this called DeepCopy:
https://github.com/myclabs/DeepCopy
$deepCopy = new DeepCopy();
$myCopy = $deepCopy->copy($myObject);
You can also add filters to customize the copy process.
I wasnt able to use DeepClone (it require php 7.1+), so I founded more simple way to clone relations in entity __clone method
$this->tags = new ArrayCollection($this->tags->toArray());
A clean way to clone a ArrayCollection:
$this->setItems(
$this->getItems()->map(function (Item $item) {
return clone $item;
})
);
Related
My question is referenced to Doctrine many-to-many relations and onFlush event
Author entitiy: http://pastebin.com/GBCaSA4Z
Book entity: http://pastebin.com/Xb2SEiaQ
I run this code:
$book = new \App\CoreBundle\Entity\Books();
$book->setTtile('book title: '.uniqid());
$book->setIsbn('book isbn: '.uniqid());
$author = new \App\CoreBundle\Entity\Authors();
$author->setName('author title: '.uniqid());
$author->addBook($book);
$entityManager->persist($author);
$entityManager->flush();
In this way all is OK.
Doctrine generate three queries: create author, create book and create links at author_books.
But I need to detach and serialize all entities at doctrine onFlush event and save them at NoSQL database. And then other script will unserialize all these entities and save them to database. And in this way Doctrine generate only two SQL queries: create book and create author. What should I do to doctrine also generate SQL query for linking table?
Here part of the script that unserialize entities:
foreach ($serializedEntities as $serializedEntity)
{
$detachedEtity = unserialize($serializedEntity);
$entity = $entityManager->merge($detachedEtity);
//$entityManager->persist($entity)
}
$entityManager->flush();
UPDATE: and I always get error like this:
Notice: Undefined index: 000000002289c17b000000003937ebb0 in /app/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php line 2776
You can define a behavior of each entity with inherited class like :
class FieldRepository extends EntityRepository{
define all specific methods
}
so each entity will be saved as defined on class
You can use also the helpers concepts, this will solve your problem faster and easy
OnFlush ---> dispache event 'onFlush' --> define behavior on the helper class
Just a helper class exemple
// Helper book class
class Booking{
public function __construct($container)
{
$this->container = $container;
$this->club_interval = $container->get('X.interval');
$this->security_context = $container->get('security.context');
// your entity manager
$this->em = $container->get('doctrine.orm.entity_manager');
$this->session = $container->get('session');
$this->translator = $container->get('translator');
$this->event_dispatcher = $container->get('event_dispatcher');
$this->price = 0;
}
public function serialize()
{
$this->session->set('booking', serialize($this->booking));
}
public function unserialize()
{
$b = unserialize($this->session->get('booking'));
$partner = null;
foreach ($b->getUsers() as $u) {
$partner = $this->em->getReference('X:User', $u->getId());;
}
$field = $this->em->getReference('X:Field', $b->getField()->getId());
$user = $this->em->getReference('X:User', $b->getUser()->getId());
$this->buildBooking($field, $b->getFirstDate(), $b->getEndDate(), $user, $partner, $b->getGuest());
}
///////////////////////////////////////////////
// here you can choose your database manager check __constructor
////////////////////////////////////////////
public function save(){
$this->em->persist($this->booking);
$this->em->flush();
if ($this->booking->getStatus() >= \X\BookingBundle\Entity\Booking::CONFIRMED) {
$event = new \X\BookingBundle\Event\FilterBookingEvent($this->booking);
$this->event_dispatcher->dispatch(\X\BookingBundle\Event\Events::onBookingConfirm, $event);
}
$this->em->flush();
return $this->booking;
}
so you can trigger this class method directly form you Symfony2 controller, use save() instead of persist()
or using event dispatcher but more tricky
if (false === $this->get('security.context')->isGranted('ROLE_USER')) {
throw new AccessDeniedException();
}
$b = $this->get('X_helper.booking');
$b->unserialize();
$b->setStatus(\X\BookingBundle\Entity\Booking::PENDING);
/////////////////////////////////////
// here use helper's method save instead of flush
$b->save();
return $this->redirect($this->generateUrl(' route '));
I want to create Game object with cloned Scenario object.
Create Game form:
Name: My game
Scenario: MyScenario (Combo box)
Basing on answer for Deep clone Doctrine entity with related entities question I have implemented __clone methods.
I'm using __clone method in prePersist method in GameAdmin class.
public function prePersist($game)
{
$user = $this->container->get('security.context')->getToken()->getUser();
$game->setAuthor($user);
$cp = clone $game->getScenario(); //Error after add this
$game->setScenario($cp); //two lines
}
I'm not sure is this a proper place for doing this operation because I'm getting MappingException:
The class 'Doctrine\ORM\Persisters\ManyToManyPersister' was not found in the chain
configured namespaces Sonata\MediaBundle\Entity, FOS\UserBundle\Entity,
Sonata\UserBundle\Entity, Application\Sonata\MediaBundle\Entity,
Application\Sonata\UserBundle\Entity, GM\AppBundle\Entity
In Scenario entity I have $tasks which is ArrayCollection. I was cloning entire collection and that cause problems.
Cloning each task in loop solves problem:
public function __clone()
{
if($this->id)
{
$this->setId(null);
$ta = new ArrayCollection();
foreach($this->tasks as $task)
{
$ta[] = clone $task;
}
$this->tasks = $ta;
}
}
I have a basic entity that has a Unidirectional OneToOne relationship with another entity. I have an instance of the owning entity that's created via a variable method as so:
$entity = new $entity;
where $entity on the right-hand side is simply a string that describes the class. When I grab the inverse entity (an Address) from the owning entity it returns a class of the same type as the controller I'm using.
$object = $entity->getAddress();
this line of code returns an object of type Ajax (the controller this code is in in CodeIgniter). The code for the getter is simple, nothing fancy:
public function getAddress() {
return $this->address;
}
What could possibly be going on here? Why would I be getting back an instance of my controller?
What is $this->address set as?
Is it possibly that you setting $this->address = $this?
Because if $this->address = $this then
$object = $entity->getAddress();
is really
$object = $entity;
which of course is an object of type $entity;
I have the following:
class A
{
public function getDependencies()
{
//returns A.default.css, A.default.js, A.tablet.css, A.tablet.js, etc,
//depending on what files exist and what the user's device is.
}
}
In class B, which extends A, if I call getDependencies I will get things like: B.default.css, B.default.js and so on.
What I want to do now is include the results of A as well, without having to override getDependencies() in B. In fact, I'm not even sure if overriding would work, at all. Is this possible?
This is for dynamic CSS/JS loading for templates, and eventually compilation for production as well.
EDIT= I should point out that what getDependencies returns is dynamically generated, and not a set of stored values.
EDIT2= The idea I have is that just inheriting from A will provide the behavior. I probably need some kind of recursion that goes through the hierarchy tree, starting from B, to B's parent, and all the way up to A, without any method overriding happening along the way.
Use parent::getDependencies(), e.g.:
class B
{
public function getDependencies()
{
$deps = array('B.style.js' 'B.default.js', 'B.tables.js' /*, ... */);
// merge the dependencies of A and B
return array_merge($deps, parent::getDependencies());
}
}
You can also try this code which uses ReflectionClass in order to iterate over all parents:
<?php
class A
{
protected static $deps = array('A.default.js', 'A.tablet.js');
public function getDependencies($class)
{
$deps = array();
$parent = new ReflectionClass($this);
do
{
// ReflectionClass::getStaticPropertyValue() always throws ReflectionException with
// message "Class [class] does not have a property named deps"
// So I'm using ReflectionClass::getStaticProperties()
$staticProps = $parent->getStaticProperties();
$deps = array_merge($deps, $staticProps['deps']);
}
while ($parent=$parent->getParentClass());
return $deps;
}
}
class B extends A
{
protected static $deps = array('B.default.js');
}
class C extends B
{
protected static $deps = array('C.default.js');
}
$obj = new C();
var_dump( $obj->getDependencies($obj) );
On Ideone.com
It's pretty easy using the reflection API.
I can simply iterate through the parent classes:
$class = new \ReflectionClass(get_class($this));
while ($parent = $class->getParentClass())
{
$parent_name = $parent->getName();
// add dependencies using parent name.
$class = $parent;
}
Credits to ComFreek who pointed me to the right place.
You can use self keyword - this will returns A class values and then you can use $this to get the B class values.
I have a PHP MVC application using Zend Framework. As presented in the quickstart, I use 3 layers for the model part :
Model (business logic)
Data mapper
Table data gateway (or data access object, i.e. one class per SQL table)
The model is UML designed and totally independent of the DB.
My problem is : I can't have multiple instances of the same "instance/record".
For example : if I get, for example, the user "Chuck Norris" with id=5, this will create a new model instance wich members will be filled by the data mapper (the data mapper query the table data gateway that query the DB). Then, if I change the name to "Duck Norras", don't save it in DB right away, and re-load the same user in another variable, I have "synchronisation" problems... (different instances for the same "record")
Right now, I use the Multiton / Identity Map pattern : like Singleton, but multiple instances indexed by a key (wich is the user ID in our example). But this is complicating my developpement a lot, and my testings too.
How to do it right ?
Identity Map
Edit
In response to this comment:
If I have a "select * from X", how can I skip getting the already loaded records ?
You can't in the query itself, but you can in the logic that loads the rows into entity objects. In pseudo-code:
class Person {}
class PersonMapper {
protected $identity_map = array();
function load($row) {
if (!isset($this->identity_map[$row['id']])) {
$person = new Person();
foreach ($row as $key => $value) {
$person->$key = $value;
}
$this->identity_map[$row['id']] = $person;
}
return $this->identity_map[$row['id']];
}
}
class MappingIterator {
function __construct($resultset, $mapper) {
$this->resultset = $resultset;
$this->mapper = $mapper;
}
function next() {
$row = next($this->resultset);
if ($row) {
return $this->mapper->load($row);
}
}
}
In practice, you'd probably want your MappingIterator to implement Iterator, but I skipped it for brevity.
Keep all loaded model instances in "live model pool". When you load/query a model, first check if it has been already loaded into pool (use primary key or similar concept). If so, return the object (or a reference) from pool. This way all your references point to the same object. My terminology may be incorrect but hopefully you get the idea. Basically the pool acts as a cache between business logic and database.
Multiton
Best option if you want to use a variety of singletons in your project.
<?php
abstract class FactoryAbstract {
protected static $instances = array();
public static function getInstance() {
$className = static::getClassName();
if (!(self::$instances[$className] instanceof $className)) {
self::$instances[$className] = new $className();
}
return self::$instances[$className];
}
public static function removeInstance() {
$className = static::getClassName();
if (array_key_exists($className, self::$instances)) {
unset(self::$instances[$className]);
}
}
final protected static function getClassName() {
return get_called_class();
}
protected function __construct() { }
final protected function __clone() { }
}
abstract class Factory extends FactoryAbstract {
final public static function getInstance() {
return parent::getInstance();
}
final public static function removeInstance() {
parent::removeInstance();
}
}
// using:
class FirstProduct extends Factory {
public $a = [];
}
class SecondProduct extends FirstProduct {
}
FirstProduct::getInstance()->a[] = 1;
SecondProduct::getInstance()->a[] = 2;
FirstProduct::getInstance()->a[] = 3;
SecondProduct::getInstance()->a[] = 4;
print_r(FirstProduct::getInstance()->a);
// array(1, 3)
print_r(SecondProduct::getInstance()->a);
// array(2, 4)