PHP содержит очень мощный инструмент — массивы. Половина функций в PHP возвращает результат как ассоциативные массивы. При работе с такими функциями легко допустить ошибки в именовании ключей массива. Понятно что для опытного разработчика это не составляет проблем, но для начинающих это частенько может стать головной болью, особенно если мы получаем огромный json или просто правим legasy код, ну а для не программистов… (таких как я) может послужить генератором говнострашного кода.
Тут приведу некий трюк, который позволяет использовать массив как объект/коллекцию. Возможно кому то это покажется глупым, а кому то даст идеи для применения.
Сразу оговорюсь что реализация рабочая для 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';
}
Ну и результат использования такого «класса»
Сразу видно с чем имеем дело.
Пример 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=1413531024.6041eee.200d59e8b9c3448e913ef319f2a26b8a");
$r = new Response(json_decode($response, true));
$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.'">';
Код классов:
/**
* 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:
В 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);
}
}
/**
* 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
Вот что получаем на выходе:
Как вариант можно добавить проверки в дочерние классы.
Плюсы в том, что если вы через год залезете в свой или чужой код IDE вам даст подсказку и вам не придется перечитывать половину кода проекта, чтобы понять что за магические константы в $_GET['magic'] и подобных строчках кода.
Если у кого нибудь есть дополнения по использованию памяти и производительности прошу отписаться в комментариях. Спасибо.
Автор: Calc