«Пишем PHP фреймворк для создания серверных частей приложений в социальных сетях»
Раздел: Социальные сети
ВступлениеКогда я только начинал разрабатывать приложения для социальных сетей, хватало 2 файлов — config.php со всеми настройками и простым классом работы с базой, и index.php который отвечал за получение параметров от клиента, обработку и отображение результатов. Естественно долго так продолжаться не могло — сотни строк кода не способствовали быстрой разработке и рефакторингу, обычные sql запросы в базу заставляли делать кучу лишних движений по обработке результатов, критические изменения — изменение источника данных, отображения, префикса таблицы или вообще типа базы или же социальной сети — грозили переписыванием множества строк кода. В сложившейся ситуации встала задача разработки компактной системы, которая позволит максимально сосредоточиться на бизнес логике и абстрагироваться от локальных параметров — типа базы, хостинга, социальной сети. Наиболее подходящей оказалась модель mvc. Ее использование позволило менять через настройки отображение (json/html/xml/что то свое) и контроллер авторизации (для смены соц сети) без редактирования кода модели. Для работы с базой написана прослойка, которая позволяет обращаться к таблицам как к переменным класса модели (например $this->user, $this->article обращаются к таблицам user и article соответственно) с заданными методами (create,load,loads,save,count) которые возвращают ожидаемые результаты, и использовать для запросов значения по умолчанию (если во всем методе используется userId=123 то можно указать это 1 раз, а не в каждом запросе). В сегодняшнем примере я покажу пример реализации с использованием базы Mysql и соц сети Facebook. Итоговая структура проекта получилась следующей: /index.php — «точка входа» для запросов от flash или ajax /system/settings.ini — все настройки базы и приложения в удобном ini формате /system/classes/core/ — папка с классами «ядра» — проверка авторизации, отображение, абстрактные методы бд и модели, специфичные для соц сети классы (например для facebook это библиотека facebook php-sdk) /system/classes/model/ — папка с моделями, которые хранятся в виде /modelName/modelName.php и реализуют класс modelName Обращение извне к системе имеет следующий вид: example.com/?act=user.getInfo (для некоторых соц сетей необходимо так же передавать user_id и auth_key) Теперь перейдем к реализации кода Точка входаВ начале файла index.php задаются все необходимые для работы define и реализуется метод __autoload для автоматической подгрузки классов. Так же нужно заметить, что для корректной работы необходим PHP 5.2+. Основной класс назван Dispatcher, служит для загрузки файлов конфигурации, проверки авторизации и входящих данных, вызова модели и передачи в отображение результатов ее работы. Класс наследован от Checker, его я разберу чуть позже. В классе всего всего 2 метода — конструктор, в котором реализованы все проверки и вызов роутера, и сам роутер. После класса вызывается собственно создание его экземпляра для начала работы. Немного о формате выходных данных. Опытным путем составили структуру наиболее удобную для флеша — result отвечает за статус обработки (1-все ок, меньше 0 — ошибка) а response содержит результат. Пример такого ответа в формете json: {«response»:{«avatar»:«1»},«result»:«1»}. Метод _error это метод класса Checker, и он как раз возвращает на view result<0. Для использования settings и _error в конструктор модели передается ссылка на диспатчер. НастройкиФаил settings.ini прост по своей структуре. [db] Блок [db] отвечает за подключение к базе, блок [api] отвечает за настройки в социальной сети, [global] определяет источник входящих данных и формат отображения. Теперь перейдем к классам из папки /system/classes/core/. Их в данном случае 7: AbstractDb, AbstractModel, Checker, Database, Registry, View + указанный выше класс Facebook. CheckerЭтот класс реализует следующие методы: конструктор, проверка авторизации, проверка input переменной, проверка input переменной-числа, метод ошибки. Констуктор просто проверяет откуда брать данные, записывает их в массив input и вызывает метод проверки авторизации: function __construct(){ Метод check_auth самый негибкий в системе. По причине того, что в 1 папке на хостинге всегда лежит версия только для 1 социальной сети, создавать лишний раз нагрузку через if смысла нет и быстрее-закомментировать неиспользуемые варианты авторизации. Вот вариант для контакта: $viewer_id = $this->_check_num('viewer_id',-1); По причине того что для авторизации в фейсбуке нужно использовать curl запрос, который занимает десятки мс, я предпочитаю кэшировать в куках переменную пользователя. Вот вариант для фейсбука с кэшированием: Функция _setup_me_cook для записи значений в куку: В итоге если мы работаем с фейсбуком, то на момент авторизации может быть только ошибка -1 (чаще всего устаревший\отсутствующий access token) для контакта — -1 -2 -3 и -4. Завершаем check_auth проверкой переданного act, который в случае ошибки возвращает -5 if(!isset($this->input['act'])||$this->input['act']==""||substr($this->input['act'],0,1)=="_") $this->_error(-5); Функции _check _check_num и _error интуитивно понятны, поэтому лишь приведу их код. Стоит лишь заметить что функция получения числа так же имеет возможность проверить число на вхождение в массив-для этого его надо передать 3 необязательным аргументом. Так как View мы создали еще раньше в конструкторе Dispatcher, то можно его использовать в методе _error. function _check($text,$err){ RegistryДанный класс в принципе стандартен, служит лишь для хранения параметров, и никаких изысков от него не нужно ViewДанный класс содержит конструктор (для указания типа отображения), общий метод show в который передается отображение, и реализации отображений для каждого типа (т.е. функции для json,xml,html). <?php Так же приложу мою вариацию функции json. Собственно по причине того, что utf и кириллицу нужно прогонять по своему, и был введен для метода show параметр check function json($data,$check){ Реализация метода _json_encode взята отсюда. AbstractModelДанный класс наследуется всеми моделями по умолчанию. Он содержит конструктор (для сохранения ссылки на Dispatcher) и реализацию метода __get для «красивой» работы с базой. Так же в этом классе очень удобно писать общие для всех моделей в данной игре методы — например какие то подсчеты очков, которые могут быть вызваны во всех методах. <?php DatabaseДля каждой базы нужно писать свой класс Database и скорее всего AbstractDb (по этой причине были попытки их объединить, но пока безуспешные). Я приведу пример для варианта mysql, основанный на mysqli. Конструктор класса ничего не принимает, и считывает параметры из Registry. Это очень удобно если нужно «автономно» работать с базой — например в cron, достаточно подключить 2 класса и записать в registry настройки. Так же не надо эти настройки передавать в AbstractModel и из нее в AbstractDb. Хотя никто не мешает переделать под передачу параметров. Основные параметры это хост, логин, пароль, название базы и кодировка. Параметр active нужен что бы не создавать сразу подключение к базе а лишь по мере необходимости (могут возникнуть ситуации, когда копия базы создана, а подключение не нужно). function Database() { Функция connect отвечает за установку соединения при active=0 function connect(){ При желании можно вызывать connect при создании базы, а любое упоминание о active из кода убрать. Далее идут реализации методов самой работы с базой. select возвращает массив ассоциативных массивов значений, create возвращает id созданной записи, count возвращает просто число, update возвращает число измененных строк, а query ничего не возвращает и служит для прямого обращения к базе и методов delete. Вспомогательные методы insert_id и affected_rows созданы на случай если нужны будут соответствующие данные. Соответственно можно реализовать любую бд, главное что бы она реализовывала данные методы и возвращала соответствующие значения. Остался самый большой и главный класс для работы с базой — AbstractDb. Он позволяет преобразовывать вызов модели вида $this->user->loads(«name»,«w=2»); в запросы select name from prefix_user where w=2 limit 0,30. AbstractDbКонструктор данного класса принимает от магического метода __get имя таблицы, обнуляет значение по умолчанию и получает соединение с базой от Registry <?php Если необходимо использовать префиксы, то можно или жестко задать $this->name="prefix_".$name; или же брать из настроек. Работа со значениями по умолчанию реализуется в 3 методах — del_default, set_default и get_default. del_default удаляет указанный ключ и пересчитывает в строку default_where если осталась одна запись (удаляем 3 первых символа («or » или «and»)). function del_default($name){ set_default записывает в массив по ключу «имя параметра» запрос where. Например «name like '%s'» будет сохранено как array(«name»=>«name like '%s'»). Это сделано что бы сразу при необходимости удалять по ключу «name» из массива. 2 параметр этой функции указывает, как относится к другим значениям дефаулт 0 как AND, 1 как OR. get_default принимает запрос пользователя (в виде строки) и возвращает пустую строку или строку запроса (для ускорения в простой ситуации и служит default_where — нам не нужен перебор). Поскольку возможна ситуация когда пользователь указал where но не указал по умолчанию, сделал наоборот или указал и то и другое\не указал ничего — where надо «клеить» в каждой ситуации. Для этого сделана универсальная функция get_where. Она принимает where пользователя, флаг «использовать значения по умолчанию»(1/0) и возвращает нормальное where. Теперь можно перейти к самим методам работы с базой. saveИспользует update mysql метод. Принимает массив параметров обновления, where пользователя и флаг where по умолчанию. Возвращает 0 или число обновленных записей. function save($values,$where="",$use_def=1){ removeИспользует delete mysql метод. Принимает where пользователя и флаг where по умолчанию. function remove($where="",$use_def=1){ countИспользует select (*) mysql метод. Принимает where пользователя и флаг where по умолчанию. function count($where="",$use_def=1){ loadФункция для загрузки 1 единственной записи (limit 0,1). В случае если запросили одно поле — возвращает сразу его значение, если несколько или все — возвращает ассоциативный массив значений. Базируется на функции loads. Принимает список полей в виде строки, where пользователя и флаг where по умолчанию, параметры группирования и сортировки. function load($expression,$where="",$use_def=1,$group_by="",$order_by=""){ loadsИспользует select mysql функции. Принимает все возможные select параметры и возвращает массив — результат работы select метода класса БД. Закомментированая строчка служит для отладки запросов. createИспользует insert into. Может принять как массив параметров (вставка 1 строчки), так и массив массивов для нескольких строк. Id по умолчанию задается как '0' Единственный минус этого метода — нельзя нормально использовать insert into select * from. При необходимости решается 1 флагом метода, но пока у меня такой необходимости не было. Зачем это все нужно, или пример использованияДля того что бы показать возможности системы — приведу код класса User. Он реализует вызовы example.com/?act=user.getInfo (проверка существования пользователя) example.com/?act=user.register (регистрация пользователя с получением из GET числа avatar) example.com/?act=user.getMe (возвращает всю информацию о пользователе) example.com/?act=user.getPoints (возвращает очки пользователя) На мой взгляд, такой код гораздо удобнее для понимания и редактирования, чем множество прямых mysql запросов в базу и помогает быстро писать код логики, при необходимости быстро внося изменения. И не смотря на такой подход — скорость выполнения совершенно не страдает, самые «сложные» методы стабильно выполняются за 2-4 мс. Дата публикации: 2011-04-01 |