2

I have a class.

<?php class WC_Swatch_Picker { private $size; private $attributes; private $selected_attributes; private $swatch_type_options; public function __construct( $product_id, $attributes, $selected_attributes ) { $this->swatch_type_options = maybe_unserialize( get_post_meta( $product_id, '_swatch_type_options', true ) ); if ( !$this->swatch_type_options ) { $this->swatch_type_options = array(); } $product_configured_size = get_post_meta( $product_id, '_swatch_size', true ); if ( !$product_configured_size ) { $this->size = 'swatches_image_size'; } else { $this->size = $product_configured_size; } $this->attributes = $attributes; $this->selected_attributes = $selected_attributes; } public function picker() { ?> <table class="variations-table" cellspacing="0"> <tbody> <?php $loop = 0; foreach ( $this->attributes as $name => $options ) : $loop++; $st_name = sanitize_title( $name ); $hashed_name = md5( $st_name ); $lookup_name = ''; if ( isset( $this->swatch_type_options[$hashed_name] ) ) { $lookup_name = $hashed_name; } elseif ( isset( $this->swatch_type_options[$st_name] ) ) { $lookup_name = $st_name; } ?> <tr> <td class="label"><label for="<?php echo $st_name; ?>"><?php echo WC_Swatches_Compatibility::wc_attribute_label( $name ); ?></label></td> <td> <?php if ( isset( $this->swatch_type_options[$lookup_name] ) ) { $picker_type = $this->swatch_type_options[$lookup_name]['type']; if ( $picker_type == 'default' ) { $this->render_default( $st_name, $options ); } else { $this->render_picker( $st_name, $options, $name ); } } else { $this->render_default( $st_name, $options ); } ?> </td> </tr> <?php endforeach; ?> </tbody> </table> <?php } 

I am trying to extend that class so that I can output the picker() method that displays <table> as a <div> instead.

Here is my attempt to extend that class.

class SSi_WC_Swatch_Picker extends WC_Swatch_Picker { public function picker() { ?> <div class="variations-table"> <?php $loop = 0; foreach ( $this->attributes as $name => $options ) : $loop++; $st_name = sanitize_title( $name ); $hashed_name = md5( $st_name ); $lookup_name = ''; if ( isset( $this->swatch_type_options[$hashed_name] ) ) { $lookup_name = $hashed_name; } elseif ( isset( $this->swatch_type_options[$st_name] ) ) { $lookup_name = $st_name; } ?> <div> <div class="label"><label for="<?php echo $st_name; ?>"><?php echo WC_Swatches_Compatibility::wc_attribute_label( $name ); ?></label></div> <div> <?php if ( isset( $this->swatch_type_options[$lookup_name] ) ) { $picker_type = $this->swatch_type_options[$lookup_name]['type']; if ( $picker_type == 'default' ) { $this->render_default( $st_name, $options ); } else { $this->render_picker( $st_name, $options, $name ); } } else { $this->render_default( $st_name, $options ); } ?> </div> </div> <?php endforeach; ?> </div> <?php } } 

My output on screen shows the <div> like I want but I get: Notice: Undefined property: SSi_WC_Swatch_Picker::$attributes and Warning: Invalid argument supplied for foreach()

I believe that it is because the parent class defines $attributes as private.

Unfortunately I cannot change the parent class.

So my noob questions is can the $attributes be accessed from the subclass somehow? I do not see a __get or __set method in the parent class so I'm guessing there isn't.

The developer is changing the private attributes to protected. So that will solve my issue of accessing the properties.

9
  • 2
    you don't have access to parent's private variables (this is why they are called private), you can save attributes into your variable in constructor though Commented Dec 30, 2014 at 0:51
  • 2
    Yeah youre going to need to modify the constructor and then keep track of everything in a separate set of member variables. Though at that point you might as well be making an entirely new class unless you need to adhere to a contract with another interface (ie. something requires an instance of WC_Swatch_Picker). Also that picker method is ugly. You shouldnt switch in and out of PHP like that in a class. I know this is a WP/WooCommerce thing, but im just throwing that out there so that you never do that on your own when you do have a choice :-) Commented Dec 30, 2014 at 0:54
  • 1
    If it is your own class, you could use protected instead of private. Then your subclass could access the parent class variables. That kind of violates encapsulation, but it's something you can do. Commented Dec 30, 2014 at 1:00
  • @Gohn67 agreed but unfortunately it's not my class, hence not being able to change the parent class. I did put a call out to the developer to see if he would change to protected but I have not heard back so trying to find an alternate workaround. Commented Dec 30, 2014 at 1:06
  • @Ken maybe you can just copy the class and replace the picker method. No need to subclass then. I don't think it's a big deal in this case. Commented Dec 30, 2014 at 1:10

2 Answers 2

1

You can use reflection:

// setup a reflector for WC_Swatch_Picker::size property $ref = new ReflectionProperty("WC_Swatch_Picker", "size"); $ref->setAccessible(true); // read the private "size" property $size = $ref->getValue($this); // update the private "size" property $ref->setValue($this, $size); 

Note: this is somewhat inefficient, so if you're going to do this a lot, you should keep a copy of the ReflectionProperty instance to be reused as needed.

Sign up to request clarification or add additional context in comments.

2 Comments

thanks for the suggestion, I will have to look into this more as its completely out of my wheelhouse. I assume I would create 2 reflections, based on the 4 private properties? Would your suggestion go inside my extended class but before the picker() method?
I think you'd probably set up 4 reflections, one for each property. And you'd probably do that in the extended class's constructor I think; that way you don't keep recreating the reflection objects. I'd also just caution that this approach is ugly and should only be used if you can't change the base class from private to protected.
1

Another possibility is to override the constructor in your child class and set your own property $attributes:

class SSi_WC_Swatch_Picker extends WC_Swatch_Picker { private $attributes; public function __construct( $product_id, $attributes, $selected_attributes ) { $this->attributes = $attributes; // Call the parent constructor. parent::__construct( $product_id, $attributes, $selected_attributes ); } // ... } 

2 Comments

This would probably cause problems because there would be two $attributes properties, one private in the base class, and one private in the subclass. Different methods would get different data. You may be able to resolve that by using pass byref (&$attributes), but I'm not sure.
You're right but there is no clean way to do that. Your solution is nice but brakes the accessibility of the property. Without modifying the parent class I cannot see a good way to do this.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.