Love Fuel?    Donate

FuelPHP Forums

Ask your question about FuelPHP in the appropriate forum, or help others by answering their questions.
problems with autoloader and apc
  • Hi guys, getting this problem when I run php with apc and the fuelphp framework: <code>
    ErrorException [ Warning ]: Illegal offset type in isset or empty COREPATH/classes/database/connection.php @ line 55:
    </code> This is what I think is happening:
    1. a request is and following class is instantiated:
    <code>class Fuel\Core\Database_MySQLi_Connection extends \Database_Connection</code> 2. since the \Database_Connection does not exist the fuelphp adds an alias to it's own class:
    <code>abstract class Fuel\Core\Database_Connection</code> 3. When you check the parent class of the class Fuel\Core\Database_MySQLi_Connection it will be Fuel\Core\Database_Connection
    4. apc now stores things into the cache and remembers that class as:
    <code>class Fuel\Core\Database_MySQLi_Connection extends Fuel\Core\Database_Connection</code>
    5. 2nd request comes in, the class Fuel\Core\Database_MySQLi_Connection is included via the op_cache, since this class does not have it's parent in the root namespace there will never be a check to see if the \Database_Connection exists, it will load the class Fuel\Core\Database_Connection straight away.
    6. in the fuelphp code there is a check to see if the current database connection is of the type \Database_Connection. The check is done like this:
    <code>if ( ! $db instanceof \Database_Connection)</code> As of version 5.1.0 the instanceof operator does not trigger a call to the autoload , since there is no \Database_Connection in the root namespace the instanceof comparison will be false, even though the the $db object is the parent Fuel\Core\Database_Connection. I have the standard apc settings, I've also tried to see if the lazy loading could help out but it didn't work out. When APC is disabled this code works without any problems, since I'm working on a high traffic site I want to have the apc enabled so I need to try to figure this one out. Any ideas on how this can be solved? To reproduce, create a controller: <code>
    class Controller_Tester extends \Fuel\Core\Controller_Rest
    { public function action_first()
    {
    apc_clear_cache();
    $test = new Model_Tester();
    $str = $test->first();
    $this->response($str);
    } public function action_second()
    {
    $test = new Model_Tester();
    $str = $test->second();
    $this->response($str);
    } }
    </code> And a model like this: <code>
    class Model_Tester extends \Fuel\Core\Model
    { function first()
    {
    $db = \Fuel\Core\DB::query('SELECT * FROM Currency');
    $res = $db->execute();
    $str = '';
    foreach ($res as $r)
    {
    $str .= $r . "\n";
    }
    return $str;
    } function second()
    {
    $db = \Fuel\Core\Database_Connection::instance('book');
    $res = DB::query('SELECT * FROM Currency')->execute($db);
    $str = '';
    foreach ($res as $r)
    {
    $str .= $r . "\n";
    }
    return $str;
    } }
    </code> Call first /tester/first and then /tester/second , if you want to make the second work add the clear apc cache function.
  • The Fuel core knows about the alias. So when Fuel\Core\Database_MySQLi_Connection is loaded, the autoloader will load \Fuel\Core\Database_Connection because of the alias. If it wouldn't been able to do that, that 'extend' would fail and the game would have been over right there and then. I use apc on all my machines, including my development and staging machines, and I've never had this, so I find it quite unlikely that it would be code related. The line causing the warning is
    if ( ! isset(static::$instances[$name]))
    
    which would indicate $name contains something you can't use as an array index. If $name isn't passed to instance() manually by you, it will be fetched from the config like so:
    $name = \Config::get('db.active');
    
    which means you will have to check your db.php config file(s) to see if you have misconfigured something. the 'active' value should be a string and should match one of the defined database configurations.
  • Well, the $name is an instance of Fuel\Core\Database_Connection since the instanceof check failed previous to that. The instanceof does a check with the \Database_Connection but since it was never instantiated it fails and thinks that the $name is a string which it's not.. Did you try the code I sent over to reproduce the error?
  • Forgot to write what versions I use: PHP Version 5.3.4
    APC: 3.1.9
  • I have a feeling you don't fully grasp what "instanceOf" does. It will tell you if a specific object is an instance of a class. If you have
    class C extends D {};
    class B extends C {};
    class A extends B {};
    
    $var = new A();
    
    then "$var instanceOf D" will return true, as by extension $var is an instance of class D. Same here, all database connection object are instances of Database_Connection, as all connectiondrivers extend that class. Again, this is not the source of your problem. If you say that $name is "an instance of", you mean it's a object? If so, that would prove my point, as you can't use an object as an array index, which is the exact error message. Again, $name should be a string, containing the name of your instance, which should be equal to the name used in your db.php to configure the connection.
  • So this is why name is a string.. 1. Database_Query::compile is called with the a $db param that is an instance of Database_MySQLi_Connection .
    2. when it checks line ( ! $db instanceof \Database_Connection) it returns false, since the \Database_Connection is not loaded in the global namespace so a call is made to get a new database instance, the param passed to the database instance is the Database_MySQLi_Connection. That's why this warning comes up... So to get back to why this fails, on step one apc already had the Database_MySQLi_Connection in the op_cache with the signature :
    class Database_MySQLi_Connection extends \Fuel\Core\Database_Connection So you might ask yourself, why is it not just \Database_Connection, after all this is what's defined in the /fuel/core/classes/database/mysqli/connection.php file.. Well, it's because when APC gets the class from PHP APC doesn't know anything about class aliases, it just knows about the "main class". So next time when APC gets the class it will be like in the signature that I showed you above and the \Database_Connection in the global namespace is never loaded, and the instanceof does not trigger any autoload so when it does the instanceof check it will always return false and it will try to create a new instance of the Database_Connection by passing an object rather than a string. This is the php source code that stores things about aliases and there is no indication to show that a class is an alias, it's just a direct map to the memory: ZEND_API int zend_register_class_alias_ex(const char *name, int name_len, zend_class_entry *ce TSRMLS_DC) /* {{{ */
    {
    char *lcname = zend_str_tolower_dup(name, name_len);
    int ret; ret = zend_hash_add(CG(class_table), lcname, name_len+1, &ce;, sizeof(zend_class_entry *), NULL);
    efree(lcname);
    if (ret == SUCCESS) {
    ce->refcount++;
    }
    return ret;
    }
    Since PHP itself does not know it's an alias then APC will not be able to tell either.. Anyway, if somebody else stubbles upon this problem, the way to make sure it works is to override the fuelcore Database_Connection class that you put in the global namespace and from that class you extend the fuelphps Database_Connection. These are the steps: 1. create file in app/classes/database/connection.php:
    abstract class Database_Connection extends \Fuel\Core\Database_Connection{
    }
    2. add following to bootstrap:
    Autoloader::add_classes(array(
    'Database_Connection' => APPPATH.'classes/database/connection.php',
    ));
  • You're still focusing on an issue with the Fuel core in relation to APC. And I keep on telling you it's not the issue. I would dare to bet that at least 80 percent of all FuelPHP applications run in an APC environment (I even do that in my development environment), and at least 95 percent of them will use a database. If what you state is the case, then all these applications would have crashed as soon as they try to access the database, and I don't buy that. Your error is:
    ErrorException [ Warning ]: Illegal offset type in isset or empty
    which has nothing to do with APC, but with the fact that you use a value as an array index that can't be used as an index. Like for example an object. I've asked you what is passed as $name, to which you replied
    $name is an instance of Fuel\Core\Database_Connection
    . So I repeat: $name should be a string with the name of the database instance. And not the instance itself! Fix your code and your problem will be fixed.
  • Could be related to have multiple databases and APC, not sure exactly why but with the code I sent it's possible to reproduce. Understand all the points that you say, I've stepped through the code line by line with xdebug also to try to understand it and it all comes down to the steps I've described above. APC is related to this since objects are loaded from the op_cache, not from the file system therefore APC has done some altering to the code. It's clear to me that you've never tried my code out or what I've described, a bit disappointing to be honest but hey, now it works and that's the most important thing..
  • It is extremely easy to reproduce, even without APC (because for the last time that is not the issue):
    // get the default database instance
    $db = \Database_Connection::instance();
    
    // and now create the error
    \Database_Connection::instance($db);
    
    and there you have your exact error. No APC involved. I have looked at your code. Fix it. You should NEVER call \Fuel\Core classes directly. NEVER. Call them from the global namespace, as documented. Not a single piece of documentation will suggest you do use the \Fuel\Core namespace. If I change it to
    <?php
    class Controller_Apc extends \Fuel\Core\Controller_Rest
    {
    
     public function action_first()
     {
      apc_clear_cache();
      $test = new Model_Apc();
      $str = $test->first();
      $this->response($str);
     }
    
     public function action_second()
     {
      $test = new Model_Apc();
      $str = $test->second();
      $this->response($str);
     }
    
    }
    
    class Model_Apc extends \Fuel\Core\Model
    {
    
     function first()
     {
      $db = \DB::query('SELECT * FROM articles');
      $res = $db->execute();
      $str = '';
      foreach ($res as $r)
      {
       $str .= $r['name'] . "\n";
      }
      return $str;
     }
    
     function second()
     {
      $db = \Database_Connection::instance('book');
      $res = \DB::query('SELECT * FROM articles')->execute($db);
      $str = '';
      foreach ($res as $r)
      {
       $str .= $r['id'] . "\n";
      }
      return $str;
     }
    
    }
    
    it runs fine, even with APC switched on.

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

In this Discussion