elgg_get_entities() returning ElggObject objects instead of subclass objects

I have a plugin with a couple custom classes.  For the sake of illustration, I've provided a simple example below:

mod/my_plugin/classes/MyBaseClass.php:

class MyBaseClass extends ElggObject {

    protected function initializeAttributes() {

        parent::initializeAttributes();

 

        $this->attributes['subtype'] = "my_base_class";

    }

}

 

mod/my_plugin/classes/MyDerivedClass.php:

class MyDerivedClass extends MyBaseClass {

    protected function initializeAttributes() {

        parent::initializeAttributes();

 

        $this->attributes['subtype'] = "my_derived_class";

    }

}

 

mod/my_plugin/start.php:

function my_plugin_init() {

    add_subtype('object', 'my_base_class', 'MyBaseClass');

    add_subtype('object', 'my_derived_class', 'MyDerivedClass');

}

I have an action that creates an object of type MyDerivedClass:

$obj = new MyDerivedClass();

// some additional initialization

$obj->save();

At this point, everything is fine.  In the action I can print $obj->getSubtype() and it correctly returns 'my_derived_class'.  I can call '$obj instanceof MyDerivedClass' and it returns true.

The problem occurs later in another view, where I want to do something with all objects of type 'my_derived_class':

$objects = elgg_get_entities(array(

    'type' => 'object',

    'subtype' => 'my_derived_class',

    'limit' => 0

))

 

foreach ($objects as $next_obj) {

    if (!($next_obj instanceof MyDerivedClass)) {

        // None of my objects are an instance of MyDerivedClass -- why?

    }

}

For some reason, $next_obj is simply an ElggObject instead of MyDerivedClass.  i.e., if I call a method from MyDerivedClass, I'll get a PHP Fatal Error (Call to undefined method).

I've successfully used the same approach for dozens of other classes across many other plugins, and it's always worked correctly.  So I conclude that I'm likely doing something very silly in this case and I'm just not seeing it.  Any ideas why I'm getting ElggObject objects and not MyDerivedClass objects?

 

  • Is MyDerivedClass available when the entities are fetched?
    I'm asking because typically you'll save entities in an action, but load them in a view on a separate pageload. So if your class is only loaded in the action, and not on the other pageload, Elgg will log a warning about an invalid class then load the entity as a standard ElggObject instead.

  • Hey Matt.

    Hmm, I thought Elgg automagically included mod/my_plugin/classes/* so I didn't need to explicitly include/require those classes in any of my views that may need them?  If that's not the case, I've been blithely assuming it was (without consequence) for quite a while.  :O

    I'm looking at some of my other plugins where I do the same sort of thing, and I don't see that I'm explicitly loading anything in the classes/ directory.

    The only thing I can see that I'm doing differently from the core plugins is that I'm always calling 'add_subtype' in my plugin's init method (rather than doing either update_subtype or add_subtype in the plugin's activate.php).  But on the other hand, I do it this way for a bunch of other plugins none of them have this particular problem.

  • add_subtype() will not update a database entry if a subtype entry already exists, that is if you created an entity of a given subtype before adding add_subtype() to your code. Check entity_subtypes table to make sure, your class is registered. I think the safer pattern is: 

    if (!update_subtype($type, $subtype, $class)) {
        add_subtype($type, $subtype, $class);
    }
    

    I am not sure using this call on system init is a good idea, as it reads from the database, additionally this prevents other plugins from modifying the class.

    Additionally, if you use namespaces, make sure the full class name actually fits into the class column, as far as I remember there is still a character limit, and it's possible the class name is being truncated.

  • Yes the newer versions of Elgg have autoloading classes if you use the conventions, but some people may still be manually loading/requiring libraries like the times of old and I wasn't sure how you were going about it.  I think IK probably nailed it though.

    Your add/updated_subtype code should go in activate.php

    It will be executed when the plugin is activated and not run on every pageload which is far more efficient and as IK pointed out allows helper plugins a chance to further modify things in the future.

  • Ismayil & Matt,

    Thank you! I added a check for update_subtype before add_subtype and now my code works as expected. I had a misunderstanding of what add_subtype did, but a closer look at lib/entities.php cleared that up.

    I appreciate the help. :)

  • Seems like we should make add_subtype() just auto-update if already exists.