Структуры и PHP

Lightning

Трудоголик
Структуры и PHP

Ассоциативные массивы безусловно очень удобная вещь. Но что-то стало мне не хватать в PHP обычных структур, как в C. Ведь структуры позволили бы быстро находить дурацкие ошибки типа: $my_array['keyy'] вместо $my_array['key'].

Вопросы:
1. Используете ли Вы по возможности объекты с открытыми данными-членами вместо ассоциативных массивов?
PHP:
//...
$my_structure = new MyStructure();
$my_structure->field1 = $value1;
$my_structure->field2 = $value2;
//...
2. Используете ли Вы по возможности объекты с закрытыми данными-членами вместо ассоциативных массивов?
PHP:
//...
$my_structure = new MyStructure();
$my_structure->set_field1($value1);
$my_structure->set_field2($value2);
//...
3. Что Вы можете посоветовать для локализации ошибок типа $my_array['keyy'] вместо $my_array['key'].
4. Написал простенький тест, сравнивающий время на создание ассоциативного массива и время на создание объекта с такими же полями. Получилось, что объект создается примерно в 5 раз медленнее. Но это не оценивает влияние на производительность в случае, если объекты будут использоваться вместо массивов во всем приложении. Я думаю, что оно мизерное, но не знаю. Может Вы знаете?
 

Gorynych

Посетитель PHP-Клуба
1. редко
2. часто + смотри http://ru.php.net/manual/en/language.oop5.overloading.php
3.
- вариант а) обрабатывать ошибки на уровне __get и __set;
-- вариант б) использовать собственный обработчик ошибок и исключений (собственно, я так и делаю, потому что он удобен для меня, для моего стиля писания и включает нужные и удобные мне функции логирования, трассировки и модификаторы работы).

4. объект медленнее, были медленнее и будут медленнее. И так всегда и везде, как говорил мои хороший знакомый про С/С++: ну если жертвуя по два байта на ссылку ты получаешь замен какую-то очевидную тебе пользу - это не страшно.
 

Alexandre

PHPПенсионер
1. Используете ли Вы по возможности объекты с открытыми данными-членами вместо ассоциативных массивов?
не всегда, с открытями данными ООП не рекомендует использовать
2. Используете ли Вы по возможности объекты с закрытыми данными-членами вместо ассоциативных массивов?
да, именно второй вариант рекомендуют использовать Титаны ООП

3. Что Вы можете посоветовать для локализации ошибок типа $my_array['keyy'] вместо $my_array['key'].
ничего не посоветую
4. Написал простенький тест, сравнивающий время на создание ассоциативного массива и время на создание объекта с такими же полями. Получилось, что объект создается примерно в 5 раз медленнее. Но это не оценивает влияние на производительность в случае, если объекты будут использоваться вместо массивов во всем приложении. Я думаю, что оно мизерное, но не знаю. Может Вы знаете?
свойства объекта в пхп реализованы на основе hashtable, как массив
к гадалке ходить не надо, чтоб знать что массив быстрее чем объект
но это не то время, чтоб им жертвовать ради нормального ООП
 

fixxxer

К.О.
Партнер клуба
это называется pure value objects :)

ArrayAccess ага. посмотри реализации класса storage во всяких там фреймворках.
 

Lightning

Трудоголик
Написал примеры кода на каждый вариант и сравниваю:

0. Ассоциативный массив

PHP:
//...

$my_structure = array();
$my_structure['field1'] = 'value1';
$my_structure['flied2'] = 'value2';    //В этой строчке опечатка

//Естественно не получаем никакого сообщения об ошибке, ошибка проявиться потом.
1. Открытые данные-члены

PHP:
//...

class MyStructure {
    public $field1;
    public $field2;
    public $field3;

    public function __construct() {
    }

}

//...

$my_structure = new MyStructure();
$my_structure->field1 = 'value1';
$my_structure->flied2 = 'value2';    //В этой строчке опечатка

//Почему-то не получаем никакого сообщения об ошибке. Может я ошибки зачем-то выключил?...
2. Закрытые данные-члены

PHP:
//...

class MyStructure {
    private $field1;
    private $field2;
    private $field3;

    public function __construct() {
    }

    public function set_field1($value) {
        $this->field1 = $value;
    }

    public function get_field1() {
        return $this->field1;
    }

    public function set_field2($value) {
        $this->field2 = $value;
    }

    public function get_field2() {
        return $this->field2;
    }

    public function set_field3($value) {
        $this->field3 = $value;
    }

    public function get_field3() {
        return $this->field3;
    }

}

//...

$my_structure = new MyStructure();
$my_structure->set_field1('value1');
$my_structure->set_flied2('value2');    //В этой строчке опечатка

//Получаем хорошее сообщение об ошибке с указанием именно на эту строчку
//Fatal error: Call to undefined method MyStructure::set_flied2() in ...
3. Магические методы set/get

PHP:
//...

class StructureException extends Exception {

    public function __construct($structure, $field) {
         parent::__construct('Undefined field "'.$field.'" in structure "'.$structure.'"');
    }
}

abstract class AbstractStructure {
    protected $type;
    protected $fields;
    protected $values = array();

    public function __set($name, $value) {
        if($this->is_defined($name)) {
            $this->values[$name] = $value;
        } else {
            $this->error($name);
        }
    }

    public function __get($name) {
        if($this->is_defined($name)) {
            return $this->values[$name];
        } else {
            $this->error($name);
        }
    }

    private function is_defined($name) {
        return in_array($name, $this->fields);
    }

    private function error($field) {
        throw new StructureException($this->type, $field);
    }  
}

class MyStructure extends AbstractStructure {
    protected $type = "MyStructure";
    protected $fields = array ('field1', 'field2', 'field3');
}


//...

$my_structure = new MyStructure();
$my_structure->field1 = 'value1';
$my_structure->flied2 = 'value2';    //В этой строчке опечатка

//Получаем жуткое сообщение об ошибке. Но по нему все-таки кое-как можно найти ее источник...
//Fatal error: Uncaught exception 'StructureException' with message 'Undefined field "flied2" in structure "MyStructure"' in ... 40 Stack trace: #0 ...: AbstractStructure->error('flied2') #1 ...: AbstractStructure->__set('flied2', 'value1') #2 {main} thrown in ...
Вариант 2 получается лучше в плане дебага, но он заставляет каждый раз писать много однотипного кода...
 

john.brown

просто кулибин
Lightning
Если немного поправиш и усовершенствуеш вариант 3, то будет очень даже нормальные сообшения :)
PHP:
class StructureException extends Exception { 

    public function __construct($structure, $field) { 
         $trace = debug_backtrace();
         $call = array_pop($trace);
         parent::__construct('Undefined field "'.$field.'" in structure "'.$structure.'" called in file "'.$call['file'].'", line '.$call['line']); 
    } 
}
abstract class AbstractStructure { 
   ...

    private function error($field) { // tut oshibka bila v imeni polja
        throw new StructureException($this->type, $field); 
    }   
} 

class MyStructure extends AbstractStructure { 
    protected $type = 'MyStructure';
    protected $fields = array ('field1', 'field2', 'field3'); 
}
$my_structure = new MyStructure(); 
$my_structure->field1 = 'value1'; 
$my_structure->flied2 = 'value2';
StructureException: Undefined field "flied2" in structure "MyStructure" called in file "D:\Apache Group\Apache2\htdocs\struct.php", line 46
 

Lightning

Трудоголик
john.brown
В этом варианте сообщения стали конечно лучше. А в данном тестовом примере вообще указывают прямо на строки с опечатками. Но вот например:

PHP:
some_function();

function some_function() {
    $my_structure = new MyStructure();
    $my_structure->field1 = 'value1';
    $my_structure->flied2 = 'value2';    //В этой строчке опечатка
}
В данном примере сообщение будет указывать на строку с вызовом some_function. Т.е. в любом случае придется самому прослеживать стек вызовов...
Но все равно спасибо.

fixxxer
Посмотри в сторону ArrayObject
Посмотрел...Но наверное плохо посмотрел...
PHP:
class MyStructure extends ArrayObject{

    public function __construct() {
        parent::__construct(
            array('field1' => 'default', 'field2' => 'default'),
            ArrayObject::ARRAY_AS_PROPS
            );
    }
}

//...

$my_structure = new MyStructure();
$my_structure->field1 = 'value1';
$my_structure->flied2 = 'value2';    //В этой строчке опечатка

//Никакого сообщения об ошибке :(
 

john.brown

просто кулибин
Lightning
Ну, можно, конечно, далше извращаться на предмет просмотра стэка, и выбора элемента, у которого $call['function'] == '__set' && $call['class'] == 'AbstractStructure']. И тогда уж совсем точно будет в большем количестве случаяв :)
 

fixxxer

К.О.
Партнер клуба
Lightning
class StrictArrayObject extends ArrayObject и перегрузить метод отвечающий за set().

зачем велосипед то изобретать.
 

Lightning

Трудоголик
.

-~{}~ 19.04.09 18:19:

fixxxer
class StrictArrayObject extends ArrayObject и перегрузить метод отвечающий за set().
Так получится же то же самое. Самому нужно проверять правильное ли имя поля и кидать исключение. Или нет?

-~{}~ 19.04.09 19:02:

1. Велосипед:
PHP:
//...

class StructureException extends Exception {

    public function __construct($structure, $field) {
         $trace = debug_backtrace();
         $error_call = array();
         for($i = 0; $i < count($trace); $i++) {
             if( ($trace[ $i ][ 'class' ] == 'AbstractStructure') && ($trace[ $i ][ 'function' ] != 'error') ) {
                 $error_call = $trace[ $i ];
                 break;
             }
         }
         parent::__construct('Undefined field "'.$field.'" in structure "'.$structure.'" called in file <b>"'.$error_call['file'].'"</b>, line <b>'.$error_call['line'].'</b>');
    }
}

abstract class AbstractStructure {
    protected $type;
    protected $fields;
    protected $values = array();

    public function __set($name, $value) {
        if($this->is_defined($name)) {
            $this->values[$name] = $value;
        } else {
            $this->error($name);
        }
    }

    public function __get($name) {
        if($this->is_defined($name)) {
            return $this->values[$name];
        } else {
            $this->error($name);
        }
    }

    private function is_defined($name) {
        return in_array($name, $this->fields);
    }

    private function error($field) {
        throw new StructureException($this->type, $field);
    }  
}

class MyStructure extends AbstractStructure {
    protected $type = "MyStructure";
    protected $fields = array ('field1', 'field2', 'field3');
}


//...

try {

    //...
    some_function();
    //...

} catch (Exception $e) {
    echo $e->getMessage();
}

//...

function some_function() {
    $my_structure = new MyStructure();
    $my_structure->field1 = 'value1';
    $my_structure->flied2 = 'value2';    //В этой строчке опечатка

    //Получаем информативное сообщение об ошибке, указывающее на строку с опечаткой:
    //Undefined field "flied2" in structure "MyStructure" called in file "E:\webdevserver\htdocs\StructuresTest.php", line 75
    //...
}
2. extends ArrayObject:
PHP:
//...

class StructureException extends Exception {

    public function __construct($structure, $field) {
         $trace = debug_backtrace();
         $error_call = array();
         for($i = 0; $i < count($trace); $i++) {
             if( $trace[ $i ][ 'class' ] == 'AbstractStructure' ) {
                 $error_call = $trace[ $i ];
                 break;
             }
         }
         parent::__construct('Undefined field "'.$field.'" in structure "'.$structure.'" called in file <b>"'.$error_call['file'].'"</b>, line <b>'.$error_call['line'].'</b>');
    }
}

abstract class AbstractStructure extends ArrayObject {
    protected $type;
    protected $fields;

    public function __construct() {
        parent::__construct(array(), ArrayObject::ARRAY_AS_PROPS);
    }

    public function offsetSet($name, $value) {
        if(in_array($name, $this->fields)) {
            parent::offsetSet($name, $value);
        } else {
            throw new StructureException($this->type, $name);
        }
    }
}

class MyStructure extends AbstractStructure {
    protected $type = "MyStructure";
    protected $fields = array ('field1', 'field2', 'field3');
}


//...

try {

    //...
    some_function();
    //...

} catch (Exception $e) {
    echo $e->getMessage();
}

//...

function some_function() {
    $my_structure = new MyStructure();
    $my_structure->field1 = 'value1';
    $my_structure->flied2 = 'value2';    //В этой строчке опечатка

    //Получаем информативное сообщение об ошибке, указывающее на строку с опечаткой:
    //Undefined field "flied2" in structure "MyStructure" called in file "E:\webdevserver\htdocs\StructuresTest2.php", line 62
    //...
}
-~{}~ 19.04.09 19:08:

Вариант с ArrayObject не на много короче.

-~{}~ 19.04.09 19:20:

Зато с ArrayObject можно использовать обращения $my_structure['field1'] и прочий функционал ArrayObject.

Вообщем с этим все ясно. Вопрос закрыт. Всем спасибо.
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
если у тебя цель сделать нормальный обработчик - пиши его,
если +-20 строк - это "на много короче", помочь не сможем
 

Lews

Новичок
Использую обычно первый способ.
Например так
PHP:
class BaseEntity {
    public function __set($name, $value) {
        $obj = new ReflectionObject ( $this );
        throw new Exception ( "You are trying to set up field $name of object " . $obj->getName () );
    }
    
    public function __get($name) {
        $obj = new ReflectionObject ( $this );
        throw new Exception ( "You are trying to get field $name of object " . $obj->getName () );
    }
}

class File extends BaseEntity{
      public $Name;
}

$file = new File();
$file->Nmae = '123'; // Ошибка
echo $file->Nmae; // Ошибка
 

Духовность™

Продвинутый новичок
1. Используете ли Вы по возможности объекты .... вместо ассоциативных массивов?
ссы сюда: http://www.phpclub.ru/paste/index.php?show=2285

3. Что Вы можете посоветовать для локализации ошибок типа $my_array['keyy'] вместо $my_array['key'].
ИМХО - ругаться при каждом обращении к несуществующему члену класса в таком случае не правильно.

В JavaScript, например, мне очень понравился факт того, что обращение к несуществующему члену класса не вызывает ОШИБКУ (которую можно получить кодом типа alert(my_none_var)), а возвращает ЗНАЧЕНИЕ - undefined. Соответственно, мы можем писать программы вот так:

<script type="text/javascript">
if (!window.my_none_var)
{
alert('Неопределенная переменная');
}
</script>

Удобно? Безусловно. Почему же в PHP надо материться при обращении к несуществующей переменной внутри класса и отдавать именно ошибку, а не NULL?

Сейчас мой класс, который приведет по ссылке выше, возвращает NULL, если идет обращение к неизвестному свойству. Это очень оказалось удобно при шаблонизации, например - мне теперь нет смысла беспокоиться о том, что переменная будет неопределена, ибо любой вызов несуществующего свойства класса-хранилища возвращает NULL.
 

Lightning

Трудоголик
ссы сюда: http://www.phpclub.ru/paste/index.php?show=2285
Да ты уже это показывал в каком-то топике. Мне такое не надо. Я так и не понял зачем тебе оно...
ИМХО - ругаться при каждом обращении к несуществующему члену класса в таком случае не правильно.
ИМХО, правильно.
В JavaScript, например, мне очень понравился факт того, что обращение к несуществующему члену класса не вызывает ОШИБКУ (которую можно получить кодом типа alert(my_none_var)), а возвращает ЗНАЧЕНИЕ - undefined. Соответственно, мы можем писать программы вот так:
Можем... Но лучше когда обращение к члену класса и проверка его существования - разные операции.
Почему же в PHP надо материться при обращении к несуществующей переменной внутри класса и отдавать именно ошибку, а не NULL?
А потому, что если кто-то вдруг напишет $my_array['keyy'] = 'value' вместо $my_array['key'] = 'value', возникнет ошибка, которая может проявиться на много позже этого кода. И, в зависимости от сложности программы, ошибка может выглядеть настолько загадочно, что придется отлаживать кучу кода, пока будет найдена злополучная строчка $my_array['keyy'] = 'value'...
Я не собираюсь спорить по этому поводу, это всего лишь приемы Защитного программирования...

-~{}~ 20.04.09 19:37:

если у тебя цель сделать нормальный обработчик - пиши его,
обработчик чего?
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
>обработчик чего?
опечаток, ошибок

-~{}~ 20.04.09 19:29:

в PHP можно ноутисы выключить - и тоже "не вызывает ОШИБКУ", a "возвращает значение"
надо только для объектов - прописывается в обработчике ошибок или _set возвращает null
только это неправильно
 

dimagolov

Новичок
Lightning, ты уж лучше TDD освой, если верификация кода заботит, чем всовывать эту верификацию навсегда дополнительным тормозом в приложение. Ошибку ты может и не допустишь, но твой код с верификаторами буедт тормозить всегда.
 

Lightning

Трудоголик
ты уж лучше TDD освой,
Осваиваю уже около 2-ух месяцев :)
Ошибку ты может и не допустишь, но твой код с верификаторами буедт тормозить всегда.
А вот фиг.
PHP:
//...

$my_structure = StructuresFactory :: create_my_structure();
$my_structure['field1'] = $value1; //работаем как с массивом, потому что ArrayAccess...

//...

class StructuresFactory {

    //...
    public function create_my_structure() {
        //Для debug-версии:
        return new MyStructure();
        //Для production-версии:
        //return array();
    }
    //...
}
примерно так...

-~{}~ 20.04.09 21:42:

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

-~{}~ 20.04.09 21:46:

Кстати о TDD, я иногда сам в тестах допускал такие ошибки типа $my_array['fild'] вместо $my_array['field'], и потом не мог разобраться почему же тест красный. Теперь хоть сразу такие ошибки всплывают.
 
Сверху