views:

41

answers:

1

Ok, this may be impossible, but I thought I'd ask before I rework the whole thing... I have a situation like this:

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 a property of an object after it has been created without declaring it in the class definition?

+3  A: 

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;
  }
halfdan
Ok, but the issue is avoiding someone trying to set a value using $object->value = 'foo'; In your example they would be returning the "piece" object if someone asked to get it, instead of triggering the __toString() method. Also they would have to use $object->data->someProperty->doSomething() to get to the methods instead of doing $object->someProperty->doSomething(). I see how sticking the objects in a protected $data property could solve some of the issues, but it would mean reworking lots of other code that depends on all of the container object's properties being objects themselves....
Andrew
Thank you for the suggestion, though! (sorry for two posts, character limit!)
Andrew
@Andrew, `$object->data` would not be accessible because it's protected. Clients would do `$object->someProperty->doSomething().` Also, `$object->value = 'foo'` would not trigger `__get` but, rather `__set`. In your `__set` function, place an `is_string` check.
webbiedave
@webbiedave I get what you're saying. I tried to find a way to implement this without breaking my other functionality, and as I said before it won't work. I have a ton of other code that depends on the idea that the properties of the binder object are all objects themselves, and it's not worth it to me to rewrite all of that code around embedding the data objects in a protected array. Thank you for your feedback, though!
Andrew
@Andrew: Then you didn't make your point very clear. This is _exactly_ what you described and wanted in your question. Either you're doing something wrong or your question is too vague.
halfdan
Ok - as a point of clarification then, in case I'm doing this wrong: do you mean overload the __get() method for just the binder object or for the entire script? I assumed because you wrote "public" and because it was what I was expecting that you meant overloading the class method __get(), which I couldn't get to work. But in counting braces you have that outside the class definition. Do you mean overloading __get() for *everything?*
Andrew
Just for the binder object. Forgot the last brace - so it's inside the class. What errors do you get? What *doesn't* work as expected?
halfdan
Ok! Problems solved... I needed to read up on the documentation more carefully but I figured out what was wrong. 1. I realized that because of method chaining you could still use the objects as before via $binder->piece->method(), which is what webbiedave was explaining but I didn't understand at first. 2. After reading webbiedave's comment the first time I thought "aha, I can just block setting a string in place of an object through the magic set method, and I focused my attention there, but: 3... (see next comment)
Andrew
3. I realized after trying the experiment again today that the reason the set method alone wasn't working is that it wouldn't fire in the case of accessible properties (which all the objects in the binder were), which finally made me realize: 4. That the get method wouldn't fire for anything else in the object that *should* be accessible and not be stored in the protected data array, because the get method wasn't going to fire unless the user attempted to get something protected or not set. So, after all of that I was able to go back through my code more intelligently, (see next comment)
Andrew
...and I tracked down a minor bug which had been throwing off the results of my experiment with your suggestion. And after a new round of experimentation I see that using the get method as you described works because the other properties in the binder object which do not need to be in the protected data array still work as expected. So in other words, between a misunderstanding of the implications of your suggestion and a bug I was getting bad results when I tried this, but now I got it working. Thanks for your follow up, as I would have given up if you hadn't insisted it would work :)
Andrew
No worries mate :) Cool, that it works now.
halfdan