Inherit static properties in subclass without redeclaration? - php

I'm having the same problem as this guy with the application I'm writing right now. The problem is that static properties are not being inherited in subclasses, and so if I use the static:: keyword in my main class, it sets the variable in my main class as well.
It works if I redeclare the static variables in my subclass, but I expect to have a large number of static properties and subclasses and wish to avoid code duplication. The top-rated response on the page I linked has a link to a few "workarounds", but it seems to have 404'd. Can anyone lend me some help or perhaps point me in the direction of said workarounds?

I'm not sure what specific workaround it was talking about, I can think of quite a few that can work. I personally wouldn't use these in any code. I'd recommend you take a look Is it possible to overuse late static binding in PHP? and rethink if there's a better way to do whatever you hope accomplish.
Also keep in mind my code is completely untested as I wrote it just here.
Method 1: Always use an array to figure
All the other methods are based on this one. Whereever you use the static property, you insert the code to detect the class and get it. I would only consider this if you never plan on using the property anywhere else.
$class = get_called_class();
if(isset(self::$_names[$class])) {
return self::$_names[$class];
}
else {
return static::NAME_DEFAULT;
}
Method 2: Use a getter/setting methods
If you plan on having it used in more than one spot, this method would be better. Some singleton patterns use a similar method.
<?php
class SomeParent {
const NAME_DEFAULT = 'Whatever defaults here';
private static $_names = array();
static function getName($property) {
$class = get_called_class();
if(isset(self::$_names[$class])) {
$name self::$_names[$class];
}
else {
$name = "Kandy"; // use some sort of default value
}
}
static function setName($value) {
$class = get_called_class();
self::$_names[$class] = $value;
}
}
Method 3: __callStatic
This is by far the most convenient method. However you need to have an object instance to use it (__get and __set can't be used statically). It is also slowest the method (much slower than the other two). I'm guessing that since you're already using static properties this is already a non-option. (If this method works for you, it'd be probably be better if you didn't use static properties)
<?php
class SomeParent {
const NAME_DEFAULT = 'Whatever defaults here';
private static $_names = array();
function __get($property) {
if($property == 'name') {
$class = get_called_class();
if(isset(self::$_names[$class])) {
return self::$_names[$class];
}
else {
return static::NAME_DEFAULT;
}
}
// should probably trigger some sort of error here
}
function __set($property, $value) {
if($property == 'name') {
$class = get_called_class();
self::$_names[$class] = $value;
}
else {
static::$property = $value;
}
}
}

To go a little further than Reece45's answer, you can use the following method to get the value of your array.
<?php
class MyParent {
public static $config = array('a' => 1, 'b' => 2);
public static function getConfig() {
$ret = array();
$c = get_called_class();
do {
$ret = array_merge($c::$config, $ret);
} while(($c = get_parent_class($c)) !== false);
return $ret;
}
}
class MyChild extends MyParent {
public static $config = array('a' => 5, 'c' => 3, 'd' => 4);
public function myMethod($config) {
$config = array_merge(self::getConfig(), $config);
}
}
class SubChild extends MyChild {
public static $config = array('e' => 7);
}
var_export(MyChild::getConfig());
// result: array ( 'a' => 5, 'b' => 2, 'c' => 3, 'd' => 4, )
$mc = new MyChild();
var_export($mc->myMethod(array('b' => 6)));
// result: array ( 'a' => 5, 'b' => 6, 'c' => 3, 'd' => 4, )
var_export(SubChild::getConfig());
// result: array ( 'a' => 5, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 7, )

To those that end up here wondering "WTF PHP", it seems there are a couple of reasons for this behavior and why it was kept, albeit weird:
static properties will always use the same memory reference, just like static vars do (source)
that same reference is shared between classes and subclasses (source)
it seems to be useful in some other contexts, so it isn't a "full" bug, just undocumented behavior. If it got "fixed", it would then cause compatibility issues with previously working code (a backwards-compatibility break).
There are two questions left, though:
why wouldn't late static bindings change that: probably related to #1
why that drawback we see isn't explained in the doc pages................ Well, that's PHP, right?

Related

Is it bad practice to use a global $config variable?

At the moment, I use a variable called $config, which is a big associative array containing settings for the application like folder configuration, database connection, urls to match, and so on. This variable is defined in its own file system/config.php which gets included by the main file system/index.php.
Some system functions which are located in i.e. system/request.php make use of this global config variable through global $config;
Is this bad practice, and why? What would be a better way to handle global configuration?
Pulling in variables from nowhere (global namespace) is bad because you don't know how they get their value and because every class or function can change them. So unless you use some sort of final, const, etc. to protect them, they will break the system on the long run and you'll spend hours finding out how they got their values.
For OOP an alternative solution is using a containers and dependency injection.
$container = new Container(array(
'x' => array(
'a' => 8,
'y' => array(
'b' => 123,
'z' => 456
)
)
));
$x = $container->getX();
$x->doSomething();
If there is strong coupling between the classes, because you don't want them to be something general, then you can do this:
class Container {
public function __construct(array $config){
$this->config = $config;
}
public function getX(){
return new X($this->$config['x']);
}
}
class X {
public function __construct(array $config){
$this->a = $config['a'];
$this->y = new Y($config['y']);
}
public function doSomething(){}
}
class Y {
public function __construct(array $config){
$this->b = $config['b'];
$this->z = new Z($config['z']);
}
}
class Z {
public function __construct($number){
$this->number = $number;
}
}
If these classes are loosely coupled, but you want to keep the hierarchy in the config (for example because you have multiple Y and Z instances in your container with different configs), then you can do this:
class Container {
public function __construct(array $config){
$this->config = $config;
}
public function getX(){
return new X($this->$config['x']['a'], $this->getXY());
}
public function getXY(){
return new Y($config['x']['y']['b'], $this->getXYZ());
}
public function getXYZ(){
return new Z($config['x']['y']['z']);
}
}
class X {
public function __construct($a, Y $y){
$this->a = $a;
$this->y = $y;
}
public function doSomething(){}
}
class Y {
public function __construct($b, Z $z){
$this->b = $b;
$this->z = $z
}
}
class Z {
public function __construct($number){
$this->number = $number;
}
}
Even if you develop without OOP you should send your dependencies as parameters instead of global variables.
function main($config){
$x = $config['x'];
$a = $x['a'];
$y = $x['y'];
doXSomething($a, $y);
}
function doXSomething($a, array $y){
$b = $y['b'];
$z = $y['z'];
$p = doY($b, $z);
// ...
}
function doY($b, $z){
$q = doZ($z);
// ...
}
function doZ($z){
// ...
}
$config = array(
'x' => array(
'a' => 8,
'y' => array(
'b' => 123,
'z' => 456
)
)
);
main($config);
You code should depend only on local variables, so you can be sure, that they are not changed from a different function when the app is running.
Another reason using global variables is bad is testability. If you write a test for a function, then you have to set a config variable. If it is deeply nested, then you do something like
$config = array('x' => array('y' => array('z' => 123)))`;
$result = doZ();
expect($result)->toBe(435);
Without global variables you can do
$result = doZ(123);
expect($result)->toBe(435);
Based on the file names I guess you had some sort of spaghetti code back then 5 years ago.
IMHO, as long as a global variable is not simulatenously modified by different processes (i.e. it's read-only for all of them), it is OK to have it in the global scope.
A way to simulate a read-only variable is to encapsulate it in a global object with a private variable in it and a public method that would return that variable.
Example:
class Config {
private $conf = array(/*read from file?*/...);
function getconf() {
//AFAIK, any attempt to write into the returned array will make PHP's
//core to duplicate it, so the original config won't be modified
//(as long as $conf does not contain objects, which are always addressed
//by reference)
return $conf;
}
}
As of PHP 7 array constants can be defined using define() You may use arrays in constant scalar expressions, so setup your $conf values and define it in a constant
$conf = ['thekey'=>'the value'];
define(conf, $conf);
echo conf['thekey'];

Dynamic constants in PHP?

Is there a way to create a class's constants dynamically? I know this sounds a bit odd but let me explain what I'm trying to do:
I have a Enum class who's attributes are defined by static const definitions
This class extends the PHP SplEnum class
Rather than type in each of these definitions in code I'd like to have a static initialiser go out to the database and pull the enumerated values
Maybe somethings like this:
class myEnum extends SplEnum {
public static function init () {
$myNameValuePair = DB_Functions::get_enum_list();
foreach ( $myNameValuePair as $name => $value) {
$const = array ( self , $name );
$const = $value;
}
}
}
I recognise that this won't actually work as it doesn't set CONST's but rather static variables. Maybe my whole idea is hair brained and there's a better technique to this. Anyway, any method to achieve the end goal is greatly appreciated.
UPDATE
I think it might be helpful to be a little more clear on my goals because I think it's entirely possibly that my use of Constants is not a good one. Basically I want to achieve is typical of the Enumerated list's requirements:
Constrain function signatures. I want to be able to ask for a "set" of values as an input to a function. For instance:
public function do_something ( ENUM_Types $type ) {}
Simple and Compact. Allow for a simple and compact syntax when used in code. For instance with the use of constants I might write a conditional statement something like:
if ( $my_var === ENUM_Types::TypeA ) {}
Dynamic enumeration. I'd like this enumeration to be managed through the frontend and stored in the database (I'm using wordpress admin screens for this in case anyone cares). At run time this "list" should be pulled out of the DB and made available to the code as an enumeration (or similar structure that achieves the goals above).
Wrap your "enum" values in a singleton and implement the (non-static) magic __get method:
<?php
class DynamicEnums {
private static $singleton;
private $enum_values;
public static function singleton() {
if (!self::$singleton) {
self::$singleton = new DynamicEnums();
}
return self::$singleton;
}
function __construct() {
$this->enum_values = array( //fetch from somewhere
'one' => 'two',
'buckle' => 'my shoe!',
);
}
function __get($name) {
return $this->enum_values[$name]; //or throw Exception?
}
public static function values() {
return self::singleton()->enum_values; //warning... mutable!
}
}
For bonus points, create a (non-OO) function that returns the singleton:
function DynamicEnums() {
return DynamicEnums::singleton();
}
Consumers of "DynamicEnums" would look like:
echo DynamicEnums::singleton()->one;
echo DynamicEnums()->one; //can you feel the magic?
print_r(DynamicEnums::values());
[edit] More enum-like.
Q: Is there a way to create a class's constants dynamically?
The answer is 'Yes', but don't do that :)
class EnumFactory {
public static function create($class, array $constants) {
$declaration = '';
foreach($constants as $name => $value) {
$declaration .= 'const ' . $name . ' = ' . $value . ';';
}
eval("class $class { $declaration }");
}
}
EnumFactory::create('darkSide', array('FOO' => 1, 'BAR' => 2));
echo darkSide::FOO . ' ' . darkSide::BAR;
Next question...
Q: Constrain function signatures. I want to be able to ask for a "set" of values as an input to a function. For instance: public function do_something ( ENUM_Types $type ) {}
According to the manual, in that case $type is must be an instance of the ENUM_Types class. But for constant it is impossible (they can't contain objects).
But wait... We can use such trick:
class Enum {
protected static $_constantToClassMap = array();
protected static function who() { return __CLASS__; }
public static function registerConstants($constants) {
$class = static::who();
foreach ($constants as $name => $value) {
self::$_constantToClassMap[$class . '_' . $name] = new $class();
}
}
public static function __callStatic($name, $arguments) {
return self::$_constantToClassMap[static::who() . '_' . $name];
}
}
class EnumFactory {
public static function create($class, $constants) {
$declaration = '';
foreach($constants as $name => $value) {
$declaration .= 'const ' . $name . ' = ' . $value . ';';
}
eval("class $class extends Enum { $declaration protected static function who() { return __CLASS__; } }");
$class::registerConstants($constants);
}
}
EnumFactory::create('darkSide', array('FOO' => 1, 'BAR' => 2));
EnumFactory::create('aaa', array('FOO' => 1, 'BAR' => 2));
echo (aaa::BAR() instanceof aaa) ? 'Yes' : 'No'; // Yes
echo (aaa::BAR() instanceof darkSide) ? 'Yes' : 'No'; // No
And after that we can use a "type hinting":
function doSomething(darkSide $var) {
echo 'Bu!';
}
doSomething(darkSide::BAR());
doSomething(aaa::BAR());
Q: Simple and Compact. Allow for a simple and compact syntax when used in code. For instance with the use of constants I might write a conditional statement something like: if ( $my_var === ENUM_Types::TypeA ) {}
You can use values of your pseudo-constants in such form:
if (darkSide::FOO === 1) {}
Q: Dynamic enumeration. I'd like this enumeration to be managed through the frontend and stored in the database (I'm using wordpress admin screens for this in case anyone cares). At run time this "list" should be pulled out of the DB and made available to the code as an enumeration (or similar structure that achieves the goals above).
You can init your enumeration by passing array to the EnumFactory::create($class, $constants):
EnumFactory::create('darkSide', array('FOO' => 1, 'BAR' => 2));
You could do something like Const = $$constant. Then you could set $contant = whatever. OR you could use a protected property since you want it to be dynamic and Constants are not. Example:
class Foo {
protected $test = '';
function set($bar){
$this->test = $bar;
}
function get($bar){
return $this->test;
}
}
$foobar = new Foo();
$foobar->set('test');
echo $foobar->get('test');
I do not recommend it, but eval() ... please don't.
I've modified autoloaders to automatically define Exception types that are missing or misspelled. Reason: You can catch an uncaught exception, but you cannot recover from the PHP_FATAL when instantiating a typo in your exception class.

Is there a special object initializer construct in PHP like there is now in C#?

I know that in C# you can nowadays do:
var a = new MyObject
{
Property1 = 1,
Property2 = 2
};
Is there something like that in PHP too? Or should I just do it through a constructor or through multiple statements;
$a = new MyObject(1, 2);
$a = new MyObject();
$a->property1 = 1;
$a->property2 = 2;
If it is possible but everyone thinks it's a terrible idea, I would also like to know.
PS: the object is nothing more than a bunch of properties.
As of PHP7, we have Anonymous Classes which would allow you to extend a class at runtime, including setting of additional properties:
$a = new class() extends MyObject {
public $property1 = 1;
public $property2 = 2;
};
echo $a->property1; // prints 1
Before PHP7, there is no such thing. If the idea is to instantiate the object with arbitrary properties, you can do
public function __construct(array $properties)
{
foreach ($properties as $property => $value)
{
$this->$property = $value
}
}
$foo = new Foo(array('prop1' => 1, 'prop2' => 2));
Add variations as you see fit. For instance, add checks to property_exists to only allow setting of defined members. I find throwing random properties at objects a design flaw.
If you do not need a specific class instance, but you just want a random object bag, you can also do
$a = (object) [
'property1' => 1,
'property2' => 2
];
which would then give you an instance of StdClass and which you could access as
echo $a->property1; // prints 1
I suggest you use a constructor and set the variables you wish when initialising the object.
I went from c# to PHP too, so I got this working in PHP:
$this->candycane = new CandyCane(['Flavor' => 'Peppermint', 'Size' => 'Large']);
My objects have a base class that checks to see if there's one argument and if it's an array. If so it calls this:
public function LoadFromRow($row){
foreach ($row as $columnname=>$columnvalue)
$this->__set($columnname, $columnvalue);
}
It also works for loading an object from a database row. Hence the name.
Another way, which is not the proper way but for some cases okay:
class Dog
{
private $name;
private $age;
public function setAge($age) {
$this->age = $age;
return $this;
}
public function getAge() {
return $this->age;
}
public function setName($name) {
$this->name = $name;
return $this;
}
public function getName() {
return $this->name;
}
}
$dogs = [
1 => (new Dog())->setAge(2)->setName('Max'),
2 => (new Dog())->setAge(7)->setName('Woofer')
];

How can I protect part of an array in php from being modified?

I have an array in php like this:
$myArray = array('name'=>'juank', 'age'=>26, 'config'=>array('usertype'=>'admin','etc'=>'bla bla'));
I need this array to be accesible along the script to allow changes in any field EXCEPT in the "config" field. Is there a way to protect an array or part of an array from being modified as if it where declared private inside a class? I tried defining it as a constant but it's value changes during script execution. Implementing it as a class would mean I'd have to rebuild the complete application from scratch :S
thanks!
I do not think you can do this using "pure" "real" arrays.
One way to get to this might be using some class that implements ArrayInterface ; you code would look like it's using arrays... But it would actually be using objects, with accessor methods that could forbid write-access to some data, I guess...
It would have you change a couple of things (creating a class, instanciating it) ; but not everything : access would still be using an array-like syntax.
Something like this might do the trick (adapted from the manual) :
class obj implements arrayaccess {
private $container = array();
public function __construct() {
$this->container = array(
"one" => 1,
"two" => 2,
"three" => 3,
);
}
public function offsetSet($offset, $value) {
if ($offset == 'one') {
throw new Exception('not allowed : ' . $offset);
}
$this->container[$offset] = $value;
}
public function offsetExists($offset) {
return isset($this->container[$offset]);
}
public function offsetUnset($offset) {
unset($this->container[$offset]);
}
public function offsetGet($offset) {
return isset($this->container[$offset]) ? $this->container[$offset] : null;
}
}
$a = new obj();
$a['two'] = 'glop'; // OK
var_dump($a['two']); // string 'glop' (length=4)
$a['one'] = 'boum'; // Exception: not allowed : one
You have to instanciate an object with new, which is not very array-like... But, after that, you can use it as an array.
And when trying to write to an "locked" property, you can throw an Exception, or something like that -- btw, declaring a new Exception class, like ForbiddenWriteException, would be better : would allow to catch those specifically :-)
You could make the array private and create a method to modify its contents that will check if someone doesn't try to overwrite the config key.
<?php
class MyClass {
private static $myArray = array(
'config' => array(...),
'name' => ...,
...
);
public static function setMyArray($key, $value) {
if ($key != 'config') {
$this::myArray[$key] = $value;
}
}
}
Then when you want to modify the array you call:
MyClass::setMyArray('foo', 'bar'); // this will work
MyClass::setMyArray('config', 'bar'); // this will be ignored
No, unfortunately there isn't a way to do what you're describing. Variables don't have any concept of public or private unless they are encapsulated within an object.
Your best solution is unfortunately to re-work the configuration into an object format. You might be able to use a small object inside your array that contains the private settings, which might allow you to only have to update a few places in your code, depending where that portion of the array is used.

How to initialize static variables

I have this code:
private static $dates = array(
'start' => mktime( 0, 0, 0, 7, 30, 2009), // Start date
'end' => mktime( 0, 0, 0, 8, 2, 2009), // End date
'close' => mktime(23, 59, 59, 7, 20, 2009), // Date when registration closes
'early' => mktime( 0, 0, 0, 3, 19, 2009), // Date when early bird discount ends
);
Which gives me the following error:
Parse error: syntax error, unexpected '(', expecting ')' in /home/user/Sites/site/registration/inc/registration.class.inc on line 19
So, I guess I am doing something wrong... but how can I do this if not like that? If I change the mktime stuff with regular strings, it works. So I know that I can do it sort of like that..
Anyone have some pointers?
PHP can't parse non-trivial expressions in initializers.
I prefer to work around this by adding code right after definition of the class:
class Foo {
static $bar;
}
Foo::$bar = array(…);
or
class Foo {
private static $bar;
static function init()
{
self::$bar = array(…);
}
}
Foo::init();
PHP 5.6 can handle some expressions now.
/* For Abstract classes */
abstract class Foo{
private static function bar(){
static $bar = null;
if ($bar == null)
bar = array(...);
return $bar;
}
/* use where necessary */
self::bar();
}
If you have control over class loading, you can do static initializing from there.
Example:
class MyClass { public static function static_init() { } }
in your class loader, do the following:
include($path . $klass . PHP_EXT);
if(method_exists($klass, 'static_init')) { $klass::staticInit() }
A more heavy weight solution would be to use an interface with ReflectionClass:
interface StaticInit { public static function staticInit() { } }
class MyClass implements StaticInit { public static function staticInit() { } }
in your class loader, do the following:
$rc = new ReflectionClass($klass);
if(in_array('StaticInit', $rc->getInterfaceNames())) { $klass::staticInit() }
Instead of finding a way to get static variables working, I prefer to simply create a getter function. Also helpful if you need arrays belonging to a specific class, and a lot simpler to implement.
class MyClass
{
public static function getTypeList()
{
return array(
"type_a"=>"Type A",
"type_b"=>"Type B",
//... etc.
);
}
}
Wherever you need the list, simply call the getter method. For example:
if (array_key_exists($type, MyClass::getTypeList()) {
// do something important...
}
I use a combination of Tjeerd Visser's and porneL's answer.
class Something
{
private static $foo;
private static getFoo()
{
if ($foo === null)
$foo = [[ complicated initializer ]]
return $foo;
}
public static bar()
{
[[ do something with self::getFoo() ]]
}
}
But an even better solution is to do away with the static methods and use the Singleton pattern. Then you just do the complicated initialization in the constructor. Or make it a "service" and use DI to inject it into any class that needs it.
That's too complex to set in the definition. You can set the definition to null though, and then in the constructor, check it, and if it has not been changed - set it:
private static $dates = null;
public function __construct()
{
if (is_null(self::$dates)) { // OR if (!is_array(self::$date))
self::$dates = array( /* .... */);
}
}
best way is to create an accessor like this:
/**
* #var object $db : map to database connection.
*/
public static $db= null;
/**
* db Function for initializing variable.
* #return object
*/
public static function db(){
if( !isset(static::$db) ){
static::$db= new \Helpers\MySQL( array(
"hostname"=> "localhost",
"username"=> "root",
"password"=> "password",
"database"=> "db_name"
)
);
}
return static::$db;
}
then you can do static::db(); or self::db(); from anywhere.
In PHP 7.0.1, I was able to define this:
public static $kIdsByActions = array(
MyClass1::kAction => 0,
MyClass2::kAction => 1
);
And then use it like this:
MyClass::$kIdsByActions[$this->mAction];
You can't make function calls in this part of the code. If you make an init() type method that gets executed before any other code does then you will be able to populate the variable then.
In my case, I'm using both static and nonstatic class properties, and I might even have main program code referencing the static part of the class before defining the class. Since static portions of classes don't have constructors, just add a manual constructor to initialize any variables requiring nontrivial calculation:
class A
{
static $a; // Initialized by Init()
static function Init()
{
A::$a=nontrivial();
{
}
...
A::Init(); // Initialize static part of class
...
$obj=new A(); // Using initialized class as an object
Here is a hopefully helpful pointer, in a code example. Note how the initializer function is only called once.
Also, if you invert the calls to StaticClass::initializeStStateArr() and $st = new StaticClass() you'll get the same result.
$ cat static.php
<?php
class StaticClass {
public static $stStateArr = NULL;
public function __construct() {
if (!isset(self::$stStateArr)) {
self::initializeStStateArr();
}
}
public static function initializeStStateArr() {
if (!isset(self::$stStateArr)) {
self::$stStateArr = array('CA' => 'California', 'CO' => 'Colorado',);
echo "In " . __FUNCTION__. "\n";
}
}
}
print "Starting...\n";
StaticClass::initializeStStateArr();
$st = new StaticClass();
print_r (StaticClass::$stStateArr);
Which yields :
$ php static.php
Starting...
In initializeStStateArr
Array
(
[CA] => California
[CO] => Colorado
)

Categories