За последние 24 часа нас посетили 30480 программистов и 1790 роботов. Сейчас ищут 1064 программиста ...

OOP в PHP

Тема в разделе "Прочее", создана пользователем Simpliest, 23 окт 2009.

  1. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    В общем, как это ни грустно....
    но

    PHP:
    1. <?php
    2. abstract class A {}
    3.  
    4. class B extends A {
    5.     public $id;
    6. }
    7.  
    8.  
    9. abstract class C {
    10.     abstract public function say(A $msg);
    11. }
    12.  
    13. class D extends C {
    14.     public function say(B $msg) {
    15.         var_dump($msg);
    16.     }
    17. }
    18.  
    19. $b = new B;
    20. $b->id = 'sample';
    21.  
    22. $d = new D;
    23. $d->say($b);
    Такое работать не будет. Несмотря на то, что B необтрузивно реализует все интерфейсы A.

    Так что для, желающих наследования с typehinting о последнем придется позаботиться самостоятельно
    Например вот так

    PHP:
    1. <?php
    2. class D extends C {
    3.     public function say(A $msg) {
    4.         if (! ($msg instanceof B)) {
    5.             throw new Exception('Instance of B required');
    6.         } else {
    7.             var_dump($msg);
    8.         }
    9.     }
    10. }
    А так хотелось без лишних телодвижений отделить структуру модели от методов работы с ней :(
     
  2. iliavlad

    iliavlad Активный пользователь

    С нами с:
    24 янв 2009
    Сообщения:
    1.689
    Симпатии:
    4
    а где бы такое работало?
     
  3. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    Сложный вопрос :) и мне лень на него искать ответ.

    Для функциональности достаточно наследования. А хотелось именно typehinting
     
  4. iliavlad

    iliavlad Активный пользователь

    С нами с:
    24 янв 2009
    Сообщения:
    1.689
    Симпатии:
    4
    так и функциональности не будет, раз функции разные.
     
  5. Kreker

    Kreker Старожил

    С нами с:
    8 апр 2007
    Сообщения:
    5.433
    Симпатии:
    0
    A и B - разные типы, поэтому ничего удивительного нет.
     
  6. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    Брр. Они абсолютно одинаковые с точки зрения интерфейса и выполняемой функции (добавить/изменить/удалить)
    И абсолютно плевать, как они там внутри это все делают, если входные и выходные параметры соответствуют интерфейсам, а функция соответствует заявленной.

    Разница даже не в типе принимаемых данных. А в их структуре.
    Условно говоря мне нужен на вход массив ровно с четырьмя/пятью/двадцатью элементами и конкретными именованными ключами. И я хочу это проконтролировать.
    Выяснилось, что кроме явного проброса Exception, этого не сделать никак.

    Внутренняя обработка абсолютно идентична у всех классов потомков. Мы берем этот массив и маппим по внутренним правилам на структуру БД.

    Была бы готовая структура БД - проблем бы не было :) Но увы, приходится плясать от того что она обязательно изменится несколько десятков раз.

    В конечном счете решится это дело кодогенерацией, как обычно, но мне банально лень :)
     
  7. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    Удивительного нет вообще. Есть лишь сожаление, что не получится уменьшить себе работы :)
    А ведет оно себя вполне логично. Потомок должен свободно заменять предка.
    А вот уже два соседних потомка - не обязательно могут заменить друг друга. Поэтому переопределение интерфейса и не работает во избежание ошибок.

    Что касается A и B разные типы - это не совсем так.

    PHP:
    1. <?php
    2. abstract class A {}
    3.  
    4. class B extends A {
    5.     public $id;
    6. }
    7.  
    8. class R extends A {
    9.  
    10. }
    11. $b = new B;
    12. $r = new R;
    13. var_dump($b instanceof A); //true
    14. var_dump($r instanceof A); //true
    15. var_dump($r instanceof B); //false
     
  8. iliavlad

    iliavlad Активный пользователь

    С нами с:
    24 янв 2009
    Сообщения:
    1.689
    Симпатии:
    4
    чего-то не понял из примера, почему а и б - это не разные типы (или не совсем разные).
    как же ж одинаковые, если одна объявлена с параметром объект типа А, а другая - типа Б?
     
  9. Kreker

    Kreker Старожил

    С нами с:
    8 апр 2007
    Сообщения:
    5.433
    Симпатии:
    0
    То, что у них один родитель, нам ничего не говорит относительно типов. Каждый класс - это отдельный тип, независимо от родителя, даже если они полностью идентичны:
    PHP:
    1.  
    2. <?php
    3. class A {}
    4. class B {}
    5. class C {
    6.     public function property(A $obj) {
    7.         echo 1;
    8.     }
    9. }
    10.  
    11. $c = new C;
    12. $a = new A;
    13. $b = new B;
    14. $c->property($a); //1
    15. $c->property($b); //Catchable fatal error: Argument 1 passed to C::property() must be an instance of A, instance of B given
    16.  
     
  10. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    Потому что B наследует и реализует все отличительные особенности A. Поэтому при проверке
    принадлежит ли B к типу A - ответ будет утвердительным.
    R и B примером ниже это уже разные типы.

    Но суть не в этой фигне.

    Вопрос, который побудил меня это проверить я описал ниже. Я просто хотел переложить часть работы на синтаксический анализатор языка. Выяснилось, что неявно перегрузить метод нельзя!
    Т.е. только magic методы или способ приведенный мной.

    Хм. Есть мысля. А что если сделать два интерфейса с разным определением одного и того же метода.
    И попробовать создать класс на их базе...
    Сейчас проверю.
     
  11. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    Все. Низя.

    PHP:
    1. <?php
    2. interface AE {
    3.     public function say(E $msg);
    4. }
    5. interface AB {
    6.     public function say(B $msg);
    7. }
    8.  
    9. class XA implements AE, AB {
    10.     public function say(E $msg) {
    11.         var_dump($msg);
    12.     }
    13.  
    14.     public function say(B $msg) {
    15.         var_dump($msg);
    16.     }
    17. }
    18.  
    Ну значит так тому и быть.
     
  12. Koc

    Koc Активный пользователь

    С нами с:
    3 мар 2008
    Сообщения:
    2.253
    Симпатии:
    0
    Адрес:
    \Ukraine\Dnepropetrovsk
    конечно низя. Это ж не ява, где значения по умолчанию реализуются перегрузкой
    [js]public int method(int a, int b, int c) {
    return a + b + c;
    }

    public int method(int a, int b) {
    return method(a, b, 2);
    }

    public int method(int a) {
    return method(a, 1);
    }
    [/js]


    а меня немного удивляет такое:
    PHP:
    1. <?php
    2. class A
    3. {
    4.     CONST B = 'foo';
    5. }
    6. class C extends A {}
    7. C::B // а нет тут ничего!
     
  13. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    Перегрузка нужна не только для значений по-умолчанию.

    В принципе все оно реализуется и на PHP через __call()
    Вот только автокомплита не будет - а это большой минус.
     
  14. TheShock

    TheShock Активный пользователь

    С нами с:
    30 май 2009
    Сообщения:
    1.255
    Симпатии:
    0
    Адрес:
    Київ
    о да, описанное в первом сообщении меня огорчило, когда я взял свой класс Map из obj-php и постарался сделать из него специализированное хранилище объектов, переопределив метод public function put ($var) так: public function put (MyObject $object). Типа того. Очень огорчило, что нельзя.
     
  15. AlexGousev

    AlexGousev Активный пользователь

    С нами с:
    25 мар 2006
    Сообщения:
    1.505
    Симпатии:
    0
    Адрес:
    Москва
    Большинство IDE понимают PHPDoc:
    PHP:
    1.  
    2. <?php
    3. /**
    4.  * Some class
    5.  * @method int borp() borp(int $int1, int $int2) multiply two integers
    6.  */
    7. class Magician {
    8.    function __call($method, $params) {
    9.       if ($method == 'borp' && count($params) == 2)
    10.          return $params[0] * $params[1];
    11.    }
    12. }
    13.  
     
  16. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    Понимать понимают. Но крайне криво.
    Т.е. таким образом можно дать знать что метод есть. И не более того.

    А какие у него параметры, что он возвращает - ничерта не показывается.
     
  17. Psih

    Psih Активный пользователь
    Команда форума Модератор

    С нами с:
    28 дек 2006
    Сообщения:
    2.678
    Симпатии:
    6
    Адрес:
    Рига, Латвия
    Simpliest
    Смотря как прописать phpDoc :)
     
  18. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    Твое кунг-фу круче моего?
    Не вопрос.

    Покажи мне, как прописать в phpDoc типы аргументов для методов которые вызываются только через __call(),
    и я тебе скажу спасибо.
     
  19. Psih

    Psih Активный пользователь
    Команда форума Модератор

    С нами с:
    28 дек 2006
    Сообщения:
    2.678
    Симпатии:
    6
    Адрес:
    Рига, Латвия
    Simpliest
    Ну извините, это скриптовый язык - вам шашечки или ехать? Объявите методы нормальным образом, как public со всеми phpDoc's, если надо - используйте интерфейсы и будет вам счастье блин. Не нужно подходить через Ж к задаче только потому, что язык умеет вот эту крутую фитчу, она новая и я должен её впихнуть! Я уже не говорю, что сам мануал настаивает на обдуманном использовании этой функциональности, т.к. она достаточно тормознутая и нужно сто раз подумать, прежде чем её использовать.
     
  20. Koc

    Koc Активный пользователь

    С нами с:
    3 мар 2008
    Сообщения:
    2.253
    Симпатии:
    0
    Адрес:
    \Ukraine\Dnepropetrovsk
    Еще ни разу не использовал __get, __set, __call, ArrayAccess. Ну не было необходимости и все тут.
    Зато все время использую get_called_class, которого нет в <5.30 и он у меня через Ж написан (парсит файл через регуоярки).
     
  21. [vs]

    [vs] Суперстар
    Команда форума Модератор

    С нами с:
    27 сен 2007
    Сообщения:
    10.559
    Симпатии:
    632
    IMHO __call относится к той же чепухе, что и goto и register_globals, хотя это все разные фичи, я говорю о том что это лишняя чепуха. Лучше написать код проще но длинее.
     
  22. Kreker

    Kreker Старожил

    С нами с:
    8 апр 2007
    Сообщения:
    5.433
    Симпатии:
    0
    Не-не.
    Вот смотри, есть объект машина. У машины, по определению есть двигатель, колеса, руль - все это объекты. Связать их можно через машину. По отдельности они так и останутся запчастями. Соответственно, доступ к этим объектам должен быть через машину, чтобы понять, что запчасти именно её. Из чего следует, что у объекта машина должны быть методы, возвращающие объекты её колес, руля, двигателя. Когда таких объектов мало, можно в классе машины их описать, когда много - не имеет смысла. Для этого и существует _call

    PHP:
    1. <?php
    2. class Car {
    3.  
    4.   function __call($method) {
    5.      //получаем данные для этого того, чтобы создать экземпляр класса конкретной запчасти машины именно этой модели
    6.      return new $method($args);
    7.   }
    8.  
    9. }
    Таким, образом, взаимодействие запчастей друг на друга будет проще:
    $mycar->колеса->передняя_ось->поворот($mycar->руль->поворот(60 градусов))
     
  23. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    Не извиню. Вы изволили сказать что это возможно.

    А ездить я предпочитаю на кодогенераторах, потому что, несмотря на все удобство __get/__set/__call в определенных моментах, они чрезвычайно медленны.

    Может и лучше, но ты потом модернизировать эту лапшу пробовал?

    В моем случае задача решилась через проброс исключения. И несколько некорректный phpDoc (хотя и A и B это банальные DTO без методов вовсе)
    PHP:
    1. <?php
    2. /**
    3.  * @param B $data
    4.  */
    5. function update(A $data) {
    6.     if (! ($data instanceof B)) {
    7.         throw new Exception('Instance of B required');
    8.     }
    9. }
    Более правильным же решением будет генерация пустого DTO с соответствующим phpDoc. Но пока переписывать лень.
     
  24. [vs]

    [vs] Суперстар
    Команда форума Модератор

    С нами с:
    27 сен 2007
    Сообщения:
    10.559
    Симпатии:
    632
    Это опять спор о ООП и функциях. Объектом может быть только машина, а двигатель, колеса и руль могут быть методами, и чтобы избежать копипаста, характеристики получать из единой сигнатуры.
    Наверное, во мне все еще живет стереотип, что много объектов - это плохо :)
     
  25. Koc

    Koc Активный пользователь

    С нами с:
    3 мар 2008
    Сообщения:
    2.253
    Симпатии:
    0
    Адрес:
    \Ukraine\Dnepropetrovsk
    а у меня сайчас такая галиматья начала писаться.

    PHP:
    1. <?php
    2. include_once 'includes/common/class.Modulator.php';
    3. // прописываем схему модуля
    4. $scheme = array(
    5.     'module' => 'news',
    6.     'defaultSubModules' => array('entries')
    7. );
    8. // типа смотрим на подмодуль
    9. switch (AdminRouter::instance()->getActiveSubmodules(0)) {
    10.     case 'categories':
    11.         include_once 'includes/admin/modules/news/categories/class.ManagerNewsCategories.php';
    12.         include_once 'includes/admin/modules/news/categories/class.ListerNewsCategories.php';
    13.        
    14.         $scheme['main'] = 'ManagerNewsCategories';
    15.         $scheme['lister'] = 'ListerNewsCategories';
    16.         $scheme['subModule'] = array('categories');
    17.         break;
    18.    
    19.     case 'entries':
    20.     default:
    21.         include_once 'includes/admin/modules/news/entries/class.ManagerNews.php';
    22.         include_once 'includes/admin/modules/news/entries/class.ListerNews.php';
    23.        
    24.         $scheme['main'] = 'ManagerNews';
    25.         $scheme['lister'] = 'ListerNews';
    26.         $scheme['subModule'] = array('entries');
    27.         break;
    28. }
    29. // вызываем наш модуль с заданной схемой
    30. new Modulator($scheme);
    31.  
    этот Modulator создаст экземпляр класса (ManagerNews или ManagerNewsCategories), добавит свою инстанцию туда через метод setModulator, потом, возможно создастся ListerNews или ListerNewsCategories и в него тоже будет помещена инстанция Модулятора.

    Вот такое гавно получается. Сам не знаю, как к такому пришел. Просто некоторыми данными между Lister* и Manager* нужно обмениваться, поэтому так и получилось.


    зыыы:
    руль - не метод. Руль тогда уж свойство. А крутить руль - метод.