Глава 8 - Внутри слоя Модели
О работе "Глава 8 - Внутри слоя Модели"
До настоящего времени большая часть обсуждения была посвящена вёрстке страниц и обработке запросов и ответов. Но бизнес-логика веб-приложений в основном зависит от модели данных. В symfony компонент по умолчанию, который реализует модель, основывается на объектно-реляционной проекции(ORM), известной как Propel project. В приложении symfony, вы обращаетесь к данным, которые хранятся в базе данных, и изменяете их посредством объекта; вы никогда явно не обращаетесь к базе данных. Это поддерживает высокий уровень абстракции и переносимость. Эта глава объясняет, как создать объектную модель данных, и каким образом обращаться и изменять данные в Propel. Также она демонстрирует интеграцию Propel в Symfony. Базы данных являются реляционными. PHP5 и symfony – объектно-ориентированные. Для того, чтобы наиболее эффективно обратиться к базе данных в объектно-ориентированном контексте, необходим интерфейс, который преобразовывает объектную логику в реляционную логику. Согласно приведенным объяснениям в Главе 1, этот интерфейс называется объектно-реляционной проекцией (ORM), и состоит из объектов, которые дают возможность доступа к данным и хранят бизнес-правила в своих пределах. Основным преимуществом ORM является возможность повторного использования, которое позволяет запрашивать методы объекта данных из различных компонентов приложения, даже из других приложений. Уровень ORM также инкапсулирует логику данных – например, подсчет рейтинга пользователя форума основывается на том, сколько он сделал вкладов в развитие форума и насколько они популярны. Когда странице необходимо отобразить рейтинг такого пользователя, она просто делает запрос на метод модели данных, не заморачиваясь о деталях подсчета. Если впоследствии подсчет изменяется, вам просто нужно будет внести поправки в метод подсчета модели, ничего не меняя в других компонентах приложения. Использование объектов вместо записей и классов вместо таблиц имеет и другое преимущество: они позволяют вам добавлять новые средства доступа к вашим объектам, которые необязательно совпадают со столбцами в таблице. Например, если у вас есть таблица, называемая клиентом, в которой есть 2 поля под названиями first_name и last_name, возможно, вы предпочтете вариант востребования просто Name. В объектно-ориентированном мире это так же просто, как и добавить новый метод средства доступа к классу Client, как в примере 8-1. С точки зрения приложения, разницы между атрибутами FirstName, LastName и Name класса Client нет. Только сам класс может определять, какие свойства соответствуют столбцу базы данных. Листинг 8-1 - Accessors Mask the Actual Table Structure in a Model Class (Маска средства доступа к фактической структуре таблицы в модели класса). Все повторяющиеся функции доступа к данным и бизнес-логика самих данных могут храниться в таких объектах. Представьте себе, что у вас есть категория ShoppingCart, в которой у вас хранятся Items (они же объекты). Чтобы получить общее количество объектов корзины для подсчёта стоимости сделанных покупок в модели класса, напишите специальный метод для инкапсулирования фактического подсчета, как показано в Листинге 8-2. Листинг 8-2 - Маска средства доступа к логическим данным При составлении процедуры доступа к данным нужно учитывать ещё один важный момент – разработчики базы данных используют разные варианты синтаксиса SQL. Переход на другую систему управления базами данных (DBMS) заставляет вас переписать часть SQL запросов, разработанных для предыдущей. Если вы строите запросы, используя независимый от базы данных синтаксис, и поручаете текущее преобразование SQL стороннему элементу, тогда вы безболезненно можете переключаться между системами базы данных. Это цель уровня абстрактных представлений базы данных. Он заставляет вас использовать определенный синтаксис для запросов и выполняет всю грязную работу по согласованию с тонкостями DBMS и оптимизации кода SQL. Основное преимущество уровня абстрактных представлений это мобильность, т.к. он дает возможность совершать переход на другую базу данных даже в середине проекта. Представьте, что вам нужно написать быстродействующую модель для приложения, но клиент все ещё не решил, какая система базы данных больше всего соответствует его требованиям. Вы можете начать создание приложения при помощи, например, SQLite, и перейти на MySQL, PostgreSQL, или Oracle, как только клиент будет готов принять решение. Просто измените одну строку в файле конфигурации, и дело в шляпе. Symfony использует Propel в качестве ORM, а Propel использует Creole для абстракции базы данных. Это два сторонних элемента, оба разработаны командой Propel, так органически интегрированы в symfony, что их можно считать частью фреймворка. Описанные в этой главе синтаксис и условные обозначения этих элементов были адаптированы таким образом, чтобы они как можно меньше отличались от синтаксиса и условных обозначений symfony. NOTE
В проекте symfony все приложения используют одну и ту же модель. В этом и весь смысл уровня проекта: перегруппировка приложений, которая зависит от общепринятого делового регламента. Это причина того, почему модель не зависит от приложений, и файлы модели сохраняются в директории lib/model/ в корневом каталоге проекта. Для того, чтобы создать объектную модель данных, которую будет использовать symfony, Вам нужно преобразовать реляционную модель в вашей базе данных, какая бы она ни была, в объектную модель данных. Для того, чтобы осуществить преобразование данных, ORM необходимо описание реляционной модели и это называется схемой. В схеме вы определяете таблицы, их соотношения, и свойства столбцов. Для логического соединения синтаксис Symfony использует формат YAML. Файлы schema.yml должны находиться в директории myproject/config/. NOTE
Symfony также читает собственный формат XML схемы Propel, далее это описано в разделе "Вне schema.yml: schema.xml" в этой главе. Как преобразить структуру базы данных в схему? Наилучшим способом понять это будет Листинг. Представьте, что у вас есть база данных блога с двумя таблицами: blog_article and blog_comment, и структурой, показанной на рисунке 8-1. Рисунок 8-1 – Структура таблицы базы данных блога Соотнесённый файл schema.yml должен выглядеть так, как показано в Листинге 8-3. Листинг 8-3 – Образец schema.yml Обратите внимание, что само имя базы данных (блог) не обнаруживается в файле schema.yml. Вместо того, база данных описывается названием логического соединения (в нашем Листинге это propel). Это потому, что фактические настройки логического соединения могут зависеть от среды, в которой работает ваше приложение. Например, когда вы запускаете ваше приложение в среде разработки, вы обращаетесь к базе данных разработки (может быть blog_dev), но с той же схемой, что и база данных правил вывода. Настройки логического соединения будут заданы в файле databases.yml, они описаны далее в этой главе в разделе "Соединение базы данных". Схема не содержит подробных настроек соединения, только название соединения, для сохранения абстракции базы данных. В файле schema.yml, первый ключ означает название схемы. Он может содержать в себе несколько таблиц, в каждой из которых может находиться ряд столбцов. Согласно синтаксису YAML, ключи оканчиваются двоеточием, и структура отмечается введением отступов (один и больше пробелов, но без табуляций). В таблице могут быть особые атрибуты, в том числе, phpName (имя класса, который будет создан). Если вы не укажете phpName для таблицы, symfony создаст его на основе варианта имени таблицы camelCase. TIP
Условное обозначение camelCase убирает нижнее подчеркивание и делает заглавной первую букву второй части слова. Варианты camelCase по умолчанию - blog_article и blog_comment выглядят как BlogArticle и BlogComment. Название этого условного обозначения происходит от внешнего вида заглавных букв внутри длинного слова, вызывающего ассоциацию с горбами верблюда. Таблица состоит из столбцов. Значение столбца может задаваться тремя разными путями: У столбцов также могут быть атрибуты phpName, по сути, это варианты названия, которое пишется с большой буквы (Id, Title, Content, и т.д.) и в большинстве случаев не нуждается в подмене. Таблицы также могут включать явно внешние ключи и индексы, равно как и несколько структурных описаний, свойственных базе данных. Чтобы узнать больше, обратитесь к разделу " Расширенный синтаксис схемы " в конце этой главы. Схема используется для того, чтобы построить классы модели слоя ORM. Для экономии времени выполнения эти классы создаются задачей командной строки под названием propel-build-model. Набор этой команды запустит анализ схемы и создание основных классов модели данных в директории lib/model/om/ вашего проекта: Кроме того, текущие классы модели данных будут созданы в lib/model/: Вы задали только 2 таблицы, и в конце вы получили 8 файлов. В этом нет ничего страшного, но стоит объяснить, в чем же дело. Зачем сохранять две версии объектной модели данных в двух разных директориях? Возможно, вам нужно будет добавить специальные методы и свойства в объектную модель (вспомните о методе getName() в Листинге 8-1). Но по мере развития вашего проекта, вы будете также добавлять таблицы и столбцы. Всякий раз, когда вы изменяете файл schema.yml, вам нужно восстанавливать классы объектной модели путем нового обращения к propel-build-model. Если ваши специальные методы были написаны в фактически созданных классах, они будут удаляться после каждого создания. Классы Base, которые хранятся в директории the lib/model/om/ это классы, непосредственно созданные из этой схемы. Вам не следует их изменять, поскольку каждый новый билд модели будет полностью удалять эти файлы. С другой стороны, специальные объектные классы, которые хранятся в директории lib/model/, фактически наследуют классы Base. Когда задача propel-build-model запрашивается существующей моделью, эти классы не изменяются. Так что сюда вы можете добавлять специальные методы. Листинг 8-4 показывает пример обычного класса модели, созданного при первом обращении к задаче propel-build-model. Листинг 8-4 – Пример файла класса модели в lib/model/Article.php Он унаследует все методы класса BaseArticle, и видоизменение схемы не повлияет на него. Механизм расширения основных обычных классов позволяет вам начать программирование, даже не зная конечную реляционную модель вашей базы данных. Соотнесённая структура файла делает модель более настраиваемой и эволюционной. Article и Comment - это объектные классы, которые представляют собой запись в базе данных. Они предоставляют доступ к столбцам записи и к соотнесенным записям. Это значит, что вы сможете узнать название статьи путем обращения к методу объекта Article, как показано в Листинге 8-5. Листинг 8-5 – Механизмы for Record Columns Are Available in the Object Class ArticlePeer и CommentPeer – это одноранговые классы; т.е., классы, которые вкючают статические методы для to operate on the tables. They provide a way to retrieve records from the tables. Their methods usually return an object or a collection of objects of the related object class, as shown in Листинг 8-6. Листинг 8-6 - Статические методы для извлечения записей доступны в классе Peer NOTE
С точки зрения модели данных, одноранговый объект не может существовать. Вот потому к методам одноранговых классов обращаются при помощи :: (для вызова статического метода), вместо обычного -> (для вызова копии метода). Итак, сочетание объекта и одноранговых классов в основной и обычной версиях So combining object and peer classes in a base and a custom version имеет результатом четыре класса, созданных посредством таблицы и описанных в схеме. По сути, существует пятый класс, созданный в директории lib/model/map/, который включает информацию о метаданных о таблице, необходимую для режима рабочего цикла. runtime environment. Но поскольку вы никогда не измените этот класс, можете забыть об этом. В symfony вы обращаетесь к данным через объекты. Если вы привыкли к реляционной модели и использованию SQL для извлечения и изменения ваших данных, методы объектной модели, вероятно, будут выглядеть замысловато. Лишь однажды ощутив мощность объектной ориентации для обращения к данным, вы её, скорее всего, полюбите. Но сначала давайте удостоверимся, что мы используем одинаковую терминологию. Реляционная и объектная модель данных оперируют похожими понятиями, но у каждой из них своя номенклатура: Когда symfony строит модель, он создает один основной объектный класс для каждой из таблиц, описанной в schema.yml. Каждый из этих классов comes with конструкторы по умолчанию, аксессоры и мутаторы, основанные на определении столбцов: новые методы getXXX() и setXXX() помогают создать объекты и предоставить доступ к свойствам объекта, как показано в Листинге 8-7. Листинг 8-7 – Методы образованного объектного класса NOTE
Образованный объектный класс называется Article, это phpName данное таблице blog_article. Если бы phpName не определилось в этой схеме, тогда класс назывался бы BlogArticle. Аксессоры и мутаторы используют вариант имен столбцов camelCase, таким образом, метод getTitle() извлекает значение столбца заголовка. Для того, чтобы установить одновременно несколько полей, вы можете использовать метод fromArray(), также создаваемые для каждого объектного класса, как показано в Листинге 8-8. Листинг 8-8 - Метод fromArray() - многократный механизм установки. Столбец article_id в таблице blog_comment полностью определяет внешний ключ к таблице blog_article. Каждый комментарий относится к одной статье и у каждой статьи может быть много комментариев. Производные классы включают пять методов, которые преобразовывают данные отношения объектно-ориентированным способом, как показано ниже: Методы getArticleId() и setArticleId() показывают, что вы можете считать столбец article_id правильным и устанавливать отношения вручную, но это не очень интересно. Преимущество объектно-ориентированного подхода более заметно в трех других методах. Листинг 8-9 показывает, как использовать производные механизмы установки. Листинг 8-9 - Внешние ключи преобразовываются в специальные механизмы установки Листинг 8-10 показывает, как использовать производные механизмы включения. Так же он демонстрирует, как располагать объекты модели. Листинг 8-10 – Внешние ключи преобразовываются в специальные механизмы включения Метод getArticle() возвращает объект класса Article, который извлекает пользу из аксессора getTitle(). Это намного лучше, чем выполнение соединения самому, что может занять несколько строк кода (начиная от запроса $comment->getArticleId()). Переменная $comments в Листинге 8-10 включает множество объектов класса Comment. You can display the first one with $comments[0] or iterate through the collection with foreach ($comments as $comment). NOTE
Объекты из модели по условию задаются именем в единственном числе, и сейчас вы поймете, почему. Внешний ключ, заданный в таблице blog_comment table является причиной создания метода getComments(), названного именем, созданным при помощи прибавления s к имени объекта Comment. Если бы вы дали имя во множественном числе, создание привело бы к методу getCommentss(), что не имеет смысла. Обращаясь к новому конструктору, вы создали новый объект, но не фактическую запись в таблице blog_article. Внесения изменений в объект тоже не влияет на базу данных. Для того, чтобы сохранить данные в базе данных, вам нужно обратиться к методу объекта save(). ORM довольно разумно, чтобы заметить отношения между объектами, поэтому сохранение объекта $article влечет за собой и сохранение соотнесенного объекта $comment. Оно также знает, или сохраненный объект имеет сохраненную копию в базе данных, поэтому обращение к save() иногда преобразовывается в SQL на INSERT, и иногда - UPDATE. Первичный ключ автоматически устанавливается методом save(), поэтому после сохранения вы можете извлечь новый первичный ключ при помощи $article->getId(). TIP
Вы можете проверить, или объект новый, путем запроса к isNew(). А если вам интересно, был ли изменен объект и нужно ли его сохранять, обратитесь к его методу isModified(). Почитав комментарии к вашим статьям, вы можете изменить свое мнение об интересе публикаций в Интернете. И если вы не ценитель иронии критиков статей, комментарии можно легко удалить при помощи метода delete(), как показано в Листинге 8-11. Листинг 8-11 – Удаление комментариев из базы данных методом delete() по соотнесенному объекту TIP
Даже после обращения к методу delete(), объект остается доступным до конца запроса. Чтобы определить, удален ли объект из базы данных, обратитесь к методу isDeleted(). Если вы знаете первичный ключ отдельной записи, используйте метод класса retrieveByPk() однорангового класса, чтобы получить соотнесенный объект. Файл schema.yml определяет поля id field как первичный ключ таблицы blog_article, поэтому эта формулировка фактически приведет к переходу на статью под id 7. Поскольку вы использовали первичный ключ, вы должны знать, что лишь одна запись будет возвращена; переменная $article включает объекта класса Article. В некоторых случаях, первичный ключ может состоять более чем из одного столбца. В таких случаях, метод retrieveByPK() принимает множественные параметры, один для каждого столбца первичного ключа. Вы также можете выбирать множественные объекты, основанные на своем первичном ключе, путем обращения к созданному методу retrieveByPKs(), который предполагает множество первичных ключей в качестве параметра. Если вы хотите извлечь более, чем одну запись, вам нужно обратиться к методу doSelect() однорангового класса, соответствующему объектам, которые вы хотите извлечь. К примеру, чтобы извлечь объекты класса Article, запросите ArticlePeer::doSelect(). Первый параметр метода doSelect() - это объект класса Criteria, по сути, обычный класс запроса оператора, определенный без SQL для абстракции базы данных. Пустой Criteria извлекает все объекты класса. К примеру, код, показанный в Листинге 8-12, извлекает все статьи. Листинг 8-12 – Извлечение записей Criteria с помощью doSelect()--Пустой Criteria Для более сложного выбора, вам нужен эквивалент WHERE, ORDER BY, GROUP BY, и других SQL-операторов. У объекта Criteria есть методы и параметры для всех этих условий. Например, чтобы получить все комментарии, написанные Стивом (Steve), по дате, создайте Criteria как показано в Листинге 8-13. Листинг 8-13 – Извлечение записей при помощи Criteria с doSelect()--Criteria с условиями Постоянные класса, переданные методам add() как параметры, относятся к именам собственным. Они получают названия от версий имен столбцов, написанных с большой буквы. К примеру, чтобы обратиться к стыкуемому столбцу таблицы blog_article,используйте постоянную класса ArticlePeer::CONTENT. NOTE
Зачем использовать CommentPeer::AUTHOR вместо blog_comment.AUTHOR, как сделать так, чтобы он в любом случае выводился в SQL-запросе? Предположим, вам нужно изменить имя авторского поля на источник базы данных. Если бы вы использовали _comment.AUTHOR, вам нужно было изменять его каждый раз при обращении к модели. С другой стороны, используя CommentPeer::AUTHOR, вам просто нужно изменить имя столбца в файле schema.yml, сохранить phpName как AUTHOR, и перестроить модель. Таблица 8-1 сопоставляет синтаксис SQL с объектным синтаксисом Criteria Таблица 8-1 – Синтаксис SQL и объектный синтаксис Criteria Other Comparison Operators Other SQL Keywords TIP
Наилучший способ узнать и понять, какие методы в созданных классах доступны - это посмотреть основные файлы в папке lib/model/om/ после создания. Названия методов довольно очевидны, но если вам нужно больше пояснений к ним, установите параметр propel.builder.addComments как true в файле config/propel.ini и перестройте модель. Листинг 8-14 приводит другой пример Criteria с множественными условиями. Он извлекает все комментарии Стива (Steve) к статьям, содержащие слово "enjoy," по дате. Листинг 8-14 – Другой пример извлечения записей с помощью Criteria с doSelect()—Criteria с условиями Поскольку SQL - это простой язык, который позволяет вам строить очень сложные запросы, объект Criteria может оперировать условиями на любом уровне сложности. Но поскольку многие разработчики думают сначала об SQL перед преобразованием условия в объектно-ориентированную логику, объект Criteria поначалу может быть сложен для понимания. Наилучшим способом понять его является изучение при помощи примерах и моделях конкретного применения. На веб-сайте проекта symfony , к примеру, полно примеров построения Criteria, которые обучат вас во многих отношениях. Вдобавок к методу doSelect(), каждый одноранговый класс имеет метод doCount(), который просто подсчитывает число записей, удовлетворяющих критерии, передаваемых в качестве параметров, и возвращает подсчет в виде целого числа. Поскольку нет объекта для возврата, процесс hydrating в этом случае не происходит, и метод doCount() быстрее, чем doSelect(). Одноранговые классы также предвидят методы doDelete(), doInsert(), и doUpdate() все они подразумевают Criteria как параметр. Эти методы позволяют вам издавать запросы DELETE, INSERT, и UPDATE в вашу базу данных. Проверьте созданные одноранговые классы в вашей модели для получения более детальной информации по данным методам Propel. В конце концов, если вам просто нужен возврат первого объекта, замените doSelect() обращением doSelectOne()l. Это может быть тот случай, когда вы знаете, что Criteria вернет лишь один результат и преимущество в том, что этот метод возвращает объект лучше, чем множество объектов. TIP
Если запрос doSelect() возвращает большое количество результатов, вам может понадобиться отобразить только их подмножество в вашем результате. Symfony provides a pager class called sfPropelPager, which automates the pagination of results. Зайдите на страницу документации http://www.symfonyproject.org/cookbook/trunk/pager, чтобы получить больше информации и примеров использования. Иногда вам не нужно извлекать объекты, но вы хотите получить комплексные результаты, подсчитанные базой данных. К примеру, чтобы получить все последние по времени создания статьи, не имеет смысла извлекать все статьи и замыкать множество в цикл. Вы предпочтете запросить базу данных на возврат лишь одного результата, так как это пропустит объектный процесс hydrating. С другой стороны, вам не нужно обращаться к командам PHP для явного управления базой данных, потому как тогда вы потеряете преимущество абстракции базы данных.
Это значит, что вам нужно обойти ORM (Propel) но не абстракцию базы данных (Creole). Запрос к базе данных при помощи Creole требует от вас выполнения следующих действий:
1. Получить соединение базы данных.
2. Построить строку запроса
3. Из нее создать оператор
4. Повторить множество результата, что следует из выполнения оператора. Если для вас это выглядит полной тарабарщиной, код в Листинге 8-15, возможно, будет более понятным. Листинг 8-15 – Обычный SQL-запрос при помощи Creole Подобно выборкам Propel, запросы Creole довольно-таки сложны, когда вы впервые начинаете пользоваться ими. Опять же, примеры из существующих приложений и учебных пособий покажут вам, что к чему. В большинстве случаев, если в таблице есть столбец под названием created_at, это значит, что он хранился во временной метке даты, когда была сделана запись. Тоже самое относится и к столбцам updated_at, которые должны обновляться всякий раз, когда обновляется сама запись, к значению текущего времени. Хорошо то, что symfony распознает названия этих колонок и обрабатывает для вас их обновления. Вам не придется вручную настраивать столбцы created_at и updated_at s; они обновятся автоматически, как показано в Листинге 8-16. Тоже самое касается и столбцов named created_on и updated_on. Листинг 8-16 – Столбцы created_at и updated_at автоматически соединяются Кроме того, механизмы установки для столбцов дат принимают формат даты как независимую переменную: По мере развития проекта symfony , вы часто начитаете прописывать логический код домена в операциях. Но запросы базы данных и обработки модели не сохраняются на уровне контроллера. Итак, вся относящаяся к данным логика должна быть перенесена на уровень модели. Всякий раз, когда вам придется выполнять тот же запрос в более чем одном месте в ваших операциях, вспомните о переносе соотнесенного кода в модель. Это помогает операциям оставаться короткими и читабельными. К примеру, представьте себе код, необходимый в блоге для извлечения 10 самых популярных статей по данному признаку (передаваемому как параметр запроса). Этот код не должен быть в операции, но должен быть в модели. Фактически, если вам нужно отобразить этот список в шаблоне, действие просто должно выглядеть вот так: Операция создает объект класса Tag из параметра запроса. Потом весь код, необходимый для запроса к базе данных, располагается в методе getPopularArticles() этого класса. Это делает операцию более короткой, и код модели может быть легко использован в другой операции. Перенос кода в более подходящее размещение является одним из приемов рефакторизации. Если вы часто выполняете это, ваш код будет легко сохраняться и пониматься другими разработчиками. Хорошим эмпирическим методом для определения необходимости выполнения рефакторизации на уровне данных это то, что код операции редко должен содержать больше, чем десять строк PHP-кода. Модель данных не зависит от используемой базы данных, но вы определенно будете использовать базу данных. Минимум информации, необходимой symfony для посылки запросов к проектной базе данных составляет имя, коды доступа, и тип базы данных. Эти настройки подключения нужно зафиксировать в файле databases.yml, расположенном в директории config/. Листинг 8-17 приводит Листинг такого файла. Листинг 8-17 – Пример настроек соединения базы данных в myproject/config/databases.yml Настройки соединения зависят от режима. Вы можете определить четкие настройки для режимов prod, dev или любого другого режима в вашем приложении. Эта конфигурация также может быть подменена при применении путем установки разных значений в специфическом для приложения файле, таком как в apps/myapp/config/databases.yml. К примеру, вы можете использовать этот подход для того, чтобы иметь другую политику безопасности для фронтального и внутреннего приложения, и определить нескольких пользователей базы данных с разными правами доступа к вашей базе данных для оперирования с ней. Для каждого режима вы можете задать разные соединения. Каждое соединение ссылается на схему, помеченную тем же именем. В листинге 8-17, соединение propel ссылается на схему propel в Листинге 8-3. Допустимые значения параметра phptype это те значения систем баз данных, которые поддерживаются Creole:
* mysql
* sqlserver
* pgsql
* sqlite
* oracle hostspec, database, username, and password это обычные настройки соединения базы данных. Они также могут писаться более кратко, чем имя источника данных (DSN). Листинг 8-18 эквивалентен всей части Листинга 8-17. Листинг 8-18 – Условное обозначение настроек соединения базы данных Если вы используете базу данных SQLite, параметр hostspec должен быть указан в пути к файлу базы данных. К примеру, если вы храните базу данных своего блога в data/blog.db, файл databases.yml будет выглядеть как Листинг 8-19. Листинг 8-19 - Настройки соединения базы данных для SQLite Созданные методы модели замечательны, но часто они бывают недостаточными. Как только вы применяете свою собственную бизнес-логику, Вам нужно ее расширить или посредством добавления новых методов, или подменив уже существующие. Вы можете добавить новые методы к незаполненным моделям класса, созданным в директории lib/model/. Используйте $this для запроса методов текущего объекта, и используйте self::, чтобы запросить статические методы текущего класса. Помните, что обычные классы наследуют методы классов Base, расположенных в директории lib/model/om/. Например, для объекта Article, созданного на основе Листинга 8-3, вы можете добавить волшебный метод __ toString ()таким образом, чтобы отклик объекта класса Article отобразил его название, как показано в Листинге 8-20. Листинг 8-20 - Настройка Модели в lib/model/Article.php: Также вы можете расширять одноранговые классы - например, добавить метод, чтобы извлечь все статьи в порядке согласно дате их создания, как показано в Листинге 8-21. Листинг 8-21 - Настройка модели в lib/model/ArticlePeer.php: Новые методы доступны так же, как и созданные, как показано в Листинге 8-22. Листинг 8-22 - Использование обычных методов модели похоже на использование созданных методов. Если некоторые из созданных методов в классах Base не соответствуют вашим требованиям, вы все еще можете подменить их в обычных классах. Просто нужно удостовериться, что вы используете ту же самую сигнатуру метода (то есть, то же самое число независимых переменных). Например, метод $article->getComments() возвращает массив объектов Comment вразброс. Если вам нужны результаты, размещенные в порядке согласно дате создания данных, и чтобы последний комментарий шел первым, тогда подмените метод getComments (), как показано в Листинге 8-23. Знайте, что оригинал метода getComments () (найденный в lib/model/om/BaseArticle.php) предполагает значение критериев и значения соединения в качестве параметров, таким образом, ваша функция должна делать то же самое. Листинг 8-23 - Подмена существующих методов модели в lib/model/Article.php: В конечном счете, обычный метод запрашивает другой из родительского класса Base, и это - отличный прием. Однако, вы можете полностью обойти его и вернуть именно такой результат, который вы хотите. Некоторые модификации модели являются универсальными и могут использоваться неоднократно. Например, методы, что позволяют подвергать объекты модели сортировке и устанавливать оптимистическую блокировку, что поможет предотвратить конфликты между параллельными сохранениями объектов; характерные для определенного класса расширения, которые можно добавлять ко многим классам. Symfony помещает эти расширения в характер изменения. Характеры изменения – это внешние классы, которые обеспечивают классы модели дополнительными методами. Классы модели уже содержат методы, и symfony знает, как расширить их посредством sfMixer (подробнее см. Главу 17). Чтобы активировать характеры изменения в ваших классах модели, вы должны изменить один параметр настройки в файле config/propel.ini: В symfony по умолчанию нет в наличии характеров изменения, но они могут быть установлены через плагины. Как только плагин характера изменения установлен, вы можете назначить классу характер изменения всего одной строкой. Например, если Ввы устанавливаете плагин sfPropelParanoidBehavior в вашем приложении, вы можете расширить класс Article при помощи этого характера изменения, добавляя нижеследующее в конец Article.class.php: После восстановления модели удаленные объекты артикля останутся в базе данных, невидимой для запросов, осуществляемых при помощи ORM, если только вы временно не отключите характер изменения с помощью sfPropelParanoidBehavior::disable().
Проверьте список плагинов symfony в wiki, чтобы найти характеры изменения (http://trac.symfony-project.com/wiki/SymfonyPlugins#Propelbehaviorplugins).
У каждого из них есть своя документация и руководство по установке. Файл schema.yml может быть простым, как показано в Листинге 8-3. Но реляционные модели часто сложны. Вот почему у схемы такой обширный синтаксис, способный оперировать почти в любой ситуации. Соединения и таблицы могут иметь определенные атрибуты, как показано в Листинге 8-24. Они установлены под ключом _attributes. Листинг 8-24 - Атрибуты соединений и таблиц. Возможно, вы захотите, чтобы ваша схема была проверена перед генерацией кода. Для этого нужно деактивировать атрибут noXSD для соединения. Соединение также поддерживает атрибут defaultIdMethod. Если ни один из них не предусмотрен, то будет использоваться «родной» метод генерации ID - например, autoincrement для MySQL, или sequences для PostgreSQL. Другие возможные величины равны none. Атрибут package похож на пространство имён; он определяет путь, где сохранены созданные классы. По умолчанию он ведет к lib/model/, но вы можете изменить его, чтобы организовать вашу модель в подмодули. Например, если вы не хотите путать основные деловые классы, и классы, определяющие сохраненный базой данных механизм статистики в той же директории, то определите две схемы с помощью модулей lib.model.business и lib.model.stats. Вы уже видели атрибут таблицы phpName, используемый для установки имени созданного класса, который отображает таблицу. Таблицы, которые содержат локализированный контент (то есть, несколько версий контента в соотносимой таблице, для локализации) также приобретают два дополнительных атрибута (подробнее см. Главу 13), как показано в Листинге 8-25. Листинг 8-25 – Атрибуты для таблиц i18n. В вашем приложении может быть больше, чем одна схема. Symfony примет во внимание каждый файл, оканчивающийся на schema.yml или schema.xml в папке config/. Если в вашем приложении будет много таблиц, или если некоторые таблицы не будут использовать одно и то же соединение, тогда для вас этот подход окажется очень полезным. Рассмотрите эти две схемы: Обе схемы используют то же соединение (propel) и классы Article и Hit будут созданы в той же директории lib/model/. Все происходит так, как будто вы написали только одну схему. Вы также можете позволять различным схемам использовать различные соединения (например, propel и propel_bis, которые следует определить в databases.yml), и систематизировать созданные классы в поддиректориях: Многие программы используют более чем одну схему. В частности, некоторые плагины имеют свою собственную схему и модуль во избежание путаницы с вашими собственными классами (подробнее см. Главу 17). Базовый синтаксис предоставляет вам два выбора: позволить symfony выводить характеристики столбца из его имени (задавая пустые значения) или определить тип с одним из типичных ключевых слов. Листинг 8-26 демонстрирует эти варианты. Листинг 8-26 – Основные атрибуты столбца Но для столбца вы можете определить намного больше. Если это так, то вам нужно определить параметры настройки столбца как ассоциативное множество, как показано в Листинге 8-27. Листинг 8-27 - Сложные атрибуты столбца Параметры столбца следующие: В качестве альтернативы атрибутам столбца foreignTable и foreignReference, вы можете добавить внешние ключи под _foreignKeys: ключ в таблице. Схема в Листинге 8-28 создаст внешний ключ в столбце user_id, сопоставляемый id столбцу в таблице blog_user. Листинг 8-28 - Альтернативный синтаксис внешнего ключа. Альтернативный синтаксис пригоден для внешних многократно обращаемых ключей и задает внешним ключам имя, как показано в Листинге 8-29. Листинг 8-29 - Альтернативный синтаксис внешнего ключа, примененный к внешнему многократно обращаемому ключу. В качестве альтернативы атрибуту столбца Index, можно добавить индексы под _indexes: ключ в таблице. Если вы хотите установить уникальные индексы, нужно использовать _uniques: заголовок вместо него. Листинг 8-30 показывает альтернативный синтаксис для индексов. Листинг 8-30 - Индексы и альтернативные индексы уникального синтаксиса Встречая столбец без значения, symfony, взмахнув волшебной палочкой, автоматически сам добавит собственное значение. См. Листинг 8-31 для деталей, добавленных к пустым столбцам. Листинг 8-31 - Детали столбца, выведенные из имени столбца Для внешних ключей symfony находит таблицу, имеющую тот же самый phpName,как и начало имени столбца, и если он что-то найдет, то он возьмет это имя таблицы как foreignTable. Symfony поддерживает локализацию контента в соотносимых таблицах. Это означает, что если у вас есть контент, подлежащий локализации, то он будет сохранен в двух отдельных таблицах: одна с постоянными столбцами и другая - с локализованными столбцами. В файле schema.yml, все становится явным, если вы называете таблицу foobar_i18n. Например, схема, показанная в Листинге 8-32, будет автоматически завершена столбцами и атрибутами таблицы для того, чтобы привести механизм локализованного контента в работу. Внутри symfony поймет это так, как будто это было написано так же, как и Листинг 8-33. Глава 13 расскажет вам о i18n больше. Листинг 8-32 – Неявный механизм i18n Листинг 8-33 – Явный механизм i18n. Вне schema.yml: schema.xml Фактически, формат schema.yml является внутренним для symfony. Когда вы вызываете Propel-команду, symfony фактически преобразовывает этот файл в созданный файл schema.xml, который является ожидаемым Propel типом файла, чтобы точно выполнить задачи по модели. Файл schema.xml содержит ту же самую информацию, что и ее эквивалент YAML. Например, Листинг 8-3 обращен в файл XML, показанный в Листинге 8-34. Листинг 8-34 - Образец schema.xml, соответствующий Листингу 8-3. Описание формата schema.xml можно найти в документации и разделах "Начало Работы" на сайте проекта Propel.
(http://propel.phpdb.org/docs/user_guide/chapters/appendices/AppendixB-SchemaReference.html). Формат YAML был разработан для того, чтобы делать схемы простыми для чтения и написания, но минус в том, что самые сложные схемы не могут быть описаны файлом schema.yml. С другой стороны, формат XML дает возможность полного описания схемы независимо от ее сложности, и включает в себя базу данных зависящих от поставщика параметров, наследование таблицы, и так далее. Symfony понимает даже схемы, написанные в формате XML. Так что если ваша схема слишком сложна для синтаксиса YAML, или если у вас есть существующая схема XML, или если вы уже знакомы с Propel синтаксисом XML, Вам не нужно переключаться на YAML синтаксис symfony. Разместите ваш файл schema.xml в проект директории config/, постройте модель, и – поехали! Все детали, приведенные, в этой главе не являются характерными для symfony, а скорее для Propel. Propel - предпочтительный объект / относительный слой абстракции для symfony, но вы можете выбрать и альтернативный. Однако, symfony работает более плавно с Propel по следующим причинам: Все классы объектной модели данных и класс Criteria являются автозагружаемыми классами. Как только вы начинаете их использовать, symfony будет сам присоединять правильные файлы, и вам не нужно вручную добавлять операторов вовлечения файла. В symfony, Propel не нужно ни запускать, ни инициализировать. Когда объект использует Propel, библиотека запускается сама. Некоторые помощники symfony используют объекты Propel в качестве параметров для достижения задач высокого уровня (таких, как нумерация страниц или фильтрация). Объекты Propel обеспечивают быстрое создание прототипов и внутренних данных для вашего приложения (Глава 14 дает более детальную информацию). Схема работает быстрее, если она написана при помощи файла schema.yml. И, как и Propel, Symphony также независим от используемой базы данных. Как уступку в использовании ORM вы должны определить структуру данных дважды: один раз для базы данных, и один раз для объектной модели. К счастью, symfony предлагает инструменты командной строки, для создания одной структуры, основанной на другой; таким образом, вы можете избежать двойной работы. Если вы запускаете ваше приложения при помощи файла schema.yml, symfony может сформировать SQL- запрос, который создает таблицы непосредственно из модели данных YAML. Чтобы воспользоваться запросом, зайдите в вашу корневую директорию проекта, и наберите следующее: Файл lib.model.schema.sql будет создан в myproject/data/sql/. Отметьте, что созданный код SQL будет оптимизирован для системы базы данных, определенной в параметре phptype файла propel.ini. Для создания таблицы вы можете использовать непосредственно файл schema.sql. Например, в MySQL наберите следующее: Созданный SQL также пригоден для восстановления базы данных в другом режиме, или для изменения на другую DBMS. Если параметры настройки соединения определены должным образом в вашем propel.ini, вы даже можете использовать команду symfony propel-insert-sql, чтобы сделать это автоматически. TIP
Командная строка также предлагает задачу наполнения вашей базы данных данными, основанными на текстовом файле. См. Главу 16 для дополнительной информации по задаче "Propel-load-data" и прикрепленные файлы YAML. Symfony может использовать уровень доступа базы данных Creole, чтобы создать файл schema.yml из существующей базы данных благодаря самоанализу (способность баз данных определять структуру таблиц, в которых они работают). Это может быть особенно полезным тогда, когда вы осуществляете обратное проектирование, или если вы предпочитаете провести сначала работу над базой данных, а потом - над моделью объекта. Чтобы это сделать, вам нужно удостовериться, что проект файл propel.ini указывает на правильную базу данных и содержит все настройки соединения, а затем вызвать команду " propel-build-schema ": Совершенно новый файл schema.yml, построенный из вашей структуры базы данных, будет создан в директории config/. Вы можете строить свою модель, основанную на этой схеме. Команда создания схемы весьма мощная и может прибавить к вашей схеме много информации, зависящей от базы данных. Поскольку формат YAML не оперирует этим видом информации от поставщика, вам нужно образовать схему XML для использования в своих целях. Вы можете сделать это просто - добавить независимую переменную xml к задаче build-schema: Вместо создания файла schema.yml будет создан файл schema.xml, полностью совместимый с Propel и содержащий всю информацию от поставщика. Но имейте в виду, что созданные схемы XML имеют тенденцию быть весьма многословными и трудными для чтения. Задачи Propel-build-sql и propel-build-schema не используют настройки соединения, указанные в файле databases.yml. Скорее, эти задачи используют настройки соединения из другого файла, который называется propel.ini и сохраняется в директории проекта config/: Этот файл содержит другие параметры настройки, которые используются в формировании генератора Propel для того, чтобы сделать созданные классы модели совместимыми с symfony. Большинство настроек являются внутренним и не представляют интереса для пользователя, за исключением нескольких: После того, как вы внесете изменения в параметры настроек propel.ini, не забудьте восстановить модель, чтобы изменения вступили в силу. Symphony использует Propel в качестве ORM и Creole - как слой абстракции базы данных. Это означает, что вы должны описать реляционную схему вашей базы данных в YAML прежде, чем создавать классы объектной модели. Затем, во времени выполнения, используйте методы объекта и одноранговые классы для извлечения информации о записях или ряде записей. Вы можете подменить их и легко расширить модель путем добавления методов к обычным классам. Настройки соединения определены файлом databases.yml, который может поддерживать более чем одно соединение. А командная строка содержит специальные задачи для того, чтобы избежать дупликации описания структуры. Уровень модели является самым сложным в структуре symfony. Одной из причин этой сложности является то, что обработка данных сама по себе запутанна. Соотносимые вопросы безопасности являются ключевыми для веб-сайта и их нельзя игнорировать. Другая причина заключается в том, что symfony больше подходит для приложений от среднего до большого масштаба в среде предметной области. В таких приложениях автоматизация, обеспеченная моделью symfony, действительно выигрышна во времени, и стоит инвестиций в изучение его свойств. Так что не медлите с тратой времени на тестирование объектов модели и методов, чтобы целиком и полностью понять их. Устойчивость и расширяемость ваших программ станут великой наградой. Рейтинг работы балловГолосовать могут только зарегистрированные пользователиРегистрацияВернуться |

