Object relationships

Overview

A good ORM package must address the mapping of relationships among objects. EZPDO makes object relationships mapping simple.

One major strength EZPDO has is, unlike other ORM packages, it handles 1:N and M:N relationships automatically. By “automatically”, we mean that EZPDO does the relationship mapping without asking you to create any auxiliary association tables and totally frees you from maintaining them later on.

Besides inheritance, two other important types of relationships are supported: aggregation and composition. We will explain what they mean shortly. (You may also want to take a look at the terms at phpPatterns (which, by the way, is a great source for applying design patterns in PHP).

Object relationships

Aggregation

Aggregation is when a class A object $a has access to a class B objects $b, object $b perhaps having been instantiated outside object $a. If object $a “dies”, object $b will continue to “live”.

[EZPDO translation] Deleting object $a will not remove object $b in datastore.

Composition

Composition happens when a class A object ($a) instantiates another object, object ($b) of another class B, object $b dies when object $a dies. In other words the class A object ($a) controls the whole of the class B object ($b).

[EZPDO translation] Deleting $a will also delete $b from datastore.

Tags for relationships

So how do we specify object relationship in EZPDO? There are two tags for this purpose:

  • Aggregation:

    @orm has [one|many] Class_X [inverse[(var)]]

  • Composition:

    @orm composed_of [one|many] Class_Y [inverse[(var)]]

As you may have noticed, the optional keyword after has or composed_of can be either “one” or “many”. If you don’t specify, “one” is assumed. If “many” is used, the corresponding variable is called a many-valued relational variable; otherwise a single-valued variable.

You may have noticed the keyword inverse in the @orm tags. We will describe it soon in Inverse of a relationship var.

Modeling 1:1, 1:N, N:1 and M:N

The @orm has/composed_of [one|many] tags give you enough ammunition to knock out any object relationships, be it 1:1 (one-to-one), 1:N (one-to-many), N:1 (many-to-one), or M:N (many-to-many).

Example 1:1

Let’s start with the simplest: a book has only one author.

 
/**
 * @orm mysql://dbuser:secret@localhost/ezpdo
 */
class Author {
  // ......
}
 
/**
 * @orm mysql://dbuser:secret@localhost/ezpdo
 */
class Book {
  /**
   * @orm has one Author
   */
  public $author;
}

Example 1:N

An author may have written many books.

/**
 * @orm mysql://dbuser:secret@localhost/ezpdo
 */
class Author {
  /**
   * An author may write more than one book
   * @orm has many Book
   */
  public $books;
}
 
/**
 * @orm mysql://dbuser:secret@localhost/ezpdo
 */
class Book {
  /**
   * Let's assume a book has only one author
   * @orm has one Author
   */
  public $author;
}

Note that this example forms a two-way reference, while Example 1:1 is a one-way reference. EZPDO handles one-way and two-way automatically.

Example M:N

Let’s model two facts:

  1. A book makes references to many other books.
  2. Writers may co-author one book.
/**
 * @orm mysql://dbuser:secret@localhost/ezpdo
 */
class Author {
  /**
   * An author may write more than one book
   * @orm has many Book
   */
  public $books;
}
 
/**
 * @orm mysql://dbuser:secret@localhost/ezpdo
 */
class Book {
 
  /**
   * A book may be co-authored by more than one author
   * @orm has many Author
   */
  public $authors;
 
  /**
   * A book may reference many other books
   * @orm has many Book
   */
  public $ref_books;
}

Note that the above example shows you EZPDO can model relationships within the same class ($ref_books) and relationships among different classes ($authors).

Inverse of a relationship var

A relationship variable can have a variable as its inverse in its related class. When two variables are specified as inverses of one another, they can be used to form bidirectional links. An update to one variable will automatically trigger a corresponding update to the other variable, maintaining consistency between the fields.

One or both variables can be many-valued, or both can be single-valued.

Using inverse() in the var @orm tag, you can make a pair of variables in two classes as inverses to each other. For example,

  // class A
  class classA {
    // @orm has one classB inverse(as)
    public $b;
  }
 
  // class B
  class classB {
    // @orm has many ClassA inverse(b)
    public $as;
  }

Variables classA::$b and classB::$a form a bidirectional link. Assigning one object to another will also install the relationship on the other direction. So

  $a->b = $b;

is equivalent to

  $a->b = $b;
  $b->as[] = $a;

if A::$b and B::$as are not specified as inverses.

Specify object relationships

With EZPDO, making associations among objects is as easy as assignement (=). What makes it even nicer is all these associations made in memory are persisted into your database automatically. As an EZPDO user, you are also totally(!) free from maintaining them in later object updating or deletion, all of which are taken care of by the EZPDO core.

See this example in the tutorial on how to specify object relationships at runtime.

23 user comments

  1. EZPDO » Blog Archive » EZPDO in the last week on March 19th, 2005:

    […] y like the way to use the @orm tag to specify O/R mapping and are impressed by the ease in handling complex object relationships. Many find it easy to use the runtime API to manage objects, and the mini o […]

  2. Theo Spears on April 21st, 2005:

    In the 1:N example

    01 /**
    02 * @orm mysql://dbuser:secret@localhost/ezpdo
    03 */
    04 class Author {
    05 /**
    06 * An author many write more than one books
    07 * @orm has many Author
    08 */
    09 public $books;
    10 }

    Line 07 should probably read
    07 * @orm has many Book

  3. ezpdo4php on April 21st, 2005:

    Thanks Theo for pointing out the typo. It’s been corrected now.

  4. EZPDO: An O/R Mapping and Persistence Solution for PHP5 on April 24th, 2005:

    […] y. As a user, you don’t even need to know how it’s done. Please take a look at object relationships and also refer to the book-author example. Pos […]

  5. EZPDO » Comments on “Cross the Divide, Object Persistence in PHP” on August 15th, 2005:

    […] This is a valid point. It would be easy to have an option to auto install object relationship on the opposite direction during compiling. [Edit: EZPDO now support bidirectional links between two objects. See description of Inverse of a relationship var.] […]

  6. EZPDO » EZPDO 1.0.3 available on August 16th, 2005:

    […] Feature request (74): This is a long-awaited feature recently brought up again by monsieurcanard. See more at Inverse of a relationship var. […]

  7. Nick on January 19th, 2006:

    Typos:

    If “many� is used, the corresponding variable is call a many-valued relational variable

    “is call” –> “is called”

    The @orm has/composed_of [one|many] tags give you enough amunition to knock out any object relationships

    “amunition” –> “ammunition”

    * An author many write more than one books

    “many” –> “may”
    “books” –> “book”

    * A book may be co-authored by more than one authors

    “authors” –> “author”

    Varibles classA::$b and classB::$a forms a bidirectional link. Assigning one object to another will also install relationship on the other direction.

    “Varibles” –> “Variables”
    “forms” –> “form”
    “install relationship” –> “install the relationship”

  8. ppmm on February 14th, 2006:

    Hi, I have a question. How can I specify a foreign key referenced by another table?
    In your example, a class Book should have an attribut which references its author’s primary key id.

    However, we don’t know which attribute in a class is the primary key. The orm tag doens’t describe a primary key constraint.

  9. ezpdo4php on February 14th, 2006:

    @nick:

    thanks for picking out the typos. got them all fixed.

    @ppmm:

    ezpdo creates an auto-incremental column for object id (the primary key) when the table is created for the class. you can specify the oid column using either the ‘default_oid_column’ option in the config file or ‘oid()’ in the class level @orm tag.

  10. swimboy on June 29th, 2006:

    I’m just starting to get the hang of ezpdo, and it seems amazing! But I’m having trouble with something that seems like it should be simple. I have two objects defined with a bidirectional M:N link, $a and $b. I use the code

    $a->bs[] = $b

    to create the relation between the two objects, but how do I destroy the link between them without deleting either object from the database?

  11. ezpdo4php on June 29th, 2006:

    To remove all ‘linked’ objects:

    $a->bs = null;
    // or
    $a->bs = array().

    To remove one (or a few) of many objects in the array, simply reassign the array, for example,

    // add two into array
    $a->bs = array($b1, $2);

    // now remove one ($a1) from array
    $a->bs = array($b2);

  12. Furgas on October 26th, 2006:

    Is there a possibility to apply some attributes to relationship? For example I have a relationship N:M between class A and class B and I want to persist information about when the relation between objects of this classes have been created.
    In my database it would be 3 tables:
    table A
    a_id (PK)
    (other attributes)

    table B
    b_id (PK)
    (other attributes)

    table A_B
    a_id (FK to A(a_id))
    b_id (FK to B(b_id))
    created (timestamp telling me when this relation was created)

    One solution it to create three classes (A, B, A_B) and two 1:N realtionships: AA_B and BA_B. Is that how it should be modelled in EZPDO for now?

  13. ezpdo4php on October 26th, 2006:

    @furgas, under the current implementation, the schema of relationship tables is fixed. this may be changed in the future. the solution you came up should work. basically you end up having something like this:

    class A {
    // ……
    }

    class B {
    // ……
    }

    class AB {

    // @orm has A
    public $a;

    // @orm has B
    public $b;

    // @orm char
    public $extra_info;
    }

  14. Aleh Krutsikau on November 28th, 2006:

    I have problem.


    class Human {
    /**
    * @orm char(64)
    */
    public $first_name;
    /**
    * @orm has many Relation
    */
    public $relations = array();
    }

    class Relation {

    /**
    * @orm char(1);
    */
    public $name;

    /**
    * @orm has one Human
    */
    public $human;
    }

    This work. But


    class Relation {

    // no have name

    /**
    * @orm has one Human
    */
    public $human;
    }

    Don’t work. why?

    Fatal error: Uncaught exception ‘epExceptionDbAdodb’ with message ‘Cannot execute query: mysql error: [1264: Out of range value adjusted for column ‘e_oid’ at row 1] in EXECUTE(”INSERT INTO `ezpdo_Relation` (`e_oid`) VALUES ('’);”) ‘ in D:\term_paper\_genealogy\ezpdo\src\db\epDbAdodb.php:195 Stack trace: #0 D:\term_paper\_genealogy\ezpdo\src\db\epDb.php(338): epDbAdodb->_execute(’INSERT INTO `ez…’) #1 D:\term_paper\_genealogy\ezpdo\src\db\epDbObject.php(1552): epDb->execute(’INSERT INTO `ez…’) #2 D:\term_paper\_genealogy\ezpdo\src\db\epDbObject.php(1226): epDbObject->_execute(’INSERT INTO `ez…’) #3 D:\term_paper\_genealogy\ezpdo\src\runtime\epManager.php(1099): epDbObject->insert(Object(epClassMap), Object(epObject)) #4 D:\term_paper\_genealogy\ezpdo\src\runtime\epManager.php(2527): epManagerBase->_commit_o(Object(epObject)) #5 D:\term_paper\_genealogy\ezpdo\src\runtime\epObject.php(1868): epManager->commit(Object(epObject)) #6 D:\term_paper\_genealogy\www\addrelation.php(12): epObjectBase->commit() #7 {main} thro in D:\term_paper\_genealogy\ezpdo\src\db\epDbAdodb.php on line 195

    I have Relation. It contains firstHuman(human), secondHuman(Human), relationTtype(RelationType . It not contains any primitive type. How this make?

    Sorry for my english.

  15. ezpdo4php on November 28th, 2006:

    @Aleh Krutsikau: This is a known issue to persist a class that doesn’t have any primitive var declared. It should be fixed in next release (1.1.5).

  16. Wilker Lúcio on December 6th, 2006:

    I have a problem trying to remove a relation in my User object:

    roles as $r)

    if($r->id == $id)
    return true;

    return false;
    }

    public function hasRoleByTitle($title) {
    foreach($this->roles as $r)
    if($r->title == $title)
    return true;

    return false;
    }

    public function newsImage() {
    $path = “files/users/newsimage/” . $this->id . “.gif”;

    if(file_exists($path)) {
    return $path;
    } else {
    return dirname($path) . “/no-image.gif”;
    }
    }
    }

    my code to register/update user data:

    if($id)
    $user = array_shift($ezpdo->find(”from User where id = ?”, $id));
    else
    $user = $ezpdo->create(”User”);

    $user->nome = $nome;
    $user->login = $login;

    if($senha)
    $user->senha = md5($senha);

    if(isset($uroles)) {
    $uroles = implode(”‘ or id = ‘”, $uroles);
    $groups = $ezpdo->find(”from Role where id = ‘{$uroles}’”);

    $user->roles = $groups;
    } else {
    $user->roles = array();
    }

    in case to add roles it works fine, but for remove this don’t work…

  17. Wilker Lúcio on December 6th, 2006:

    sorry, only re-sending the code of my User class:

    class User extends Base {
    /**
    * @orm char(80)
    */
    public $name;

    /**
    * @orm char(80)
    */
    public $login;

    /**
    * @orm char(32)
    */
    public $password;

    /**
    * @orm has many Role
    */
    public $roles = array();

    public function __construct() {
    parent::__construct();
    }

    public function hasRole($id) {
    foreach($this->roles as $r)
    if($r->id == $id)
    return true;

    return false;
    }

    public function hasRoleByTitle($title) {
    foreach($this->roles as $r)
    if($r->title == $title)
    return true;

    return false;
    }

    public function newsImage() {
    $path = “files/users/newsimage/” . $this->id . “.gif”;

    if(file_exists($path)) {
    return $path;
    } else {
    return dirname($path) . “/no-image.gif”;
    }
    }
    }

  18. ezpdo4php on December 6th, 2006:

    @Wilker, maybe it’s the formatting. i can’t seem to find the intended ‘role’ removal. could you please post the code in the forums for better viewing? thanks.

  19. Wilker Lúcio on December 6th, 2006:

    i posted in the forum ;)

    the part of the to remove is this:

    if(isset($uroles)) {
    $uroles = implode(”‘ or id = ‘”, $uroles);
    $groups = $ezpdo->find(”from Role where id = ‘{$uroles}’”);

    $user->roles = $groups; //here change the relation (remove/add roles, is a new array)
    } else {
    $user->roles = array(); //here clear the relation
    }

    Thanks for attention

  20. benlatrobe on December 18th, 2006:

    Base on your example about 1:N relation,
    I have created Author $a,
    a Book $b,
    and to create the relation $a->books = array($b).
    when I want to retrieve all the Authors,
    I used $authors = $m->get(’Author’),
    and used foreach to display each instance foreach($authors as $author);
    I used $author->books to retrieve all the books been relate to that author,
    and it is not working ($author->books[0] returns nothing),
    and when I echo $author->books->count() is returns 0.
    is there anything I missed?

  21. benlatrobe on December 18th, 2006:

    I think I’ve found the problem.
    In my real implementation,
    I’ve inheritance of Author,
    and because problem have found in one table many classes,
    I guess it also cause my problem in relation.
    Am I right?

  22. XNic on January 12th, 2007:

    Ufff… Today I just finished wrote exporter from Rational Rose Static model to php class files with EZPDO syntax ;) Automatic generation avaliable ;)

  23. conrad_hex on May 22nd, 2007:

    In the “inverse” example, I think it would be simpler to understand if you stuck with the Author->Books relationship. Switching to “ClassA” and “ClassB” doesn’t buy you anything, and makes it much less clear what’s happening.

    Thanks!

Post your comments

XHTML: tags you can use <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>

Couldn't find your convert utility. Check that you have ImageMagick installed.