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

Invalid Model Relation

I'm working on a module to accept vouchers in a store. The vouchers are created in the database beforehand, and when the user types the voucher in the checkout, it is registered to him. I'm having a problem in my integration test suite with an invalid relation that pops up when the full test suite is run.

The Voucher model looks like this:


class Voucher extends \Phalcon\Mvc\Model
{
    protected $id;
    protected $code;
    protected $amount;
    protected $campaignId;
    protected $expireAt;
    protected $expiredAt;
    protected $userId;
    protected $registeredAt;
    protected $orderId;
    protected $amountUsed;
    protected $usedAt;
    protected $isActive;
    protected $createdAt;

    public function initialize()
    {
        $this->setSource('vouchers');
        $this->useDynamicUpdate(true);
        $this->skipAttributesOnCreate(['created_at', 'updated_at']);
        $this->skipAttributesOnUpdate(['created_at', 'updated_at']);
        $this->setup([
            'notNullValidations' => false,
        ]);
        $this->belongsTo('campaignId', '\MyApp\Core\Model\VoucherCampaign', 'id', ['alias' => 'campaign']);
        $this->belongsTo('userId', '\MyApp\Core\Model\User', 'id', ['alias' => 'user']);
        $this->belongsTo('orderId', '\MyApp\Core\Model\Order', 'id', ['alias' => 'order']);
    }

    public function columnMap()
    {
        return [
            'id' => 'id',
            'hash' => 'hash',
            'code' => 'code',
            'amount' => 'amount',
            'campaign_id' => 'campaignId',
            'expire_at' => 'expireAt',
            'expired_at' => 'expiredAt',
            'user_id' => 'userId',
            'registered_at' => 'registeredAt',
            'order_id' => 'orderId',
            'amount_used' => 'amountUsed',
            'used_at' => 'usedAt',
            'is_active' => 'isActive',
            'created_at' => 'createdAt',
        ];
    }

    // Getter and Setters
}

The test looks like this:

<?php

class VoucherManagerTest extends \PHPUnit_Framework_TestCase

    public function testRegisterVoucher()
    {
        $user = $this->getUserFixture();
        $voucherCampaign = $this->getCampaignFixture();

        $voucher = new Voucher();
        $voucher->setHash(md5(1));
        $voucher->setCode($voucherCode);
        $voucher->setAmount(12);
        $voucher->campaign = $voucherCampaign;
        $voucher->setExpireAt(new \DateTime('+1 day'));
        $voucher->setIsActive(true);
        $resultCreateVoucher = $voucher->create();
        $this->assertTrue($resultCreateVoucher);

        $actual = $this->voucherManager->registerVoucher($voucherCode, $user);

        // Asserts come here
    }
}

The registerVoucher method looks like this:

<?php

    /**
     * Register voucher to user
     * 
     * @param string $voucherCode
     * @param \MyApp\Core\Model\User $user
     * @return boolean
     */
    public function registerVoucher($voucherCode, User $user)
    {
        $voucher = $this->voucherRepository->getVoucherByCode($voucherCode); // The error occurs in this call

I've changed the following method for debugging purposes:

<?php

    /**
     * Get voucher by code
     * 
     * @param string $voucherCode
     * @return type
     */
    public function getVoucherByCode($voucherCode)
    {
        $v = new Voucher();
        $db = $v->getReadConnection();
        $fetch = $db->fetchAll('SELECT order_id FROM vouchers');
        var_dump('Database', $fetch);

        $voucher = Voucher::findFirst([
                'conditions' => 'isActive = true AND userId IS NULL AND code = ?1',
                'bind' => [
                    1 => $voucherCode
                ]
        ]);
        var_dump('getOrderId', $voucher->getOrderId(), 'order->getId()', $voucher->order->getId());

        return $voucher;
    }

The output looks like this:

string(8) "Database"
array(1) {
  [0]=>
  array(2) {
    ["order_id"]=>
    NULL
    [0]=>
    NULL
  }
}
string(10) "getOrderId"
NULL
string(14) "order->getId()"
string(1) "1"

From this output we can conclude that:

  1. The database is correct;
  2. The Voucher model is also correct, since $voucher->getOrderId() is null;
  3. The order relation is incorrect, since $voucher->order->getId() is 1.

I've tried refreshing the model but it didn't help. This error does not occur when running this test all by itself, so it seems it is being influenced by a previous test.

Is there some kind of cache for the relations? If so, is there any way to disable it? Is this a bug?

Maybe it has something to do with the reported issue #2040 (https://github.com/phalcon/cphalcon/issues/2040).



9.2k

Hey Carlos!

I currently develop a portal that has a lot of models and relations. I do as well test my stuff as you do. I ran into similar problems. Best practise for working with models for me was to just always pass IDs to the related models. I never use the aliased magic member variables. I use them for reading only. Secondly ->belongsTo means it really belongs to the model. so changing an orderId after reading the magic alias "order" is impossible. So I decided to change everything into a hasOne instead of a belongsTo. This makes testing a lot easier. I usually unset magic variables before I change their corresponding IDs. I do this if i have read the magic variable before writing the ID in my test cases.

Hope this helps for testing.

Hey Phalcon Team, there is a serious issue with your ORM. We're talking here about the persistence layer. I cannot trust Phalcon ORM anymore since its behavior is unpredictable. I'm already planning a refactory to completely remove Phalcon ORM from 2 application we have recently developed using it.

Help me out here @quasipickle!

Cheers!

I'm doing the same as @Richi, because indeed the ORM behavior is unpredictable. This part of the framework really needs some love, or finish the 2.0 so we all can contribute using Zephir.

Hi all,

I've managed to build a simple test that shows the bug happening.

https://github.com/camcima/phalcon-model-bug

Just clone it and run phpunit. I've built 3 versions: sqlite, mysql and postgresql, and the bug happens in all of them.

Good debugging!