views:

47

answers:

3

Hello, I've been trying to think of a way to dynamically return property values for a class using __call instead of creating a slew of functions whose only purpose would be to return those values. The idea I have is to be able to request ( for example ) $this->fetch_PersonFirstName() and have the class check if $this->person["first_name"] is set and return the value. Another example would be calling $this->fetch_BookGenreTitle() and have the class return the value of $this->book["genre"]["title"]. I know something like this would have to do a bit of checking in order for it to automatically determine that, for example, since $this->book["genre_title"] doesn't exist, then it should check for $this->book["genre"]["title"].

So far I've come up with code that ( for some reason ) works for returning the values of an array ( such as my person example ) but my problem quickly develops when I try to return the values of a multidimensional array ( such as with my above book example ). Bare in mind, I'm still trying to think of a way for the __call method to check for the existence of one, and if it doesn't exist, then the other.

Please, throw me a line here. I've been banging my head against the wall trying to figure this out and it's killing me.

<?php

class Test
{
    protected $person;
    protected $book;

    public function __construct()
    {
        $this->person["first_name"] = "John Smith";
        $this->book["genre"]["title"] = "Suspense";
    }

    public function __call($method, $args)
    {
        $args = implode(", ", $args);

        if (preg_match("/^fetch_([A-Z]{1,}[a-z]{1,})(.*)?/", $method, $match))
        {
            print_r($match);
            echo "<br><br>";
            $property   = strtolower($match[1]);
            $indexes    = $match[2];

            if (property_exists("Test", $property))
            {
                if ($indexes)
                {
                    $indexes = preg_split("/(?<=[a-z])(?=[A-Z])/", $indexes);

                    $num_indexes    = count($indexes);
                    $count_indexes  = 1;

                    for ($count=0; $count<$num_indexes; $count++)
                    {
                        $record     = strtolower($indexes[$count]);
                        $index .= $record;
                        $array .= "{$record}";

                        $var_index  = $index;
                        $var_array  = $array;

                        echo $var_index." {$count}<br>";
                        echo $var_array." {$count}<br>";
                        //print_r($this->{$property}{$var_array});

                        if ($count_indexes == $num_indexes)
                        {
                            if (isset($this->{$property}{$var_index}))
                            {
                                return $this->{$property}{$var_index};
                            }
                            else
                            {
                                return $this->{$property}{$var_array};
                            }
                        }
                        else
                        {
                            $index .= "_";
                        }

                        $count_indexes++;
                    }
                    echo "<br><br>";
                }
                else
                {
                    return $this->{$property};
                }
            }
        }
    }
}
?>

<?php


    $test = new Test();
    echo $test->fetch_PersonFirstName();
    echo "<br><br>";
    echo $test->fetch_BookGenreTitle();
?>
A: 

You need to take a look at property overloading, not method overloading as you've already figured out in the question's title yourself.

David Kuridža
Thank you. I'd experimented with __set and __get a while ago but for some reason it didn't occur to me to go that route with this.
waywardspooky
A: 

Given "BookGenreTitle" :

  1. Use some sort of regex to separate "Book", "Genre", and "Title"
  2. property_exists($this, "Book")
  3. array_key_exists("genre", $this->book)
  4. If key exists, return $this->book["genre"]
  5. If key doesn't exist, array_key_exists("genre_title", $this->book)
  6. If key exists, return $this->book["genre_title"]
  7. If key doesn't exist, array_key_exists("genre", $this->book) && array_key_exists("title", $this->book["genre"])
  8. Keep going

There's probably a way to use a loop or some sort of recursion instead of hard-coding the maximum depth, but I won't get into that now...

Oh, and as the other poster said, what you want is property overloading (__get and __set).

kijin
I see what you mean in your example. So, instead of requesting $this->fetch_BookGenreTitle() and the __call method, I would request $this->fetch_BookGenreTitle as a property and use the __get method, right?
waywardspooky
@waywardspooky Yes, exactly. Not sure how much performance difference it would make (it's all magic anyway) but there's no reason to use methods unless you need to pass arguments. Oh, and drop the `fetch_` prefix, it's ugly and unnecessary.
kijin
A: 

Thanks again folks. I think I finally have my solution, which works for multidimensional arrays or any depth and accounts for determining whether a property is, for example $this->book["genre_title"] or $this->book["genre"]["title"] :)

I'm posting the code below as someone else may randomly find it useful in the future

<?php

class Test
{
    protected $person;
    protected $book;

    public function __construct()
    {
        $this->person["first_name"] = "John Smith";
        $this->book["genre"]["title"] = "Suspense";
    }

    public function __get($var)
    {
        if (preg_match_all("/([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)/", $var, $matches))
        {
            $matches        = $matches[1];
            $property       = strtolower($matches[0]);

            if (property_exists($this, $property))
            {
                unset($matches[0]);

                $matches        = array_values($matches);
                $num_matches    = count($matches);

                $var = &$this->{$property};

                if (!$num_matches)
                {
                    return $var;
                }
                else
                {
                    foreach($matches as &$match)
                    {
                        $match  = strtolower($match);
                        $index .= $match;

                        if ($probe = $this->iterateArray($var, $index))
                        {
                            $var = $probe;
                            unset($index);
                        }
                        elseif ($probe = $this->iterateArray($var, $index))
                        {
                            $var = $probe;
                        }
                        else
                        {
                            $index .= "_";
                        }
                    }

                    return $var;
                }
            }
        }
    }

    public function iterateArray($var, $index)
    {
        if (array_key_exists($index, $var))
        {
            return $var[$index];
        }
        else
        {
            return false;
        }
    }
}
?>

<?php


    $test = new Test();
    echo $test->PersonFirstName;
    echo "<br><br>";
    echo $test->BookGenreTitle;
?>

There's more than likely some ways to improve/streamline the code, in which case anyone wanting to do so is more than welcome to post an improved version.

waywardspooky