0

I am building many custom shortcodes and to off load a lot of reusable code I created an abstract base class for all my shortcodes classes to extend.

However when I try to use a reference to $this when inside add_shortcode() function I receive this error:

Fatal error: Using $this when not in object context in...

However outside of the add_shortcode() function (in other class methods) my child class is able to use $this predictably referencing itself and its parent class.

Here is a simplified version of my child shortcode class and parent abstract class

Parent Class

abstract class ABS_Shortcode {

    protected $order;

    public function __construct() {
        $this->order = '';
    }

    public function set_order_prop($value) {
        $this->order = $value;
    }

    public function get_order_prop() {
        return $this->order;
    }

    protected function setup_default_options() {
       //Setups up defaults not important for example. But does get called correctly from child class
    }
}

Child Class

class WC_Product_Categories extends ABS_Shortcode {

    protected $shortcode_base = 'wc_product_categories';

    public function __construct() {
        parent::__construct();
        // Call to method in parent class works fine
        $this->setup_default_options();
        // Child method works fine
        $this->map_shortcode();
        // Have to use __CLASS__ instead of $this here to not throw an error. Not sure why
        add_shortcode( $this->shortcode_base, array( __CLASS__, "build_shortcode") );
    }
    protected function map_shortcode() {
        // Code to handle building shortcode attributes, not necessary for example
    }

    public function build_shortcode($atts, $content = null) {
        extract( shortcode_atts (
            // Setup defaults
            array(
                'category'                      => '',
                'posts_per_page'                => '',
                'orderby'                       => '',
                'order'                         => '',
                'items_per_row'                 => '',
                'hide_empty'                    => '',
            ), $atts) );

        //Call to parent class property or method throws error when using $this
        //Fatal error: Using $this when not in object context in...
        $this->order = 'ASC';
        $this->set_order_prop('ASC');

        //Call using parent:: works to call parent method. 
        //But inside parent class when using $this throws original error
        //Fatal error: Using $this when not in object context in...
        parent::set_order_prop('ASC');

        // Try calling parent class property
        // Fatal error: Access to undeclared static property: ABS_VC_Shortcode::$order
        parent::$order;

    }
}
// Create class
$wc_categories = new WC_Product_Categories();

All these errors disappear when not called from inside the shortcode function (build_shortcode), $this behaves correctly either referencing the child or parent class properties and methods.

I am wondering why the reference to $this throws errors when using the add_shortcode() function. And how can I get around this?

thanks

  • You should never use extract, it encourages bad code and prevents automated tools and IDEs from reading code – Tom J Nowell Apr 22 '15 at 2:27
  • @George just in addition to Tom's answer: it's also not recommended to use the add_{action, shortcode} functions in the constructor, to make it easier to test (see for example gmazzap's great answer here). I think the Codex example on how to use shortcodes in a class is probably confusing for many, because it's not correct, it's missing the static keyword. – birgire Apr 22 '15 at 2:53
  • If I'm understanding it would be better to call my hooks for any class from a "public" init method as opposed to the constructor? – Benjamin Apr 22 '15 at 3:06
  • I just updated the Codex example with the missing static keyword. Yes, seperate it with an init() method, for example. @George – birgire Apr 22 '15 at 3:14
3

Your issue is in the callable object you add:

array( __CLASS__, "build_shortcode")

This translates to:

array( "WC_Product_Categories", "build_shortcode")

Which is equivalent to:

WC_Product_Categories::build_shortcode();

But build_shortcode is not a static function/method, and static functions/methods do not have a $this object, because there is no object associated.

What you actually want is:

array( $this, 'build_shortcode' )

Which translates to:

$this->build_shortcode();

Which is what you expected.

Final Notes

  • A lot of people use &$this instead. This is wrong. It's a holdover from PHP4, and doesn't do the same thing in very subtle ways. When you see this, remove the & reference
  • Do not use the extract function. It takes the contents of an array and spills it out into the current namespace. Anything could be in there. It also encourages bad code, hides were variables come from, and cripples IDE code intel and automated tooling. Avoid it at all costs.
  • I could have sworn I tried using $this inside the add_shortcode call, like I always do with class methods, but it was throwing that error so I switched to using CLASS instead and worked. What you said makes perfect sense I appreciate the help. – Benjamin Apr 22 '15 at 2:38
  • For extracting the attributes of the shortcode should I use an array_merge instead, merge $atts and a custom defaults array? – Benjamin Apr 22 '15 at 2:47
  • Yes that works well – Tom J Nowell Apr 23 '15 at 20:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.