Проектирование и разработка веб-приложения на основе технологий Symfony Framework

  • Вид работы:
    Дипломная (ВКР)
  • Предмет:
    Информационное обеспечение, программирование
  • Язык:
    Русский
    ,
    Формат файла:
    MS Word
    455,66 Кб
  • Опубликовано:
    2017-07-05
Вы можете узнать стоимость помощи в написании студенческой работы.
Помощь в написании работы, которую точно примут!

Проектирование и разработка веб-приложения на основе технологий Symfony Framework

Министерство образования и науки Российской Федерации

Федеральное государственное бюджетное образовательное учреждение

высшего образования

«Тульский государственный педагогический университет им. Л.Н. Толстого»

Кафедра информатики и информационных технологий







ВЫПУСКНАЯ КВАЛИФИКАЦИОННАЯ РАБОТА

(БАКАЛАВРСКАЯ РАБОТА)

на тему: Проектирование и разработка веб-приложения на основе технологий Symfony Framework

Оглавление

Введение

Глава 1. Теоретические основы технологий Symfony Framework

.1 История развития веб-технологий и существующие проблемы

.2 Обзор типовых решений в области веб-разработки

.3 Модель MVC и ООП в веб-программировании

.4 Назначение и установка Symfony Framework

.5 Структура Symfony Framework

1.5.1 Конфигурация

1.5.2 Бандлы

1.5.3 СущностиDoctrine

1.5.4 Маршрутизация

.5.5 Контроллеры

.5.6 Шаблонизатор Twig

.5.7 Генерация форм и валидация

.5.8 Безопасность

.5.9 Сервисы

1.5.10 Консольные команды

.5.11 Механизмы тестирования

.6.Развертывание приложенияSymfony

Глава 2. Создание приложения на основе технологий Symfony Framework

.1 Постановка задачи

.2 Настройка develop-сервера и установка Symfony

.3 Установка дополнительных библиотек через composer

.4 Вёрстка шаблона, npm, webpack

.5 Генерация сущностей и форм

.6 Определение маршрутов и контроллеров

.7 Создание и настройка сервисов

.8 Написание консольных команд и заданий cron

.9 Тестирование

.10 Перенос проекта на production-сервер

Заключение

Список литературы

Приложения

Введение

В современном информационном обществе возрастает роль и популярность интернет-сервисов. В связи с огромной популярностью веб-приложений ужесточились требования к возможностям функционала, дизайну, отказоустойчивости, безопасности разрабатываемого программного обеспечения. На данном этапе развития веб-технологий существенна фрагментация подходов к разработке, многие из которых не стандартизированы, что затрудняет работу с кодом. Их стандартизация предполагает последовательный переход к корректному оформлению кода новых разработок и переоформлению уже существующих.

Актуальность выпускной квалификационной работы определена неразрешёнными проблемами в области веб:

·  возрастание нагрузки на сервер;

·        уязвимость приложений;

·        обеспечение масштабируемости приложения;

·        необходимость версионирования кода;

·        стандартизация кода;

·        обеспечение администрирования и поддержки приложения.

Цель выпускной квалификационной работы - выполнить анализ возможностей технологий Symfony Framework и реализовать приложение интернет-магазина компьютерных игр на основе современных подходов в области веб-разработки. Для достижения поставленной цели были определены следующие задачи:

·  провести анализ современных типовых технологий и моделей в области веб;

·        выполнить анализ возможностей Symfony Framework в области веб;

·        выполнить проектирование и реализацию веб-приложения на базе Symfony Framework;

·        выполнить тестирование функционала приложения.

Практическую значимость выпускной квалификационной работы можно охарактеризовать как адаптацию оригинальной документации SymfonyFramework на русский языки разработку веб-приложения на основе современного ООП-подхода.

Выпускная квалификационная работа состоит из введения, двух глав, заключения, списка литературы и семи приложений.

Глава 1. Теоретические основы технологий Symfony Framework

 

.1 История развития веб-технологий и существующие проблемы

веб приложение symfony framework

В 1991 году британский учёный, изобретатель Тим Бернерс-Ли создал первую веб-страницу. Несмотря на то, что интернет, как таковой существовал ещё с 60-х годов 20-го века, именно появление WWW (Всемирной паутины) послужило огромным толчком для развития веб-технологий и появления взаимодействия пользователя с веб-браузером[1].

На ранних этапах развития веб-технологий исходный код веб-страницы представлял собой простой набор html-тегов, а для просмотра таких страниц существовал единственный браузер «WorldWideWeb», позже переименованный в «Nexus». С 1994г. по 1996г. произошло несколько важных событий:

•  Создание консорциума Всемирной паутины (1994г.)

•  Выпуск браузеров Netscape Navigator (1994г.) и Internet Explorer (1995г.)

•  Появление языка программирования PHP (1995г.)

•  Появление языка программирования JavaScript (1995г.)

•  Появление языка разметки CSS (1996г.)

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

В период с 2000г. по настоящее время в сети возникло огромное количество социальных сетей, поисковиков, развлекательных сервисов, интернет-магазинов и т. д.

По состоянию на 15 марта 2017-го всемирная паутина насчитывает свыше 1150млн. сайтов, а также 3500млн. Интернет-пользователей [2].

В связи с огромной популярностью веб-приложений ужесточились требования к возможностям функционала, дизайну, отказоустойчивости, безопасности и т. д. В области веб на данный момент существует ряд проблем, обусловленные различными факторами [3]:

•  Нагрузки на сервер. Обусловлены популярностью сайта и высокой посещаемостью. Владельцу веб-ресурса приходится заботиться об оптимизации ресурсов и обеспечивать высокую производительность сервера.

•  Небезопасность. Веб-приложения подвергаются хакерским атакам с целью заполучения конфиденциальных данных. Необходимо защищать сервер от различных видов атак. От простого перебора пароля до масштабных DDOS-атак.

•  Версионирование кода. При разработке крупного проекта весь процесс создания приложения разбивается на отдельные задачи, над которыми, как правило, работают несколько разработчиков. Необходимо обеспечивать удобный доступ для разработчика к различным версиям и задачам по проекту.

•  Использование единых стандартов и подходов при написании кода. Данная проблема неразрывно связана с проблемой, указанной выше. При разработке проекта несколькими программистами или при передаче его другим лицам необходимо обеспечить его удобочитаемость, возможность легко дополнять и изменять, код должен соответствовать общеизвестным стандартам, а архитектура приложения должна быть построена на общепринятых подходах.

•  Выбор оптимальных для решения конкретной задачи языков программирования, разметки, IDE, системы управления базами данных, шаблонизаторов и т. д. Для каждого конкретного проекта оптимальными являются различные технологии. Необходимо учитывать его масштабность, область применения, планируемые нагрузки, квалификацию разработчиков и т. д. И с учётом всех факторов выбирать именно те, или иные средства разработки.

•  Оптимизация кода с целью получения наилучшего быстродействия. Не только удобочитаемость кода и использование единых подходов должны учитываться при написании кода. Необходимо его постоянная оптимизация (отказ от запросов к БД в цикле, минификация стилей и скриптов, создания механизма кеширования и т. д.)

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

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

•  Использование сторонних библиотек и зависимостей. При разработке проекта часто появляются задачи, которые уже были реализованы в той, или иной степени сторонними разработчиками и оформленными ими в виде библиотек (компонентов, модулей). Необходимо обеспечивать возможность использование таких библиотек и удобное подключение их в разрабатываемому проекту.

•  Тестирование. Данная проблема связана с проблемой обеспечения поддержки проекта. Чем больше кода было покрыто тестами, тем меньше непредвиденных багов возникнет в процессе функционирования приложения.

•  Создание хоста разработки. Так как любое веб-приложение разрабатывается для работы на сервере, в процессе разработки у программиста должна иметься возможность работы с сервером, который в некоторый степени эмулирует работу «боевого» хоста. Таковым может являться локальный хост, виртуальная машина, удалённый хост.

•  Написание документации, технического задания, общение с заказчиком. Любое приложения разрабатывается «для кого-то», даже если программист пишет его сам для себя. В связи с этим вместе с приложением должна поставляться удобная документация и периодическая консультация заказчика по работе с приложением.

•  Проектирование и создание удобного и интуитивно-понятного пользовательского интерфейса. Так как в конечном счёте с любым веб-приложением будет взаимодействовать пользователь, необходимо обеспечить для него удобное и простое взаимодействие с приложением.

 

.2 Обзор типовых решений в области веб-разработки


·  Операционная система Ubuntu 16.04 (рис. 1)

Является одной из самых популярных операционных систем семейства Linux. Выпускается в редакции Server и Desktop. Обладает предустановленным сервером Apache2, PHP7.0, mysql и ещё целым рядом программ, предназначенных для веб-разработки. Все популярные инструменты и технологии как правило создаются конкретно для пользователей данной операционной системы.

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

Для максимально удобного использования Ubuntu необходимы знания консольных команд, в которой происходит большинство взаимодействий с системой. Нет необходимости постоянного использования браузера и поиска нужных программ, скачивания их, настройки и т.д. Все эти действия заменят ввод пары команд в консоли.

Система является защищенной к вирусным атакам, обладает встроенными инструментами шифрования, распространяется бесплатно, обладает большим сообществом, регулярно обновляется [4].

Рис. 1. Рабочий стол Ubuntu 16.04

·  Язык программирования PHP 7

Является намного быстрее по сравнению со своим предшественником PHP5.6 согласно источнику html5.by (рис. 2)

Рис.2. Тест производительности PHP7 при работе различных фреймворков

В PHP7 были убраны многие устаревшие функции, введена поддержка строгой типизации, добавлены новые методы, операторы, синтаксис кодирования Unicode, групповая декларация use, множество новых возможностей синтаксиса, улучшенная обработка исключений, анонимные функции.

·  Язык программирования ECMAScript 6

Одним из самых важных нововведений в последних версиях ECMAScript является полноценная поддержка использования классов, несмотря на то, что они, по сути, являются синтаксическим сахаром (конструкцией языка для упрощения реализации программного кода) прототипами [5].

Из остальных значительных нововведений можно выделить поддержку импорта и экспорта модулей, строковые шаблоны, введение нового способа объявления переменных через let и const, а также появление стрелочных функций.

·  Менеджер пакетов Composer

Composer - это пакетный менеджер для языка PHP. Он упрощает установку каких-либо сторонних библиотек, используемых в проекте, перенос проекта от одного разработчику к другому.

Работа с composer осуществляется через командную строку. При установке какого-либо пакета, располагающегосяв репозитории, имеется возможность указания версии. Composer обладает встроенным автозагрузчиком классов, прост в установке и конфигурации[6].

·  Менеджер пакетов NPM

NPM представляет собой менеджер пакетов, входящий в состав NodeJS. Имеет огромную базу приложений в репозитории, упрощают установку и интеграцию различных js, css и прочих библиотек [7]. Так же, как и с Composer, взаимодействие осуществляется через командную строку, но имеется и возможность использования графических оболочек. Содержимое конфигурационного файл представляет собой json-массив, аналогично таковому в Composer.

·  Шаблонизатор Twig

Twig является шаблонизатором, написанным на языке PHP. Позволяет жёстко разграничить понятия Controller и Viewer, так как при написании шаблона страницы используется только характерные для Viewer функции и синтаксис, который в свою очередь является более удобным и сокращённым по сравнению с PHP [8].

Twig обладает встроенной системой кеширования страниц, позволяет расширять свой функционал в зависимости от подробностей при разработке проекта путём написания дополнительных классов и методов, является шаблонизатором по умолчанию в Symfony.

У Twig открытый исходный код, имеется русскоязычная документация.

·  ORMDoctrine создаёт слой абстракций над БД и позволяет связать их с концепциями объектно-ориентированного программирования. Работа с базой данных представляет собой исключительно взаимодействие с объектами. Каждая таблица - объект [9].

Doctrine избавляет программиста от написания дублирующегося кода в виде однотипных SQL-запросов, а синтаксис взаимодействия с объектами абсолютно не зависит от выбранной СУБД, обладает системой защиты от SQL-инъекций, обеспечивает сохранение целостности данных, имеет обширную документацию. При необходимости изменения СУБД код переписывать не нужно.

·  Система контроля версий Git

Git является системой контроля версий, обладает как и возможностью консольного управления, так и графической оболочкой, интегрирован во множество IDE, имеет высокую производительность [10].

·  IDEPhpStormидеально подходит для работы с Symfony, Drupal, WordPress, Zend Framework, Laravel, Magento, Joomla!, CakePHP, Yii, и другими фреймворками. Он индексирует весь код проекта, поддерживает множество языков, автодополнение, рефакторинг кода, подсветку ошибок, встроенные инструменты для работы с SFTP, консолью, базами данных и т. д. [11].

Для студентов PhpStorm распространяется бесплатно.

·  Инструмент для сборки фронтенда Webpack

Инструмент для сборки фронтенда, обеспечивающиый объединение различных модулей, минимизацию файлов, использование различных загрузчиков и преобразователей. Обладает гибкими настройками, автоматической пересборкой при изменении, проектирование в зависимости от окружения [12].

·  CSS-фреймворк Material Design Lite

Позволяет использовать элементы MaterialDesign на сайте. Данный фреймворк не связан ни с каким js-фреймворком и является универсальным. Страницы, разработанные с помощью MDL будет придерживаться современных принципов веб-дизайна, поддерживать мобильные браузеры, будут адаптивны и минималистичны.

MDL включает в себя новые версии управления пользовательским интерфейсом, такие как кнопки, флажки и текстовые поля. Библиотека также включает в себя расширенные и специализированные функции, такие как карты, макеты колонок, ползунки, счетчики, вкладки и многое другое [13].

·  Язык стилей LESS

Язык существенно расширяет стандартные возможности CSS, добавляет возможность использования переменных, вложенности, примесей, функций, операций. Любой валидный CSS код является валидным и в LESS [14].

·  PHP-фреймворк Symfony3 (рис. 3)

Один из самых популярных и современных PHP-фреймворков на данный момент. Он использует модель MVC, поддерживает множество различных баз данных, в составе фреймворка поставляется Doctrine, Twig, Composer, а также ряд других библиотек. Весь исходный код фреймворка соответствует стандартам PSR, имеет поддержку PHP7. Symfony имеет собственный хост разработки, подробную документацию по функционалу с подробными примерами. Обновления появляются регулярно с периодичностью в несколько недель [15].

Рис.3. Панель статистики в Symfony3

·  БД PostgreSQL

Является объектно-реляционной базой данных, то есть содержит технологии, реализующие объектно-ориентированный подход [16]. Некоторые преимущества PostgreSQLпо сравнению с MySQL:

o Подзапросы можно писать отдельно, давая им имена.

o   Большое количество различных типов данных, в том числе возможность добавления пользовательских типов.

o   Ограничения самой базы данных, обеспечивающие целостность данных. Возможность ограничения по некоторому диапазону, условию и т. д.

o   Тип json и полноценная работа с ним.

o   Хранимые процедуры на любых языках.

o   Репликация работает быстро и консистентно.

o   При правильной настройке запросы выполняются в разы быстрее.

o   Строгий режим. В отличии от MySQL, PostrgreSQL не позволит сделать двусмысленный запрос и получить обрезанную строку, случайное число и т. д.

o   Полнотекстовый поиск.

o   Последовательности. В MySQL есть только AUTO_INCREMENT на поле таблицы, с итерацией по одному. В PostgreSQL этот механизм живет отдельно от таблицы, что можно использовать для самых разных потребностей, кроме того можно их создавать зацикленными.

 

.3 Модель MVC и ООП в веб-программировании


MVC (Model, View, Controller) - шаблон проектирования проектов. Он был разработан задолго до появления WWW, но широкое распространение получил именно в веб-разработке. Адаптировавшись под потребности разработчиков, и став основой многочисленному количеству фреймворков, данный шаблон стал очень популярным и востребованным среди веб-разработчиков [17].

Основной идеейMVCявляется чёткое разделение функционирования приложения (рис. 4).

Рис. 4. Компоненты MVC

Контроллеры обрабатывают запросы от пользователя, будь то обычный GET-запрос, отправка формы, AJAX-запрос и т. д. При получении каких-либо данных, например, параметр в url, задача контроллера - вызвать модель, если необходимо отправить определённые данные от пользователя в методы модели, получить результат и вернуть его на вывод (отобразить вид) в требуемом формате (JSON, HTML, XMLи т. д.) Как правило, внутри контроллера не должно быть кода, связанного с непосредственной работой с БД, сложных вычислений, обработки данных и т. д. Распространённой ошибкой разработчиков является то, что бизнес-логика описывается внутри контроллеров, а на модель возлагается только работа с базой данных, из-за чего объём кода контроллеров сильно засоряется и становится сложно читаемым. А так же нарушается основная идея MVC-разделение функциональности.Единственная задача контроллера - получить данные от пользователя, отправить в модель, получить результат и отправить его на вывод.

Модель реализует логику работы приложения - описание сущностей, конфигурацию приложения, подключение к БД, выборки, валидацию, механизмы защиты данных, взаимодействие с внешними сервисами, обработка ошибок и т. д. Основная задача модели - сформировать данные для отправки их в контроллер, а также работа с полученными данными, т. е. реализация бизнес-логики приложения.

Вид получает данные от контроллера и выводит их пользователю в необходимом формате. Как правило таковым является HTML-код, который отображается браузером, но также это может быть JSON-массив (в ответ на AJAX-запрос), сформированный PDF, XML, DOCи т. д. Единственная задача вида - отобрать данные.

В связи со спецификой модели MVC, а именно - чёткого разделения функционирования приложения, при разработке приложений, в том числе в основе многих фреймворков, используется объектно-ориентированное программирование [18]. Основой данного подхода служит единая точка входа, как правило файл index.php (зависит от настроек сервераи файла .htaccess), расположенный в корне сайта, и определённый внутри него автозагрузчик классов. Любой запрос к сайту должен быть перенаправлен именно к этому файлу.

При вызове в любом участке кода какого-либо класса функция __autoload, указанная в листинге 1, пытается подключить файл с соответствующим именем с учётом пространства имён. В случае отсутствия такового выполнение скрипта прекращается. Это обеспечивает подключение только необходимых в процессе выполнения скрипта файлов.

Следующей неотъемлемой частью данного подхода является роутер. Он обрабатывает входную url-строку и определяет какой метод класса-контроллера должен быть вызван. В общем случае каждой url-строке ставится в соответствие определённый метод класса-контроллера. В случае, если соответствие не установлено, отображается страница 404.

<?

function__autoload($className)

{

$className = ltrim($className, '\\');

$fileName = '';($lastNamespacePos = strrpos($className, '\\')) {

$namespace = substr($className, 0, $lastNsPos);

$className = substr($className, $lastNamespacePos + 1);

$fileName = str_replace('\\', '/', $namespace) . '/';

}

$fileName = $_SERVER['DOCUMENT_ROOT'].'/'.strtolower($fileName).$className.'.php';(file_exists($fileName)) {_once($fileName);

} else {('Класс ' . $className . ' не найден');

}

}

Листинг 1. Пример простейшего автозагрузчика классов

Класс-контроллер представляет собой набор методов, задача которых получить входные данные, обработать их с помощью методов классов-моделей и возвратить ответ, который будет выведен пользователю. В приложении таковых классов может быть множество, они могут наследоваться от базового абстрактного класса-контроллера, содержать вспомогательные методы, которые не вызываются роутером. Но основная их задача, в соответствии с MVC- содержать методы, которыеполучают входные данные от пользователя и возвращают ему результат обработки.

Классы модели описывают бизнес-логику приложения. Они могут содержать описание сущностей базы данных, сложные вычисления, методы для взаимодействия с различными внешними сервисами, обработчики событий и т. д. Каждый класс-модель содержит свойства и методы, объединённые общей логикой и предназначением. Экземпляры данных классов могут быть использованы как в контроллерах, так и в других классах модели.

Вид, в контексте ООП в простейшем случае представляет собой php-шаблоны, содержащие вёрстку и наполняемые данными, пришедшими от контроллера. В более сложных проектах в качестве шаблонов используются специальные библиотеки, предоставляющие механизм наследование шаблонов, переопределения отдельных блоков, специальный упрощённый синтаксисдля вывода данных, встроенное кэширование.

 

.4 Назначение и установка Symfony Framework


Использование фреймворков позволяет структурировать разработку, повторно использовать общие модули, облегчает долгосрочное обслуживание и масштабируемость в соответствии со стандартными правилами разработки. По сравнению с CMS, фреймворки обеспечивают гибкость и могут быть использованы для разработки приложений, которые наиболее точно соответствуют заявленным требованиям, но в тоже время требуют определённые технические знания, связанные с разработкой, интеграцией и техническим обслуживанием [19].

По сравнению с самописными приложениями, использование фреймворков гарантирует соответствие кода современным стандартам, структурирование, поддержку и масштабируемость. А также сокращает время разработки, позволяет повторно использовать модули [20].

SymfonyFramework-свободный PHP-фреймворк, разработанный компанией SensioLabs в соответствии с паттерном MVC и предназначенный для создания сайтов и веб-приложений. Он простроен на основе компонентов Symfony, каждый из которых может быть использован отдельно от самого фреймворка. Первый релиз состоялся 22 октября 2005 года.Последней версией является 3.3.2 (6 июня 2017 года), требует PHP 5.5.9 и выше, поддерживает большое количество баз данных (PostgrerSQL, MySQL, SQLite) благодаря ORM-инструменту Doctrine. Одна из самых популярных CMS - Drupal 8 основа на SymfonyFramework [21].предоставляет специальное приложение под названием Symfony Installer для упрощения создания приложений Symfony. Этот установщик представляет собой исполняемый файл, совместимый с PHP 5.4, который должен быть установлен в системе только один раз:

#Linux иMacOSmkdir -p /usr/local/bincurl -LsS https://symfony.com/installer -o /usr/local/bin/symfonychmod a+x /usr/local/bin/symfony

#Widnows-r "readfile('https://symfony.com/installer');"> symfony

Листинг 2. Команды для установки SymfonyInstaller

После установки Symfony Installerдля создания приложения Symfony необходимо ввести команду «symfonynewproject_name».

Эта команда создает новый каталог, project_name/ который содержит пустой проект, основанный на последней версии стабильной версии Symfony. Кроме того, установщик проверяет, соответствует ли система техническим требованиям для выполнения приложений Symfony. Если нет, отобразится список изменений, необходимых для удовлетворения этих требований.

Также имеется возможность создавать приложения Symfony с помощью Composer, менеджера зависимостей, используемого современными PHP-приложениями. Дляэтогоиспользуетсякоманда «composercreate-projectsymfony/framework-standard-editionproject_name». Для обновления компонентов Symfonyиспользуется команда «php bin/console server:run».использует внутренний веб-сервер PHP (доступный с PHP 5.4) для запуска приложений при их разработке. Запуск приложения Symfony осуществляется командой «php bin/console server:run». После этого необходимо перейти в браузере по адресу #"897027.files/image005.gif">

Рис. 5. Страница приветствия Symfony 2.8.0

 

 

1.5 Структура Symfony Framework


В последующих подпунктах будет произведён анализ технологий SymfonyFrameworkна основе официальной документации [15].

 

.5.1   Конфигурация

Каждое приложение Symfony состоит из набора бандлов (пакетов), которые добавляют инструменты (севрисы) в проект. Каждый бандл может быть настроен через файлы конфигурации, которые по умолчаниюрасположены в каталоге app/config (см. листинг 3).

imports:

- { resource: parameters.yml }

{ resource: security.yml }

{ resource: services.yml }:: '%secret%': { resource: '%kernel.project_dir%/app/config/routing.yml' }

# ...

# Twig Configuration:: '%kernel.debug%'_variables: '%kernel.debug%'

Листинг 3. Главный файл конфигурации config.yml

Любой файл конфигурации может быть описан с помощью файлов в формате YAML, XMLи PHP. По умолчанию используется YAML, так является наиболее простым, чистым и читаемым. XMLболее мощный, чем YAML, и поддерживает автодополнение IDE. PHPнаиболее мощный, но менее читаемый, чем стандартные форматы конфигурации.

Для того, чтобы использовать какие параметры можно настроить необходимо использовать справочный раздел, либо команду «php bin/console config:dump-reference название_библиотеки». Для импорта настроек используется специальный ключ imports, который работает аналогичного includeв PHP. Другой специальный ключ называется parameters: он используется для определения переменных, на которые можно ссылаться в другом файле конфигурации. Чтобы ссылаться на параметр, необходимо окружить его название двумя знаками процента. Параметры, которые являются уникальными для каждого сервера, например, учетные данные базы данных, должны быть описаны в файле paramentrs.yml, а данный файл исключен из версионного контроля. Но данные параметры могут быть описаны в файле paramentrs.yml.distв виде фиктивных значений (шаблонов). При разворачивании проекта через composerSymfonyпредложит ввести фактические значения данных параметров.

В Symfonyсуществует возможность использования различного набора параметров в зависимости от окружения. При разработке на локальной машине используются dev-конфигурация, при переносе и развертывании проекта на сервере, будет использована prod-среда.

 

.5.2 Бандлы

Бандл похож на плагин в различныхПО. Главное отличие заключается в том, что все бандлы в Symfony, включая базовые компоненты фреймворка, написаны для конкретного пользователя. Бандлы-основа Symfony. Онипредоставляют гибкость в использовании предварительно встроенных функций, упакованных в сторонние пакеты или для распространения собственных пакетов. Это позволяет легко выбирать, какие функции включить в приложении, и оптимизировать их с учётом собственных потребностей.

Пакет представляет собой просто структурированный набор файлов в каталоге, реализующем одну функцию. Каждый каталог содержит все, что связано с этой функцией, включая файлы PHP, шаблоны, таблицы стилей, файлы JavaScript, тесты и все остальное. Каждый аспект функции существует в бандле, и каждая функция расположена в бандле.

Бандлы, используемые в приложениях, должны быть включены путем регистрации их в методе registerBundles() класса AppKernel в файле app/AppKernel.php. Таким образом можно контролировать, какие бандлы необходимо использовать в проекте, а какие нет. Для вызова классов используется автозагрузчик, поставляемый вместе с composer.

Для создания нового бандла необходимо создать директорию src/Имя_разработчика/Название_бандлаBundle, а в ней класс с именем Имя_разработчикаНазвание_бандлаBundle и унаследоваться от базового класса Symfony\Component\HttpKernel\Bundle\Bundle. Далее зарегистрировать созданный бандл в классе AppKernel. В качестве альтернативы можно использовать консольную команду php bin/console generate:bundle --namespace=Имя_разработчика/Название_бандлаBundle. Данная команда генерирует базовый контроллер, шаблон и ресурс маршрутизации.

Каждый бандл обладает следующе структурой каталогов:

·  Controller/

Содержит контроллеры бандла.

·  DependencyInjection/

Содержит определенные классы расширения Dependency Injection, которые могут импортировать конфигурацию сервисов, описывать собственные сервисы и т. д. (этот каталог не обязателен).

·  Resources/config/

Конфигурация бандла, включая конфигурацию маршрутизации (например routing.yml).

·  Resources/views/

Содержит шаблоны, организованные по имени контроллера (например, Random/index.html.twig).

·  Resources/public/

Содержит веб-ресурсы (изображения, таблицы стилей и т. д.), которыебудут скопированы или символически связаны в с web/каталогом проекта с помощью команды assets:install в консоли.

·  Tests/

Содержит все тесты для бандла.

Бандл может быть любого размера и содержать различный набор функций, но должен содержать только те файлы, которые необходимо использовать в рамках этого бандла, быть масштабируем, и написан так, чтобы другой разработчик имел возможность использовать созданный бандл в своём проекте.

Установка стороннего бандла происходит в три этапа:

.   Поиск названия бандла на сайте#"897027.files/image006.gif">

Рис. 6. Пример сопоставления таблицы БД с PHP-объектом

Эта информация представляется в виде «метаданных», набора правил, который сообщает Doctrine, как именно класс и его свойства должны быть сопоставлены с конкретной таблицей базы данных. Эти метаданные могут быть указаны в нескольких разных форматах, включая YAML, XML или непосредственно внутри класса с помощьюDocBlock аннотаций. Разработчик имеет возможность выбрать наиболее удобный способ определения метаданных, но наиболее распространённым вариантом является использование аннотаций. Бандл может принимать только один формат определения метаданных. Например, невозможно сопоставить определения метаданных YAML с аннотированными определениями сущностей PHP.Имя таблицы является необязательным и, если оно опущено, будет определяться автоматически на основе имени класса сущности.

<?AppBundle\Entity;Doctrine\ORM\Mapping as ORM;

/**

* @ORM\Entity

* @ORM\Table(name="product")

*/

{

/**

* @ORM\Column(type="integer")

* @ORM\Id

* @ORM\GeneratedValue(strategy="AUTO")

*/$id;

/**

* @ORM\Column(type="string", length=100)

*/$name;

/**

* @ORM\Column(type="decimal", scale=2)

*/$price;

/**

* @ORM\Column(type="text")

*/$description;

}

ПримерORM-сущностиDoctrineс описанием в виде аннотаций

После создания сущностей необходимо проверить её описание с помощью команды «phpbin/consoledoctrine:schema:validate».

Не смотря на то, что Doctrine имеет представление о том, как сохранить объект в базе данных, класс приведённый в листинге 5 является бесполезным. Поскольку Product - это обычный PHP-класс со privateсвойствами, необходимо создать public методы getter и setter (например getName(), setName($name)), чтобы получить доступ к его свойствам в остальном кодеприложения. Команда «php bin/console doctrine:generate:entities AppBundle/Entity/Product» может автоматически генерировать эти шаблонные методы. Эта команда гарантирует, что все геттеры и сеттеры будут созданы для класса. Это безопасная команда -имеется возможность запускать её неограниченное количество раз: она генерирует только геттеры и сеттеры, которые не существуют (т. е. не заменяют существующие методы). Генератор сущности Doctrine создает простые геттеры/сеттеры. При необходимости можно добавить любую логику в соответствии с потребностями создаваемого приложения.

Для автоматического создания всех таблиц базы данных, необходимых для каждого известного объекта используется команда «php bin/console doctrine:schema:update -force». Эта команда сравнивает, как должна выглядеть база данных (на основе информации о сопоставлении сущностей) с тем, как она выглядит на самом деле, и выполняет инструкции SQL, необходимые для обновления схемы базы данных. Другими словами, если добавить новое свойство с метаданными сопоставления в класс сущности и запустить эту команду, она выполнит инструкцию «ALTER TABLE», необходимую для добавления этого нового столбца в существующую таблицу.

Еще один способ воспользоваться этой функциональностью - это миграции, которые позволяют создавать операторы SQL и сохранять их в классах миграции, которые можно систематически запускать на производственном сервере, чтобы безопасно и надежно обновлять и отслеживать изменения в схеме базы данных.

Независимо от того, используются ли возможности миграции, команда «doctrine:schema:update» должна использоваться только во время разработки.

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

<?

// src/AppBundle/Controller/DefaultController.php

use AppBundle\Entity\Product;Symfony\Component\HttpFoundation\Response;Doctrine\ORM\EntityManagerInterface;Doctrine\Common\Persistence\ManagerRegistry;(EntityManagerInterface $em)

{

// создание нового объекта класса Product

$product = newProduct();

// устанавливает значения свойств

$product->setName('Keyboard');

$product->setPrice(19.99);

$product->setDescription('Ergonomic and stylish!');

// сообщениеDoctrine, о намерении сохранить объект

$em->persist($product);

// фактическое выполнение запросаINSERT

$em->flush();

//возвращениеответаResponse('Saved new product with id '.$product->getId());

}

Листинг 6. Сохранение объекта в базу данных

Метод flush() использует механизмы кэширования и подготавливает единый запрос к базе в независимости от количества создаваемых объектов.

Для получения объекта из базы данных используются механизмы, аналогичные тем, что указаны в листинге 7.

<?Doctrine\ORM\EntityManagerInterface;($productId, EntityManagerInterface $em)

{

// получение репозитория объекта и запрос сущности по её ID

$product = $em->getRepository('AppBundle:Product')

->find($productId);

// вывод сообщения об ошибке в случае отсутствия сущности с указанным ID

if (!$product) {$this->createNotFoundException(

'No product found for id '.$productId

);

}

}

Листинг 7. Получение сущности из базы данных

Для запроса объектов из базы можно использовать различные методы репозитория. Таковыми могут быть как стандартные методы (findBy, find, findAll, findOneByи т. д.), так и методы, которые определены в репозитории сущности самим разработчиком.

При рендеринге страницы, панель инструментов веб-отладки внизу страницы отображает количество запросов и время, необходимое для их выполнения.

Если количество запросов к базе данных слишком велико, значок станет желтым, указывая на то, что что-то может быть неверным. При нажатии на значок, можно открыть Symfony Profiler и посмотреть конкретные запросы, которые были выполнены.

После получения объекта, имеется возможность его обновить, как это указано в листинге 8.

<?Doctrine\ORM\EntityManagerInterface;($productId, EntityManagerInterface $em)

{

// получение репозитория объекта и запрос сущности по её ID

$product = $em->getRepository('AppBundle:Product')->find($productId);

// вывод сообщения об ошибке в случае отсутствия сущности с указанным ID

if (!$product) {$this->createNotFoundException(

'No product found for id '.$productId

);

}

// установка нового значения свойства

$product->setName('Newproductname!');

// фактическое выполнение запросаINSERT

$em->flush();

// перенапрваление на главную страницу

return $this->redirectToRoute('homepage');

}

Листинг 8. Обновление объекта в базе данных

Для удаления объекта используются метод $em->remove(). Для фактического удаления используется метод $em->flush();

В случае необходимости использования более сложных запросов используется DQL - родной язык Doctrine. Он очень схож с языком SQL, за одним большим отличием - при построении запросов необходимо думать с точки зрения выбора объектов PHP, а не строк в базе данных.Такие запросы можно создавать непосредственно внутри методов классов контроллеров, но для организации Doctrineпредоставляются специальные классы репозитория, которые позволяют хранить всю логику запросов в одном централизованном месте.

<?

$query = $em->createQuery(

'SELECT pAppBundle:Product pp.price > :priceBY p.price ASC'

)->setParameter('price', 19.99);

$products = $query->getResult();

Листинг 9. Пример построения запроса на языке DQL

позволяет сосредоточиться на объектах и на том, как они используются в приложении, и обеспечивает сохранность базы данных. Это связано с тем, что Doctrine позволяет использовать любой объект PHP для хранения данных и полагается на информацию метаданных для сопоставления данных объекта с конкретной таблицей базы данных.

1.5.4 Маршрутизация

Наличие человекопонятных URL-адресов (ЧПУ) является абсолютной необходимостью для любого серьезного веб-приложения. Например, index.php?article_id=57 проигрываетпосравнению с /read/intro-to-symfony.

Маршрутизатор Symfony позволяет определять URL-адреса, которые могут быть сопоставлены с различными областями приложения. Маршрут представляет собой карту из URL пути к контроллеру. Маршруты, как и обычные файлы конфигурации могут быть описаны с помощью аннотаций, YAML, XML или PHP.

# app/config/routing.yml_list:: /blog: { _controller: AppBundle:Blog:list }_show:: /blog/{slug}: { _controller: AppBundle:Blog:show }

Листинг 10. Пример определения маршрутов в формате YAML

При создании маршрутов имеется возможность определения параметров, передаваемых в URL, задавать значения по умолчанию для этих параметров, производить их валидацию по регулярному выражению, указывать допустимые типы запросов к контроллеру ($_GET, $_POST), сопоставлять метод контроллера, устанавливать языковые стандарты по запросу. Имя маршрута не может начинаться с цифры, а также быть длиннее 32 символов.загружает все маршруты для приложения из одного файла конфигурации маршрутизации: app/config/routing.yml. Но изнутри этого файла можно загружать любые другие файлы маршрутизации. Фактически, по умолчанию Symfony загружает конфигурацию маршрута аннотаций из каталога бандла Controller/, при использовании YAML, XML, PHPиз каталога Recourses/config.

 

.5.5   Контроллеры

Контроллер - это созданная функция PHP, которая считывает информацию из Request объекта Symfony и создает и возвращает Response объект. Ответ может быть HTML-страницей, JSON, XML, файлом, перенаправлением, ошибкой 404 или чем-либо еще. Контроллер выполняет произвольную логику, необходимую приложению для отображения содержимого страницы.Он может считывать информацию из запроса, загружать ресурс базы данных, отправлять электронную почту или задавать информацию на сеансе пользователя. Но во всех случаях контроллер в конечном итоге возвращает Responseобъект, который будет доставлен обратно клиенту.

Для удобства Symfony поставляется с двумя дополнительными базовыми Controllerи AbstractController классами. Это позволяет расширить или получить доступ к нескольким вспомогательным методам.Разница между Controller и AbstractController в том, что при расширении AbstractController нет возможности получить доступ к сервисам напрямую через $this->get()или $this->container->get().

Базовый класс содержит следующие методы для удобного определения метода контроллера:

·  Перенаправление - return $this->redirectToRoute('homepage');

·        Рендершаблона - return $this->render(index.html.twig', array('name' => $name));

·        Получение сервисов в качестве аргументов контроллера. Symfony поставляетсяс большим количеством полезных объектов, называемых сервисы. Они используются для создания шаблонов, отправки электронных писем, запросов к базе данных и любой другой «работы». Для получения нужногосервиса в контроллере, необходимо указать в качестве аргумента метода контроллерапеременную нужного класса или интерфейса. Для получения полного списка доступных сервисов используется команда «php bin/console debug:container -types».

·        Доступкконтейнеру - $mailer = $this->get('mailer');

·        Отображениеошибки 404. throw $this->createNotFoundException('The product does not exist');

·        Управление сессиями - $session->set('foo', 'bar');

·        Flash-сообщения - $this->addFlash('notice', 'Your changes were saved!');

·        ВозвращениеJSON-объектов - return $this->json(array('username' => 'jane.doe'));

·        Возвращениефайлов - return $this->file('/path/to/some_file.pdf');

·        Объект Request в качестве аргумента контроллера. Используется для получения данных из массивов $_POST, $_GET, а также значений определённых в маршрутизации параметров.

Request класс имеет несколько открытых свойств и методов, которые возвращают любую необходимую информацию о запросе.

Как и объект Request, Responseобъект также имеет публичное свойство headers. Он имеет методы для получения и настройки заголовков ответов. Имена заголовков нормализуются так, что использование Content-Typeэквивалентно content-typeили даже content_type.

Единственное требование для контроллера - вернуть Responseобъект. Response класс является абстракцией вокруг ответа HTTP -текстовое сообщение, заполненное заголовками и содержанием, которое отправляется обратно клиенту.

 

.5.6   Шаблонизатор Twig

Контроллеры несут ответственность за обработку каждого запроса, поступающего в приложение Symfony, и обычно в конечном итоге выводят шаблон для создания содержимого ответа.

На самом деле контроллер делегирует большую часть тяжелой работы в другие места, чтобы код можно было тестировать и повторно использовать. Когда контроллер должен сгенерировать HTML, CSS или любой другой контент, он передает работу в механизм шаблонов. Шаблон - это простой текстовый файл, который может генерировать любой текстовый формат (HTML, XML, CSV, LaTeX и т. д.) Наиболее знакомым типом шаблона является PHP-шаблон - текстовый файл, обработанный PHP, который содержит сочетание текста и кода PHP. Но Symfony предлагает собственный язык шаблонов под названием Twig. Twig позволяет писать сжатые, читаемые шаблоны, которые более дружелюбны к веб-дизайнерам и, в некоторых аспектах, более мощные, чем шаблоны PHP.определяет три типа специального синтаксиса:

{{ ... }}

«Вывести что-то»: печатает переменную или результат выражения в шаблоне.

{% ... %}

«Сделать что-то»: тег, который управляет логикой шаблона; Он используется для выполнения таких операторов, как, например, for-loops.

{# ... #}

«Комментировать что-то»: это эквивалент /* comment */синтаксиса PHP. Он используется для добавления одиночных или многострочных комментариев. Содержание комментариев не включено в отображаемые страницы.также содержит фильтры, которые модифицируют контент перед визуализацией. Twig поставляется с обширным списком тегов, фильтров и функций, которые доступны по умолчанию. Имеется возможность добавить собственные пользовательские фильтры, функции с помощью расширения Twig.

Код Twig похож на PHP-код, с небольшими отличиями. В листинге 11 используется стандартный тег for и функция cycle() для печати десять div тегов, с чередованиемзначений «odd» и «even».

{% for i in 1..10 %}

<div>

{# some HTML here #}

</div>

{% endfor %}

Листинг 11. Пример синтаксиса шаблонизатора Twig

Шаблоны Twig должны быть простыми и не будут обрабатывать теги PHP. Система шаблонов Twig предназначена для отображения вида, а не программной логики.также может делать то, что не может сделать PHP, например, управление пробелами, песочница, автоматическое экранирование HTML, ручное контекстное выходное экранирование и включение пользовательских функций и фильтров, которые влияют только на шаблоны. Twig содержит возможности, которые упрощают и создание шаблонов писем.обладает высоким быстродействием, так как каждый шаблон автоматически компилируется в собственный PHP-класс и кэшируется. Во время разработки Twig повторно компилирует шаблоны после внесения каких-либо изменений.

Чаще всего шаблоны в проекте имеют общие элементы, такие как верхний колонтитул, нижний колонтитул, боковая панель и т. д. В Symfony эта проблема рассматривается по-разному: шаблон может быть интегрирован с другим. Это работает точно так же, как и классы PHP: наследование шаблонов позволяет создать базовый шаблон «макета», содержащий все общие элементы сайта, определенные как блоки с помощью тега {% blockname%}text{% endblock%} («PHP-класс с базовыми методами»). Дочерний шаблон может расширять базовый макет с помощью тега {% extends %}и переопределять любые его блоки («дочерний класс PHP, который переопределяет определенные методы его родительского класса»).

По умолчанию шаблоны могут располагаться в двух разных местах:

·  app/Resources/views/

Views Каталог приложения может содержать базовые шаблоны приложений (например, макеты приложения и шаблоны пакета приложений), а также шаблоны, которые переопределяют шаблоны бандлов сторонних разработчиков.

·  vendor/path/to/Bundle/Resources/views/

Каждый сторонний бандл содержит свои шаблоны в своем Resources/views/ каталоге (и подкаталогах).

При необходимости включить код шаблона в другой шаблон используется функция {{ include() }}. Для вывода ссылки на страницу из маршрутизатора функция {{ path(имя_маршрута, {имя параметра, значение_параметра}) }}.

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

 

.5.7   Генерация форм и валидация

Работа с форматами HTML является одной из наиболее распространенных и сложных задач для веб-разработчика. Symfony включает компонент Form, который упрощает работу с формами.Компонент Symfony Form представляет собой отдельную библиотеку, которая может использоваться вне проектов Symfony.

После создания класса сущности В Symfony имеется возможность создания объекта формы, а затем его отображение в шаблоне.

<?AppBundle\Controller;AppBundle\Entity\Task;Symfony\Bundle\FrameworkBundle\Controller\Controller;Symfony\Component\HttpFoundation\Request;Symfony\Component\Form\Extension\Core\Type\TextType;Symfony\Component\Form\Extension\Core\Type\DateType;Symfony\Component\Form\Extension\Core\Type\SubmitType;Controller

{(Request $request)

{

// Создание объекта сущности

$task = newTask();

$task->setTask('Write a blog post');

// Генерацияформы

$form = $this->createFormBuilder($task)

>add('task', TextType::class)

>add('dueDate', DateType::class)

>add('save', SubmitType::class, array('label' =>'Create Post'))

>getForm();

//Отображениешаблона$this->render('default/new.html.twig', array(

'form' => $form->createView(),

));

}

}

Листинг 12. Создание объекта формы

Создание формы требует относительно небольшого кода, потому что объекты формы Symfony построены с помощью «конструктора форм». Задача создателя формы - написать простой генератор формы, форма сгенерируется автоматически.

В примере на листинге 12 добавлено два поля в форму - task и dueDate - соответствующие свойствам taskи dueDateсвойствам Task класса. Так же назначается каждому свойству «тип» (например, TextTypeи DateType), представленный его полным именем класса. Помимо прочего, он определяет, какой HTML-тег отображается для этого поля.Также добавляется кнопка отправки с пользовательской надписью для отправки формы на сервер.

Когда форма была создана, следующим шагом будет ее рендеринг. Это делается путем передачи объекта формы «view» в шаблон и с помощью набора вспомогательных функций формы. Для отображения полной формы необходимы три строки в Twig:

·  form_start(form)

Отображает начальный тег формы, включая атрибут enctype при использовании загрузки файлов.

·  form_widget(form)

Отображает все поля, включая сам элемент поля, метку и любые сообщения об ошибках проверки для поля.

·  form_end(form)

Отображает конечный тег формы и любые поля, которые еще не были отображены, если была произведена ручная визуализация каждого поля. Это полезно для визуализации скрытых полей и использования автоматической защиты CSRF.

По умолчанию форма отправит POST запрос обратно на тот же контроллер, который отображает её. Для обработки полученных данных используется код, указанный в листинге 13.

// ОтправкавформуобъектаRequest

$form->handleRequest($request);

if ($form->isSubmitted() && $form->isValid()) {

// Получениеданныхизформы

$task = $form->getData();

// Сохранение полученных данных в базе

$em->persist($task);

$em->flush();$this->redirectToRoute('task_success');

}

Листинг 13. Получение и обработка данных из формы

В Symfony валидация применяется к базовому объекту. Другими словами, вопрос заключается не в том, является ли «форма» валидной, а является ли объект валидным после того, как форма применила к нему предоставленные данные. Вызов $form->isValid()- это сокращение, которое запрашивает у объекта, имеет ли он валидные данные.

Проверка выполняется путем добавления в класс сущности набора правил (называемых ограничениями). Это может быть реализовано, как с помощью аннотация, так и файлов YAML, XMLи PHP. Валидация производится с помощью классов внутри пространства имён Symfony\Component\Validator\Constraints. Доступные ограничения:

Основные ограничения

·    NotBlank

·              Blank

·              NotNull

·              IsNull

·              IsTrue

·              IsFalse

·              Type

Строковые ограничения

·    Length

·              Email

·              Url

·              Regex

·              Ip

·              Uuid

Числовые ограничения

·    Range

Сравнительные ограничения

·    EqualTo

·              NotEqualTo

·              IdenticalTo

·              NotIdenticalTo

·              LessThan

·              LessThanOrEqual

·              GreaterThan

·              GreaterThanOrEqual

Ограничения по дате

·    Date

·              DateTime

·              Time

Ограничения коллекции

·    Choice

·              Collection

·              Count

·              UniqueEntity

·              Language

·              Locale

·              Country

Файловые ограничения

·    File

·              Image

Финансовые и числовые ограничения

·    Bic

·              CardScheme

·              Currency

·              Luhn

·              Iban

·              Isbn

·              Issn

Другие ограничения

·    Callback

·              Expression

·              All

·              UserPassword

·              Valid

При выводе формы имеется возможность добавления атрибутов для валидации формы средствами HTML 5, указания типов полей (textarea, email, money, url, password, choice, date, file и т. д.), надписи. При отсутствии фактического указания этих параметров, Symfonyпытается «угадать» их значение. Если это происходит некорректно, необходимо вручную переопределить данные параметры.

Symfonyпредоставляет возможность создания формы в отдельном автономном PHP-классе, который затем может быть повторно использован в любом месте приложения. По умолчанию такие классы располагаются в директории Formбандла и могут быть использованы в контроллерах.

 

.5.8   Безопасность

Организация безопасности в Symfonyпроисходит в три этапа:

1. Первоначальная настройка security.yml(аутентификация);

2.      Запрет доступа к приложению (авторизация);

.        Получение текущего объекта пользователя.

Система безопасности настраивается в файле app/config/security.yml. Конфигурация по умолчанию представлена в листинге 14.

security::_memory:: ~::: ^/(_(profiler|wdt)|css|images|js)/: false:: ~

Листинг 14. Конфигурация безопасности по умолчанию

В данном файле определяются все настройки безопасности приложения. Например, ссылка, на которую будет перенаправлен пользователь при попытке входа на защищённую страницу, ссылка на страницу выхода, разрешение анонимного пользователя, параметры кодирования пароля и т. д.

На следующем этапе в параметре access_controlуказывается список защищённых маршрутов и права доступа к ним, а в параметре role_hierarchy категории групп прав, иерархия ролей.

Одним из основных параметров является «users», который определяет, каким образом будет задана информация о пользователях (непосредственно из файла или конкретной сущности).Внутри контроллера и шаблона имеется возможность проверить права текущего пользователя, проверить, является ли он авторизованным,и, в зависимости от этого, выполнять различные действия.

Symfonyпредоставляет возможность создания собственных обработчиков событий при возникновении различных событий связанных с безопасностью (авторизация, выход, регистрация, ввод неверного пароля и т. д.)

 

.5.9   Сервисы

SymfonyFrameworkнаполнен полезными объектами: объект «Mailer» помогает отправлять электронные письма, а другой объект может помочь сохранить данные в базе данных. Почти все, что делает приложение фактически выполняется одним из этих объектов. При установке нового бандла увеличивается количество доступных объектов.

В Symfony эти объекты называются сервисами, и каждый сервис находится внутри особого объекта, называемого контейнером сервисов. После получения контейнера сервисов, можно получить сервис, используя идентификатор эго сервиса с помощью метода get(): «$logger = $container->get('logger');»

Контейнер позволяет централизовать способ конструирования объектов. Для получения полного списка доступных сервисов из контейнера необходимо ввести консольную команду: «php bin/console debug:container».

Собственные сервисы по умолчанию располагаются в папке Serviceв каталоге бандла. После его создания необходимо зарегистрировать сервис в конфигурационном файле. При необходимости использования другого сервиса внутри текущего, в конструкторе сервиса указывается аргумент с типом необходимого сервиса, что также нужно указать в файле конфигурации.

При запросе сервиса, контейнер создает новый объект и возвращает его. Но если сервис не был запрошен, он никогда не будет сконструирован.Это экономит память и скорость. Он также создается только один раз: один и тот же экземпляр возвращается каждый раз, когда запрашивается.

 

.5.10 Консольные команды

Symfony Framework предоставляет множество команд через bin/consoleскрипт (например,команда bin/console cache:clear). Эти команды создаются с помощью компонента Console. Он так же используется для создания пользовательских команд.

Команды определяются в классах, которые должны быть созданы в директории Command бандла (например, AppBundle\Command), и их имена должны заканчиватьсясуффиксом Command.

Внутри данных классов имеется возможность получения сервисов из контейнера, добавление аргументов (методом addArgument), вывод в консоль (методом writeln), определения названия (setName), описания (setDescription) и помощи (setHelp) по команде.

Команды имеют три метода жизненного цикла, которые вызываются при выполнении команды:() (необязательный)

Этот метод выполняется до interact() и execute() методов. Его основная цель - инициализировать переменные, используемые в остальных командных методах.() (необязательный)

Этот метод выполняется после initialize() и до execute(). Его цель - проверить, отсутствуют ли некоторые из параметров / аргументов и интерактивно спросить у пользователя эти значения. Это последнее место, где можно запросить отсутствующие параметры / аргументы. После этой команды отсутствующие параметры / аргументы приведут к ошибке.() (обязательный)

Этот метод выполняется после interact() и initialize(). Он содержит логику, которую необходимость выполнить.

1.5.11. Механизмы тестирования

Symfony интегрируется с независимой библиотекой, называемой PHPUnit. Каждый тест - будь то unit-тест или функциональный тест - это класс PHP, который должен находиться в каталоге tests/ бандла.PHPunit конфигурируется в файле phpunit.xml.dist в корне проекта Symfony. Unit-тест (модульный тест) - это тест для одного класса PHP, также называемого unit. Написание модульных тестов Symfony ничем не отличается от написания стандартных модульных тестов PHPUnit. Как правило таким тестам подвергаются классы низкого уровня. Это означает, что контроллеры и сервисы, обладающие большим количеством зависимостей сложно поддаются модульному тестированию, так как основная его идея - проверка конкретных методов и участков кода на соответствие ожидаемым значениям. В листинге 15 приведёт простейший класс и пример возможного тестирования с помощью PHPUnit. Для запуска всех тестов в проекте используется консольная команда «phpunit». Функциональные тесты проверяют интеграцию различных уровней приложения (от маршрутизации до представления). Они ничем не отличаются от модульных тестов, так как используют PHPUnit, но они имеют специфический рабочий процесс:

·  Создать запрос;

·        Проверить ответ;

·        Нажать ссылку или отправить форму;

·        Проверить ответ;

·        Повторить.

Функциональные тесты - это простые PHP-файлы, которые обычно располагаются в tests/AppBundle/Controller каталоге бандла. Например, длятестирования страниц, обрабатываемых PostControllerклассом, создаётся новый PostControllerTest.php файл, который расширяет специальный WebTestCaseкласс. Пример такого теста представлен в листинге 16.

// src/AppBundle/Util/Calculator.php

namespace AppBundle\Util;

{($a, $b)

{$a + $b;

}

}

// tests/AppBundle/Util/CalculatorTest.phpTests\AppBundle\Util;AppBundle\Util\Calculator;PHPUnit\Framework\TestCase;TestCase

{()

{

$calc = new Calculator();

$result = $calc->add(30, 12);

// Отображает, что тест пройден успешно

$this->assertEquals(42, $result);

}

}

Листинг 15. Примерописанияunit-теста

<?

// tests/AppBundle/Controller/PostControllerTest.phpTests\AppBundle\Controller;Symfony\Bundle\FrameworkBundle\Test\WebTestCase;WebTestCase

{()

{

//Создаётклиента

$client = static::createClient();

// Создаётget-запроскстранице /post/hello-world

$crawler = $client->request('GET', '/post/hello-world');

// При наличие на странице текста «HelloWorld» тест считается успешно пройденным

$this->assertGreaterThan(

,

$crawler->filter('html:contains("Hello World")')->count()

);

}

}

Листинг 16. Пример функционального теста

Для запуска функциональных тестовкласс WebTestCaseзагружает ядро ​​приложения. При тестировании данным способом доступно множество методов для имитации поведения реального пользователя, такие как нажатие на кнопку или ссылку, отправка формы, загрузка файлов и т. д. Основной идей такого тестирования является поиск соответствий фактического поведения приложения с ожидаемым. Это достигается путём полной эмуляции работы ядра Symfony, вместе со всеми её сервисами, эмуляции клиента, а также сравнения статусов ответа, HTML-тегов, атрибутов форм, перехода по ссылкам с ожидаемыми (правильными).

 

.6 Развертывание приложенияSymfony


Развертывание приложения Symfony может быть сложной и разнообразной задачей в зависимости от настроек и требований приложения.

Типичные шаги для развертывании приложения Symfony, включают:

·  Загрузка кода на рабочий сервер;

·        Установка зависимостей (обычно это выполняется через Composer и может быть выполнено перед загрузкой);

·        Выполнение миграции базы данных или аналогичных задач для обновления любых измененных структур данных;

·        Очистка кеша.

Развертывание может также включать другие задачи, такие как:

·  Пометка определенной версии кода как релиза в системе контроля версиями;

·        Создание временного промежуточногоокружения для создания обновленной настройки «offline»;

·        Выполнение любых тестов для обеспечения стабильности кода и / или сервера;

·        Удаление ненужных файлов из web/каталога, для очисткиproduction-среды;

·        Очистка внешних систем кэширования (например, Memcached или Redis).

Существует несколько способов развертывания приложения Symfony. Начните с нескольких основных стратегий развертывания и создайте оттуда.

Самый простой способ развертывания приложения - копирование файлов вручную через FTP / SCP (или аналогичный метод). У этого есть свои недостатки, поскольку отсутствует система версионирования при дальнейшем обновлении. Этот метод также требует выполнить некоторые шаги вручную после передачи файлов.

При использовании системы контроля версий (например, Git или SVN), необходима её поддержка на стороне сервера, а также выполнение дополнительных действий, после копирования файлов из версионного контроля.

Существуют также инструменты, помогающие облегчить процесс развертывании. Некоторые из них были специально адаптированы к требованиям Symfony.

·  Capistrano с плагином Symfony- это средство автоматизации и развертывания удаленного сервера, написанное на Ruby. Плагин Symfony - это плагин для облегчения задач, связанных с Symfony.

·  sf2debpkg

Помогает создать пакет Debian для проекта Symfony.

·  Magallanes

Это инструмент развертывания, подобный Capistrano, построен на PHP.

·  Fabric

Эта библиотека на основе Python предоставляет базовый набор операций для выполнения локальных или удаленных shell-команд и выгрузки / загрузки файлов.

После развертывания исходного кода необходимо сделать несколько общих вещей:

a) Проверить соответствие системным требования с помощью «php bin/symfony_requirements») Настроить app/config/parameters.yml файл) Установить / обновитьбиблиотекиспомощьюкоманды «composer install --no-dev --optimize-autoloader») Очиститькэш Symfony командой «php bin/console cache:clear --env=prod --no-debug --no-warmup») Дополнительно, в зависимости от конфигурации, могут потребоваться дополнительные вещи:

·        Выполнение миграций баз данных

·        Очистка кэша APC

·        Запуск assets:install

·        Добавление / редактирования заданий CRON

·        Публикация frontend-зависимостей в CDN

·        Запуск тестов

·        Контроль качества

После всех выполненных действий веб-приложение готово к работе и использованию на удалённом сервере.

Глава 2. Создание приложения на основе технологий Symfony Framework

 

.1 Постановка задачи


Основным преимуществом Symfony Framework является сочетание комплекса современных подходов в области веб-проектирования: соответствие кода современным стандартам, структурирование, поддержку и масштабируемость, реализацию ООП-подхода при описании структуры и функционала, сокращение времени разработки, возможность повторно использовать модули.

В рамках выпускной квалификационной работы была определена практическая задача - реализациявеб-приложения на основе технологий SymfonyFrameworkс учётом современных подходов в области проектирования, разработки, оформления, тестирования программного продукта. Актуальным и популярным сервисом в области веб можно считать типовой интернет-магазин, к которому в современных условиях предъявляются следующие требования:

·  масштабируемость и универсальность структуры данных и функционала;

·        стандартизация кода;

·        сокращение времени на обработку запросов, загрузки страниц;

·        устойчивость к высоким нагрузкам;

·        эргономичность использования программного продукта, как для разработчика, так и целевой пользовательской аудитории;

·        обеспечение безопасности хранимых и передаваемых данных;

·        организация обратной связи с пользователем.

На основе анализа технологий SymfonyFramework, приведённого в главе 1, в качестве программной реализации возможностей его компонентов и подходов был реализован интернет-магазин, демонстрирующий типовой процесс разработки веб-приложения, отвечающего вышеприведённым требованиям.

 

.2 Настройка develop-сервера и установка Symfony


В качестве операционной системы, используемой при написании проекта была выбрана Ubuntu 16.04 с предустановленным PHP7.0, который необходим для корректного функционирования многих бандлов Symfony. После обновления системы PHPбыл обновлён до версии 7.1, а также были произведены следующие настройки PHP:

·  Включено расширение JSON

·        Включено расширение ctype

·        В php.ini указан date.timezone

·        Установлен модуль PHP-XML

·        Установлен модуль libxml

·        Установлен модуль PHP-GD

·        Включена функция mbstring

·        Установлены и включены модули PDO и PDO_PGSQL

·        А также изменены настройки php.ini

o short_open_tag = On

o   magic_quotes_gpc = Off

o   register_globals = Off

o   session.auto_start = Off

Далее была установлена СУБД PostgreSQLс помощью команды в терминале Ubuntu«sudo apt-get install postgresql postgresql-client», а также сконфигурирована и создана база данных «symfony_game_shop» с помощью команд, представленных в листинге 16.

su - postgresUSER root WITH PASSWORD 'root';DATABASE symfony_game_shop;ALL PRIVILEGES ON DATABASE symfony_game_shopto root;

\q

Листинг 16.Создание базы данных «symfony_game_shop»

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

·  aptinstallcomposer (установка менеджера зависимостей composer)

·        aptinstallnodejs(установка менеджера зависимостей NPM)

·        npm install --global webpack@1.12.11 (установкасборщикаfrontend webpack)

·        npminstall --globalbower(установка менеджера зависимостей bower)

·        composerglobalrequirefriendsofphp/php-cs-fixer (установкаприложения для приведения кода к современным стандартам)

·        composerglobalrequirephpunit/phpunit(установкаприложения для тестирования приложения)

·        echo 'exportPATH="$PATH:$HOME/.composer/vendor/bin"' >> ~/.bashrc (добавлениеприложенийcomposerв глобальную область видимости)

·        source ~/.bashrc (применение изменений в файле ~/.bashrc)

Установка Symfonyбыла произведена с помощью SymfonyPluginдляIDEPhpStorm, которая также обладает рядом возможностей для удобства разработки (подсветка синтаксиса, характерного только для Symfony, поддержка шаблонизатора Twig, ORMDoctrine, помощь при генерации форм и т. д.)

После установки были удалены файлы, связанные со стандартным базовым бандлом, установлена система контроля версий Git, а файлы проекта включены в версионный контроль, с учётом содержимого файла .gitignore (см. приложениеA).

├───app/

│ ├───Resources/ - шаблоны сторонних бандлов

│ ├───config/ - основные файлы конфигурации приложения

│└───...

├───assets/

│ └───src/ - файлы frontendдля сборки

├───bin/ - исполняемые файлы

├───src/ - PHPкод проекта

│ └───Difuks/

│ └───DazzleBundle/ - основной бандл

│├───Admin/ -классы конфигурации административной панели

│├───Command/ - классы консольных команд

│ ├───Controller/ - контроллеры

│├───Entity/ - сущности

│├───EventHandler/ - обработчики событий

│├───Extension/- расширения библиотек

│├───Form/ - классы форм

│├───Repository/ - репозитории сущностей

│ ├───Resources/

││├───config/ - конфигурационные файлы бандла

││└───views/ - шаблоны Twig

│├───Services/ - сервисы

│└───Tests/- тесты

├───var/ - кеш, логики и прочие сгенерированные файлы

├───web/- корневой каталог веб-сайта

└───... - конфигурационные файлы composer, npm, webpack, phpunit, php-cs, .gitignore

Листинг 17. Структура каталога файлов frontend

Все файлы, связанные с основным функционалом приложения сосредоточены в директории src/DiFuks/DazzleBundle, за исключениям основных конфигурационных фалов и файлов, связанных с frontend. Общая файловая структура проекта представлена в листинге 17.

 

.3 Установка дополнительных библиотек через composer


В процессе разработки приложения были установлены следующие библиотеки, путём добавление информации о них в конфигурационный файл composer.json:

·  friendsofsymfony/user-bundle - добавляет поддержку системы пользователей с поддержкой базы данных в Symfony. Он обеспечивает гибкую структуру для управления пользователями, которая направлена на решение общих задач, таких как регистрация пользователей с подтверждением, сброс пароля, хранения и управление пользователями с помощью Doctrine[22].

·        gregwar/image-bundle - предоставляетуправлениеизображениями и API для Symfony и Twig.

·        league/color-extractor - извлекает цвета из изображений и сортирует их по частоте использования.

·        sonata-project/admin-bundle - добавляет возможности для генерации и управления административной панелью[24].

·        sonata-project/doctrine-orm-admin-bundle-интегрирует sonata-project/admin-bundle сORM Doctrine.

Дляустановкивсехиспользуемыхбиблиотекиспользоваласькоманда «composerinstall», адляустановкиfronted-зависимостей sonata-project/admin-bundle команды «bower install ./vendor/sonata-project/admin-bundle/bower.json» и «php bin/console assets:install web/assets».

2.4 Вёрсткашаблона, npm, webpack


В качестве менеджера frontend-зависимостей был использован npm. В файле package.json (см. приложениеA)был сформирован список библиотек, используемых при вёрстке шаблона. В качестве css-фреймворка выбран «materialdesignlight», предоставляющийосновные компоненты вёрстки элементов в стиле materialdesign, а также реализующий её адаптивность. Возможность использовать такие возможности, как написание стилей на языке less, использование синтаксиса ECMAScript6 (классы в JavaScript, импорт / экспорт модулей и т. д.) реализуют библиотеки npmпод названием babel, less-loader, file-loader, webpack, css-loader.

Для осуществления жёсткого разграничения между frontendи backend, а также для преобразования lessв css и приведения js-файлов к поддерживаемому устаревшими браузерами виду,был использован сборщик frontendwebpack, а также организована файловая структура, которая отделяет все файлы стилей, изображения и скрипты от основного backend-функционала приложения.Webpackбыл установлен через менеджер зависимостей npmи сконфигурирован с учётом доступных библиотек и модулей.

В процессе вёрстки шаблона была использована методология БЭМ (Блок, Элемент, Модификатор), которая позволяет создавать расширяемые и повторно используемые компоненты интерфейса [24]. Все файлы стилей, скрипты, изображения, шрифты хранятся в папке assets/src/директории проекта.

Таким образом, для каждого маршрута средствами webpackпри загрузки страницы подключаются четыре файла - общие файлы cssи js, а также используемые только в этом маршрутеcssи js, определяемые точкой входа. Сгенерированные файлы располагаются в директории web/assets/build/ и исключены из версионного контроля.

├───blocks/ - содержит отдельные повторно используемые блоки

│ ├───basket/ - содержит js-код и стили для виджета корзины

│ │ ├───basket.js

│ │└───basket.less

│ ├───feedback/ - содержит js-код и стили для виджета обратной связи

│ │ ├───feedback.js

│ │└───feedback.less

│ └───...- содержит блоки для других виджетов на сайте

├───img/- содержит изображения

│ ├───icons/

│ │ ├───...

│ └───logo.png

├───modules/ - содержит скрипты и стили, используемые во всём сайте или в различных блоках

│ └───...

├───admin_common.js - точка входа для страниц административной панели

├───basket.js - точка входа для страницы корзины

├───catalog.js - точка входа для страницы каталога товаров

├───common.js - точка входа, общая для всех публичных страниц

└───... - точки входа для других страниц

Листинг 18. Структура каталога файлов frontend

В директории src/Difuks/DazzleBundle/Resources/views/ содержатся шаблоны twig-базовые шаблоны публичной части сайта, административной панели, email-сообщения; шаблоны компонентов административной панели, вижетов, страниц публичной части и различные почтовые шаблоны.

2.5 Генерация сущностей и форм


База данных была спроектирована таким образом, чтобы отобразить различные типы связей (один ко многим, многие к одному, многие ко многим), использовать различные типы данных, правила валидации и разнообразные возможности ORMDoctrine.

Для реализации поставленной задачи был создан набор сущностей. Все поля и их типы указаны на рисунке 7. Для каждого свойства (за исключением ID) созданы методы для получения и изменения значения (геттеры и сеттеры). Связи между сущностями реализованы с помощью объектно-реляционной модели PostgreSQL, их типы так же указаны на рисунке 7. В некоторых сущностях используется свойство «code», являющееся уникальным, и валидируемое по регулярному выражению - оно должно содержать только символы латиницы, символы «-, _» и цифры. Это поле используется для формирования человекопонятных url.

·  Key - ключи игр для добавления в корзину. Содержит вспомогательный метод для получения статуса ключа на основе связанной корзины.

·        Game - игры. Содержит вспомогательные методы для получения списка не купленных ключей, обновления рейтинга на основе добавленного отзыва, получения рейтинга в целочисленном формате, получения цены с учётом скидки, определения добавлена ли игра в «избранное» для конкретного пользователя, получения количества отзывов.

·        News - новости сайта.

·        User - пользователи.

·        Genre - жанры игр.

·        Image - изображения, используемыевсущностяхGame (логотип, скриншоты), News, Genre, Settings.

Рис. 7. Схема реализуемой базы данных

 - корзина пользователя. Содержит вспомогательные методы для получения текущей стоимости корзины с учётом скидок, фактической стоимости после покупки игр с учётом скидок и без, получения количества игр в корзине, добавления и изменения количества игр и ключей в связанной сущности BasketProduct, получения количества конкретной игры, удаления ключей из корзине по определённой игре.

·  Review - отзывы об игре.

·        Discount - скидки.

·        Feedback - обратная связь.

·        Settings - настройкисайта.

·        Developer - разработчики игр.

·        Publisher - издатели игр.

·        NewsReview - отзывы о новостях.

·        SiteReview - отзывы о сайте.

·        Subscribes - подписки на новости сайта.

·        BasketProduct - товары корзины (игра и её количество). Содержит вспомогательный метод для определения стоимости с учётом скидки

ВприложенииBпредставленасущностьGameввидеклассаDoctrine. Генерация сущностей осуществлялась с помощью консольной командыSymfony, а также доработана вручную с учётом реализации необходимых связей, методов и правил валидации.

Помимо самих сущностей были созданы три репозитория, упрощающие процесс выполнения специфичных запросов данных:

·  GameRepository - содержит методы для получения общего количества игр в базе данных, максимальной цены, минимальной цены с учётом скидок, минимального и максимального допустимого возраста, игр со скидкой, а также метод для выборки игр по фильтру. Данный репозиторий представлен в приложении C.

·        BasketRepository - содержит методы для получения корзины по текущему пользователю, и выборки «старых» корзин.

·        BasketProductRepository - содержит метод, переопределяющий базовый метод findAll, с целью изменения направления сортировки (повозрастанию ID).

На основе некоторых сущностей были созданы классы для генерации пользовательских форм, а именно: форма обратной связи (представлена вместе с сущностью Feedbackв приложении D), форма изменения данных пользователя и форма регистрации.

 

.6 Определение маршрутов и контроллеров


Конфигурация маршрутов разделена на три части, а классы контроллеров на четыре по их предназначению, каждая группа маршрутов обладает собственным префиксом. Также в основном файле конфигурации маршрутизации подключаются файлы настроек маршрутов для бандла аутентификации пользователей и административной панели.

Были выделены следующие группы маршрутов и контроллеров:

·  Public. Маршруты обладают префиксом «/»и определяют доступ к страницам публичной части сайта. Контроллер отвечает за простое отображения страниц. Код конфигурации данной группы маршрутов и обрабатывающий их класс контроллера представлен в приложении E.

·        Ajax. Маршруты обладают префиксом «/ajax/» и определяет доступ через Ajax. Контроллер за изменение каких-либо данных модели и, в некоторых случаях отображения виджетов в качестве результата этих изменений.

·        Widget. Маршрутов не содержит. Контроллер отвечает за отображение часто используемых встраиваемых элементов интерфейса (виджетов).

·        Order. Маршруты обладают префиксом «order». Контроллер отвечает за обработку запросов со стороны стороннего сервиса оплаты и внесение результатов этих запросов в модель.

 

.7 Создание и настройка сервисов


Сервисы были разделены на три категории по их назначению:

·  EventHandler - обработчики событий.

·        Extension - расширения сторонних сервисов.

·        Services - сервисы в привычном понимании Symfony.

Обработчики событий представлены двумя классами:

·  AuthenticationHandler - содержит методы для обработки события успешного входа и ошибки авторизации.

·        FlushHandler - содержит методы для обработки события обновления данных в БД, а именно для рассылки почты при перехода игры в статус «выпущена» и добавлении новости.

Расширения представлены классами, добавляющие возможность использовать функцию Roundв запросах DQL, а также добавляющие функции getJs (получает jsпо имени маршрута), getCss(получает cssпо имени маршрута)и file_exists (стандартная функции определения существования файла в PHP) в шаблонизатор Twigдля более удобной интеграции с frontend.

Сервисы представлены двумя классами:

·  OrderService (представлен в приложении F) -осуществляет интеграцию с системой оплаты «Робокасса» [25].Содержит методы для формирование url, используемого для перехода в систему оплаты, обработки результата запроса от службы оплаты, отправки emailоб успешной оплате, обновления количества покупок игры и даты последней покупки, обработки запроса на странице завершения оплаты, получения текущей корзины пользователя, добавления необходимого количества игр в корзину, изменения количества находящейся в корзине игры, удаления игры из корзины, удаления старых корзин.

·        SocialService - содержит методы для добавления игры в избранное, emailв список подписок, отзыва об игре, сообщения обратной связи, отписки от новостей.

После создания собственных сервисов и добавления сторонних, их необходимо настроить. Были созданы классы (в директории src/Difuks/DazzleBundle/Admin/), определяющие и структурирующие отображение сущностей в административной панели. Отправка почты настроена таким образом, что email, требующие немедленной отправки (уведомление о регистрации, смены пароля и т. д.) отсылаются моментально, а остальные (отправка новости, уведомление о выходе игры) сохраняются в специальный файл и могут быть отправлены только после выполнения специальной консольной команды (для защиты от блокировки за спам-рассылку). Собственные сервисы определены и настроены в соответствии с требуемыми зависимостями. Файл настроек собственных сервисов представлен в приложении F.

2.8 Написание консольных команд и заданий cron


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

В предыдущих пунктах были реализованы методы для удаления таких корзин. Но чтобы это удаление происходило не вручную, а автоматически была реализована консольная команда, удаляющая корзины, ожидающие оплаты (по умолчанию старее трёх суток), а также неоплаченные корзины (по умолчанию одни сутки). Программный код данной команды представлен в приложении G. После ввода «phpbin/console difuks:dazzle:basket:clear-old» заброшенные корзины, если они существуют, удаляются, а в консоли выводится количество удалённых корзин. Тем не менее, данный процесс всё ещё не автоматизирован. Для того, чтобы команда выполнялась автоматически, необходимо добавит её в задания cron[26]. Для систем Debian (в данном случае Ubuntu) это реализуется командой в терминале: «echo "0 0 * * * root cd /path/to/project/ && bin/console difuks:dazzle:basket:clear-old >> var/logs/cron.log" >> /etc/crontab». После ввода данной команды очистка заброшенных корзин будет производиться каждые сутки в 00:00 по серверному времени.

Также необходимо автоматизировать отправку почты, сохранённой во временный файл и ожидающей отправки с помощью команды в терминале «echo "0 * * * * root cd /path/to/project/ && bin/console swiftmailer:spool:send --message-limit=5 --env=prod >> var/logs/cron.log" >> /etc/crontab». Отправка почты будет производится каждый час по 5 emailмаксимум. Это гарантирует защиту от блокировки за рассылку спама.

2.9 Тестирование


Тестирование осуществлялось с помощью встроенных механизмов Symfony, расширяющих возможности PHPUnit [27]. Были реализованы функциональные тесты, осуществляющие проверку корректности выполнения основных функций реализуемого веб-приложения, а именно:

·  Корректное осуществление авторизации;

·        Корректная деавторизация;

·        Корректный статус ответа всех страниц публичной части;

·        Осуществление перенаправления неавторизованного пользователя на страницу авторизации при попытке входа в административную панель, а также на страницу корзины; корректное отображения этих страниц для авторизованного пользователя;

·        Корректное осуществление добавления игры в корзину (существование хотя бы одной доступной для покупки игры, невозможность покупки неавторизованному пользователю).

Программный код последнего из указанных тестов и конфигурация PHPUnitпредставлена в приложении G. Для запуска тестов необходимо выполнить команду: «phpunit», находясь в корневой директории проекта. В случае неуспешного прохождения какого-либо из тестов в консоли отобразиться описание ошибки и дополнительная информация по ней.

 

.10 Перенос проекта на production-сервер


Перенос проекта на production-сервер состоял из следующих этапов:

·  Приведения всего программного кода к стандартам Symfony (расширение стандартов PSR-2) с помощью утилиты php-cs-fixer [28].

·        Отправка всех изменений на сервере разработки в систему контроля версий.

·        Аренда виртуального сервера на хостинге FirstVDS. Характеристики арендованного сервера: Процессор Intel Xeon 2,4 ГГц (1 ядро), оперативная память 1ГБ, диск HDD+SSD 30 ГБ, операционная система Ubuntu 16.04.

·        Регистрация домена difk.ruи изменение серверов имённа серверы имён FirstVDS для доступа к серверу по адресу.

·        Подключение по sshк удалённому серверу.

·        УстановкаPHP 7.1, PostgreSQL, git, composer, npm, webpack, bower, phpunit.

·        Настройка PHP, создание базы данных PostgreSQL(процесс аналогичен таковому на сервере разработки).

·        Клонирования репозитория из системы контроля версий.

·        Миграция базы данных.

·        Запуск тестов PHPUnit.

·        Добавление заданий cron.

·        Настройка виртуального хоста apache2 (для доступа из сети по адресу difk.ruк папке webприложения).

·        Перезапуск apache2.

После всех выполненных действий веб-приложение Symfonyготово к работе и доступно по адресу http://difk.ru.

Заключение


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

В ходе анализасуществующих современных технологий и моделей в области веб были обозначены проблемы в области веб-разработки и приведены некоторые типовые решения, способствующие устранению обозначенных проблем и противоречий. В качестве методологии в области веб-проектирования был обозначен шаблон проектирования MVC, реализующий ООП-подход в области веб.

Технологии SymfonyFrameworkотвечают современным требованиям и стандартам, поддерживают шаблон проектирования MVC, обеспечивают безопасность доступа к данным, увеличивают быстродействие запросов, обладают высоким уровнем масштабируемости. Основные компоненты SymfonyFramework: бандлы, Doctrine, маршрутизация, контроллеры, шаблонизатор Twig, сервисы и т.д. - предоставляют инструментарий для разработки сложно структурируемых веб-приложений в соответствии с принятыми стандартами в области веб. Отсутствие официальной русскоязычной документации по SymfonyFramework поставило дополнительную задачу по адаптации официальной документации разработчика на русский язык.

На основе анализа моделей и подходов в области веб, инструментария и функционала SymfonyFramework в качестве демонстрации программной реализации возможностей его компонентов и подходов был реализован интернет-магазин, иллюстрирующий типовой процесс разработки веб-приложения, отвечающего современным требованиям. В работе большое внимание было уделено техническим вопросам по настройке и развертыванию проекта на production-сервере.

Для разработанного приложения были написаны функциональные автоматизированные тесты, на основании которых осуществлено тестирование приложения.

Таким образом цель выпускной квалификационной работы - выполнить анализ возможностей технологий Symfony Framework и реализовать приложение интернет-магазина компьютерных игр на основе современных подходов в области веб-разработки - была достигнута.

Результаты выпускной квалификационной работы могут быть использованы как теоретическое пособие по SymfonyFramework, а практическая разработка - в качестве модели проектирования и реализации веб-приложения. При соответствующей адаптации к предметной области приложение может быть использовано как полноценный интернет-магазин.

Список литературы

1.  History of the Web. URL: http://webfoundation.org/about/vision/history-of-the-web/ (датаобращения: 13.05.2016)

2.       Internet Live Stats. URL: http://www.internetlivestats.com/ (датаобращения: 15.04.2017)

3.  Selmanovic D.. The 10 Most Common Mistakes Web Developers Make. URL: https://www.toptal.com/web/top-10-mistakes-that-web-developers-make (дата обращения: 15.04.2017)

4.      HelmkeM., JosephE., ReyJ., BallewP., HillB.The Official Ubuntu Book. Upper Saddle River, NJ: Prentice Hall, 2014. 368 c.

5.       Simpson K. You Don't Know JS: ES6 & Beyond. Sebastopol, CA: O'Reilly Media, 2015. 280 с.

6.      Composer. URL: https://getcomposer.org/doc/ (дата обращения 19.04.2016)

7.      Npm Documentation. URL: https://docs.npmjs.com/ (датаобращения 19.04.2017)

.        Potencier F. The Twig Book. Paris: SensioLabs, 2017. 156 с.

.        Romer M. PHP Persistence: Concepts, Techniques and Practical Solutions with Doctrine. New York City, NA: Apress, 2016. 107 с.

.        Chacon S., Straub B. Pro Git. New York City, NA: Apress, 2016. 456 с.

.        Chaudhary M., Kumar A. Birmingham. PhpStorm Cookbook. Birmingham: Packt Publishing, 2014. 256 с.

.        Webpack. URL: https://webpack.js.org/ (дата обращения 10.04.2017)

13.    Material Design Light. URL: https://getmdl.io/ (датаобращения 21.04.2017)

.        Less.js: Getting started. URL:http://lesscss.org/ (датаобращения 21.04.2017)

15.     Potencier F. Symfony. The Book. Paris: SensioLabs, 2016. 219 с.

16.     Stones R. Beginning Databases with PostgreSQL: From Novice to Professional. New York City, NA: Apress, 2016. 664 с.

17.    Hopkins C. The MVC Pattern and PHP. URL: https://www.sitepoint.com/the-mvc-pattern-and-php-1/ (дата обращения 11.04.2017)

18.    BiererDoug. PHP 7 ProgrammingCookbook. Birmingham: Packt, 2016. 610 с.

19.     A Framework or a CMS? What is better to choose? URL: http://www.web-and-development.com/a-framework-or-a-cms-what-is-better-to-choose/ (дата обращения 11.04.2017)

20.     SymfonyversusFlatPHP. URL: https://symfony.com/doc/current/introduction/from_flat_php_to_symfony2.html (дата обращения 11.04.2017)

21.    Symfony. URL: https://www.drupal.org/project/symfony (дата обращения 11.04.2017)

22.    FOSUserBundle. URL: http://knpbundles.com/FriendsOfSymfony/FOSUserBundle (дата обращения 11.04.2017)

23.    SonataAdminBundle. URL: https://sonata-project.org/bundles/admin/3-x/doc/index.html (дата обращения 11.04.2017)

24.    MethodologyBEM. URL: https://en.bem.info/methodology/ (дата обращения 20.04.2017)

25.    Robokassa user manual. URL: https://docs.robokassa.ru/en/ (датаобращения 25.04.2017)

.        CronHowto. URL: https://help.ubuntu.com/community/CronHowto (дата обращения 25.04.2017)

27.    BergmannS. PHPUnitManual. Siegburg: Sebastian Bergmann, 2017. 175 с.

28.    PHP Coding Standards Fixer. URL: http://cs.sensiolabs.org/ (датаобращения 10.06.2017)

Приложение A. Конфигурационныефайлы

/app/config/parameters.yml

/.idea/

/var/

/vendor/

/web/assets/

/web/cache/

/web/upload/

/node_modules/

/bower_components/

.php_cs.cache

/web/bundles/

Листинг 19. Содержимоефайла.gitignore

{

"name": "symfony-game-shop",

"version": "0.0.1",

"dependencies": {

"assets-webpack-plugin": "^3.2.0",

"autoprefixer": "^6.3.6",

"babel-core": "^6.17.0",

"babel-loader": "^6.2.5",

"babel-preset-es2015": "^6.16.0",

"bower": "^1.7.2",

"clean-webpack-plugin": "^0.1.8",

"clndr": "^1.4.6",

"css-loader": "^0.23.1",

"css-mqpacker": "^4.0.1",

"exports-loader": "^0.6.2",

"extract-text-webpack-plugin": "^1.0.1",

"file-loader": "^0.8.5",

"getmdl-select": "^1.0.4",

"imports-loader": "^0.6.5",

"jquery": "^1.11.3",

"jquery-mousewheel": "^3.1.13",

"jquery-ui": "^1.10.5",

"jquery.dotdotdot": "^1.7.4",

"less": "^2.3.1",

"less-loader": "^2.2.2",

"material-design-lite": "^1.3.0",

"moment": "^2.15.1",

"normalize.css": "^4.1.1",

"nouislider": "^9.2.0",

"picturefill": "^3.0.2",

"postcss-loader": "^0.8.2",

"resolve-url-loader": "^1.4.3",

"slick-carousel": "^1.6.0",

"style-loader": "^0.13.0",

"underscore": "^1.8.3",

"url-loader": "^0.5.7",

"webpack": "^1.12.11"

}

}

Листинг20. Содержимоефайлаpackage.json

Приложение B. КласссущностиGame

<?php(strict_types=1);Difuks\DazzleBundle\Entity;Doctrine\ORM\Mapping as ORM;Doctrine\Common\Collections\ArrayCollection;

/**

* Game.

*

* @ORM\Table(name="game")

* @ORM\Entity(repositoryClass="Difuks\DazzleBundle\Repository\GameRepository")

*/

{

/**

* @var int

*

* @ORM\Column(name="id", type="integer")

* @ORM\Id

* @ORM\GeneratedValue(strategy="AUTO")

*/$id;

/**

* @var string

*

* @ORM\Column(name="name", type="string", length=255)

*/$name;

/**

* @var string

*

* @ORM\Column(name="code", type="string", length=255, unique=true)

*/$code;

/**

* @var Image

*

* @ORM\ManyToOne(targetEntity="Image", cascade={"persist"})

* @ORM\JoinColumn(nullable=true)

*/$logo;

/**

* @var \DateTime

*

* @ORM\Column(name="release_date", type="datetime")

*/$releaseDate;

/**

* @var string

*

* @ORM\Column(name="site", type="string", length=255)

*/$site;

/**

* @var string

*

* @ORM\Column(name="video", type="string", length=255)

*/$video;

/**

* @var int

*

* @ORM\Column(name="age_restrictions", type="integer")

*/$ageRestrictions;

/**

* @var string

*

* @ORM\Column(name="description", type="text")

*/$description;

/**

* @var string

*

* @ORM\Column(name="system_requirements", type="text")

*/$systemRequirements;

/**

* @var float

*

* @ORM\Column(name="price", type="float")

*/$price;

/**

* @var Genre[]|ArrayCollection

*

* @ORM\ManyToMany(targetEntity="Genre", inversedBy="games", cascade={"persist"})

* @ORM\JoinTable(name="game_genre")

*/$genres;

/**

* @var Developer

*

* @ORM\ManyToOne(targetEntity="Developer", cascade={"persist"}, inversedBy="games")

* @ORM\JoinColumn(nullable=false)

*/$developer;

/**

* @var Publisher

*

* @ORM\ManyToOne(targetEntity="Publisher", cascade={"persist"})

* @ORM\JoinColumn(nullable=false)

*/$publisher;

/**

* @var Image[]|ArrayCollection

*

* @ORM\ManyToMany(targetEntity="Image", inversedBy="games", cascade={"persist"})

* @ORM\JoinTable(name="game_screenshots")

*/$screenshots;

/**

* @var Key[]|ArrayCollection

*

* @ORM\OneToMany(

* targetEntity="Key",

* mappedBy="game",

* orphanRemoval=true,

* cascade={"persist"}

* )

*/$keys;

/**

* @var bool

*

* @ORM\Column(name="multiplayer", type="boolean")

*/$multiplayer;

/**

* @var Review[]|ArrayCollection

*

* @ORM\OneToMany(

* targetEntity="Review",

* mappedBy="game",

* orphanRemoval=true,

* cascade={"persist"}

* )

*/$reviews;

/**

* @var Discount

*

* @ORM\ManyToOne(targetEntity="Discount", inversedBy="games", cascade={"persist"})

* @ORM\JoinColumn(nullable=true, onDelete="SET NULL")

*/$discount;

/**

* @var BasketProduct[]|ArrayCollection

*

* @ORM\OneToMany(

* targetEntity="BasketProduct",

* mappedBy="game",

* orphanRemoval=true

* )

*/$basketProducts;

/**

* @var int

*

* @ORM\Column(name="buy_count", type="integer", nullable=true)

*/$buyCount;

/**

* @var \DateTime

*

* @ORM\Column(name="last_buy", type="datetime", nullable=true)

*/$lastBuy;

/**

* @var float

*

* @ORM\Column(name="rate", type="float", nullable=true)

*/$rate;

/**

* @var bool

*

* @ORM\Column(name="is_released", type="boolean", options={"default" : true})

*/$isReleased;

/**

* @var bool

*

* @ORM\Column(name="is_rus", type="boolean", options={"default" : true})

*/$isRus;__construct()

{

$this->releaseDate = new \DateTime();

$this->genres = new ArrayCollection();

$this->keys = new ArrayCollection();

$this->screenshots = new ArrayCollection();

$this->reviews = new ArrayCollection();

$this->basketProducts = new ArrayCollection();

$this->buyCount = 0;

$this->rate = 0;

}__toString()

{(string) $this->getName();

}()

{$this->id;

}(string $name)

{

$this->name = $name;$this;

}()

{$this->name;

}(string $code)

{

$this->code = $code;$this;

}()

{$this->code;

}(Image $logo)

{

$this->logo = $logo;$this;

}()

{$this->logo;

}(\DateTime $releaseDate)

{

$this->releaseDate = $releaseDate;$this;

}()

{$this->releaseDate;

}(string $site)

{

$this->site = $site;$this;

}()

{$this->site;

}(string $video)

{

$this->video = $video;$this;

}()

{$this->video;

}(int $ageRestrictions)

{

$this->ageRestrictions = $ageRestrictions;$this;

}()

{$this->ageRestrictions;

}(string $description)

{

$this->description = $description;$this;

}()

{$this->description;

}(string $systemRequirements)

{

$this->systemRequirements = $systemRequirements;$this;

}()

{$this->systemRequirements;

}(float $price)

{

$this->price = $price;$this;

}()

{(int) $this->price;

}(Genre $genre)

{

$this->genres->add($genre);$this;

}(Genre $genre)

{

$this->genres->removeElement($genre);$this;

}()

{$this->genres;

}(ArrayCollection $genres)

{

$this->genres = $genres;

}(Image $screenshot)

{

$this->screenshots->add($screenshot);$this;

}(Image $screenshot)

{

$this->screenshots->removeElement($screenshot);$this;

}()

{$this->screenshots;

}(ArrayCollection $screenshots)

{

$this->screenshots = $screenshots;

}()

{$this->developer;

}(Developer $developer)

{

$this->developer = $developer;$this;

}()

{$this->publisher;

}(Publisher $publisher)

{

$this->publisher = $publisher;$this;

}()

{

$unPayedKeys = new ArrayCollection();($this->keys as $key) {($key->getStatus() == 0) {

$unPayedKeys->add($key);

}$unPayedKeys;

}(Key $key)

{

$this->keys->add($key);

$key->setGame($this);$this;

}(Key $key)

{

$this->keys->removeElement($key);$this;

}()

{$this->reviews;

}(Review $review)

{

$this->reviews->add($review);

$review->setGame($this);

$rate = $review->getRate();

$count = 1;($this->getReviews() as $newReview) {

$rate += $newReview->getRate();

++$count;

}

$rate = round($rate / $count, 1);

$this->setRate($rate);$this;

}(Review $review)

{

$this->reviews->removeElement($review);$this;

}()

{$this->rate;

}(float $rate)

{

$this->rate = $rate;$this;

}()

{(int) $this->getRate();

}(bool $multiplayer)

{

$this->multiplayer = $multiplayer;$this;

}()

{$this->multiplayer;

}()

{$this->discount;

}($discount)

{

$this->discount = $discount;$this;

}()

{

$this->discount = null;$this;

}()

{($this->getDiscount()) {

$discountPrice = $this->getPrice() - $this->discount->getValue() * $this->getPrice() / 100;

} else {

$discountPrice = $this->getPrice();

}(int) $discountPrice;

}()

{$this->getDiscountPrice() != $this->getPrice();

}()

{$this->basketProducts;

}()

{$this->buyCount;

}($buyCount)

{

$this->buyCount = $buyCount;$this;

}()

{$this->lastBuy;

}(\DateTime $lastBuy)

{

$this->lastBuy = $lastBuy;$this;

}()

{$this->isReleased;

}(bool $isReleased)

{

$this->isReleased = $isReleased;$this;

}(User $user = null)

{($user == null) {;

}

$favorites = $user->getFavoriteGames();($favorites as $favorite) {($favorite->getId() == $this->getId()) {;

}

};

}()

{$this->reviews->count();

}()

{$this->isRus;

}(bool $isRus)

{

$this->isRus = $isRus;$this;

}()

{$this->getKeys()->count();

}

}

Листинг 21. КласссущностиGame

Приложение C. РепозиторийGameRepositoryсущностиGame

<?Difuks\DazzleBundle\Repository;Doctrine\ORM\EntityRepository;Doctrine\ORM\Query\Expr\Join;EntityRepository

{(): int

{

$db = $this->createQueryBuilder('t');$db

>select('count(t.id)')

>getQuery()

>getSingleScalarResult();

}()

{

$db = $this->createQueryBuilder('t');$db

>select('MAX(t.price)')

>getQuery()

>getSingleScalarResult();

}()

{

$minPrice = $this->getEntityManager()

>createQuery(

'SELECT(MIN(CASE WHEN g.discount IS NULL THEN g.price ELSE (g.price - g.price * d.value / 100) END),1)DifuksDazzleBundle:Game gJOIN DifuksDazzleBundle:Discount dg.discount = d'

)

>getSingleScalarResult();$minPrice;

}()

{

$db = $this->createQueryBuilder('t');$db

>select('MAX(t.ageRestrictions)')

>getQuery()

>getSingleScalarResult();

}()

{

$db = $this->createQueryBuilder('t');$db

>select('MIN(t.ageRestrictions)')

>getQuery()

>getSingleScalarResult();

}(array $filter = [], array $sort = [], int $page = 1, $count = 9)

{

$query = $this->createQueryBuilder('g');

$actualPrice = 'CASE WHEN g.discount IS NULL THEN g.price ELSE (g.price - g.price * d.value / 100) END';

$query->select("g, $actualPrice AS HIDDEN price");

$query

>leftJoin('DifuksDazzleBundle:Discount', 'd', Join::WITH, 'g.discount = d');(isset($filter['price']['min'])) {

$minPrice = $filter['price']['min'];

$query->andWhere("$actualPrice>= $minPrice");

}(isset($filter['price']['max'])) {

$maxPrice = $filter['price']['max'];

$query->andWhere("$actualPrice<= $maxPrice");

}(isset($filter['age']['min'])) {

$ageMin = $filter['age']['min'];

$query->andWhere("g.ageRestrictions >= $ageMin");

}(isset($filter['age']['max'])) {

$ageMax = $filter['age']['max'];

$query->andWhere("g.ageRestrictions <= $ageMax");

}(isset($filter['rate']['min'])) {

$rateMin = $filter['rate']['min'];

$query->andWhere("g.rate >= $rateMin");

}(isset($filter['rate']['max'])) {

$rateMax = $filter['rate']['max'];

$query->andWhere("g.rate <= $rateMax");

}(isset($filter['isReleased'])) {

$query->andWhere('g.isReleased = TRUE');

}(isset($filter['isDiscount'])) {

$query->andWhere('g.discount IS NOT NULL');

}(isset($filter['genre'])) {

$query->andWhere(':genres MEMBER OF g.genres');

$query->setParameter('genres', $filter['genre']);

}

$order = $sort['order'];

$by = $sort['by'];($order == 'price') {

$query->addOrderBy('price', $by);

} else {

$query->addOrderBy("g.$order", $by);

}

$games = $query->getQuery()->setMaxResults($count * $page)->setFirstResult(($page - 1) * $count)->getResult();

$totalCount = count($query->select('g.id')->orderBy('g.id')->getQuery()->getResult());[

'elements' => $games,

'page' => [

'count' => ceil($totalCount / $count),

'current' => $page,

],

];

}()

{

$db = $this->createQueryBuilder('g');

$db->where('g.discount IS NOT NULL')->setFirstResult(0)->setMaxResults(10);$db->getQuery()->getResult();

}

}

Листинг 22. РепозиторийGameRepositoryсущности Game

Приложение D. Класс генерации формы на основе сущности

<?Difuks\DazzleBundle\Entity;Doctrine\ORM\Mapping as ORM;Symfony\Component\Validator\Constraints as Assert;

/**

* Feedback.

*

* @ORM\Table(name="feedback")

* @ORM\Entity()

*/

{

/**

* @var int

*

* @ORM\Column(name="id", type="integer")

* @ORM\Id

* @ORM\GeneratedValue(strategy="AUTO")

*/$id;

/**

* @var string

*

* @Assert\NotBlank()

* @ORM\Column(name="name", type="string", length=255, nullable=false)

*/$name;

/**

* @var string

*

* @Assert\NotBlank()

* @Assert\Email()

* @ORM\Column(name="email", type="string", length=255, nullable=false)

*/$email;

/**

* @var string

*

* @Assert\NotBlank()

* @ORM\Column(name="text", type="text", length=255, nullable=false)

*/$text;

/**

* @var \DateTime

*

* @ORM\Column(name="date", type="datetime")

*/$date;__construct()

{

$this->date = new \DateTime();

}()

{$this->id;

}()

{$this->name;

}(string $name)

{

$this->name = $name;

}()

{$this->email;

}(string $email)

{

$this->email = $email;

}()

{$this->text;

}(string $text)

{

$this->text = $text;

}()

{$this->date;

}(\DateTime $date)

{

$this->date = $date;

}

}

Листинг 22. СущностьFeedback

<?Difuks\DazzleBundle\Form;Difuks\DazzleBundle\Entity\Feedback;Symfony\Component\Form\AbstractType;Symfony\Component\Form\Extension\Core\Type\EmailType;Symfony\Component\Form\Extension\Core\Type\TextType;Symfony\Component\Form\Extension\Core\Type\TextareaType;Symfony\Component\Form\FormBuilderInterface;Symfony\Component\OptionsResolver\OptionsResolver;AbstractType

{(FormBuilderInterface $builder, array $options)

{

$builder

>add('name', TextType::class, [

'label' =>'Имя',

'required' =>false,

'mapped' =>true,

])

>add('email', EmailType::class, [

'label' =>'Email',

'required' =>false,

])

>add('text', TextareaType::class, [

'label' =>'Текстобращения',

'required' =>false,

])

;

}(OptionsResolver $resolver)

{

$resolver->setDefaults([

'data_class' => Feedback::class,

]);

}

}

Листинг 23. Класс генерации формы обратной связи

Приложение E. Маршруты и контроллер публичной части сайта

index:: /: { _controller: DifuksDazzleBundle:Public:index }:: /genres/: { _controller: DifuksDazzleBundle:Public:genres }_all:: /catalog/: { _controller: DifuksDazzleBundle:Public:catalog }:: /catalog/{code}: { _controller: DifuksDazzleBundle:Public:catalog }:: /product/{code}: { _controller: DifuksDazzleBundle:Public:product, code: default }:: /basket/: { _controller: DifuksDazzleBundle:Public:basket }:: /feedback/: { _controller: DifuksDazzleBundle:Public:feedback }:: /unsubscribe/{hash}: { _controller: DifuksDazzleBundle:Public:unsubscribe }

Листинг 24. Конфигурация маршрутов публичной части

<?

declare(strict_types=1);Difuks\DazzleBundle\Controller;Difuks\DazzleBundle\Entity\Feedback;Difuks\DazzleBundle\Entity\Game;Difuks\DazzleBundle\Entity\Genre;Difuks\DazzleBundle\Entity\Subscribes;Difuks\DazzleBundle\Form\FeedbackType;Symfony\Bundle\FrameworkBundle\Controller\Controller;Symfony\Component\HttpFoundation\Response;Controller

{(): Response

{$this->render('DifuksDazzleBundle:Public:index.html.twig');

}(): Response

{

$genres = $this->getDoctrine()->getRepository(Genre::class)->findBy([], ['id' =>'ASC']);

$totalCount = $this->getDoctrine()->getRepository(Game::class)->count();$this->render('DifuksDazzleBundle:Public:genres.html.twig', ['genres' => $genres, 'totalCount' => $totalCount]);

}(Genre $genre = null): Response

{$this->render('DifuksDazzleBundle:Public:catalog.html.twig', ['genre' => $genre]);

}(Game $game): Response

{$this->render('DifuksDazzleBundle:Public:product.html.twig', ['game' => $game]);

}(): Response

{$this->render('@DifuksDazzle/Public/basket.html.twig');

}(): Response

{

$form = $this->createForm(FeedbackType::class);$this->render('@DifuksDazzle/Public/feedback.html.twig', ['form' => $form->createView()]);

}(Subscribes $subscribe): Response

{

$this->get('difuks.dazzle.social_service')->unSubscribe($subscribe);$this->render('@DifuksDazzle/Public/unsubscribe.html.twig');

}

}

Листинг 25. Контроллер публичной части сайта

Приложение F. Сервис для работы с корзиной и заказами. Настройкасервисов

<?Difuks\DazzleBundle\Services;Difuks\DazzleBundle\Entity\Basket;Difuks\DazzleBundle\Entity\BasketProduct;Difuks\DazzleBundle\Entity\Game;Doctrine\ORM\EntityManager;Symfony\Component\DependencyInjection\Container;

{$container;$pass;$pass2;$login;$em;$mailer;__construct(Container $container, EntityManager $em, \Swift_Mailer $mailer)

{

$this->container = $container;

$this->mailer = $mailer;

$this->em = $em;

$this->login = $container->getParameter('robokassa.login');

$this->pass = $container->getParameter('robokassa.password');

$this->pass2 = $container->getParameter('robokassa.password2');

}

/**

* Получает url для перевода в систему оплаты.

*

* @param int $id id заказа

* @param float $sum сумма заказа

*

* @return string url

*/(int $id, float $sum): string

{

$descr = 'Оформление заказа №'.$id;

$crc = md5("$this->login:$sum:$id:$this->pass");

$url = "https://auth.robokassa.ru/Merchant/Index.aspx?MrchLogin=$this->login&".

"OutSum=$sum&InvId=$id&Description=$descr&SignatureValue=$crc&IsTest=1";

return $url;

}

/**

* Обрабатывает результат запроса от службы оплаты.

*

* @param int $id id заказа

* @param float $sum сумма заказа

* @param string $crc хэш

*

* @throws \Exception в случае несовпадения хэша

*/(int $id, float $sum, string $crc): void

{

$crc = strtoupper($crc);

$myCrc = strtoupper(md5("$sum:$id:$this->pass2"));($myCrc != $crc) {\Exception('Неверныеданныеоплаты. Хеш '.$myCrc.' и '.$crc.' несовпадают');

}

$basket = $this->em->getRepository(Basket::class)->find($id);

$fromEmail = $this->container->getParameter('mailer_user');($basket->getPaymentState() != 2) {

$this->sendEmailAboutPay($basket, $fromEmail);

$this->refreshGamesRate($basket);

$basket->setPaymentState(2);

$this->em->persist($basket);

$this->em->flush();

}

}

/**

* Отправляет email об успешной оплате.

*

* @param Basket $basket

* @param string $email

*/(Basket $basket, string $email): void

{

$body = [];

$keys = $basket->getKeys();

/*

*/($keys as $key) {

$body[$key->getGame()->getName()][] = $key->getKey();

}

$message = \Swift_Message::newInstance()

>setSubject('Покупкаигр')

>setFrom($email)

>setTo($basket->getUser()->getEmail())

>setBody($this->container->get('templating')->render(

'@DifuksDazzle/Email/keys.send.html.twig',

['body' => $body]

),

'text/html');

$this->mailer->send($message);

}

/**

* Обновляет количество покупок игры, а так же дату последней покупки.

*

* @param Basket $basket

*/(Basket $basket): void

{

$products = $basket->getProducts();($products as $product) {

$game = $product->getGame();

$currentCount = ($game->getBuyCount()) ?: 0;

$game->setBuyCount($currentCount + $product->getQuantity());

$game->setLastBuy(new \DateTime());

$this->em->persist($game);

$this->em->flush();

}

}

/**

* Обрабатывает запрос на странице завершения оплаты.

*

* @param int $id id заказа

* @param float $sum сумма заказа

* @param string $crc хэш

*

* @throws \Exception в случае несовпадения хэша

*

* @return string текст с результатом

*/

publicfunctiongetDone(int $id, float $sum, string $crc): string

{

$crc = strtoupper($crc);

$myCrc = strtoupper(md5("$sum:$id:$this->pass"));($myCrc != $crc) {\Exception('Неверныеданныеоплаты. Хеш '.$myCrc.' и '.$crc.' несовпадают');

}

$basket = $this->em->getRepository(Basket::class)->find($id);(isset($basket)) {($basket->getPaymentState() != 2) {

$basket->setPaymentState(1);

$this->em->persist($basket);

$this->em->flush();

}'Операция прошла успешно. После проведения оплаты ключи отправят вам на email. Спасибо за покупку!';

} else {'Нет заказа с таким номером';

}

}

/**

* Получает текущую корзину пользователя.

*

* @return Basket

*/(): Basket

{

$user = $this->container->get('security.token_storage')->getToken()->getUser();

$basket = $this->em->getRepository(Basket::class)->getCurrentBasketByUser($user);

return $basket;

}

/**

* Добавляет необходимое количество игр в корзину. Возвращает оставшееся количество ключей.

*

* @param Basket $basket

* @param Game $game

* @param int $quantity

*

* @return int

*/(Basket $basket, Game $game, int $quantity): int

{

$basket->addGame($game, $quantity);

$this->em->persist($basket);

$this->em->flush();$game->getKeysCount();

}

/**

* Изменят количество находящейся в корзине игр

*

* @param BasketProduct $basketProduct

* @param int $quantity

*/(BasketProduct $basketProduct, int $quantity): void

{

$basketProduct->getBasket()->changeGameCount($basketProduct->getGame(), $quantity);

$this->em->persist($basketProduct);

$this->em->flush();

}

/**

* Удаляетигруизкорзины.

*

* @param BasketProduct $basketProduct

*/(BasketProduct $basketProduct): void

{

$basket = $basketProduct->getBasket();

$basket->removeProduct($basketProduct);($basket->getProductCount() == 0) {

$this->em->remove($basket);

} else {

$this->em->persist($basket);

}

$this->em->flush();

}

/**

* Удаляет старые корзины.

*

* @param int $notPayDay количество дней для удаления корзин в статусе неоплачено

* @param int $payDay количество дней для удаления корзин в статусе ожидания оплаты

*

* @return int число удалённых корзин

*/

publicfunctionclearOldBasket(int $notPayDay, int $payDay): int

{

$oldBaskets = $this->em

>getRepository(Basket::class)

>getOldBaskets(

$notPayDay,

$payDay

);

$count = count($oldBaskets);($oldBaskets as $basket) {

$this->em->remove($basket);

}

$this->em->flush();$count;

}

}

Листинг 26. Сервис для работы с корзиной и заказами

services:.dazzle.file.twig.extension:: Difuks\DazzleBundle\Extension\Twig\FileExtension: ['@service_container']:

{ name: twig.extension }.dazzle.flush_handler:: Difuks\DazzleBundle\EventHandler\FlushHandler: ['@swiftmailer.mailer.cron', '@service_container']:

{ name: doctrine.event_listener, event: onFlush }.dazzle.authentication_handler:: Difuks\DazzleBundle\EventHandler\AuthenticationHandler: ['@router', '@security.authorization_checker'].dazzle.order_service:: Difuks\DazzleBundle\Services\OrderService: ['@service_container', '@doctrine.orm.entity_manager', '@swiftmailer.mailer.moment'].dazzle.social_service:: Difuks\DazzleBundle\Services\SocialService: ['@service_container', '@doctrine.orm.entity_manager'].dazzle.form.registration:: Difuks\DazzleBundle\Form\RegistrationType:

{ name: form.type, alias: difuks_dazzle_user_registration }.dazzle.form.profile:: Difuks\DazzleBundle\Form\ProfileType:

{ name: form.type, alias: difuks_dazzle_user_profile }

Листинг 27. Файл настройки собственных сервисов

Приложение F. Класс консольной команды очистки заброшенных корзин

<?(strict_types=1);Difuks\DazzleBundle\Command;Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;Symfony\Component\Console\Input\InputInterface;Symfony\Component\Console\Input\InputOption;Symfony\Component\Console\Output\OutputInterface;ContainerAwareCommand

{()

{

$this->addOption('not-pay-day', null, InputOption::VALUE_REQUIRED, 'Количестводнейдляудалениекорзинсостатусом 0', 1);

$this->addOption('pay-day', null, InputOption::VALUE_REQUIRED, 'Количестводнейдляудалениекорзинсостатусом 1', 3);

$this

>setName('difuks:dazzle:basket:clear-old')

->setDescription('Очищает старые корзины')

>setHelp('Очищает корзины со статусом 0 (старее одного дня) и 1 (старее трёх дней)');

}(InputInterface $input, OutputInterface $output)

{

$notPayDay = (int) $input->getOption('not-pay-day');

$payDay = (int) $input->getOption('pay-day');

$count = $this->getContainer()->get('difuks.dazzle.order_service')->clearOldBasket($notPayDay, $payDay);

$output->writeln((new \DateTime())->format('d.m.Y H:i:s')." Remove $count baskets");

}

}

Листинг 28. Класс консольной команды очистки заброшенных корзин

Приложение G. Функциональный тест виджета добавления в корзину и конфигурация PHPUnit

<?Difuks\DazzleBundle\Tests\Functional\Controller;Difuks\DazzleBundle\Tests\Functional\BaseControllerTest;BaseControllerTest

{()

{

$crawler = $this->client->request('GET', '/');

$gameButtons = $crawler->filter('button:contains("Вкорзину")');

$this->assertTrue($gameButtons->count() >0, 'Ни одной доступной для покупки игры');

if ($gameButtons->count()) {

$this->checkUrl('/ajax/add-to-basket/'.$gameButtons->first()->attr('data-id'));

$answer = json_decode($this->client->getResponse()->getContent(), true);

$this->assertTrue(isset($answer['error']) && $answer['error'] == true, 'Добавлениевкорзинудоступнонеавторизованномупользователю');

$this->logIn();

$this->checkUrl('/ajax/add-to-basket/'.$gameButtons->first()->attr('data-id'));

$this->logout();

}

}

}

Листинг 29. Функциональный тест виджета добавления в корзину

<?xml version="1.0" encoding="UTF-8"?>

<!-- https://phpunit.de/manual/current/en/appendixes.configuration.html -->

<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance":noNamespaceSchemaLocation="http://schema.phpunit.de/4.8/phpunit.xsd"="false"="true"="app/autoload.php"

<php>

<ini name="error_reporting" value="-1" />

<server name="KERNEL_DIR" value="app/" />

</php>

<testsuites>

<testsuite name="Project Test Suite">

<directory>src/Difuks/DazzleBundle/Tests/</directory>

</testsuite>

</testsuites>

<filter>

<whitelist>

<directory>src</directory>

<exclude>

<directory>src/*Bundle/Resources</directory>

<directory>src/*/*Bundle/Resources</directory>

<directory>src/*/Bundle/*Bundle/Resources</directory>

</exclude>

</whitelist>

</filter>

</phpunit>

Листинг 30. КонфигурационныйфайлPHPUnit

Похожие работы на - Проектирование и разработка веб-приложения на основе технологий Symfony Framework

 

Не нашли материал для своей работы?
Поможем написать уникальную работу
Без плагиата!