We have moved our forum to GitHub Discussions. For questions about Phalcon v3/v4/v5 you can visit here and for Phalcon v6 here.

Bug in ORM? How to access related entities?

This is my ERD: https://minus.com/i/0CzNcg881AfW

Entities:

<?php
class Game extends \Phalcon\Mvc\Model
{
    /** @var int Unique ID */
    public $ID;
    /** @var string Title */
    public $name;
    /** @var string Vendor */
    public $vendor;
    /** @var DateTime Release date */
    public $released;
    /** @var string Thumbnail image path */
    public $image;
    /** @var string About text */
    public $about;
    /**
     * Initialize foreign keys
     */
    public function initialize()
    {
        $this->hasMany('ID','Translation','ID'); //required if ORM dumps database schema before queries?
    }
    /**
     * Get ISO 8601 formatted date
     */
    public function toISO8601(){}
    /**
     * Create DateTime object after fetch
     */
    protected function afterFetch()
    {
        $this->released = new DateTime($this->released);
    }
    /**
     * Get table name
     * @return string
     */
    function getSource()
    {
        return 'games';
    }
}
class Translation extends \Phalcon\Mvc\Model
{
    /** @var int Unique ID */
    public $ID;
    /** @var Game Related game */
    protected $game;
    /** @var User User who translates it */
    public $translator;
    /** @var string Language code (PL, EN, DE, SK) */
    public $language;
    /** @var int Completion indicator (0-100) */
    public $complete;
    /** @var Translation base translation */
    public $base;
    /**
     * Set virtual foreign keys
     */
    public function initialize()
    {
        $this->belongsTo('game','Game','ID');
    }
    /**
     * Get table name
     * @return string
     */
    function getSource()
    {
        return 'translations';
    }
}

Entity names are singular, table names are plural. We have a translation. Now we want to get related game. If Translation::game is public, {{translation.game}} returns just game's ID. I changed its visibility to protected. Now ORM gets related game. But there is a problem. If you access the same related property twice, you get a fatal error:

{{translation.game.name}} first time it is OK
{{translation.game.name}} second time - exception

Catchable fatal error: Object of class Game could not be converted to string

Phalcon bug?

How to name foreign keys columns in tables to follow standards? Should its name be the same as entity or table's name or different?

  • game
  • gameid

IMO good ORM should map all foreign keys to related objects without any workarounds.

How to write models? Like the following?

public $id; // ID small or capital letters?
public $gameid; // only ID numeric
protected $game; // useless in Phalcon but I need an information for NetBeans IDE

You don't need to declare the model variables explicitely. Translation->ID (for example) will exist automatically.

Setting the property to "protected" might also be causing the problem.

If I change the property to "public", it contains game's ID because it's not handled by Model::__get(). Then {{translation.game.name}} triggers "Notice: Trying to get property of non-object".

Try not declaring it at all. Phalcon will take care of that for you.

I removed all properties in Game and Translation class. Still "Notice: Trying to get property of non-object" because ORM doesn't handle "game" property as related entity. If I don't declare it protected or private, it's created while fetching data as public. If I define it as protected/private, yes, it works, but only one time. Read 1st post with Volt examples.

Try setting an alias for the relationship. You can alias it simply as "game".

I'm not sure why this might work, but I've noticed setting an alias does clear up some problems related to related models.



43.9k

+1 using aliases saves you a lot of headache !

Let's use an alias:

class Translation extends \Phalcon\Mvc\Model
{
    public function initialize()
    {
        $this->belongsTo('game', 'Game', 'ID', ['alias'=>'lol']);
    }
}

Let's try it in Volt:

{{translation.lol.ID}}
{{translation.lol.name}} - No error! It works!
{{translation.lol.name}} - To be sure, it still works!

Using alias helps but that's not the point. Would you call your entity "lol"? Let's analyze the relationship:

  1. In MySQL column translations.game refers to games.ID.
  2. In Translation class we set relationship belongsTo('game','Game','ID').
  3. "Game" entity name conflicts with "game" property (also column name)?

Perhaps I should change column name from "game" to "gameid" to avoid using aliases. Don't you think it's a bug in Phalcon?

{{translation.game.name}} first time it works
{{translation.game.name}} but second time it fails

How to access relationships manually without using __get() method? Possible to resolve many-to-one relationships automatically (Translation::game would contain object, not integer)?

I looked at your ERD lots of times and never noticed the fact your translation table has a "game" column. That is certainly what is causing the problem. My advice would be to change the column name as you suggested.

This isn't really a bug. What's happening is you have a relationship with the same name as a property. When you set your alias, the relationship then had a different name, so there were no problems. changing the column name should fix it.

It's worth to post it as bug. This error occurs when you access relationship property for the 2nd time. Remember it works for the 1st time.

I will change field name to gameid or game_id but we need a discussion about naming standards and usability. I was programming in Java. There was no conflict. Foreign keys were automatically mapped to related objects. So to access game's ID you write translation.getGame().getID() or translation.game.ID when you set properties public.