Bean Pattern in PHP5

confguru

ExAdmin
Команда форума
Кто знает Java - полноценная замена кода или нет?

===
Taking advantage of PHP5′s magic methods __get, __set, and __call, the bean pattern can be implemented pretty easily in PHP. Here’s an example of it in Java:
PHP:
public class UserBean {

    protected String username, email, location;

    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }

    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }

    public String getLocation {
        return location;
    }
    public void setLocation(String location) {
        this.location = location;
    }
}
One of the annoyances of this (at least for me) is that you end up creating a bunch of getter/setter methods that don’t do anything special – they either return the encapsulated variable or set it. So, I wrote a base class that uses PHP5′s magic methods to take care of all of those generic getters and setters.

PHP:
abstract class Bean {
    public function __call($name, $arguments) {
        if (strpos($name, 'get') === 0) {
            $property = strtolower(substr($name,3,1)) . substr($name,4);
            return $this->$property;
        }
        elseif (strpos($name, 'set') === 0) {
            $property = strtolower(substr($name,3,1)) . substr($name,4);
            return $this->$property = $arguments[0];
        }
        else {
            throw new Exception("Method $name does not exist");
        }
    }

    public function __get($name) {
        $getter = 'get' . ucfirst($name);
        if (method_exists($this, $getter)) {
            return call_user_func(array($this, $getter));
        }
        return $this->$name;
    }

    public function __set($name, $value) {
        $setter = 'set' . ucfirst($name);
        if (method_exists($this, $setter)) {
            call_user_func(array($this, $setter), $value);
        }
        else {
            $this->$name = $value;
        }
    }
}
Using the above abstract class, the example UserBean in PHP would become just:

PHP:
class UserBean extends Bean {
    protected $username, $email, location;
}
And you can access the bean as follows, without having to write any of these functions:

PHP:
$user = new UserBean();
$user->setUsername('Jim');
echo $user->getUsername();
If you needed to do any additional work in the getter function, you can just create a getter function like you normally would:

PHP:
class UserBean extends Bean {
    protected $username, $email, location;
    public function getUsername() {
        return "User: $username";
    }
}
In addition, if you want cleaner syntax (i.e. when mixing PHP with your HTML), you could also access properties of the bean directly, and the getter/setter functions will be called automatically if they exist. Since the properties are “protected”, attempts to access the properties will get routed to the __get and __set functions, where it will attempt to call a getter/setter if it exists.

PHP:
$user = new UserBean();
$user->username = 'Jim';
echo $user->username;;
 

Духовность™

Продвинутый новичок
Я даааааавно такую практику применяю в своем фреймворке. Сначала все было построено на магических методах __get/__set и члены свойств объекта получал/присваивал посредством
PHP:
$user->username = 'Jim';
echo $user->username;
Потом решил, что
PHP:
$user->getUsername()/$user->setUsername()
выглядит красиво и создал виртуальные методы через __call

Потом выяснилось, что некоторым членам класса надо писать геттеры и сеттеры явно, в виду какой-то их логики. Проблемы возникли с сеттером, т.к. у меня сеттер автоматически валидирует значение и заносит информацию об ошибках во внутреннее пространство объекта модели. Пришлось ввести понятие как explicit method, который выглядит так (с префиксом _):
PHP:
    protected function _setUrl($url)
    {
        return $url === 'http://' ? null : $url;
    }

    protected function _setPassword($password)
    {
        if ($password === null || $password === '')
        {
            return null;
        }

        return md5($password);
    }
данные методы срабатывают только в том случае, если виртуальные отработали и не нашли ошибок валидации.
Call выглядит так:
PHP:
    public function __call($method_name, $argument)
    {
        $args = preg_split('/(?<=\w)(?=[A-Z])/', $method_name);

        $action = array_shift($args);

        $property_name = strtolower(implode('_', $args));

        if (!isset(static::$model_attributes[$property_name]))
        {
        	throw new BadMethodCallException(
                'Вызов неизвестного метода ' . get_class($this) . '::' . $method_name
            );
        }

        switch ($action)
        {
            case 'get':
                return $this->$property_name;

            case 'set':
                $this->$property_name = $argument[0];

                $has_errors = isset($this->validate_errors[$property_name]);

                $explicit_method = '_' . $method_name;

                // Смотрим, имеется ли в классе явно объявленный set-метод (с префиксом "_") для
                // данного свойства и имеются ли ошибки валидации.
                // Если метод явно объявлен, а ошибок валидации нет, то применяем метод
                // для текущего состояния свойства.
                if (method_exists($this, $explicit_method) && !$has_errors)
                {
                   $this->data[$property_name] = $this->$explicit_method($this->$property_name);
                }

                return $this;
        }
    }
к чему я все это написал?...
 

AmdY

Пью пиво
Команда форума
я тоже делаю get/set с префиксом и закрытый (protected function _getIdUser()), __call не использую.
все вызовы только как аттрибута $obj->IdUser либо $obj['IdUser'], $obj->getIdUser() - не делаю

admin
примеры не равноценные, в php-шном получается что можно работать с аттрибутом, которого не существует. А ещё он может выступать в качестве павлика морозова и вызывать закрытые методы, начинающиеся с get или изменять таскать методы, которые не предпологалось вызывать как аттрибуты
$obj->getArticle($idUser, $lang); // его нельзя таскать по $obj->Article
 

confguru

ExAdmin
Команда форума
AmdY
У меня есть клиент на java - его надо на php переписать с сохранением логики и структуры.
Попробую скажу, спасибо за помощь.
 

fixxxer

К.О.
Партнер клуба
Геттеры-сеттеры в неразумном применении вообще опасны, т.к. маскируют того же "паблика морозова" под ООП.

А для штук типа ArrayObject писать геттеры-сеттеры ручками, это, конечно, вообще извращение :)

Вообще, я считаю, что __get/__set нужны только для ArrayObject-ов и вариаций на их тему, а __call для делегирования (вот например у меня есть обертки над Pdo и PdoStatement, которые неудобно делать наследованием). Сеттеры же нужны для инъекции зависимостей.
 

craz

Нестандартное звание
а __call для делегирования (вот например у меня есть обертки над Pdo и PdoStatement, которые неудобно делать наследованием).
можно посмотреть как то? А то я вчера пришел к выводу что _call для делегирования опасно...
 

fixxxer

К.О.
Партнер клуба
Да тупо.

$this->Pdo = new PDO ...

public function __call($method, $args) {
// тут неплохо проверить наличие метода и кинуть exception если что
return call_user_func_array(array($this->Pdo, $method), $args)
}

Кроме отсутствия автокомплита ничем не опасно. Удобно тем, что для тестирования можно подсунуть вместо Pdo что угодно.
 

atv

Новичок
PHP:
$user = new UserBean();
$user->username = 'Jim';
echo $user->username;
Такое работать не будет, так как свойство username существует и объявлено как protected. Будет ошибка доступа к свойству.
 

Духовность™

Продвинутый новичок
PHP:
$user = new UserBean();
$user->username = 'Jim';
echo $user->username;
Такое работать не будет, так как свойство username существует и объявлено как protected. Будет ошибка доступа к свойству.
поэтому надо хранить данные при таком подходе в массиве. как я и делаю. а в модели держать карту описания свойств объекта, которые могут в нем существовать и быть вызваны через магические методы __get/__set и магические сеттеры get/set/Username.
 

fixxxer

К.О.
Партнер клуба
PHP:
$user = new UserBean();
$user->username = 'Jim';
echo $user->username;
Такое работать не будет, так как свойство username существует и объявлено как protected. Будет ошибка доступа к свойству.
PHP:
~$ cat 1.php
<?php
class ThinkBeforeYouPostAComment {

    protected $wtf = "wow!";

    public function __get($key) {
        if (!isset($this->$key)) {
            throw new Exception("FFUUUUUUUUUUUUU!!!");
        }
        return $this->$key;
    }

}

$Obj = new ThinkBeforeYouPostAComment;
print_r($Obj->wtf);
~$ php 1.php
wow!
P.S. Не надо пользоваться музейными версиями PHP.
 

Mols

Новичок
Ровно минуту искал.
Но оно и из доки понятно было... (наличие __call я имею в виду)

PHP:
abstract class Zend_View_Abstract implements Zend_View_Interface


    /**
     * Accesses a helper object from within a script.
     *
     * If the helper class has a 'view' property, sets it with the current view
     * object.
     *
     * @param string $name The helper name.
     * @param array $args The parameters for the helper.
     * @return string The result of the helper output.
     */
    public function __call($name, $args)
    {
        // is the helper already loaded?
        $helper = $this->getHelper($name);

        // call the helper method
        return call_user_func_array(
            array($helper, $name),
            $args
        );
    }
 

craz

Нестандартное звание
Ровно минуту искал.
Но оно и из доки понятно было... (наличие __call я имею в виду)
ну сказали же хелперы, я в них и искал

короче все забудьте про это, чето я или туплю или че, но там такое понаписано... там что происходит не пойму если я плагин не тот начну просить у вида?
 

atv

Новичок
Не надо пользоваться музейными версиями PHP.
Вау, какую фигню сделали, раньше было правильнее. Теперь в __get нужно контролировать, какое protected свойство можно отдавать а какое нет, а иначе тупо получаем доступ к любому свойству!
 

Mols

Новичок
craz
Да ничего там страшного нет.
Сначала ищется лоадер плагинов. Не нашли - исключение.
Потом лоадер ищет плугин. Не нашел - исключение.
Если метод отсутствует в плугине (а сам плугин нашли) - то просто будет варнинг. Но понятно же, что можно и его наличие проверить...

Почему Зенд не проверяет наличие метода я хз))) Но они в доке очень акцентируют внимание на том, как должен быть назван метод в плугине чтобы всё работало.
 

craz

Нестандартное звание
craz
Да ничего там страшного нет.
Сначала ищется лоадер плагинов. Не нашли - исключение.
Потом лоадер ищет плугин. Не нашел - исключение.
я не нашел именно то место где пробрасывается исключение
PHP:
return call_user_func_array(
            array($helper, $name),
            $args
        );
все нашел - вот поэтому я и говорю опасно это.. 10 минут, чтобы найти как он проделигировал метод лоад из лоадера и там пробросил исключение...

в самом вью не явно какие имена можно давать то есть ну был бы там хотя бы свич из разрешенных куда не шло, но получилась бы лапша это понятно...

опасно но не как не избежишь если надо подписывать столько плагинов на лоад..
 

AmdY

Пью пиво
Команда форума
fixxxer
ты же мне сам показывал в каком-то посте, что isset не совсем торт (false если значение равно NULL). для проверки лучше использовать property_exists
 
Сверху