PHP: Sub class static inheritance - children share static variables? - php

As you can see below I have a super class (Article) and two sub classes. I want each of the sub classes to have a static array that shall hold all it's objects.
abstract class Article
{
public static $articles = array(); // Variable for storing all the objects of each sub-class.
public function add_Object_To_Array()
{
array_push(self::$articles, $this);
}
}
class Report extends Article{}
class Interview extends Article{}
-Making two Report objects and adding them to their array:
$tmp = new Report();
$tmp->add_Object_To_Array();
$tmp = new Report();
$tmp->add_Object_To_Array();
-Making two Interview objects and adding them to their array:
$tmp = new Interview();
$tmp->add_Object_To_Array();
$tmp = new Interview();
$tmp->add_Object_To_Array();
print_r(Report::$articles);
print_r(Interview::$articles);
-The above script spits out the two arays:
Array
(
[0] => Report Object()
[1] => Report Object()
[2] => Interview Object()
[3] => Interview Object()
)
Array
(
[0] => Report Object()
[1] => Report Object()
[2] => Interview Object()
[3] => Interview Object()
)
Which looks pretty similar if you ask me, but the first one should only contain Reports, and the second one only Interviews.
1. It seems that there is only one array, why is it only one array?
2. I have a static container of objects in the same class, is this bad coding? (Any suggestions?)
I'm pretty new to php, but have a background from java.

Everything is going into only one array for two reasons:
The $articles property is only defined in the Article class.
Static class properties do not get inherited the same way you might expect if you're used to non-static properties. While they are available to the child classes, they're still referencing a single variable on the parent class - resulting in the behavior you're seeing here where both child classes are sharing the same array.
The only way to prevent this is to define a separate array in each of your child classes, like this:
class Report extends Article {
public static $articles = array();
}
class Interview extends Article {
public static $articles = array();
}
This actually makes sense if you think of the static variable declarations as code that gets run when the class is defined. Creating a static variable and assigning an empty array to it happens when the Article class is defined. It doesn't happen again when the Interview and Report classes are defined. There's only one time that an empty array is getting assigned - there's only one shared variable.
You're using self in your add_Object_To_Array() method instead of static.
self:: refers to the class it is defined in, so since your add_Object_To_Array() method is defined in the Article class, it'll refer to the Article::$articles array.
static:: Is available starting in PHP 5.3, and refers to the class it is called on. This is known as Late Static Binding, and will result in add_Object_To_Array() referring to either Report::$articles or Interview::$articles depending on the type of the object you're calling it on.
This code will reference the two arrays that we declared in the first step:
public function add_Object_To_Array() {
array_push(static::$articles, $this);
}

I thought I'd present an alternate solution that changes your design slightly, but doesn't require static definitions in each subclass. Depending on your usage, this may or may not be a better option than the first solution.
This solution uses get_class() to find the type of the object we're storing, and then stores it in the parent class - in a sub-array keyed by classname:
abstract class Article
{
public static $articles = array();
public function add_Object_To_Array()
{
// get the actual class of the current object
$class = get_class($this);
// define an empty subarray for this class if we haven't seen it before
if (!isset(self::$articles[$class])) {
self::$articles[$class] = array();
}
// add this object to the appropriate subarray
array_push(self::$articles[$class], $this);
}
}
class Report extends Article{}
class Interview extends Article{}
The above code does not use late static binding, so it'll work with any PHP version, instead of just PHP 5.3+.
When you run this version with your original example, you'll get output like this:
Array
(
[Report] => Array
(
[0] => Report Object
(
)
[1] => Report Object
(
)
)
[Interview] => Array
(
[0] => Interview Object
(
)
[1] => Interview Object
(
)
)
)
If you do have PHP 5.3 or later, you can extend this even more and use the get_called_class() function to define a getInstances() static method (in the Article class) that works like this:
public static function getInstances()
{
$class = get_called_class();
// return isset(self::$articles[$class]) ? self::$articles[$class] : array();
if (isset(self::$articles[$class])) {
return self::$articles[$class];
} else {
return array();
}
}
Then you can call this method in your example like this:
print_r(Report::getInstances());
print_r(Interview::getInstances());

Related

static properites does not exist into the object

class SubObject
{
static $static_a= 0;
public $normal_a=0;
public function __construct() {
++$this->normal_a;
++self::$static_a;
}
}
$obj1 = new SubObject();
print_r($obj1);
result is :
SubObject Object
(
[normal_a] => 1
)
My question is that why its not display output as:
SubObject Object
(
[normal_a] => 1
[static_a] => 1
)
Does static properites does not exist into the object ? Static variable or property are the way to preserver value of the variable within the context of different instance?
The static properties are attributes of the class (all instances), not an attribute of a specific instance. Here's another class ...
class Dog {
public static $species = 'mammal';
public $furColour;
public function __construct($furColour) {
$this->furColour = $furColour;
}
}
$myDog = new Dog('brown');
All dogs are mammals, in other words the entire "class" of dogs are mammals, so it makes sense to store the $species attribute at the class level (not in every instance of the class). Not all dogs have the same fur colour, that is an attribute of a specific instance of the class know as "Dog".
So, as decided by whoever designed the print_r function, it only prints attributes specific to the instance, not all the attributes of the entire class (or set of all instances). This design decision makes sense. Especially for classes that, for example, define 10's or even 100's of attributes to be used a constants: you don't want to see all these every time you print_r to debug.
FYI, if your app has a real need to get the static values, I think this works
print_r( (new ReflectionClass('SubObject'))->getStaticProperties() );
SubObject Object
(
[normal_a] => 1
[static_a] => 1
)
normal_a and static a are properties.when you dump an object it returns properties and their values.

How to get the items inside an array in Object Oriented PHP?

I have the following variable available inside a view:
$recent_posts
It is an array so I performed a foreach loop on it and var dumped the result like so:
<? foreach($recent_posts as $post): ?>
<pre><?= var_dump($post) ?></pre>
<? endforeach ?>
This is the output I received from the vardump:
Array
(
[obj] => models\Tag Object
(
[large_image:protected] =>
[small_image:protected] =>
[_obj_id] => 13493
[_validator] =>
[_values:protected] => Array
(
)
[_dirty_values:protected] => Array
(
)
[_type:protected] =>
[_properties:protected] => Array
(
)
[_is_loaded:protected] =>
[_is_static:protected] =>
)
)
How can I retrieve the values for each post. For example how can I get the large_image for a post? I tried this (without knowing what the heck I'm doing) and not surprisingly it didn't work:
<?= $post->large_image ?>
large_image is protected, you cannot access protected members out side of the class (this context).
You have two options, add a getter function for large_image or make it public.
getter is a function that exposes private or protected member, for example
public function get_large_image(){
return $this->large_image;
}
Since $post->large_image is a protected property, you are not allowed to access it outside of the class (or derived classes). I presume there might be a getter method by which you will be able to retrieve the value though (something like get_large_image() perhaps).
To determine what methods are available on the object, either view the source code of the accompanying class, or use reflection:
$refl = new ReflectionClass( $post );
var_dump( $refl->getMethods() );
If there's no method available to get the value, I would not advise you to alter the class, by making the property public (it's been made protected for a reason I presume), or alter the class at all, if it is not your own.
Rather, I would suggest, if possible, you extend the class and create a getter method for the value:
<?php
class MyTag
extends Tag
{
// I would personally prefer camelCase: getLargeImage
// but this might be more in line with the signature of the original class
public function get_large_image()
{
return $this->large_image;
}
}
Of course, this will get tricky soon, if you don't have the means to control the instantiation of the objects.
$post['obj']->large_image should do it.
That property is protected so you may not have access unless you are in the class.

Extending ArrayObject in PHP properly?

Problem: I am trying to extend PHP's ArrayObject as shown below. Unfortunately I can't get it to work properly when setting multi-dimensional objects and instead an error thrown as I have the strict settings enabled in PHP. (Error: Strict standards: Creating default object from empty value)
Question: How can I modify my class to automatically create non-existing levels for me?
The code:
$config = new Config;
$config->lvl1_0 = true; // Works
$config->lvl1_1->lvl2 = true; // Throws error as "lvl1" isn't set already
class Config extends ArrayObject
{
function __construct() {
parent::__construct(array(), self::ARRAY_AS_PROPS);
}
public function offsetSet($k, $v) {
$v = is_array($v) ? new self($v) : $v;
return parent::offsetSet($k, $v);
}
}
Taking a more oop view of your issue, you can create a class that models the concept of an multi-dimensional object.
The solution im posting doesn't extends from ArrayObject to achieve the goals you mention. As you tagged your question as oop, i think it´s important to reinforce the separation the way you store an object's state from how do you access it.
Hope this will help you achieve what you need!
From what you said, an multi-dimensional object is one that:
handles multiple levels of nested information
it does so by providing reading/writing access to the information via properties
behaves nicely when undefined properties are accessed. This means that, for example, you do the following on an empty instance: $config->database->host = 'localhost' the database and host levels are initialized automatically, and host will return 'localhost' when queried.
ideally, would be initialized from an associative arrays (because you can already parse config files into them)
Proposed Solution
So, how can those features be implemented?
The second one is easy: using PHP's __get and __set methods. Those will get called whenever an read/write is beign done on an inaccessible property (one that's not defined in an object).
The trick will be then not to declare any property and handle propertie's operations through those methods and map the property name being accessed as a key to an associative array used as storage. They'll provide basically an interface for accessing information stored internally.
For the third one, we need a way to create a new nesting level when a undeclared property is read.
The key point here is realizing that the returned value for the property must be an multi-dimensional object so further levels of nesting can be created from it also: whenever we´re asked for a property whose name is not present in the internal array, we´ll associate that name with a new instance of MultiDimensionalObject and return it. The returned object will be able to handle defined or undefined properties too.
When an undeclared property is written, all we have to do is assign it's name with the value provided in the internal array.
The fourth one is easy (see it on __construct implementation). We just have to make sure that we create an MultiDimensionalObject when a property's value is an array.
Finally, the fist one: the way we handle the second and third features allows us to read and write properties (declared and undeclared) in any level of nesting.
You can do things like $config->foo->bar->baz = 'hello' on an empty instance and then query for $config->foo->bar->baz successfully.
Important
Notice that MultiDimensionalObject instead of beign itself an array is it composed with an array, letting you change the way you store the object's state as needed.
Implementation
/* Provides an easy to use interface for reading/writing associative array based information */
/* by exposing properties that represents each key of the array */
class MultiDimensionalObject {
/* Keeps the state of each property */
private $properties;
/* Creates a new MultiDimensionalObject instance initialized with $properties */
public function __construct($properties = array()) {
$this->properties = array();
$this->populate($properties);
}
/* Creates properties for this instance whose names/contents are defined by the keys/values in the $properties associative array */
private function populate($properties) {
foreach($properties as $name => $value) {
$this->create_property($name, $value);
}
}
/* Creates a new property or overrides an existing one using $name as property name and $value as its value */
private function create_property($name, $value) {
$this->properties[$name] = is_array($value) ? $this->create_complex_property($value)
: $this->create_simple_property($value);
}
/* Creates a new complex property. Complex properties are created from arrays and are represented by instances of MultiDimensionalObject */
private function create_complex_property($value = array()){
return new MultiDimensionalObject($value);
}
/* Creates a simple property. Simple properties are the ones that are not arrays: they can be strings, bools, objects, etc. */
private function create_simple_property($value) {
return $value;
}
/* Gets the value of the property named $name */
/* If $name does not exists, it is initilialized with an empty instance of MultiDimensionalObject before returning it */
/* By using this technique, we can initialize nested properties even if the path to them don't exist */
/* I.e.: $config->foo
- property doesn't exists, it is initialized to an instance of MultiDimensionalObject and returned
$config->foo->bar = "hello";
- as explained before, doesn't exists, it is initialized to an instance of MultiDimensionalObject and returned.
- when set to "hello"; bar becomes a string (it is no longer an MultiDimensionalObject instance) */
public function __get($name) {
$this->create_property_if_not_exists($name);
return $this->properties[$name];
}
private function create_property_if_not_exists($name) {
if (array_key_exists($name, $this->properties)) return;
$this->create_property($name, array());
}
public function __set($name, $value) {
$this->create_property($name, $value);
}
}
Demo
Code:
var_dump(new MultiDimensionalObject());
Result:
object(MultiDimensionalObject)[1]
private 'properties' =>
array
empty
Code:
$data = array( 'database' => array ( 'host' => 'localhost' ) );
$config = new MultiDimensionalObject($data);
var_dump($config->database);
Result:
object(MultiDimensionalObject)[2]
private 'properties' =>
array
'host' => string 'localhost' (length=9)
Code:
$config->database->credentials->username = "admin";
$config->database->credentials->password = "pass";
var_dump($config->database->credentials);
Result:
object(MultiDimensionalObject)[3]
private 'properties' =>
array
'username' => string 'admin' (length=5)
'password' => string 'pass' (length=4)
Code:
$config->database->credentials->username;
Result:
admin
Implement the offsetGet method. If you are accessing a non exist property, you can create one as you like.
As you are extend ArrayObject, you should use the array way [] to set or get.
Copied pasted your code and it works fine on my PHP test box (running PHP 5.3.6). It does mention the Strict Standards warning, but it still works as expected. Here's the output from print_r:
Config Object
(
[storage:ArrayObject:private] => Array
(
[lvl1_0] => 1
[lvl1_1] => stdClass Object
(
[lvl2] => 1
)
)
)
It is worth noting that on the PHP docs there is a comment with guidance related to what you're trying to do:
sfinktah at php dot spamtrak dot org 17-Apr-2011 07:27
If you plan to derive your own class from ArrayObject, and wish to maintain complete ArrayObject functionality (such as being able to cast to an array), it is necessary to use ArrayObject's own private property "storage".
Detailed explanation is linked above but, in addition to offsetSet which you have and offsetGet which xdazz mentions, you also must implement offsetExists and offsetUnset. This shouldn't have anything to do with your current error but it is something you should be mindful of.
Update: xdazz' second-half has the answer to your problem. If you access your Config object as an array, it works without any errors:
$config = new Config;
$config[ 'lvl1_0' ] = true;
$config[ 'lvl1_1' ][ 'lvl2' ] = true;
Can you do that or are you restricted to the Object syntax for some reason?

How do you create a factory that dynamically extends a class so its type changes but it inherits parent methods?

What I would like to do is have a static factory function that you can give a series of attributes and it returns an object that is of a previously undeclared class that extends a known class.
Basically:
<?php
class foo {
public $a;
function increment($b = 1){
$this->a += $b;
}
}
function factory($name, $a){
//returns an object of class $name that extends foo with $this->a set to $a
}
so that if I write the code:
<?php
$bar = factory("bar",12);
$bar->increment(5);
print_r($bar);
if(is_a($bar, "foo")){
echo "is a Foo";
}
$moo = factory("moo", 4);
$moo->increment();
print_r($moo);
if(is_a($moo, "foo")){
echo "is a Foo";
}
I get: [edit]
bar Object
(
[a] => 17
)
is a Foo
moo Object
(
[a] => 5
)
is a Foo
But I don't know where to start looking for the commands necessary to do this. I think that in my factory function I need to somehow declare that the value of $name extends parent class but makes no changes to it, then constructs a new $name. That way it has all the functionality of the parent class, just a different type.
Check out the PHP reflection API has the methods you need to extract and build the new class there but, how to go about doing it and then creating an instance of it im not sure of. I do know its possible though because im pretty sure this is how Mocking works in PHPUnit. You might also want to look at the various Mock object related classes in PHPUnit to get some ideas as well.
That said unless you are actually adding/overloading methods, why would you even want to do this? Why not just use a property in the object or use a an interface? Whats the goal here?

OO PHP protected properties not available in foreach loop?

I have a validation class which I would like to use to check all values in my application are within allowed constraints.
I am passing an object to a static function within the validation class, from another class (in this case User)
function validate() {
$errors = Validation::validate($this);
}
In the validation class, I create a new object and then proceed through the properties of the passed parameter object (or at least that is what I would like to do).
function validate($object) {
$validation = new Validation();
print_r($object);
print_r('<br />');
foreach($object as $key => $val) {
print_r($val);
isset($val->maxlength) ? $validation->validateLengthNoMoreThan($val->value, $val->maxlength) : null;
isset($val->minlength) ? $validation->validateLengthAtLeast($val->value, $val->minlength) : null;
isset($val->required) && ($val->required == true) ? $validation->validateNotBlank($val->value) : null;
}
return $validation->errors;
}
I am printing out values within the function purely for test purposes. What I don't understand is why the object prints out fine outside of the foreach loop, but if I try to access the values within the loop, nothing is displayed.
This is what is displayed OUTSIDE of the foreach loop:
User Object (
[username:protected] => Property Object ( [value] => aaa [maxlength] => 12 [minlength] => 3 [required] => 1 )
[firstname:protected] =Property Object ( [value] => aaa [maxlength] => 12 [minlength] => 3 [required] => 1 )
[lastname:protected] =Property Object ( [value] => aaa [maxlength] => 12 [minlength] => 3 [required] => 1 )
)
The validation class does NOT extend the User class, so I understand why the values would not be available, just not why they are available outside of the loop but not inside of it.
Also, what would be the best way to carry out validation on protected/private properties?
Any advice/tips would be greatly appreciated.
Thanks.
From the docs ( http://us3.php.net/manual/en/language.oop5.visibility.php ):
Members declared protected can be accessed only within the class itself and by inherited and parent classes.
and from http://us3.php.net/manual/en/function.print-r.php :
print_r(), var_dump() and var_export() will also show protected and private properties of objects with PHP 5. Static class members will not be shown.
Just because print_r() can print something, doesn't mean it can be accessed by your code. consider print_r(), var_dump() and var_export() to be 'special' functions.
As Scott Saunders correctly indicates the PHP docs say you can't do it but PHP makes this object information available via var_export.
Consequently you can abuse eval to get private and protected object attributes like this:
function get_object_vars_all($obj) {
$objArr = substr(str_replace(get_class($obj)."::__set_state(","",var_export($obj,true)),0,-1);
eval("\$values = $objArr;");
return $values;
}
Here's a quick example...
class Test {
protected $protectedAttrib = "protectedVal";
private $privateAttrib = "privateVal";
public $publicAttrib = "publicVal";
}
$test = new Test();
print_r(get_object_vars_all($test));
...outputs....
Array
(
[protectedAttrib] => protectedVal
[privateAttrib] => privateVal
[publicAttrib] => publicVal
)
You really shouldn't do this because it defeats the purpose of using OO, but you can!
In addition note that many people don't like using eval for various reasons, there's a discussion here.
Use a reflection on the Object to access protected or private values:
$refl = new ReflectionObject($object);
$prop = $refl->getProperty('yourproperty');
$prop->setAccessible(true);
$value = $prop->getValue($object);
The same warning as given by DespairTyre applies to this solution: There are reasons why properties are protected or private. However, there are also situations where you don't want to change the code of a specific class but need to access it's properties...
You can get around this issue by foreach'ing the properties inside the actual object. So each object must have a validate() function, which you could enforce with an interface. E.g.:
class MyClass implements Validator {
$var1;
$var2;
public function validate(){ //required by Validator interface
$validation = new Validation();
foreach($this as $k=>$v) {
//run validations
}
return $validation->errors;
}
}
You can to use get_object_vars() function:
$vars = get_object_vars($obj);
foreach ($vars as $field=>$value){
...
}
It works well with protected fields.
If the properties of the User object are protected or private, then the foreach won't traverse them. That may explain why you cannot access them within the foreach statement.
If this is the case, it is easily solvable by using SPL ArrayIterator interface : http://www.php.net/manual/en/class.arrayiterator.php

Categories