No value of property after setting visibility

Im pretty sure the issue below is a case of me not fully understanding PHP 7.4 Typed Properties (aka PEBKAC), but after retrieving values from DB over ActiveRecord I end up with the following:

object(mister42\models\music\Collection)#90 (10) {
  ["artist":"mister42\models\music\Collection":private]=>
  uninitialized(string)
  ["created":"mister42\models\music\Collection":private]=>
  uninitialized(string)
  ["id":"mister42\models\music\Collection":private]=>
  uninitialized(int)
  ["image":"mister42\models\music\Collection":private]=>
  uninitialized(?string)
  ["image_color":"mister42\models\music\Collection":private]=>
  uninitialized(?string)
  ["image_override":"mister42\models\music\Collection":private]=>
  uninitialized(?string)
  ["status":"mister42\models\music\Collection":private]=>
  uninitialized(string)
  ["title":"mister42\models\music\Collection":private]=>
  uninitialized(string)
  ["user_id":"mister42\models\music\Collection":private]=>
  uninitialized(int)
  ["year":"mister42\models\music\Collection":private]=>
  uninitialized(int)
  ["_attributes":"yii\db\BaseActiveRecord":private]=>
  array(11) {
    ["id"]=>
    int(281014)
    ["user_id"]=>
    int(1)
    ["artist"]=>
    string(12) "Angel Theory"
    ["year"]=>
    string(4) "2004"
    ["title"]=>
    string(15) "Fatal Condition"
    ["image"]=>
    string(7725) "<<binary string deleted>>"
    NULL
    ["image_color"]=>
    string(7) "#DFE4E8"
    ["status"]=>
    string(10) "collection"
    ["created"]=>
    string(19) "2019-10-10 20:52:32"
    ["updated"]=>
    string(19) "2020-06-04 22:56:10"
  }
  ["_oldAttributes":"yii\db\BaseActiveRecord":private]=>
  array(11) {
    ["id"]=>
    int(281014)
    ["user_id"]=>
    int(1)
    ["artist"]=>
    string(12) "Angel Theory"
    ["year"]=>
    string(4) "2004"
    ["title"]=>
    string(15) "Fatal Condition"
    ["image"]=>
    string(7725) "<<binary string deleted>>"
    ["image_override"]=>
    NULL
    ["image_color"]=>
    string(7) "#DFE4E8"
    ["status"]=>
    string(10) "collection"
    ["created"]=>
    string(19) "2019-10-10 20:52:32"
    ["updated"]=>
    string(19) "2020-06-04 22:56:10"
  }
  ["_related":"yii\db\BaseActiveRecord":private]=>
  array(0) {
  }
  ["_relationsDependencies":"yii\db\BaseActiveRecord":private]=>
  array(0) {
  }
  ["_errors":"yii\base\Model":private]=>
  NULL
  ["_validators":"yii\base\Model":private]=>
  NULL
  ["_scenario":"yii\base\Model":private]=>
  string(7) "default"
  ["_events":"yii\base\Component":private]=>
  array(0) {
  }
  ["_eventWildcards":"yii\base\Component":private]=>
  array(0) {
  }
  ["_behaviors":"yii\base\Component":private]=>
  array(0) {
  }
}

How can I have uninitialized in the Object and values in _attributes at the same time? How do I get rid of the uninitialized after self::findOne?

Try updating Yii version.

This is on version 2.0.35.

<?php

namespace mister42\models\music;

use Yii;

class Test extends \yii\db\ActiveRecord
{
    private string $artist;
    private string $created;
    private int $id;
    private ?string $image;
    private ?string $image_color;
    private ?string $image_override;
    private string $status;
    private string $title;
    private string $updated;
    private int $user_id;
    private int $year;

    public static function getEntryLastModified(int $id): int
    {
        $data = self::find()->where(['id' => $id])->limit(1)->one();
        var_dump($data);exit; #shows output above
        return isset($data->updated) ? Yii::$app->formatter->asTimestamp($data->updated) : time();
    }

    public static function tableName(): string
    {
        return '{{%discogs_collection}}';
    }
}

Same result on 2.0.36-dev

My understanding is that it is thus important to make sure it is given a value before accessing a property. The best ways to enforce this are making sure the property is set in the constructor or giving them default values.

A ‘__construct’ method may be overkill, but you could assign default values which might look like this:

private string $created = "";
private int $id = 0;
private ?string $image = null;

I don’t think I should have to do that, as that would be a job of the framework. However, Yii still won’t set any values:

private string $title = '';

public static function getEntryLastModified(int $id): int
{
    $data = self::find()->where(['id' => $id])->limit(1)->one();
    var_dump($data->title);
    var_dump($data->attributes['title']);
    exit;
}
string(0) ""
string(15) "Fatal Condition"

No error, but an empty string from the assignment.

Alright. Now I remember the issue. Here it is:

I don’t think that issue applied here:

  • It is talking about user entered data from a form. The error there is at the moment of validating data.
  • I am trying to retrieve data from database (no need to re-validate), but am now facing empty object values.

I may be wrong, but Yii2 assigns db/ActiveRecord values to _attributes If they are listed in ::attributes(), and to real properties otherwise. So you have to override::attributes() to return empty array… but thus you’ll break getDirtyAttributes and everything that relies on in: smart update and updateAttributes, because they rely on _attributes and _oldAttributes.

So, I see no way to use typed properties in Yii2 the way you want.

I have been able to bring it down to a much more basic issue. Yii2 does not set the value to any property with a set visibility.

  • public $value; causes $this->value to be NULL
    object(mister42\models\test\Test)#113 (11) {
      ["value"]=>
      NULL
      ["_attributes":"yii\db\BaseActiveRecord":private]=>
      array(2) {
        ["id"]=>
        int(1)
        ["value"]=>
        string(9) "Yii2 test"
      }
      ["_oldAttributes":"yii\db\BaseActiveRecord":private]=>
      array(2) {
        ["id"]=>
        int(1)
        ["value"]=>
        string(9) "Yii2 test"
      }
      ["_related":"yii\db\BaseActiveRecord":private]=>
      array(0) {
      }
      ["_relationsDependencies":"yii\db\BaseActiveRecord":private]=>
      array(0) {
      }
      ["_errors":"yii\base\Model":private]=>
      NULL
      ["_validators":"yii\base\Model":private]=>
      NULL
      ["_scenario":"yii\base\Model":private]=>
      string(7) "default"
      ["_events":"yii\base\Component":private]=>
      array(0) {
      }
      ["_eventWildcards":"yii\base\Component":private]=>
      array(0) {
      }
      ["_behaviors":"yii\base\Component":private]=>
      array(0) {
      }
    }
    
    var_dump($data->value); //NULL
  • public $value = 'test'; causes $this->value to be string(4) "test"instead of the assign attribute value
    object(mister42\models\test\Test)#113 (11) {
      ["value"]=>
      string(4) "test"
      ["_attributes":"yii\db\BaseActiveRecord":private]=>
      array(2) {
        ["id"]=>
        int(1)
        ["value"]=>
        string(9) "Yii2 test"
      }
      ["_oldAttributes":"yii\db\BaseActiveRecord":private]=>
      array(2) {
        ["id"]=>
        int(1)
        ["value"]=>
        string(9) "Yii2 test"
      }
      ["_related":"yii\db\BaseActiveRecord":private]=>
      array(0) {
      }
      ["_relationsDependencies":"yii\db\BaseActiveRecord":private]=>
      array(0) {
      }
      ["_errors":"yii\base\Model":private]=>
      NULL
      ["_validators":"yii\base\Model":private]=>
      NULL
      ["_scenario":"yii\base\Model":private]=>
      string(7) "default"
      ["_events":"yii\base\Component":private]=>
      array(0) {
      }
      ["_eventWildcards":"yii\base\Component":private]=>
      array(0) {
      }
      ["_behaviors":"yii\base\Component":private]=>
      array(0) {
      }
    }
    
    var_dump($data->value); //string(4) "test"
  • removing the visibility works as expected
    object(mister42\models\test\Test)#113 (10) {
      ["_attributes":"yii\db\BaseActiveRecord":private]=>
      array(2) {
        ["id"]=>
        int(1)
        ["value"]=>
        string(9) "Yii2 test"
      }
      ["_oldAttributes":"yii\db\BaseActiveRecord":private]=>
      array(2) {
        ["id"]=>
        int(1)
        ["value"]=>
        string(9) "Yii2 test"
      }
      ["_related":"yii\db\BaseActiveRecord":private]=>
      array(0) {
      }
      ["_relationsDependencies":"yii\db\BaseActiveRecord":private]=>
      array(0) {
      }
      ["_errors":"yii\base\Model":private]=>
      NULL
      ["_validators":"yii\base\Model":private]=>
      NULL
      ["_scenario":"yii\base\Model":private]=>
      string(7) "default"
      ["_events":"yii\base\Component":private]=>
      array(0) {
      }
      ["_eventWildcards":"yii\base\Component":private]=>
      array(0) {
      }
      ["_behaviors":"yii\base\Component":private]=>
      array(0) {
      }
    }
    
    
    var_dump($data->value); //string(9) "Yii2 test"

If your validator is e.g. string, then add filter for all stringual attributes:

[
   [['name'], 'filter', filter => 'trim'], // <- typecast NULL/numeric to string
   [['name'], 'string'],
]

Same for numeric - typecast to numeric etc.

IMHO - strict types are putting extra burden to a developer who has to extra pay attention to all incoming types - which may be hard especially for user inputs. Nice example why over-strictyping in PHP is becoming contra-productive.

@lubosdz it is true for this use-case but strict types are really helpful in all layers not connected with user input.

This is not connected to any user input. That why it is weird to me to have to run a validator to get it working.