This may be impossible, but I thought I'd ask before I rework the whole thing…:
I have an object class that can receive "pieces", which are also objects. It works basically like this:
class myObject {
//Receives an associative array of "Piece" objects
function __construct($objects) {
foreach( $objects as $k=>$v ) {
$this->{$k} = $v;
}
}
}
I'm omitting a lot of code obviously, but I hope this gives you the idea. Basically I have a ton of different "piece" objects that do different things, and if I pass an array of them into myObject then myObject becomes a very flexible and useful class for doing all kinds of different things.
So, I could use this to create a Book object and have pieces that included a "Author Piece" and an "ISBN Piece", and those pieces would know how to validate data etc. So I might have $book with objects set to the member variables author and isbn.
This is great because I can do things like:
echo $book->author; //all Pieces have a __toString() method.
echo $book->author->firstName;
$book->author->showBio();
$book->author->contactForm();
…and so on.
Now to the point. This system works great, and one of the things that makes it great is that I can pick and choose any of these pieces that I like and stick them into an object to bind them together.
But the problem is, I don't want someone else who might use the code later to try:
$book->author = "John Doe";
…because then they'd just have a value instead of the author object. I'd like that to give them an error and instruct them to do this instead:
$book->author->setName("John Doe");
So because I don't know in advance what pieces might be in any individual object (and the entire point is to be able to have the freedom to instantly assemble any kind of object), I can't just set private $author in the class declaration.
I tried fooling around with __get() and __set() a bit, but I couldn't get it to work without compromising the functionality of the objects as they are now.
So, like I said, I know this may be impossible, but before I give up, I thought I'd ask. Is there a way to protect the property of an object after it has been created without declaring it in the class definition?
It's absolutely not impossible. You should overwrite the magic __get and __set functions
Like this:
class myObject {
protected $data = array();
public function __construct($objects) {
foreach( $objects as $k=>$v ) {
$this->data[$k] = $v;
}
}
/* your code */
public function __get($name) {
if(array_key_exists($name, $this->data)) {
return $this->data[$name];
}
return null;
}
Related
Consider this hypothetical scenario:
I have a Stomach class, which has a contents property, to which objects of class Food should be assigned. One way to do that would be to use a kind of a setter, e.g. setContents($food).
But lets suppose that we assign food directly, as in $stomach->contents = $orange.
Suppose also that whenever a Food object is assigned to contents we need to change the object's eaten property to true. Is there a way to do that without using a method of an object that it's being assigned to (in this case, the $stomach's setter)?
Basically, my question is: can we call a method whenever an object is assigned as a property to another object? Also, even if it is possible, is it bad design? Sorry if this is a stupid question, all of this is pretty new to me.
The best OOP solution here would be to create a method that indicates an action, like eat().
To ensure that the right Object is eaten, you could define an Interface (Say Food). This interface may define a method setEaten(). The Stomach (though I would prefer Mammal or something similar that actually can eat) can then call setEaten().
Since it created some controversy in the comments, I want to point out that an object definition should as closely as possible reflect what it actually does. To reduce object coupling it is best to avoid directly accessing object properties from an other class, however there is nothing wrong with using setters instead of actions when it makes sense (note that I used one in the definition of Food), and it may often depend on the developer view.
However, here it makes sense. Consider the case "A monkey eats a banana" (for simplicity please allow me to just materialize that banana out of thin air).
$m = new Monkey();
$m->eat(new Banana());
perfect. Now lets try a setter.
$m->setContents(new Banana());
Now we have a problem, the monkey already contains a lot of things, like bones, blood, muscles a brain, etc. So setContents makes no sense here. You could try $m->getStomach()->setContents(new Banana()) but that would just increase object coupling.
I agree with #dualed about the eat method. That said the way to acheive this to make all properties private/protected and then use __get/__set to proxy to the setters.
class Stomach {
protected $contents;
public function setContents(Food $food) {
$this->contents = $food;
$food->eaten = true;
}
public function __set($name, $value) {
$method = array($this, 'set' . $name);
if(is_callable($method)) {
return call_user_func_array($method, array($value));
}
}
public function __get($name) {
$method = array($this, 'get'.$name);
if(is_callable($method)) {
return call_user_func($method);
}
}
}
Youll notic i use is_callable as opposed to method_exists because if youre working on somehting complex with virtual methods is_callable should take those in to account whereas method_exists relies on the method being defined in the class hierarchy.
__set function will help you here. But make sure you dont have any property named contents defined in class. Do it like this
class Stomach{
private $props = array();
public __set($prop, $value){
if($prop === 'contents' and $value instanceof Food){
$this->prop[$prop] = $value;
}
}
}
Other similar questions:
Why I should use __get() and __set()-magic methods in php?
When do/should I use __construct(), __get(), __set(), and __call() in PHP?
First, I completely understand how to implement __get and __set methods in PHP and have come across scenarios where using these methods is beneficial.
However, I have encountered, and written, code that looks like the following:
class SomeObject {
protected $data = array();
public function __set($key, $val) {
$this->data[$key] = $val;
}
public function __get($key) {
return $this->data[$key];
}
}
Why do we need to do this? I have used objects with no __get or __set methods defined and can still add undefined properties, retrieve the values associated with those properties, invoke isset() and unset() on those properties.
Do we really need to implement these magic methods, using code like above, when objects in PHP already exhibit similar behavior?
Note, I'm not referring to when more code is used in the __set and __get methods for special handling of the data requested but very basic implementation like the code above.
If you are using them just as a glorified wrapper over an array (i.e. absolutely no extra logic), I don't believe they offer any benefit. They do offer a drawback however, in that you need to write the code for them. And that code may be buggy or incomplete.
Did you leave out __isset for brevity? Because if you do use __get and __set but do not provide an __isset, you 've just written yourself a bug.
See what I did there?
In the example you gave, it's very easy to overwrite $data with a new array. If you were dealing with properties, you would have to iterate over an array and perform $this->$key = $data; (to which you may have data leakage).
In more detail:
protected function setData($arr)
{
$this->data = $arr;
}
vs.
protected function setData($arr)
{
foreach($arr as $key => $data)
{
$this->$key => $data;
}
}
With the example above, keys which don't appear in $arr in the current call, but have in previous calls, will not be overwritten, which can lead to undesired results.
In the end, it just depends on what makes the most sense for you.
As a side note, in the example code you gave, you should be sure you're implementing __isset() when you have __get(). If you don't, you'll get unexpected results when using functions like isset() or empty().
To me the sample code looks like "We want all the downsides of an object combined with all the misuse potential of an array"
The only reason I can see for going for something like that is when you are starting to refactor from arrays into objects.
You replace all [''] access with -> but you don't break anything once it compiles again.
In the next step you can start adding rules for specific properties.
public function __set($key, $val) {
if($key == "money") { throw new Exception("Can't touch this"); }
$this->data[$key] = $val;
}
public function __get($key, $val) {
if($key == "money") { return $this->x * $this->y; }
$this->data[$key] = $val;
}
But in general I don't see any use going in that direction. Creating real "value objects" for OO data structures seems a lot more useful.
Doing something like that just for the sake of "I like -> better than [''] isn't a valid use case in my book."
For most use cases with "Objects as data structures" I'd much rather have:
class SomeObject {
protected $a, $b, $c;
public function __construct(TypeA $a, TypeB $b, TypeC $c) {
$this->a = $a;
$this->b = $b;
$this->c = $c;
}
/** #return TypeA */
public function getA($key) {
return $this->a;
}
// an so on
}
That documents the expected values. Works with native type hinting without the need of writing that your self and to me is what I'd expect to get as a "data structure".
If there is a good reason that a class should act like an array then ArrayAccess seems like a much better choice then that kind of magic to me.
There are quite a few different reasons you might want to use this, it all depends on how you want to control your class. A couple of examples:
1.) You have some attributes on your object that are related to a key -> value database. If more values are added, you would like to update the database with these. So in this method, you would have some sort of flag that indicates there is a $new_data array that needs to be synchronized with the database
2.) You want to strictly enforce that an object will only have the variables attached to it that you want to be there. If one doesn't exist for it, this can throw an exception.
3.) You've abstracted a database such that gets/sets will have an immediate impact on their corresponding values in it.
And many others.
In your example, no, you don't.
However, when we go into more complicated classes, you may want to define setters and getters. A sample __set would then be :
function __set($key, $val) {
if (method_exists($this, 'set'.ucfirst($key))) {
$this -> {'set'.ucfirst($key)}($val);
} else {
$this -> data[$key] = $val;
}
}
With this code you can simply define a function setTestKey which will set $this->data['testKey'] to the value in $value, and call it via $storage->testKey = 1;
I'm with you, I've never been a fan of magic methods because I feel that it breaks encapsulation. If you allow calling code to dictate object behavior that the object isn't aware of you risk un-expected behavior, which is harder to track down compared to having static setters/getters accessing defined member variables.
With that being said I have seen some clever instances where magic methods appear to be useful. One instance is calling a logger and defining the loglevel in which you want the message to appear.
Consider the following (I use this in my production code):
// Only works with php 5.3 or greater (uses __callstatic)
class MyCustom_Api_Log
{
/**
* Wrapper method for getting a symfony logger object
*
* #return object
*/
public static function getLogger()
{
return sfContext::getInstance()->getLogger();
}
/**
* Magic method for logging all supported log levels
*
* #param string
* #param array
*/
public static function __callStatic($level, $params)
{
// Only concerned with message at this time
// However any number of params can be passed in
$message = shift($params);
self::getLogger()->$level($message);
}
}
// All these calls are made to __callStatic, so no need for individual methods
MyCustom_Api_Log::err(array('Oh noes something went wrong'));
MyCustom_Api_Log::warn(array('Oh noes something went wrong'));
MyCustom_Api_Log::notice(array('Oh noes something went wrong'));
As you can see, I can call the magic method with err, warn, or notice and logs my messages at that log level.
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 */
/* ... */
}
I've been suggested to use Mappers to separate object storage in db from real object behaviour, which sounds like a nice idea. But being used to other OOP languages like Java or SmallTalk, I always try to encapsulate my objects the most I can.
I've read several mappers examples and some of them access the variables of the domain object directly (which would be my last choice, though I know it's more comfortable).
The only solution I currently come up with is something like:
class User{
public __construct($mapped_data){
foreach($data as $key=>$value){
$this->$key=$value;
}
...
}
class UserMapper{
private populate($user,$db_data){
...
$map=array('id'=>'id','psw'=>'password','mail'=>'email', ...);
foreach($db_data as $key=>$value){
$mapped_data[$map[$key]]=$value;
}
return new User($mapped_data);
}
}
The map is because the class User doesn't have to know the db column names at all.
Is this approach correct? Is there a better one?
Thanks
I would suggest that Data Access Objects are a more appropriate pattern for what you want to achieve. The Mapper that you show above just increases the complexity but doesn't really provide much in the way of benefits.
In terms of getters and setters, you can use some of PHP's magic methods to clean up the code above. The following allows you to store your data in a protected array while providing access to it that looks like it's just via the object property, but is actually through a method (so that you can provide your own logic there; make things read only, or hide some, etc).
<?php
class User {
protected $_data = array();
public function __construct($data) {
$this->_data = $data;
}
// Get any value
public function __get($key) {
return array_key_exists($key, $this->_data) ? $this->_data[$key] : null;
}
// Set any value; leave out to make the data read only outside the class
public function __set($key, $val) {
$this->_data[$key] = $val;
}
// You should also implement __isset() and __unset()
}
You can then access the data like
<?php
$user = new User(array('foo' => 'bar'));
// Outputs "bar"
echo $user->foo;
You might also simplify this all for yourself by using an existing PHP ORM library.
If I have two classes that each extend a common parent, is it possible to convert between them?
class Foo
{
public $bar;
}
class FooDatabase extends Foo
{
public function load() {}
public function save() {}
}
class FooFlatfile extends Foo
{
public function load() {}
public function save() {}
}
$foo = new FooDatabase;
$foo->bar = 'elf';
Using this code as an example, I want to convert $foo from an instance of FooDatabase to FooFlatfile, while retaining the value of the property bar.
Edit: I do realise that actually doing this isn't such a great idea in practice. Rather, I came across a situation in which this was a potential fix, and became curious about how it might be achieved.
This is possible although not advised. Take a look at A dark corner of PHP: class casting. Basically:
Serialize the class;
Change the class name in the serialized form; then
Deserialize it.
Yes, it's a hack.
The bigger question: is why do you want to do this? If you have the need to change class it's a very strong indicator that your object model is bad. It reminds me of the old examples of introductory OO they used to give:
Person class;
Salesman extends Person;
Manager extends Person.
which is a horrible example for the same reason: what if someone changes from a Salesman to a Manager? Compare that to the composition-based approach where Person has-a Job (where Manager and Salesman are instances or subclasses of Job). Much more sound approach.
Lastly, I'll add that if some code you can't change is forcing you to do this, you'd be best off using some sort of adapter or facade:
class A { ... }
class B {
public function asA() {
$ret = new A;
// configure;
return $ret;
}
...
}
Again this is much more sound than any sort of automatic property copying class changing process.
Can you accomplish what you need by creating a new one and copying the values over?
$foo2 = new FooFlatFile;
$foo2 = foo->bar;
If doesn't get done what you need, please give us more details.
Response to comment:
If you are planning on doing that often, it would be easy to create a member function to return all members in some sort of array or struct. However, at that point, you really need to ask yourself "why am I doing this?" The other responder is spot on in saying that if you are regularly wanting to do this, you have designed your classes very badly.
If you've got two classes you are wanting to switch between, remove what is separating them, and make one class. Either they are fundamentally the same, in which case you could get away with switching between them, but you would be far better served by making just one class.
Or they are fundamentally different from each other, in which case you still need two classes. If you have a database, and you're trying to turn it into a flat file, you need to have an export function translate the database into flat data. Dynamically changing classes would be like using Notepad to open up an Oracle file: it would not yield the data in a meaningful, usable manner. To go between them, you write 'export' and 'import' functions that use the same data structure as each other. You create a new one, export from the old, import into the new, and delete the old.
I'm frankly not sure what else to say, because I can't imagine a scenario where it would make sense to do this. If you could give more details about the background of what you're trying to solve, I could offer better advice.
You can do something like this, has some limitations (limits on types of constructors and can't copy private vars) but it can be made to work.
class Foo {
public $bar;
protected function convertTo($classname) {
$rc_other_class = new ReflectionClass($classname);
if ($rc_other_class->isSubclassOf(get_class())) {
// there's a limitation here in that the constructor can't take params
$other_class = new $classname();
$rc_this_class = new ReflectionClass(get_class());
$properties = $rc_this_class->getProperties();
foreach ($properties as $property) {
if (!$property->isStatic() &&
$property->getDeclaringClass()->getName() == get_class()) {
$property = $property->getName();
if (property_exists($this, $property)) {
// this will throw if you try to copy a private var, add a filter to
// getProperties to prevent this but you probably want the error
$other_class->$property = $this->$property;
}
}
}
} else {
return false;
}
}
}