I'm wondering if there is an easy or recommended way of modifying just a portion of an array defined as static when doing inheritance in php. I'm using a static array to set up definition parameters in an object model to create XML query data for a web service. This particular services uses a single datatype for various parameters that consists of two values (keys) for a 'Code' and a 'Description' relating to the code.
My model allows me to specify both 'default' and an array of 'valid' values that my code can check against when creating the DOMElement. Granted, this particular usage only consists of two values so it would not be incredibly difficult to just define specific classes similar to the CodeDescriptionType to handle these, but I see where it could be valuable to create other similarly inherited classes for specific request types and some of the data structures are MUCH longer than just two key/value pairs. Here's a sample of what the code looks like:
class CodeDescriptionType extends Shipping_xmlAbstract {
public static $_elementDefs = array(
'Code' =>
array(
'type' => 'string',
'min' => 1,
'max' => 1
),
'Description' =>
array(
'type' => 'string',
'min' => 0,
'max' => 1
)
);
public function __construct($name,$elem=null) {
parent::__construct($name,null,$elem);
}
}
I added $name to this version of my xmlAbstract so that when multiple data elements are using the same structure, I can just pass in the key-name for that structure to create the appropriate DOM Element in the XML based on the CodeDescriptionType. What I would like to be able to do is to add 'default' => "value" and 'valid' = array(1,2,3,4) parameters under just the 'code' key. If all else fails, I can add an additional two parameters to this class to pass in those two but I'd be curious to know if there's a way to modify the contents of a static array when inheriting from a parent class. (array_merge won't work in the static context)
First, you ask:
I'm wondering if there is an easy or recommended way of modifying just a portion of an array defined as static when doing inheritance in php.
I'm not quite sure whether you're looking for a very domain-specific answer for your exact use case or a general answer, but here is one way you might add those properties in some subclass:
class Sub extends CodeDescriptionType {
public function __construct( $name, $elem = null ) {
parent::__construct( $name, null, $elem );
self::$_elementDefs['Code']['default'] = 'value';
self::$_elementDefs['Code']['valid'] = array( 1, 2, 3, 4 );
}
}
$c = new Sub( 'test', 'elem' );
print_r( Sub::$_elementDefs );
You can access static properties of a superclass in PHP using the self keyword; the operation itself can be performed by simply assigning new values using the appropriate PHP array access notation.
It's true that you cannot perform any operation other than an assignment when you declare static (or any member) variables, but you can certainly modify it any way you'd like after declaration.
Is this what you're looking for? This code will properly add default and valid keys to the static array defined in your parent class according to your description in the question.
You also ask:
I'd be curious to know if there's a way to modify the contents of a static array when inheriting from a parent class. (array_merge won't work in the static context)
class Super {
public static $array1 = array( 1, 2 );
}
class Sub extends Super {
public static $array2 = array( 3, 4 );
public function __construct() {
self::$array2 = array_merge( super::$array1, self::$array2 );
}
}
$s = new Sub;
print_r( Sub::$array2 );
This works just fine; the arrays are merged. Of course, the merge would only happen if you instantiated the class; you could also put it in a static method. However, since there is no such thing as a static constructor in PHP, performing the actual merge requires the member to be public (so you can perform the merge yourself, outside of the class) or putting the merge code into the constructor or a static method that you call manually.
More information/test code revealing things about static inheritance in PHP:
class Super {
public static $array1 = array( 1, 2 );
}
class Sub extends Super {
public static $array1 = array( 3, 4 );
public function __construct() {
print_r( self::$array1 );
print_r( parent::$array1 );
print_r( super::$array1 );
}
}
This will print exactly as expected: parent and super will both spit out the array defined in the superclass, while self will spit out the array defined in the subclass.
I ran into another need to create a new set of definitions for my DOMDocument wrapper mentioned in this post. I still haven't found any built-ins to merge array elements but due to the nature of this particular application I don't really want to just write standard inherited properties either because that would kind of defeat the purpose of writing a wrapper around DOMDocument in the first place. (If I end up going that route, I'll just write something to translate PHP object properties to DOMDocument elements and attributes - which may yet happen if I can find a way to easily build the defs from the xsd automatically)
I forgot this thread existed but essentially ended up doing what the answers suggested now that I have a better handle on the scopes in PHP after working with it for a few years. The new definitions I'm creating have a number of inherited (extension) types in the xsd to start with. So defining definitions in the PHP to help build the eventual elements in the DOMDocument being wrapped at multiple levels in the models is required anyway.
In the abstract parent base class (the one that actually does the dirty-work of wrapping the DOMDocument and building the various sub DOMElement pieces-parts) I added the following function:
public static function cascadeDefs($layer) {
if(!class_exists($layer)) {
$layerNS = __NAMESPACE__ . '\\DataTypes\\' . $layer;
if(class_exists($layerNS))
$layer = $layerNS;
}
if(class_exists($layer))
foreach($layer::$_elementDefs as $k => $v)
if(!isset(static::$_elementDefs[$k]))
static::$_elementDefs[$k] = $v;
}
I considered having it back-trace to figure out what the calling class was rather than passing in the $layer variable, but found it more useful to pass the class name in, as it allows me to also 'import' definitions from a class outside the PHP inheritance structure.
So, for example, if I have a one definition for a 'customerPaymentProfileType' type which shares base element (customerPaymentProfileBaseType class) names with a couple of other variations of payment profiles. Then there is another type which has an additional parameter but is otherwise identical to the customerPaymentProfileType.
One of the things I do with these definitions is I assign the 'element name' that is to show up in the final xml output from the DOMElement created in any sub-branch of the final xml within the main constructor of the base abstract class. Most of the 'base' definitions in the XML amount to the equivalent of an abstract class in PHP, not being used directly to define an element type but instead only serving as an extension for other types. customerPaymentProfile, however, is used directly as an XML type in one definition.
I also have an issue that when the Extended (Ex) type definition is used, the XML element name is different and normally I pass the name it at the first non-abstract level.
So rather than inherit from the simple type, I can inherit from baseType (which extends the main wrapper and includes the core element definitions), add the one unique to the extended definition, define the [different] element name, then import the additional parameters in the basic version.
// technically extends customerPaymentProfileType but that is not an abstract and has different naming
class customerPaymentProfileExType extends customerPaymentProfileBaseType
{
//public static $_attrNames = array();
public static $_elementDefs = array(
'customerPaymentProfileId' => array(
'type' => 'string', // numeric string
'min' => 0
)
);
/**
* inherited child of the xmlAbstract::__construct to build a request XML data element
* #param null|array $elem
*/
public function __construct($elem=null) {
parent::__construct('paymentProfile',null,$elem);
self::cascadeDefs(get_parent_class(__CLASS__));
// add types from customerPaymentProfileType also
self::cascadeDefs('customerPaymentProfileType');
}
}
under normal circumstances, the inherited array elements can be imported in from the parent with just the call to:
self::cascadeDefs(get_parent_class(__CLASS__));
Related
I'm pretty new to PHP and am trying to understand how to access properties of objects.
I have a class called Dog and in it's class I have the following:
class Dog extends Resource {
protected $_data = array(
'dog_id' => array(
'type' => Resource::PROPERTY_TYPE_INTEGER,
'value' => null
),
'owner_id' => array(
'type' => Resource::PROPERTY_TYPE_INTEGER,
'value' => null
)
)
}
Now I'm trying to use this data in a controller.
$drm = DogResouceModel::create();
$fido = array_values($drm->find($dog_id))[0];
This works and I get the desired Dog object as $fido. How would I get to the owner_id? Using XDebug in Eclipse I can see that the values are all attached to $fido, I just can't seem to figure out how to get to them. I've tried things along the lines of:
$owner_id = $fido["_data"]["owner_id"];
$owner_id = $fido->_data->owner_id;
The common way to do this is to implement getter/setter methods:
class Dog extends Resource
{
protected $data = [];//initialize as before (no need for the underscore)
public function getOwnerId()
{
return $this->data['owner_id']['value'];
}
//setter example
public function setOwnerId($id)
{
if (!is_numeric($id)) {
throw new InvalidArgumentException('Owner id is numeric');
}
$this->data['owner_id']['value'] = (int) $id;
return $this;
}
}
The rational behind this is that getters and setters allow you to validate data, normalize it and define, in greater levels of detail, how an object should behave if, for example, a value isn't available (ie: if owner_id is null, you might want to throw an exception in some cases).
The fact that you can see them in an XDebug session is simply because the data is set, and XDebug allows you to inspect the state of all objects. It doesn't follow the rules of inheritance in that it can see all values that are in play, regardless of them being private, protected or public. Xdebug is a PHP extension, it's written in C and sits on top of the Zend engine (the PHP internals), it is not bound by the syntax, grammar or whatever else that is part of PHP.
Think of Xdebug as an MRI scanner or X-ray machine: it allows you to see what is going on underneath the skin. It's a tool doctors use, like debuggers are tools that developers use to diagnose problems.
Looking at what you've tried, even if $data were public, neither approach would've worked:
$owner_id = $fido["_data"]["owner_id"];
The problem here is that this plies that $fido is an array, or an object that implements the ArrayAccess interface. We don't know what Resource looks like, so maybe it does (more on that later).
$owner_id = $fido->_data->owner_id;
This is closer to what you could do if _data were public, but only if its value were an object, too (which it isn't, it's an array).
So what should you write if $_data were public? Well simply this:
$ownerId = $fido->_data['owner_id']['value'];//to get the value
$ownerIdArray = $fido->_data['owner_id'];//will be an array
If Resource implements the ArrayAccess interface, chances are it relies on the $_data property, and returns the values for each key. In that case you might be able to write:
$ownerId = $fido['owner_id']
or even iterate over the object:
foreach ($fido as $propery => $value) {
//do stuff
}
Check the PHP documentation for details on the ArrayAccess interface
Because _data is protected, it's not possible to directly access it from outside the Dog or Resource classes. Instead, you should add an accessor to either Dog or Resource (as appropriate) that retrieves the requested data.
class Dog extends Resource {
public function getOwerId() {
return $this->_data['owner_id']['value'];
}
}
You could also force _data to be accessible by hacking it using the Reflection API, but that's really not something you should be doing on production code.
OOP Programming PHP
You are using OOP, grats dude (or dudette :P)!
PHP is great for this, however it has it's limitations, for example, accessing properties.
You have used protected $_data = array( /* your info */ );, this means it is only visible to the class and any class that extends this.
For example, if you had these classes:
class Foo
{
protected $foo = "Foo";
}
class Bar extends Foo
{
private $bar = "bar";
}
You could add a function, as such:
public function getFooBar()
{
return "{$this->foo} {$this->bar}";
}
Inside the Bar class, that simply returns the value of $foo and $bar, meaning if you use this code:
$bar = new Bar();
print $bar->getFooBar(); // prints "Foo bar"
But you cannot do this:
print $bar->foo;
print $bar->bar;
These are seen as private properties of the Bar class and as such cannot be accessed outside of the class.
When you use protected or private properties, if you need to access them outside the class, create a get function for them.
Why does the debugger show the private and protected properties?
When using a debugger such as XDebug, it is designed to show all properties within an object, this is so that the person debugging (you), can see exactly what's in an object and be able to create some sort of functionality to access this if needed. Without this, debugging tools would be rather useless.
I hope this helps you out :P
I am in a paradox with the following code snippet and I am not sure what to call it.
I have defined a very simple class which has no variable yet. Now, in the constructor, I am accepting an array of keys and values and assigning variables on the fly like this, using a foreach loop:
class Food{
function Food($construct){
foreach($construct as $key=>$value){
$this->$key = $value;
}
}
}
If I created an instance now with the input like so:
$food = new Food(array('name' => 'chicken' , 'unit' => 'kg' , 'calorie' => 10000));
I would have got:
var_dump($food);
object(Food)[1]
public 'name' => string 'chicken' (length=7)
public 'unit' => string 'kg' (length=2)
public 'calorie' => int 10000
How is this even possible?
This is possible in PHP and is the default implementation unless you have stated otherwise (via __get() and __set()). Creating public members on the fly is possible only for the current instance, it does not create it for the class overall. And it's possible from inside the class or from outside (e.g. via the instance).
$food->smth = 100;
will create public smth
An empty __set() magic method can prevent this behavior
public function __set($name, $value) { }
For the second question:
It is not safe and using public members at all is not safe (unless you have really good reason for exposing your properties). The convention says you have to have mostly private/protected members with accessors for them. So you can have the controll for the class from inside the class rather than from the instance of it. And for many other reasons, including code reusability.
That variable isn't uninitialized, it's just undeclared.
Declaring variables in a class definition is a point of style for readability. Plus you can set accessibility (private or public).
Anyway, declaring variables explicitly has nothing to do with OOP, it's programming-language-specific. In Java you can't do that because variables must be declared explicitly.
reference - http://stackoverflow.com/questions/1086494/when-should-i-declare-variables-in-a-php-class
Would it make more sense in the following situation to create a new object type instead of using an array:
I have a simple structure with two values: Name & Age
$ages = array(
array(
'name' => 'bill',
'age' => 22
),
array(
'name' => 'bob',
'age' => 50
),
// etc
);
The problem is that this structure is generated in one class and passed through a controller and then used in another class.
Therefore it feels like those two classes are tied together as one must know the array keys of this structure that is generated in another.
Is there any design pattern that solves this?
I don't think you need (of even if there is) a design pattern for generating an object/data structure in one class, and consuming it in another. It is the basic premise of working with classes. Also, as alfasin mentioned, working with objects are neater than arrays. Also in the future, you could have better interactions with other objects, if such a need arises.
I would go all the way and define a Person model class. Something like this
Class Person {
protected _age;
protected _name;
public function __construct($name = null, $age = null) {
if ($name) setName($name);
if ($age) setAge($age);
}
public function getName() {
return $this->_name;
}
public function setName($name) {
return $this->_name = (string) $name;
}
public function getAge() {
return $this->_age;
}
public function setAge($age) {
return $this->_age = (int) $age;
}
}
You can then use this class to create your datastructure as follows:
$persons = array(new Person('bill', 22),new Person('bob', 50));
This array can then be passed by your controller and used like this in the view:
foreach($persons as $person) {
echo $person->getName();
echo $person->getAge();
}
This design pattern is called MVC (Model View Controller) and very popular and well documented, although interpretations my differ.
This might look like overkill for your simple structure, but it will save you a lot of time when having to extend your code in the future.
(the code is not tested, but i think it should work just fine)
Since it's a simple structure you can work with it, but in general it's recommended to work with objects. If you'll want to add fields in the future, add levels (nested arrays) - maintenance will be easier as you're program will be more modular and less coupled:
// I - easier to use
$bill_age = $ages->get_age('bill');
// function get_age() is implemented in the class which
// makes you code easier to maintain and easier to understand
// II - this implementation is dependent on the structure of $ages
// if you'll change $ages - you'll have to change all the uses:
$bill_arr = $ages[0];
$bill_age = $bill_arr['age'];
Further, if you'll have calls like II on different places in the code, changing $ages structure will result in changing all these places, while if you implement I - you have only one place in the code to change (the implementation of get_age($name) inside the class).
I think you can have one class which will contain the Keys for this structure and then both class will share this class to get key instances.
In that way you won't have to keep track of keys in both the classes. Moreover, anytime you can add more keys without changes much here n there. Less Coupling and More Flexibility.
I understand that multiple inheritance1 is simply not supported in PHP, and while many "hacks" or workarounds exist to emulate it, I also understand that an approach such as object composition is likely more flexible, stable, and understandable than such workarounds. Curiously, PHP's 5.4's traits will be the fitting solution, but we're not quite there yet, are we.
Now, this isn't simply an "amidoinitrite?" question, but I'd like to ensure that my approach makes sense to others.
Given I have classes Action and Event (there are more, but we'll keep it brief) and they both require (near) identical methods, the obvious approach would be; create a common base class, extend and go; they are, after all, conceptually similar enough to constitute being siblings in a class hierarchy (I think)
The problem is Event needs to extend a class (Exception) that itself cannot extend anything. The methods (and properties) all pertain to "attribute" values, we'll call them "options" and "data", where "options" are values stored at class level, and "data" are values stored at instance level.
With exception of (no pun intended) the Exception class, I can simply create a common class that all pertinent objects extend in order to inherit the necessary functionality, but I'm wondering what I can do to avoid the seemingly inevitable code duplication in Event; also, other classes that are not conceptually similar enough to be siblings need this functionality.
So far the answer seems to be, using the object composition approach, create a Data class, and manage it at two points:
At object instantiation, create a Data instance to be used with the object as "data".
At some point (through a static initialize() method perhaps) create a Data instance to be used statically with the class as "options".
Interfaces, named IData and IOption for example, would be implemented by classes needing this functionality. IData simply enforces the instance methods of the Data class on the consumer, and calls would be forwarded to the instance Data property object, whereas IOption would enforce similarly named methods (substitute "data" for "option") and those methods would forward to the static Data property object.
What I'm looking at is something like this (the methods are somewhat naive in appearance, but I've slimmed them for brevity here):
interface IData{
public function setData($name, $value);
public function putData($name, &$variable);
public function getData($name = null);
}
interface IOption{
public static function initializeOptions();
public static function setOption($name, $value);
public static function setOptions(Array $options);
public static function getOptions($name = null);
}
class Data implements IData{
private $_values = array();
public function setData($name, $value){
$this->_values[$name] = $value;
}
public function putData($name, &$variable){
$this->_values[$name] = &$variable;
}
public function getData($name = null){
if(null === $name){
return $this->_values;
}
if(isset($this->_values[$name])){
return $this->_values[$name];
}
return null;
}
}
class Test implements IData, IOption{
private static $_option;
private $_data;
public static function initializeOptions(){
self::$_option = new Data();
}
public static function setOption($name, $value){
self::$_option->setData($name, $value);
}
public static function setOptions(Array $options){
foreach($options as $name => $value){
self::$_option->setData($name, $value);
}
}
public static function getOptions($name = null){
return self::$_option->getOptions($name);
}
public function __construct(){
$this->_data = new Data();
}
public function setData($name, $value){
$this->_data->setData($name, $value);
return $this;
}
public function putData($name, &$variable){
$this->_data->putData($name, $variable);
return $this;
}
public function getData($name = null){
return $this->_data->getData($name);
}
}
So where do I go from here? I can't shake the feeling that I'm moving away from good design with this; I've introduced an irreversible dependency between the client classes and the storage classes, which the interfaces can't explicitly enforce.
Edit: Alternatively, I could keep the reference to Data (wherever necessary) public, eliminating the need for proxy methods, thus simplifying the composition. The problem then, is that I cannot deviate from the Data class functionality, say for instance if I need to make getData() act recursively, as this snippet exemplifies:
function getData($name = null){
if(null === $name){
// $parent_object would refer to $this->_parent
// in the Test class, given it had a hierarchal
// implementation
return array_replace($parent_object->getData(), $this->_values);
}
// ...
}
Of course, this all boils down to separate definitions on a per-class basis, to support any deviation from a default implementation.
I suppose the end-all here, is that I'm having trouble understanding where code duplication is "alright" (or more accurately, unavoidable) and where I can extract common functionality into a container, and how to reference and use the contained functionality across classes, deviating (typically negligibly) where necessary. Again, traits (in my cursory testing on beta) seem to be a perfect fit here, but the principle of composition has existed long before 5.4 (and PHP entirely for that matter) and I'm certain that there is a "classic" way to accomplish this.
1. Interestingly, the page for multiple inheritance at Wikipedia has been flagged for copyright investigation. Diamond problem seemed like a fitting substitute.
EDIT: I've just read your question again and you seem to be suggesting that you are actually using the getters and setters to manipulate the data. If this is the case then could you provide me with more detail on what it is that you're trying to achieve. I suspect that how you've decided to model your objects and data is what has led you to this situation and that an alternative would solve the problem.
You don't need multiple inheritance. You don't even need most of the code you've written.
If the purposes of classes 'Data' and 'Option' is to simply store data then use an array. Or, if you prefer the syntax of an object cast the array to an object or an instance of stdClass:
$person = (object)array(
'name' => 'Peter',
'gender' => 'Male'
);
OR
$person = new stdClass;
$person->name = 'Peter';
$person->gender = 'Male';
Having a whole bunch of getters and setters that don't actually do anything to the data are pointless.
I am creating a class which I will use to store and load some settings. Inside the class all settings are stored in an array. The settings can be nested, so the settings array is a multidimensional array. I want to store and load the settings using the magic methods __get and __set, so the settings can act as class members. However, since I'm using nested methods, I can't get the __set method to work when I try to access a nested setting.
The class is like this:
class settings
{
private $_settings = array();
//some functions to fill the array
public function __set($name, $value)
{
echo 'inside the __set method';
//do some stuff
}
}
And the code to use this class:
$foo = new settings();
//do some stuff with the class, so the internal settings array is as followed:
//array(
// somename => somevalue
// bar => array (
// baz = someothervalue
// qux = 42
// )
// )
$foo->somename = something; //this works, __set method is called correctly
$foo->bar['baz'] = somethingelse; //Doesn't work, __set method isn't called at all
How can I get this last line to work?
When accessing an array using this method, it actually goes through __get instead. In order to set a parameter on that array that was returned it needs to be returned as a reference: &__get($name)
Unless, what you mean is that you want each item that is returned as an array to act the same way as the parent object, in which case you should take a look at Zend Framework's Zend_Config object source for a good way to do that. (It returns a new instance of itself with the sub-array as the parameter).
This would work:
$settings = new Settings();
$settings->foo = 'foo';
$settings->bar = array('bar');
But, there is no point in using magic methods or the internal array at all. When you are allowing getting and setting of random members anyway, then you can just as well make them all public.
Edit after comments (not answer to question above)
Like I already said in the comments I think your design is flawed. Let's tackle this step by step and see if we can improve it. Here is what you said about the Settings class requirements:
settings can be saved to a file or a database
settings might need to update other parts of the application
settings need to be validated before they are changed
should use $setting->foo[subsetting] over $setting->data[foo[subsetting]]
settings class needs to give access to the settings data for other classes
first time an instance is made, the settings need to be loaded from a file
Now, that is quite a lot of things to do for a single class. Judging by the requirements you are trying to build a self-persisting Singleton Registry, which on a scale of 1 (bad) to 10 (apocalyptic) is a level 11 idea in my book.
According to the Single Responsibility Principle (the S in SOLID) a class should have one and only reason to change. If you look at your requirements you will notice that there is definitely more than one reason to change it. And if you look at GRASP you will notice that your class takes on more roles than it should.
In detail:
settings can be saved to a file or a database
That is at least two responsibilites: db access and file access. Some people might want to further distinguish between reading from file and saving to file. Let's ignore the DB part for now and just focus on file access and the simplest thing that could possibly work for now.
You already said that your settings array is just a dumb key/value store, which is pretty much what arrays in PHP are. Also, in PHP you can include arrays from a file when they are written like this:
<?php // settings.php
return array(
'foo' => 'bar'
);
So, technically you dont need to do anything but
$settings = include 'settings.php';
echo $settings['foo']; // prints 'bar';
to load and use your Settings array from a file. This is so simple that it's barely worth writing an object for it, especially since you will only load those settings once in your bootstrap and distribute them to the classes that need them from there.
Saving an array as an includable file isnt difficult either thanks to var_export and file_put_contents. We can easily create a Service class for that, for example
class ArrayToFileService
{
public function export($filePath, array $data)
{
file_put_contents($filePath, $this->getIncludableArrayString($data));
}
protected function getIncludableArrayString($data)
{
return sprintf('<?php return %s;', var_export($data, true));
}
}
Note that I deliberatly did not make the methods static despite the class having no members of it's own to operate on. Usign the class statically will add coupling between the class and any consumer of that class and that is undesirable and unneccessary.
All you have to do now to save your settings is
$arrayToFileService = new ArrayToFileService;
$arrayToFileService->export('settings.php', $settings);
In fact, this is completely generic, so you can reuse it for any arrays you want to persist this way.
settings might need to update other parts of the application
I am not sure why you would need this. Given that our settings array can hold arbitrary data you cannot know in advance which parts of the application might need updating. Also, knowing how to update other parts of the application isnt the responsiblity of a data container. What we need is a mechanism that tells the various parts of the application when the array got updated. Of course, we cannot do that with a plain old array because its not an object. Fortunately, PHP allows us to access an object like an array by implementing ArrayAccess:
class HashMap implements ArrayAccess
{
protected $data;
public function __construct(array $initialData = array())
{
$this->data = $initialData;
}
public function offsetExists($offset)
{
return isset($this->data[$offset]);
}
public function offsetGet($offset)
{
return $this->data[$offset];
}
public function offsetSet($offset, $value)
{
$this->data[$offset] = $value;
}
public function offsetUnset($offset)
{
unset($this->data[$offset]);
}
public function getArrayCopy()
{
return $this->data;
}
}
The methods starting with offset* are required by the interface. The method getArrayCopy is there so we can use it with our ArrayToFileService. We could also add the IteratorAggregate interface to have the object behave even more like an array but since that isnt a requirement right now, we dont need it. Now to allow for arbitrary updating, we add a Subject/Observer pattern by implementing SplSubject:
class ObservableHashMap implements ArrayAccess, SplSubject
…
protected $observers;
public function __construct(array $initialData = array())
{
$this->data = $initialData;
$this->observers = new SplObjectStorage;
}
public function attach(SplObserver $observer)
{
$this->observers->attach($observer);
}
public function detach(SplObserver $observer)
{
$this->observers->detach($observer);
}
public function notify()
{
foreach ($this->observers as $observers) {
$observers->update($this);
}
}
}
This allows us to register arbitrary objects implementing the SplObserver interface with the ObservableHashMap (renamed from HashMap) class and notify them about changes. It would be somewhat prettier to have the Observable part as a standalone class to be able to reuse it for other classes as well. For this, we could make the Observable part into a Decorator or a Trait. We could also decouple Subject and Observers further by adding an EventDispatcher to mediate between the two, but for now this should suffice.
Now to notify an observer, we have to modify all methods of the class that should trigger a notification, for instance
public function offsetSet($offset, $value)
{
$this->data[$offset] = $value;
$this->notify();
}
Whenever you call offsetSet() or use [] to modify a value in the HashMap, any registered observers will be notified and passed the entire HashMap instance. They can then inspect that instance to see whether something important changed and react as needed, e.g. let's assume SomeComponent
class SomeComponent implements SplObserver
{
public function update(SplSubject $subject)
{
echo 'something changed';
}
}
And then you just do
$data = include 'settings.php';
$settings = new ObservableHashMap($data);
$settings->attach(new SomeComponent);
$settings['foo'] = 'foobarbaz'; // will print 'something changed'
This way, your settings class needs no knowledge about what needs to happen when a value changes. You can keep it all where it belongs: in the observers.
settings need to be validated before they are changed
That one is easy. You dont do it inside the hashmap/settings object at all. Given that the HashMap is just a dumb container holding arbitrary data that is supposed to be used by other classes, you put the validation into those classes that use the data. Problem solved.
should use $setting->foo[subsetting] over $setting->data[foo[subsetting]]
Well, yeah. As you probably have guessed already, the above implementation doesnt use this notation. It uses $settings['foo'] = 'bar' and you cannot use $settings['foo']['bar'] with ArrayAccess (at least to my knowledge). So that is somewhat of a limitation.
settings class needs to give access to the settings data for other classes
This and the next requirement smell like Singleton to me. If so, think again. All you ever need is to instantiate the settings class once in your bootstrap. You are creating all the other classes that are required to fulfill the request there, so you can inject all the settings values right there. There is no need for the Settings class to be globally accessible. Create, inject, discard.
first time an instance is made, the settings need to be loaded from a file
See above.
The part $foo->bar is actually calling __get, this function should (in your case) return an array.
returning the right array in the __get would then be your solution.
As has been stated, this is because it is the array stored in $foo->bar that is being modified rather than the class member. The only way to invoke __set behaviour on an 'array' would be to create a class implementing the ArrayAccess interface and the offsetSet method, however this would defeat the purpose of keeping the settings in the same object.
A reasonably neat and common work around is to use dot delimited paths:
class Settings {
protected $__settings = array();
// Saves a lot of code duplication in get/set methods.
protected function get_or_set($key, $value = null) {
$ref =& $this->__settings;
$parts = explode('.', $key);
// Find the last array section
while(count($parts) > 1) {
$part = array_shift($parts);
if(!isset($ref[$part]))
$ref[$part] = array();
$ref =& $ref[$part];
}
// Perform the appropriate action.
$part = array_shift($parts);
if($value)
$ref[$part] = $value;
return $ref[$part];
}
public function get($key) { return $this->get_or_set($key); }
public function set($key, $value) { return $this->get_or_set($key, $value); }
public function dump() { print_r($this->__settings); }
}
$foo = new Settings();
$foo->set('somename', 'something');
$foo->set('bar.baz', 'somethingelse');
$foo->dump();
/*Array
(
[somename] => something
[bar] => Array
(
[baz] => somethingelse
)
)*/
This also makes it clearer you are not manipulating instance variables, as well as allowing arbitrary keys without fear of conflicts with instance variables. Further processing for specific keys can be achieved by simply adding key comparisons to get/set e.g.
public function set(/* ... */) {
/* ... */
if(strpos($key, 'display.theme') == 0)
/* update the theme */
/* ... */
}