Solved thread

This post is marked as solved. If you think the information contained on this thread must be part of the official documentation, please contribute submitting a pull request to its repository.

hasOne doesn't work. Relationship not stored

Hi there I have a problem. I'm tring resolve for 2 days but nothing


namespace Core\Model\Entity;

use Core\Model\Traits\Id as IdTrait;
use Core\Model\Traits\Nombre as NameTrait;
use Core\Model\Traits\Email as EmailTrait;

/**
 * Class User
 *
 * @package Core\Model\Entity
 */
class User extends Base {

    //columns
    const COL_ID = 'id';
    const COL_NAME = 'name';
    const COL_EMAIL = 'email';
    const COL_AVATAR_ID = 'avatarId';

    //relationships
    const REL_AVATAR = 'avatar';

    //traits all private properties
    use IdTrait;
    use NameTrait;
    use EmailTrait;

    /**
     * 
     * @var Multimedia|null
     */
    private $avatarId = null;

    public function initialize()
    {
        //tabla
        $this->setSource("users");

        //avatar
        $this->hasOne(
            self::COL_AVATAR_ID,
            Multimedia::class,
            Multimedia::COL_ID,
            [
                'alias' => self::REL_AVATAR,
            ]);

    }

    //others setters and getters

    public function setAvatar(Multimedia $multimedia = null) {
    $this->avatar = $multimedia;
    return $this;
    }

    public function getAvatar() {
    return $this->avatar;
    }
}   
namespace Core\Model\Entity;

use Core\Model\Traits\Id;

/**
 *
 * @package Core\Model\Entity
 */
class Multimedia extends Base
{

    const COL_ID = 'id';
    const COL_FILENAME = 'filename';

    //traits all private columns
    use Id;

    /**
     * Nombre y ruta del archivo
     * @var string|null
     */
    private $filename = null;   

}

$user = new User();
$user->setName('John');

$multimedia = new Mutlimedia();
$multimedia->setFilename('image.jpg');

$user->setAvatar($multimedia);
$user->save();

$user->getAvatar(); //yeah works!!! I get the Multimedia

//next request
$user = User::find();//find the previous created user
$user->getAvatar(); //null :( doesn't work. avatarId = Null in the db 

The user and multimedia was created but avatarId is null in the database and in the next request the user has not an avatar Someone has any idea that does not work?



5.0k

public function setAvatar(Multimedia $multimedia = null) { $this->avata = $multimedia; return $this;

avata - it may be a mistake?
edited Mar '16

Thanks Syurmo but it's a transcription mistake. I edited the post. Maybe enviroment help

  • PHP 5.6.17 x86
  • Phalcon 2.1.0 RC1 (2.1.0r 22th march) x86
  • Windows 7 64bits

it's so strange... If I find some existing records for example


$u = User::findFirst(12); //this user exists
$m = Mutlimedia::findFirst(234); //this multimedia exists
$u->setAvatar($m);
$u->update();

User is not modified and Multimedia is cloned (created new record)

edited Mar '16

Even hasMany don't work... I can belive it

I have a default setup

Phalcon sometimes I get tired



114.7k
Accepted
answer
edited Apr '16

First:

$user = User::find();

returns array.

Second - hasOne is some kind of typo and misunderstanding imho, at least most of phalcon devs don't know how to use it. It's actually working SAME AS hasMany, just returns ONE MODEL without array so actually it's for related object to access parent object. So for example your datbase schema:

user: id | name | avatar

avatar: id | name

To make 1-1 relation you need to set unique on avatar and in phalcon:

User class:

public function initialize()
{
    $this->belongsTo('avatar','Avatar','id',['alias'=>'avatar'])
}

Avatar class:

public function initialize()
{
    $this->hasOne('id','User','avatar',['alias'=>'user']);
}

And there is some example code:

$user = new User();
$user->assign([
   'name'=>'Test'
]);
$avatar = new Avatar();
$avatar->setName("asd");
$user->setAvatar($avatar);
$user->create();
/** @var Avatar[] $avatars */
$avatars=Avatar::find();
/** @var User[] $users */
$users = User::find();
foreach($users as $user2)
{
    var_dump($user2->getAvatar()->getName()); // returns asd
}
foreach($avatars as $avatar)
{
    var_dump($avatar->getUser()->getName()); // returns test
}

Also what is important if you want to implement your own getters/setters for related objects then they must be like this:

    public function getAvatar()
    {
        return $this->getRelated('avatar'); // alias name
    }

    public function setAvatar($avatar)
    {
        $this->avatar = $avatar // must be column name name
    }
    public function getUser()
    {
        return $this->getRelated('user'); // alias name
    }

If it's fixed your problem then mark it as solved.

edited Apr '16

Thanks Wojciech works!!

Other question now I set the relationship in null but not is recorded. User continues having an avatar


//Class User 
        $this->belongsTo(
            self::COL_AVATAR_ID,
            Multimedia::class,
            Multimedia::COL_ID,
            [
                'alias' => self::REL_AVATAR,
                "foreignKey" => [
                    "allowNulls" => true,
                    "message"    => "Multimedia doesnt exists.",
                ]
            ]);

public function setAvatar(Multimedia $multimedia = null)
    {
        $this->{self::REL_AVATAR} = $multimedia;
        return $this;
    }

    /**
     * @return Multimedia|null
     */
    public function getAvatar()
    {
        return $this->getRelated(self::REL_AVATAR);
    }

//example if find a user with Multimedia in avatar
$user = User::findFirst();
$user->getAvatar(); //ok its a Multimedia
$user->setAvatar(null);
$user->save(); //dont store the null relation 
//next request
$user->getAvatar();//wrong its a Multimedia not an null

Sometimes I don't understand Phalcon Thx

edited Apr '16

I know it's silly but if you have diffrent column name and alias name(for example avatar_id and avatar) then setter must set value of self::COL_AVATAR_ID. Alias is pretty much only for getter.

Well actually i think it should be consider as a bug, beacase it's some inconsistency, beacause if we remove self::REL_AVATAR from our class. and set it outside from it like here:

$user = User::findFirst();
$user->getAvatar(); //ok its a Multimedia
$user->avatar=null;
$user->save(); //dont store the null relation 
//next request
$user->getAvatar();//wrong its a Multimedia not an null

Then it's working. But if we want our own setter then we must column name.

Well i tested it and there is some problem only when setAvatar(null), if we for example change it for other avatar it's working find with relation names in setter method. But i don't have idea why it's working outside of class, in both cases there is magic __set called(if we don't have this property) in one case it works, in another don't.

Well Wojciech I think this is a little bug. I will report it.

Anyway I get a simple solutions for this


/**
     * @param Multimedia|null $multimedia
     * @return User
     */
    public function setAvatar(Multimedia $multimedia = null)
    {
        if ($multimedia === null) {
            $this->{self::COL_AVATAR_ID} = null;//set the column null too
        }

        $this->{self::REL_AVATAR} = $multimedia; //if is null or not is irrelevant
        return $this;
    }

Thanks for all!

I already reported it :)