В первой части я показал вам, как настроить 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 в качестве своего механизма шаблонов).
Тщательно соблюдайте три вещи:
-
Посмотрите, как параметры, которые мы определили в маршруте
book_list
(page
иkey
), передаются в вызов функции по имени. Symfony не заботится о порядке появления параметров, но требует строгого совпадения имен. -
Посмотрите, как параметры
searchAction
в форме передаются вsearchAction
и как мы получаем необходимую информацию из представленных данных. -
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 код шаблона также оказался довольно простым.
В следующей и последней части этой серии мы рассмотрим несколько передовых методов:
- пагинация
- Динамически добавлять теги, связанные с книгой
- Динамически создать водяной знак для обложки книги
- …и более
Оставайтесь с нами и загружайте исходный код, чтобы экспериментировать самостоятельно!