Claudio Albertin
Claudio Albertin

Reputation: 2116

"Could not execute Model_Hotel::__construct()" with mysql_fetch_object, properties remain uninitialized

I'm running PHP 5.3.6 with MAMP 2.0.1 on Mac OS X Lion and I'm using Kohana 3.1.3.1.

I try to override the __set() and __get() methods of the standard Kohana ORM module with my own methods. Some of my models extend my class which extends the ORM class.

<?php

class ORM_Localization extends ORM
{

    protected static $_language_cache = array();
    protected $_languages = array();

    public function __construct($id = NULL)
    {
        parent::__construct($id);

        // Load languages which are accepted by the client
        $this->_languages = array_keys(Application::languages());
    }

    public function __get($name)
    {
        /* Debugging #1 */ echo 'GET'.$name;
        /* Debugging #2 */ // exit;
        return $this->localization($name);
    }

    public function __set($name, $value)
    {
        /* Debugging #3 */ var_dump($this->_table_columns);
        /* Debugging #4 */ echo 'SET::'.$name;
        /* Debugging #5 */ // exit;
        $this->localization($name, $value);
    }

    public function localization($name, $value = NULL)
    {
        if (!array_key_exists($name, $this->_table_columns)) {
            // Load avaiable languages from database if not already done
            if (empty(ORM_Localization::$_language_cache)) {
                ORM_Localization::$_language_cache = ORM::factory('languages')
                    ->find_all()
                    ->as_array('id', 'identifier');
            }

            $languages = array();

            // Find the IDs of the languages from the database which match the given language
            foreach ($this->languages as $language) {
                $parts = explode('-', $language);

                do {
                    $identifier = implode('-', $parts);

                    if ($id = array_search($identifier, ORM_Localization::$_language_cache)) {
                        $languages[] = $id;
                    }

                    array_pop($parts);
                } while($parts);
            }

            // Find localization
            foreach ($languages as $language) {
                $localization = $this->localizations->where('language_id', '=', $language)->find();

                try {
                    if ($value === NULL) {
                        return $localization->$name;
                    } else {
                        $localization->$name = $value;
                    }
                } catch (Kohana_Exception $exception) {}
            }
        }

        if ($value === NULL) {
            return parent::__get($name);
        } else {
            parent::__set($name, $value);
        }
    }

}

But in PHP 5.3.6, I get the following error message:

Exception [ 0 ]: Could not execute Model_Hotel::__construct()

Model_Hotel extends this class and does not have an own construct. This is the code of Model_Hotel, a standard Kohana ORM model: http://pastie.org/private/vuyig90phwqr9f34crwg

With PHP 5.2.17, I get another one:

ErrorException [ Warning ]: array_key_exists() [function.array-key-exists]: The second argument should be either an array or an object

In Kohana, my model which extends my class is called by mysql_fetch_object(), somewhere deep in the orm modules code.

However, if I echo the called property in __set() and exit (#4 and #5) afterwards, it outputs "SET::id" and doesn't show the error message.

If I var_dump() $this->_table_columns (or any other property of this class, #3), I get "NULL" which is the value that this property has before it's initialized. If I repeat the same with $this->_languages, I get an empty array which should be filled with several languages. It's like the class was never initialized with the __construct. This explains the error which I get in PHP 5.2.17, because $this->_table_columns is NULL and not an array.

I can uncomment my __construct and I still get the same error, the fault has to be in my localization() method.

I searched for several days now and I have absolutely no idea what could be wrong.

Upvotes: 2

Views: 1214

Answers (3)

Aaron Bruce
Aaron Bruce

Reputation: 1170

I have a bit more information on this question, as I ran into the same problem.

1) I found a PHP bug report that documents that in mysqli and mysql_fetch_object __set() is called before __construct(). The report is from 2009 and supposedly was fixed, but in the version of 5.3 I'm running it still seems to happen. Here it is: https://bugs.php.net/bug.php?id=48487

2) If, in the process of executing your __set() method you throw an exception or some other error occurs you'll get this vague "can't call X::__construct()" message instead of the real exception/error message.

I could resolve my problem thusly:

public function __set($key, $val){
    if($constructor_has_not_run){
        $this->__construct();
    }

    //do stuff
}

I'm a bit lucky in my case because I can let the constructor run twice without any problems, but you might not be in the same boat.

Upvotes: 0

Kemo
Kemo

Reputation: 7042

ORM::__set() is expected to load cast data from mysql_fetch_object() call. You must allow it to set casted value for this cause:

public function __set($column, $value)
{
    if ( ! isset($this->_object_name))
    {
        // Object not yet constructed, so we're loading data from a database call cast
        $this->_cast_data[$column] = $value;
    }
    else
    {
        // Set the model's column to given value
        $this->localization($column, $value);
    }
}

Altough I don't like this solution either, you should be overriding ORM::set() instead.

Upvotes: 3

genesis
genesis

Reputation: 50974

__construct() is called automatically after initialization of an object.

$class = new Object;

Upvotes: 1

Related Questions