Skip navigation.

Removing an object in a one-to-many relationship

I have the following problem using Xyster.

I have this code in my ChildMapper:

$this->hasOne('parent', array('class'=>'Parent', 'id' => 'parentId');

and this code in my ParentMapper:

$this->hasMany('children', array('class'=>'Child', 'id' => 'parentId');

If I read the docs correctly, this should setup a one-to-many relationship between the Parent and Child entities using Parent->children and Child->parent.

If I have a Child object that I want to remove from the relationship with a Parent object (not from the database, the Child can live on its own!), I expect to be able to do Parent->children->remove(Child). I expect that the Child's parentId gets set to NULL, but apparently the Child gets deleted from the database.

It seems that this is the intended behaviour of 'remove', but is there an other way to remove enitites from the set without deleting them from the database? I could do Child->parent = NULL, but that seems a bit strange...

I also noticed that if I

I also noticed that if I break the relationship by doing Child->parent = null that the Parent's ->children set still has the 'removed' child as one of its values (in the cache). refreshing Child and Parent does not help here.

Any ideas?

New code

I've traced deeply into Xyster and apparently the child entites get deleted because they are missing from the $_items array and are therefore always deleted when the parent entity is saved.

I've written some code to unlink an entity from a set. This does almost the same as a remove, but doesn't delete the unlinked entities when their parent entity is saved. Here follows the code. I left a commented line in in Xyster_Orm_Set::unlink(). I _think_ that unlinking an entity does not make it 'dirty', so I didn't mark it as such. Since the parent relationship also needs to be broken in all the children that you unlink(), the entity gets marked as dirty anyway...

  • Xyster/Collection/Abstract.php line 39:

     /*
      * The collection of unlinked items
      */
     protected $_unlinks = array();

  • Xyster/Collection/Abstract.php line 181:

     /**
      * Unlinks the specified value from the collection
      *
      * @param mixed $item The value to unlink
      * @return boolean If the value was in the collection
      */
     public function unlink( $item )
     {
          $before = $this->count();
          foreach( $this->_items as $key=>$value ) {
               if ( $value === $item ) {
                    unset($this->_items[$key]);
                    $this->_unlinks[$key] = $value;
               }
          }
          return $this->count() != $before;
     }

  • Xyster/Orm/Set.php line 159:

        return array_values(array_diff(array_diff($this->_base,$this->_unlinks),$this->_items));

  • Xyster/Orm/Set.php line 251:

    /**
     * Unlinks the specified value from the collection
     *
     * @param mixed $item The value to unlink
     * @return boolean If the value was in the collection
     */
    public function unlink( $item )
    {
        $unlink = parent::unlink($item);
        if ( $unlink && $this->_entity ) {
           // $this->_entity->setDirty();
        }
        return $unlink;
    }

belongsTo?

Did you try "belongsTo" instead of "hasOne" ?

―DC

Property = null

Actually, I read your question wrong.

Currently, any modifications to the parent's set will be saved to the database. If you want to remove a child's association to a parent, the idea you had of assigning null to the relationship property name is correct.

―DC

Setting a child's parent

Setting a child's parent relation property to NULL does not work correctly for me. After that, the child indeed has no parent, but the parent still 'has' the child! Or at least, the 'children' property of the parent still has the child in the collection. I think this is a bug in Xyster, of I have definitely done something wrong!

Also, were the parent to automatically remove this child from its set, it would still count as a 'child deletion' to the rest of the code, and delete the child on its way out.

Christ van Willegen

Hmm

Hmm, you might be onto something. Looking at the Xyster_Orm_Mapper_Abstract::save method, it does this:

    } else if ( $relation->getType() == 'many' ) {
    
        $map = $this->_factory->get($relation->getTo());
        array_walk($added, array($map, 'save'));
        array_walk($removed, array($map, 'delete'));
    
    }

The problem comes in where sometimes the the foreign key fields on the child entity (the one in the set) are not allowed to be null. In this case, the record should be deleted. If they're allowed to have null, the developer might want the foreign key fields to just be nulled out. This sounds like a configuration setting on each entity type.

On the other hand, I'm in the middle of rewriting the ORM base, so I'm not sure what I will do regarding that until I get to it.

In the meantime, I'm glad you've found a way around it. A better way would be to subclass Xyster_Orm_Set, add in your unlink logic, and have your entity sets inherit from your new class.

I'll keep you posted about the progress with the new ORM system.

―DC

User login