Статьи

Создание веб-приложения с помощью Symfony 2: разработка

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

Маршруты

Наш экземпляр Symfony использует формат YAML для настройки маршрутов для приложения. Пример раздела маршрутов этого сайта показан ниже:

Расположение файла: src/tr/rsywxBundle/Resources/config/routing.yml

  home: pattern: / defaults: { _controller: trrsywxBundle:Default:index } contact: pattern: /contact defaults: _controller: FrameworkBundle:Template:template template: 'trrsywxBundle:Default:contact.html.twig' book_list: pattern: /books/list/{page}/{key} defaults: page: 1 key: null _controller: trrsywxBundle:Book:list books_search: pattern: /books/search defaults: {_controller: trrsywxBundle:Book:search} requirements: _method: POST book_detail: pattern: /books/{id}.html defaults: { _controller: trrsywxBundle:Book:detail} по  home: pattern: / defaults: { _controller: trrsywxBundle:Default:index } contact: pattern: /contact defaults: _controller: FrameworkBundle:Template:template template: 'trrsywxBundle:Default:contact.html.twig' book_list: pattern: /books/list/{page}/{key} defaults: page: 1 key: null _controller: trrsywxBundle:Book:list books_search: pattern: /books/search defaults: {_controller: trrsywxBundle:Book:search} requirements: _method: POST book_detail: pattern: /books/{id}.html defaults: { _controller: trrsywxBundle:Book:detail} по  home: pattern: / defaults: { _controller: trrsywxBundle:Default:index } contact: pattern: /contact defaults: _controller: FrameworkBundle:Template:template template: 'trrsywxBundle:Default:contact.html.twig' book_list: pattern: /books/list/{page}/{key} defaults: page: 1 key: null _controller: trrsywxBundle:Book:list books_search: pattern: /books/search defaults: {_controller: trrsywxBundle:Book:search} requirements: _method: POST book_detail: pattern: /books/{id}.html defaults: { _controller: trrsywxBundle:Book:detail} 

Каждое приложение нуждается в точке входа. Это «домашний» маршрут. pattern определяет pattern URI, которому должен соответствовать маршрут. Поскольку это точка входа, используется /. defaults:_controller определяет действие, которое будет выполнять приложение, когда этот маршрут соответствует. Обратите внимание на формат FQN, который он использовал для сопоставления маршрута (и схемы) для действия, которое нужно выполнить. В этом случае это означает, что всякий раз, когда мы входим на сайт только с его доменом, действие index в контроллере по Default под пространством имен trrsywxBundle будет запускаться.

Есть другие параметры, которые вы можете установить в шаблоне и по умолчанию. Например, маршрут book_list имеет шаблон с 2 параметрами в URI: page означает текущую страницу, когда для отображения результата требуется более одной страницы (я рассмотрю разбиение на страницы в части 3), а key — это ключевое слово, которое используется. искать книги, соответствующие этому key (в моей текущей реализации я ищу только начало заголовка книги). Все параметры в pattern должны быть в паре фигурных скобок.

В book_list:default я book_list:default по умолчанию для двух вышеупомянутых параметров: 1 для page и ноль для key . Таким образом, простой URI, такой как http://localhost/app_dev.php/books/list страницу всех книг, а http://localhost/app_dev.php/books/list/3/Beauty просто перечислите 3-ю страницу всех книг, название которых начинается с Beauty (например, «Красавица и чудовище»).

Вы также заметите, что у маршрута books_search есть requirements:_method установлен в POST . Это ограничивает метод вызова для этого шаблона только POST (при отправке формы), так как я не хочу, чтобы пользователи просто посещали /books/search в браузере. В моей реализации этот маршрут предназначен для внутреннего использования и должен перенаправлять на другую страницу с результатом поиска, соответствующим представленным критериям (POSTed) через форму.

Полную документацию по маршрутам можно найти на сайте Symfony .

Контроллеры

Следующим шагом является определение контроллеров. У меня есть всего 4 контроллера, расположенных в src/tr/rsywxBundle/Controller . А именно, это: BookController.php, DefaultController.php, LakersController.php и ReadingController.php. Я группирую функции, связанные с различными маршрутами, основываясь на их функциональности.

Я покажу здесь только два контроллера, соответствующие book_list и books_search .

  <?php class BookController extends Controller { // ... Many other functions here, see source public function listAction($page, $key) { $em = $this->getDoctrine()->getManager(); // Get the Entity Manager $rpp = $this->container->getParameter('books_per_page'); // Get the global parameter for how many books to show on one page $repo = $em->getRepository('trrsywxBundle:BookBook'); // Get the repository list($res, $totalcount) = $repo->getResultAndCount($page, $rpp, $key); // Get the result $paginator = new \tr\rsywxBundle\Utility\Paginator($page, $totalcount, $rpp); // Init the paginator $pagelist = $paginator->getPagesList(); // Get the pagelist used for navigation return $this->render('trrsywxBundle:Books:List.html.twig', array('res' => $res, 'paginator' => $pagelist, 'cur' => $page, 'total' => $paginator->getTotalPages(), 'key'=>$key)); // Render the template using necessary parameters } public function searchAction(Request $req) { $q = $req->request->all(); // Get the posted data $page = 1; // Get which page to display $key = $q['key']; // Get the search criteria $em = $this->getDoctrine()->getManager(); $rpp = $this->container->getParameter('books_per_page'); $repo = $em->getRepository('trrsywxBundle:BookBook'); list($res, $totalcount) = $repo->getResultAndCount($page, $rpp, $key); $paginator = new \tr\rsywxBundle\Utility\Paginator($page, $totalcount, $rpp); $pagelist = $paginator->getPagesList(); return $this->render('trrsywxBundle:Books:List.html.twig', array('res' => $res, 'paginator' => $pagelist, 'cur' => $page, 'total' => $paginator->getTotalPages(), 'key' => $key)); } } 

Типичным способом контроллера он выполняет три вещи:

  • Получить всю подготовительную работу (входные параметры, получить Entity Manager, Repository и т. Д.);
  • Получить результаты из хранилища;
  • Отображать результаты (с дальнейшей обработкой или без нее) путем рендеринга шаблона (Symfony использует Twig в качестве своего механизма шаблонов).

Тщательно соблюдайте три вещи:

  1. Посмотрите, как параметры, которые мы определили в маршруте book_list ( page и key ), передаются в вызов функции по имени. Symfony не заботится о порядке появления параметров, но требует строгого совпадения имен.

  2. Посмотрите, как параметры searchAction в форме передаются в searchAction и как мы получаем необходимую информацию из представленных данных.

  3. getParameter — это функция для извлечения глобальных параметров. Глобальные параметры определены в файле app/config/parameters.yml.dist (автоматически сгенерированным с помощью composer в .yml) и выглядят так:

  books_per_page: 10 

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

Подробный документ о контроллерах можно найти здесь .

Сущности и хранилища

В методологии ORM сущность является объективным отражением таблицы базы данных. Вместо этого, выполняя собственные команды SQL в вашем PHP для манипулирования данными, мы можем использовать интуитивно понятные и простые способы CRUD-данных. Хотя сущности, сгенерированные Symfony, содержат простые методы для извлечения данных по идентификатору, в надлежащем приложении этого далеко не достаточно. Хранилища предназначены для предоставления более специализированных способов манипулирования данными.

Сущности генерируются с помощью команды консоли / терминала: php app\console doctrine:generate:entity (см. Часть 1 или официальную документацию .) Сгенерированные файлы сущностей PHP расположены в src/tr/rsywxBundle/Entity . Посмотрите на эти файлы PHP, чтобы понять, как таблицы базы данных отображаются (через ORM) в класс PHP.

Примечание: не вносите никаких изменений в эти файлы PHP напрямую. Они предназначены для создания Symfony.

Чтобы создать хранилище для хранения всех настроенных методов манипулирования базой данных, вам нужно сделать две вещи:

  • Измените соответствующие файлы сопоставления ORM (расположены в src / tr / rsywxBundle / Resources / config / doctrine), чтобы указать класс репозитория для этого объекта базы данных;
  • Создайте все необходимые функции, чтобы обеспечить необходимые возможности для работы с базой данных (например, получение данных)

Возьмем таблицу сбора моих книг (и ее сущность, например):

Расположение файла: src/tr/rsywxBundle/Resources/config/doctrine/BookBook.orm

  tr\rsywxBundle\Entity\BookBook: type: entity repositoryClass: tr\rsywxBundle\Entity\BookRepository # Add this line to indicate that we will use BookRepository.php as the repository class for BookBook table table: book_book fields: ... 

После этого снова запустите php app\console doctrine:generate:entity . Вы заметите, что новый файл BookRepository.php создан в src/tr/rsywxBundle/Entity . Теперь вы можете открыть этот файл и создать свои собственные функции для манипулирования данными:

  public function getResultAndCount($page, $rpp, $key=null) { // Get the book count $em = $this->getEntityManager(); if ($key == 'null') { $q1 = $em->createQuery('select count(b.id) bc from trrsywxBundle:BookBook b'); } else { $qstr = sprintf("select count(b.id) bc from trrsywxBundle:BookBook b where b.title like '%s%%'", $key); $q1 = $em->createQuery($qstr); } $res1 = $q1->getSingleResult(); $count = $res1['bc']; // Now get the wanted result specified by page $repo = $em->getRepository('trrsywxBundle:BookBook'); $q2 = $repo->createQueryBuilder('r') ->setMaxResults($rpp) ->setFirstResult(($page - 1) * $rpp) ->orderBy('r.id', 'desc'); if ($key <> 'null') { $key = $key . '%'; $q2->where('r.title like :key') ->setParameter('key', $key); } $q2 = $q2->getQuery(); $res2 = $q2->getResult(); return array($res2, $count); } 

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

Одним из них является использование $em->createQuery() , которое использует аналогичную грамматику, которую мы будем использовать при выдаче команды SQL в базе данных. Разница только в сегменте from . Вместо использования необработанного имени таблицы ( book_book ) мы используем имя класса таблицы ( trrsywxBundle:BookBook ).

getSingleResult используется в моем первом запросе, так как я ожидаю, что по этому запросу будет возвращен только один результат (количество книг, соответствующих определенным критериям, или ВСЕ книги).

Во втором запросе я использовал createQueryBuilder и createQueryBuilder все методы, чтобы установить необходимые параметры SQL (начальная запись, количество записей, порядок и условие where для фильтрации).

getResult используется, так как мы ожидаем более одной записи.

Наконец, мы упаковываем результаты в массив: $res2 в качестве результирующего набора данных и $count в качестве счетчика результирующего набора данных. Они будут дополнительно обработаны в вызывающей функции контроллера (в этом случае создайте объект Paginator для облегчения навигации).

Представления и шаблоны

До сих пор мы занимались «фоновой» работой. Ничто не презентабельно без представления. В Symfony, как и в большинстве сред, представление эквивалентно шаблону. Как я упоминал ранее, Symfony использует Twig в качестве движка шаблонов. Это просто учиться и быстро отображать.

Я покажу вам сегмент шаблона Twig, который я использовал для отображения страницы со списком книг.

(Я не очень хороший веб-дизайнер, поэтому я использую Bootstrap 3.0 для быстрой загрузки дизайна моей страницы. Вы можете использовать свой собственный дизайн или готовые шаблоны).
Примечание: ниже приведен фрагмент всего шаблона.

Расположение файла: src/tr/rsywxBundle/Resources/views/Books/List.html.twig

 {% set active=2 %} {% extends 'trrsywxBundle:Default:index.html.twig' %} {% block title %}<title>{{ "RSYWX | Book Collection | Page }}{{cur}}</title>{% endblock %} {% block content %} <div class="container"> <div class="row"> <div class="col-md-6"> <h3>My book collection</h3> </div> <div class="col-md-6"> <h3>Page {{cur}} of {{total}}</h3> </div> <div class="col-md-4"> <form class="form-inline" method="post" action="{{path('books_search')}}" name="searchform" id="searchform"> <div class="form-group"> <input type="text" class="form-control" required="required" placeholder="Search the beginning of a book title" name="key" id="key" value="{{key}}"/> <input type="hidden" name="cur" id="cur" value="{{cur}}"/> </div> <button type="submit" class="btn btn-primary">Search</button> </form> </div> <br> <div class="row"> <div class="col-md-12"> <table class="table table-striped"> <tbody> <tr> <td><strong>ID</strong></td> <td><strong>Title</strong></td> <td><strong>Author</strong></td> <td><strong>Purchase Date</strong></td> <td><strong>Location</strong></td> </tr> {% for book in res %} {% set author=book.author%} {%if author ==''%} {%set author='(anonymous)'%} {%endif%} <tr> <td><a href="{{path('book_detail', {'id':book.id})}}">{{book.id}}</a></td> <td><a href="{{path('book_detail', {'id':book.id})}}">{{book.title}}</a></td> <td>{{author}}</td> <td>{{book.purchdate|date('Ym-d')}}</td> <td>{{book.location}}</td> </tr> {% endfor %} </tbody> </table> </div> </div> <div class="row"> <div class="col-md-6"> <div class="pager"> <ul> <li class="previous"><a href="{{path('book_list', {'page':1, 'key':key})}}">First</a></li> {%if cur==1%} <li class="previous disabled"><a href="{{path('book_list', {'page':cur-1, 'key':key})}}">Previous</a></li> {%else%} <li class="previous"><a href="{{path('book_list', {'page':cur-1, 'key':key})}}">Previous</a></li> {%endif%} {%if cur==total%} <li class="previous disabled"><a href="{{path('book_list', {'page':cur, 'key':key})}}">Next</a></li> {%else%} <li class="previous"><a href="{{path('book_list', {'page':cur+1, 'key': key})}}">Next</a></li> {%endif%} <li class="previous"><a href="{{path('book_list', {'page':total, 'key':key})}}">Last</a></li> </ul> </div> </div> </div> </div> {% endblock %} 

Несколько вещей для обсуждения здесь:

  • Чтобы отобразить что-либо, используйте {{obj.member}} ; чтобы управлять потоком или манипулировать данными, используйте {% control statement or manipulation statement %} . На самом деле, это две и только две грамматические структуры в Twig.
  • {% extends %} может использоваться для расширения макета страницы из родительского (базового) макета. Это очень полезно для дизайна страницы.
  • {% block title %}...{% endblock %} заменяет содержимое в родительском макете собственным содержимым.
  • {{path(...)}} используется для генерации URI, соответствующих этому маршруту в шаблоне. Это очень важная вспомогательная функция, которую будут использовать все. Также обратите внимание, как передаются параметры этого маршрута.
  • {% for ... in ... %} используется для итерации набора результатов. Это также очень часто используется.
  • {% set ... %} используется для установки локальной переменной.
  • Остальное — обычный HTML.

Как только мы запустим приложение, наш рендер должен выглядеть примерно так:

Вывод

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

В следующей и последней части этой серии мы рассмотрим несколько передовых методов:

  • пагинация
  • Динамически добавлять теги, связанные с книгой
  • Динамически создать водяной знак для обложки книги
  • …и более

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