Пользователь
0,2
рейтинг
5 июля 2014 в 22:30

Разработка → Делаем из массивов объекты tutorial

PHP*
image
PHP содержит очень мощный инструмент — массивы. Половина функций в PHP возвращает результат как ассоциативные массивы. При работе с такими функциями легко допустить ошибки в именовании ключей массива. Понятно что для опытного разработчика это не составляет проблем, но для начинающих это частенько может стать головной болью, особенно если мы получаем огромный json или просто правим legasylegacy код, ну а для не программистов… (таких как я) может послужить генератором говнострашного кода.

Тут приведу некий трюк, который позволяет использовать массив как объект/коллекцию. Возможно кому то это покажется глупым, а кому то даст идеи для применения.

Сразу оговорюсь что реализация рабочая для PHPStorm, в других IDE нужно вам проверять.

Часть примеров будет взята с потолка, часть будет взята из Instagram api.

Сначала примеры.

Примеры


Пример 1. Работаем с данными формы.

Мы имеем форму:
<form method="post" action="actionurl?route=myroute">
    <label for="id">clientID:</label><input id="id" name="id" type="text" value="6041eee77ac94e6ba83fbcec61ad46c4"><br>
    <label for="secret">ClientSecret:</label><input id="secret" name="secret" type="text" value="539141addd854a8c8d431465d6016590"><br>
    <label for="tag">Tag:</label><input id="tag" name="tag" type="text" value=""><br>
    <input type="submit" value="subscribe" name="subscribe"> | <input type="submit" value="unsubscribe" name="subscribe"> | <input type="submit" value="list" name="subscribe">
</form>


При отправке получаем $_POST. Мне всегда не нравилось такое использование (проверку существования ключей опускаю)
$id = $_POST['id];
Куда интереснее использовать все возможности нашего IDE и знать за ранее о переменных в нашем POST.
Создаем класс для формы:
/* @property $id string*/
/* @property $secret string */
/* @property $tag string*/
/* @property $subscribe string value from MyForm::const*/
class MyForm extends Post {
    const SUBSCRIBE  = 'subscribe';
    const UNSUBSCRIBE = 'unsubscribe';
    const LIST_ = 'list';
}

Ну и результат использования такого «класса»
image
Сразу видно с чем имеем дело.

Пример 2. Работаем с сессиями.

Нам нужно работать с сессиями максимально просто.
Наш класс:
/* @property $var1 string */
/* @property $var2 string */
/* @property $var3 string */
class MySession extends Session{
    function __construct()
    {
        parent::__construct('mySession');
    }
} 


Класс для сессий (код ArrayClass будет в конце):
class Session extends ArrayClass {
    function __construct($arrayName = 'session')
    {
        if(!isset($_SESSION[$arrayName]))
            $_SESSION[$arrayName] = array();
        parent::__construct($_SESSION[$arrayName]);
    }

    public static function init(){
        session_start();
    }
}

Это нам позволяет спокойно работать так:
$s = new MySession();
$s->var1 = 10;

Всё просто и прозрачно.

Пример 3. Instagram, json и сложные массивы

Нам нужно вызвать API. Делаем это примерно так:
$response = file_get_contents("https://api.instagram.com/v1/tags/instaprint/media/recent?access_token=14135310***************************9f2a26b8a");
$r = new Response(json_decode($response, true));
//$r = new Response($response); также сработает

$body = "Data count = ".$r->data->count();
$body.= "\n";
$tags = $r->data;
$data = new TagData($r->data->get(0));
$url = $data->images->standard_resolution->url;
$body.= $data->id;
$body.= "Image0 url: ".$url;
$body.= '<img src="'.$url.'">';

Код классов:
Немного Instagram API
/**
 * Class Response
 * @package instagram
 *
 * @property Meta $meta
 * @property \ArrayClass $data
 * @property Pagination $pagination
 *
 */
class Response extends \ArrayClass {
    public function getMeta(){
        return new Meta($this->get('meta'));
    }

    public function getPagination(){
        return new Pagination($this->get('pagination'));
    }
}

/**
 * Class Meta
 * @package instagram
 *
 * @property integer $code
 */
class Meta extends \ArrayClass {

} 

/**
 * Class Pagination
 * @package instagram
 *
 * @property integer $next_max_tag_id
 * @property integer $next_max_id
 * @property integer $next_min_id
 * @property string $next_url
 */
class Pagination extends \ArrayClass {

} 

/**
 * Class TagsData
 * @package instagram
 *
 * @property $attribution
 * @property \ArrayClass $tags
 * @property string $type
 * @property $location
 * @property $comments
 * @property $filter
 * @property integer $created_time
 * @property $link
 * @property $likes
 * @property Images $images
 * @property \ArrayClass $users_in_photo
 * @property Caption $caption
 * @property boolean $user_has_liked
 * @property integer $id
 * @property User $user
 */
class TagData extends \ArrayClass {

    public function getImages(){
        return new Images($this->get('images'));
    }

    public function getCaption(){
        return new Caption($this->get('caption'));
    }

    public function getUser(){
        return new User($this->get('user'));
    }
}

/**
 * Class Image
 * @package instagram
 *
 * @property Image $low_resolution
 * @property Image $thumbnail
 * @property Image $standard_resolution
 */
class Images extends \ArrayClass {
    function __get($name)
    {
        return new Image($this->$name);
    }
}

/**
 * Class Image
 * @package instagram
 *
 * @property string $url
 * @property string $width
 * @property string $height
 */
class Image extends \ArrayClass {

} 

/**
 * Class Caption
 * @package instagram
 *
 * @property integer $created_time
 * @property string $text
 * @property User $from
 * @property int $id
 *
 */
class Caption extends \ArrayClass{
    public function getFrom(){
        return new User($this->get('from'));
    }
}

/**
 * Class From
 * @package instagram
 *
 * @property string $username
 * @property string $website
 * @property string $profile_picture
 * @property integer $id
 * @property string $full_name
 * @property string $bio
 */
class User extends \ArrayClass{

}




Как это выглядит в IDE:
image

В 2х словах. Мы получаем json от Instagram и заворачиваем его в наши классы. На выходе получаем структуру классов и помощь от нашей IDE.

Ну а теперь сам ArrayClass:
class ArrayClass implements Iterator {
    private $array;
    //...
    /**
     * @param array|ArrayClass|json_string $array
     */
    function __construct(&$array);
    // ...
    public function get($index);
    // ...
    public function count();
    public function ar(){
        return $this->array;
    }

    public function json(){
        return json_encode($this->array);
    }
}

Полный код ArrayClass
<?php
/**
 * Created by PhpStorm.
 * User: calc
 * Date: 02.07.14
 * Time: 0:57
 */

class ArrayClass implements Iterator {
    private $array;
    private $haveNext = false;
    /**
     * @param array|ArrayClass $array
     */
    function __construct(&$array)
    {
        if($array === null) $array = array();
        if($array instanceof ArrayClass){
            $this->array = &$array->array;
        }
        else if(is_string($array)){
            $this->array = json_decode($array, true);
        }
        else{
            $this->array = &$array;
        }

        $this->rewind();
    }

    function __get($name)
    {
        return $this->get($name);
    }

    function __set($name, $value)
    {
        $this->array[$name] = $value;
    }

    function __isset($name)
    {
        return isset($this->array[$name]);
    }

    function __unset($name)
    {
        unset($this->array[$name]);
    }

    public function get($index){
        if(isset($this->array[$index])){
            if(is_array($this->array[$index])){
                return new ArrayClass($this->array[$index]);
            }
            return $this->array[$index];
        }
        return null;
        //return isset($this->array[$index]) ? $this->array[$index] : null;
    }

    public function count(){
        return count($this->array);
    }

    public function ar(){
        return $this->array;
    }

    /**
     * (PHP 5 >= 5.0.0)<br/>
     * Return the current element
     * @link http://php.net/manual/en/iterator.current.php
     * @return mixed Can return any type.
     */
    public function current()
    {
        return current($this->array);
    }

    /**
     * (PHP 5 >= 5.0.0)<br/>
     * Move forward to next element
     * @link http://php.net/manual/en/iterator.next.php
     * @return void Any returned value is ignored.
     */
    public function next()
    {
        $this->haveNext = next($this->array);
    }

    /**
     * (PHP 5 >= 5.0.0)<br/>
     * Return the key of the current element
     * @link http://php.net/manual/en/iterator.key.php
     * @return mixed scalar on success, or null on failure.
     */
    public function key()
    {
        return key($this->array);
    }

    /**
     * (PHP 5 >= 5.0.0)<br/>
     * Checks if current position is valid
     * @link http://php.net/manual/en/iterator.valid.php
     * @return boolean The return value will be casted to boolean and then evaluated.
     * Returns true on success or false on failure.
     */
    public function valid()
    {
        return $this->haveNext;
    }

    /**
     * (PHP 5 >= 5.0.0)<br/>
     * Rewind the Iterator to the first element
     * @link http://php.net/manual/en/iterator.rewind.php
     * @return void Any returned value is ignored.
     */
    public function rewind()
    {
        $this->haveNext = $this->array === array() ? false : true;
        reset($this->array);
    }

    public function json(){
        return json_encode($this->array);
    }
}




Мы создаем обертку массиву и даем этой обертке пару интересных возможностей
  • Универсальный конструктор. Можно создать объект из массива (даже из $_POST, $_GET и т.д), из себе подобного ArrayObject, из json строки
  • метод get для получения значений по ключу, удобно для цифровых значений (0..n), можно использовать в цикле for($i) $a->get($i), для массивов, которые содержат и текстовые ключи.
  • Ну json() — просто завернуть в json


Вот что получаем на выходе:
image

Как вариант можно добавить проверки в дочерние классы.
Плюсы в том, что если вы через год залезете в свой или чужой код IDE вам даст подсказку и вам не придется перечитывать половину кода проекта, чтобы понять что за магические константы в $_GET['magic'] и подобных строчках кода.

Если у кого нибудь есть дополнения по использованию памяти и производительности прошу отписаться в комментариях. Спасибо.

PS
GET, POST, SESSION
class Get extends ArrayClass {
    function __construct()
    {
        parent::__construct($_GET);
    }
}

class Post extends ArrayClass {
    function __construct()
    {
        parent::__construct($_POST);
    }
}

class Session extends ArrayClass {
    function __construct($arrayName = 'session')
    {
        if(!isset($_SESSION[$arrayName]))
            $_SESSION[$arrayName] = array();
        parent::__construct($_SESSION[$arrayName]);
    }

    public static function init(){
        session_start();
    }
}

@Calc
карма
17,0
рейтинг 0,2

Похожие публикации

Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (10)

  • +3
    Конечно это все здорово, но почему бы не использовать MVC или хотя бы часть его, для всего этого. Есть тысячи компонентов и фреймворков для работы с формами, HTTP запросами, моделями, сессиями и прочим, а Вы создали странный костыль, который не несет никакого полезного функционала, а лишь подсказывает свойство в автокомплите. Таким же образом можно и ArrayObject дополнить аннотацией "@ property" ведь все равно свойство модели нужно описать, но ко всему у него нативная скорость работы.
    • 0
      С формами согласен. Но есть проекты без фреймворков.
      Также ни кто не запрещает в дочерних классах запретить __set.
      Тот же самый Yii дает возможность работать с сессиями как с массивом, а мы хотим как с объектом.
      Можно конечно сериализовать свой объект и десереализовать, можно обернуть массив.
      Тут обертка больше для json подходит. Отсутствует проверка на isset или key_exist, если нет переменной — получим null.
      Опять же это можно инкапсулировать в любом методе get (например дефолтное значение).
      Идея была в том, что в будущем можно изменить наши классы под изменение в системе. И все изменения останутся в одном дереве классов, а не по всему проекту с конструкциями isset и т.д.
      А MVC — не панацея. В модели можно использовать наше дерево классов и сделать свой фасад и т.д.

      Заголовок
      json
      {
        "pagination": {
          "next_max_tag_id": "1404127307134234",
          "deprecation_warning": "next_max_id and min_id are deprecated for this endpoint; use min_tag_id and max_tag_id instead",
          "next_max_id": "1404127307134234",
          "next_min_id": "1404412273269367",
          "min_tag_id": "1404412273269367",
          "next_url": "https://api.instagram.com/v1/tags/yrakir/media/recent?access_token=1413531024.60************************319f2a26b8a&max_tag_id=1404127307134234"
        },
        "meta": {
          "code": 200
        },
        "data": [
          {
            "attribution": null,
            "tags": [
              "фокусник",
              "иллюзионист",
              "ведущий",
              "волшебник",
              "yrakir",
              "ведущийиллюзионистюрийкир89299142016",
              "юрийкир",
              "юракир"
            ],
            "type": "image",
            "location": {
              "latitude": 55.8588,
              "longitude": 37.567405
            },
            "comments": {
              "count": 0,
              "data": []
            },
            "filter": "X-Pro II",
            "created_time": "1404412273",
            "link": "http://instagram.com/p/p_8ET7Kkh3/",
            "likes": {
              "count": 36,
              "data": [
                {
                  "username": "timofeev081",
                  "profile_picture": "http://images.ak.instagram.com/profiles/profile_197497217_75sq_1382615347.jpg",
                  "id": "197497217",
                  "full_name": "Pavel Timofeev"
                },
                {
                  "username": "denissmile5",
                  "profile_picture": "http://images.ak.instagram.com/profiles/profile_196038812_75sq_1394347739.jpg",
                  "id": "196038812",
                  "full_name": "denissmile5"
                },
                {
                  "username": "sitnikovalyusia",
                  "profile_picture": "http://images.ak.instagram.com/profiles/profile_299334845_75sq_1360006870.jpg",
                  "id": "299334845",
                  "full_name": "Людмила Ситникова"
                },
                {
                  "username": "valeryana_pashkova",
                  "profile_picture": "http://photos-g.ak.instagram.com/hphotos-ak-xpa1/924007_817454458267534_846281437_a.jpg",
                  "id": "191689127",
                  "full_name": "Matil•Da"
                }
              ]
            },
            "images": {
              "low_resolution": {
                "url": "http://scontent-a.cdninstagram.com/hphotos-xpa1/t51.2885-15/10507870_1473305886242980_1292181890_a.jpg",
                "width": 306,
                "height": 306
              },
              "thumbnail": {
                "url": "http://scontent-a.cdninstagram.com/hphotos-xpa1/t51.2885-15/10507870_1473305886242980_1292181890_s.jpg",
                "width": 150,
                "height": 150
              },
              "standard_resolution": {
                "url": "http://scontent-a.cdninstagram.com/hphotos-xpa1/t51.2885-15/10507870_1473305886242980_1292181890_n.jpg",
                "width": 640,
                "height": 640
              }
            },
            "users_in_photo": [],
            "caption": {
              "created_time": "1404412273",
              "text": "КОПЧИК - это маленький полицейский !!! #юракир #юрийкир #yrakir #ведущий #волшебник #ведущийиллюзионистюрийкир89299142016 #фокусник #иллюзионист",
              "from": {
                "username": "yrakir",
                "profile_picture": "http://images.ak.instagram.com/profiles/profile_259027705_75sq_1398769213.jpg",
                "id": "259027705",
                "full_name": "Иллюзионист +79299142016"
              },
              "id": "756587441920035212"
            },
            "user_has_liked": false,
            "id": "756587441483827319_259027705",
            "user": {
              "username": "yrakir",
              "website": "",
              "profile_picture": "http://images.ak.instagram.com/profiles/profile_259027705_75sq_1398769213.jpg",
              "full_name": "Иллюзионист +79299142016",
              "bio": "",
              "id": "259027705"
            }
          }
        ]
      }
      

      • +2
        Не нужно изобретать велосипед! Вот отличный сериализатор.
      • 0
        Учитывая, что существуют микрофреймворки, например Silex, и что эти же микрофреймворки можно прикручивать и к существующим проектам, которые без фреймворков, то писать проекты «без фреймворков» можно только в учебных целях.

        Даже в учебных целях лучше изучить какой-то популярный инструмент, при этом получить опыт, поддержку от сообщества и возможность расширять проект.
  • +2
    А зачем было писать ArrayClass? Есть же встроенный ArrayObject.

    ArrayObject implements IteratorAggregate , ArrayAccess , Serializable , Countable
    


    Он же возвращает встроенный ArrayIterator.

    public ArrayIterator getIterator ( void )
    


    arrayobject
    arrayiterator

    ArrayIterator легко далее сделать рекурсивным, уже сами погуглите recursive.

    Зачем изобретать велосипеды? Разве что в учебных целях.
    Правильнее было бы максимально использовать встроенный объекты и SPL по максимуму.

    А если уж делать свою коллекцию, по которой хочется итерироваться сразу, то как-то так.

    class Collection implements
    \ArrayAccess, \Serializable, \Iterator, \Countable
    


    Для примера реализации можно взглянуть на ArrayCollection от Doctrine.

    Collection
    ArrayCollection
    • 0
      Идея была именно в жесткой связи с IDE и ухода от isset
      $a = new ArrayObject();
      
      var_dump($a->my);
      $a->my = 1;
      var_dump($a->my);
      
      echo "======\n";
      $ar = array();
      $a = new ArrayClass($ar);
      
      var_dump($a->my);
      $a->my = 1;
      var_dump($a->my);
      

      [web@centos html]$ php test.php
      PHP Notice:  Undefined property: ArrayObject::$my in /home/web/html/test.php on line 13
      NULL
      int(1)
      ======
      NULL
      int(1)
      
      

      и во втором случае IDE сразу подчеркивает «access via magic metod»
      также Code Inspect дает сразу 3 ошибки. Чего не происходит в первом случае.

      ну и
      print_r($ar);
      Array
      (
          [my] => 1
      )
      
      


      и на месте $ar может быть любой системный массив. ($_GET, $_POST) и т.д.
      Даже если моя реализация тяжелее, она завязана на IDE и сразу показывает где у нас могут быть ошибки, что не скажешь про ArrayObject

      И «навешивать» сразу много интерфейсов не есть хорошо. Они могут ни когда не пригодиться, нужны будут — люди довесят. Я не привел законченное решение, и это не библиотека. Люди захотят — изменят/используют. Просто идея по некой типизации массивов.

      ArrayObject создает копии массивов.
      Представьте, у вас 1000000 элементов.
      • 0
        В ArrayClass все ацессоры работают через нативные методы + мы используем существующую память, а не стараемся ее раздувать копированием массивов.

        var_dump($a->my);
        $a->my = 1;
        var_dump($a->my);
        $ar['my'] = 2;
        var_dump($a->my);
        
        print_r($ar);
        
        • 0
          ar = array();
          
          for($i=0; $i<1000; $i++){
              $ar[$i] = $i;
          }
          
          //memory usage (895752)
          //$a = new ArrayObject($ar);
          
          //memory usage (799632)
          $a = new ArrayClass($ar);
          
          var_dump($a->my);
          $a->my = 1;
          var_dump($a->my);
          var_dump($ar['my']);
          
      • 0
        Вот еще пару мини решений, чтобы уйти от isset

        get-in
        LazyAccess-to-PHP-arrays

        Вторая ссылка похожа на ваш ArrayClass по идее.

        NetBeans не показывает, что вызов через магический метод. Это фишка PhpStorm.
        • 0
          Ну у меня есть единомышленники :)
          Ну и еще
          Netbeans:

          Netbeans
          image

          Проблема в том, что комменты она не все ест, а так эффект такой же. Или вы не об этом?

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.