
Проект на Github
Узнать как установить нужную вам часть руководства, можно в описании к репозиторию по ссылке. (Например, если вы хотите начать с это урока не проходя предыдущий)
Домашняя страница
Мы начнём эту часть с создания домашней страницы. В обычном блоге записи обычно отсортированы от новых к старым. Запись целиком будет доступна по ссылке со страницы блога. Так как мы уже создали маршрут, контроллер и отображение для домашней страницы мы можем легко их обновить.
Получение записей. Выполнение запросов к базе данных.
Чтобы отобразить записи блога нам необходимо их извлечь из базы данных. Doctrine 2 предоставляет нам Doctrine Query Language (DQL) и QueryBuilder для достижения этой цели (Вы можете также сделать обычный SQL запрос через Doctrine 2, но такой метод не рекомендуется, поскольку это уводит нас от абстракции базы данных которую предоставляет нам Doctrine 2. Мы будем использовать QueryBuilder, поскольку он обеспечивает хороший объектно-ориентированный способ для нас, чтобы сгенерировать DQL, который мы можем использовать для запросов к базе. Давайте обновим метод indexAction в контроллере Page расположенного
src/Blogger/BlogBundle/Controller/PageController.php
для получения записей из базы данных. // src/Blogger/BlogBundle/Controller/PageController.php
class PageController extends Controller
{
public function indexAction()
{
$em = $this->getDoctrine()
->getManager();
$blogs = $em->createQueryBuilder()
->select('b')
->from('BloggerBlogBundle:Blog', 'b')
->addOrderBy('b.created', 'DESC')
->getQuery()
->getResult();
return $this->render('BloggerBlogBundle:Page:index.html.twig', array(
'blogs' => $blogs
));
}
// ..
}
Мы начали с получения экземпляра QueryBuilder из Manager. Это позволит начать создавать запрос используя множество методов, которые предоставляет нам QueryBuilder. Полный список доступных методов можно посмотреть в документации к QueryBuilder. Лучше всего начать с вспомогательных методов. Это такие методы как select(), from() и addOrderBy(). Как и в случае предыдущих взаимодействий с Doctrine 2, мы можем использовать короткие аннотации для обращения к сущности Blog через BloggerBlogBundle:Blog (учтите, это тоже самое что и Blogger\BlogBundle\Entity\Blog). Когда мы закончили, указывать критерии для запроса, мы вызываем метод getQuery(), который возвращает экземпляр DQL. Мы не можем получить результаты из объекта QueryBuilder, мы всегда должны сначала преобразовать это в экземпляр DQL. Экземпляр объекта DQL предоставляет нам метод getResult() который возвращает коллекцию записей Блога. Позже мы увидим, что экземпляр объекта DQL имеет целый ряд методов для возврата результатов, включая getSingleResult() и getArrayResult().
Отображение
Теперь у нас есть коллекция записей и нам нужно отобразить их. Замените контент домашней страницы, расположенной
src/Blogger/BlogBundle/Resources/views/Page/index.html.twig
{# src/Blogger/BlogBundle/Resources/views/Page/index.html.twig #}
{% extends 'BloggerBlogBundle::layout.html.twig' %}
{% block body %}
{% for blog in blogs %}
<article class="blog">
<div class="date"><time datetime="{{ blog.created|date('c') }}">{{ blog.created|date('l, F j, Y') }}</time></div>
<header>
<h2><a href="{{ path('BloggerBlogBundle_blog_show', { 'id': blog.id }) }}">{{ blog.title }}</a></h2>
</header>
<img src="{{ asset(['images/', blog.image]|join) }}" />
<div class="snippet">
<p>{{ blog.blog(500) }}</p>
<p class="continue"><a href="{{ path('BloggerBlogBundle_blog_show', { 'id': blog.id }) }}">Continue reading...</a></p>
</div>
<footer class="meta">
<p>Comments: -</p>
<p>Posted by <span class="highlight">{{blog.author}}</span> at {{ blog.created|date('h:iA') }}</p>
<p>Tags: <span class="highlight">{{ blog.tags }}</span></p>
</footer>
</article>
{% else %}
<p>There are no blog entries for symblog</p>
{% endfor %}
{% endblock %}
Мы ввели одну из управляющих структур Twig, for..else..endfor. Если вы ранее не использовали шаблонизаторы, вероятно вам будет знаком следующий PHP код.
<?php if (count($blogs)): ?>
<?php foreach ($blogs as $blog): ?>
<h1><?php echo $blog->getTitle() ?><?h1>
<!-- rest of content -->
<?php endforeach ?>
<?php else: ?>
<p>There are no blog entries</p>
<?php endif ?>
Управляющая структура Twig for..else..endfor представляет собой более простой способ достижения этой задачи. Большая часть кода в шаблоне домашней страницы касается вывода информации блога в формате HTML. Однако, здесь есть несколько моментов которые нам нужно учесть. Во-первых, мы используем Twig path функцию для создания маршрутов. Так как страница блога требует ID записи переданной в URL, мы должны вставить его в качестве аргумента в функцию path. Это можно увидеть в этом фрагменте кода:
<h2><a href="{{ path('BloggerBlogBundle_blog_show', { 'id': blog.id }) }}">{{ blog.title }}</a></h2>
Во-вторых мы возвращаем контент используя
<p>{{blog.blog(500) }}</p>
Число 500 является максимальной длиной поста который мы хотим получить обратно из функции. Для этого нам необходимо обновить метод getBlog, который Doctrine 2 сгенерировало для нас ранее. Обновите метод getBlog в сущности Blog, расположенный src/Blogger/BlogBundle/Entity/Blog.php
// src/Blogger/BlogBundle/Entity/Blog.php
public function getBlog($length = null)
{
if (false === is_null($length) && $length > 0)
return substr($this->blog, 0, $length);
else
return $this->blog;
}
Так как метод getBlog должен вернуть весь пост в блоге, мы установим параметр $length который будет иметь значение по умолчанию null. Если передается значение null, возвращается вся запись.
Теперь, если вы введете в ваш браузер localhost:8000 вы должны увидеть домашнюю страницу, отображающую последние записи в блоге. У вас также должна быть возможность перейти к полной записи блога, нажав на название записи или на ссылку "Продолжить чтение… (Continue reading)".

В то время как мы можем запросить записи в контроллере, это не самое лучшее место для этого. Запрос будет лучше разместить за пределами контроллера из-за целого ряда причин:
Мы не сможем повторно использовать запрос в другом месте приложения, без дублирования кода QueryBuilder.
Если бы мы продублировали код QueryBuilder, мы должны были бы сделать несколько изменений в будущем, если запрос нуждался бы в изменении.
Разделение запроса и контроллера позволит нам протестировать запрос независимо от контроллера.
Doctrine 2 предоставляет классы Репозитория для облегчения этой задачи.
Репозитории Doctrine 2
Мы уже немного поговорили о классах Репозитория Doctrine 2 в предыдущей главе, когда мы создавали страницу блога. Мы использовали реализацию класса по умолчанию Doctrine\ORM\EntityRepository для извлечения записи блога из базы данных с помощью метода find(). Так как нам нужен пользовательский запрос к базе, нам нужно создать пользовательский Репозиторий. Doctrine 2 может помочь в решении этой задачи. Обновите метаданные сущности Blog в файле src/Blogger/BlogBundle/Entity/Blog.php
// src/Blogger/BlogBundle/Entity/Blog.php
/**
* @ORM\Entity(repositoryClass="Blogger\BlogBundle\Entity\Repository\BlogRepository")
* @ORM\Table(name="blog")
* @ORM\HasLifecycleCallbacks()
*/
class Blog
{
// ..
}
Вы можете видеть, что мы определили пространство имен для класса BlogRepository с которым эта сущность ассоциирована. Так как мы обновили метаданные Doctrine 2 для сущности Blog, нам необходимо повторно запустить команду doctrine:generate:entities следующим образом.
$ php app/console doctrine:generate:entities Blogger\BlogBundle
Doctrine 2 создаст оболочку класса BlogRepository, расположенного
src/Blogger/BlogBundle/Entity/Repository/BlogRepository.php
<?php
namespace Blogger\BlogBundle\Entity\Repository;
/**
* BlogRepository
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class BlogRepository extends \Doctrine\ORM\EntityRepository
{
}
Класс BlogRepository расширяет класс EntityRepository который предоставляет метод find(), который мы использовали ранее. Давайте обновим класс BlogRepository, переместив в него код QueryBuilder из контроллера Page.
<?php
namespace Blogger\BlogBundle\Entity\Repository;
/**
* BlogRepository
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class BlogRepository extends \Doctrine\ORM\EntityRepository
{
public function getLatestBlogs($limit = null)
{
$qb = $this->createQueryBuilder('b')
->select('b')
->addOrderBy('b.created', 'DESC');
if (false === is_null($limit))
$qb->setMaxResults($limit);
return $qb->getQuery()
->getResult();
}
}
Мы создали метод getLatestBlogs который будет возвращать последние записи в блоге, таким же образом как код QueryBuilder делал это в контроллере. В классе репозитория мы имеем прямой доступ к QueryBuilder с помощью метода createQueryBuilder(). Мы также добавили параметр по умолчанию $limit, таким образом мы можем ограничить количество возвращаемых результатов. Результатом запроса будет то же, что было в случае с контроллером. Вы, возможно, заметили, что нам нет нужды указывать объект с помощью метода from(). Это связано с тем, что мы находимся в BlogRepository, который связан с сущностью Blog. Если мы посмотрим на реализацию метода createQueryBuilder в классе EntityRepository мы можем увидеть, что метод from() был вызван для нас.
// Doctrine\ORM\EntityRepository
public function createQueryBuilder($alias, $indexBy = null)
{
return $this->_em->createQueryBuilder()
->select($alias)
->from($this->_entityName, $alias, $indexBy);
}
Наконец давайте обновим метод indexAction в контроллере Page для использования BlogRepository.
// src/Blogger/BlogBundle/Controller/PageController.php
class PageController extends Controller
{
public function indexAction()
{
$em = $this->getDoctrine()
->getManager();
$blogs = $em->getRepository('BloggerBlogBundle:Blog')
->getLatestBlogs();
return $this->render('BloggerBlogBundle:Page:index.html.twig', array(
'blogs' => $blogs
));
}
// ..
}
Теперь, когда мы обновим домашнюю страницу будет выведено точно тоже самое, что и раньше. Все, что мы сделали, это реорганизовали код так, чтобы правильно оформленные классы выполняли задачи правильно.
Подробнее о модели: Создание сущности Comment
Записи — это только половина истории, когда речь идет о ведении блога. Мы также должны позволить читателям возможность комментировать записи в блоге. Эти комментарии также должны быть сохранены и связаны с сущностью Blog так как запись может содержать много комментариев.
Мы начнем с определения основы, класса сущности Comment. Создайте новый файл, расположенный в
src/Blogger/BlogBundle/Entity/Comment.php
и вставьте <?php
// src/Blogger/BlogBundle/Entity/Comment.php
namespace Blogger\BlogBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass="Blogger\BlogBundle\Entity\Repository\CommentRepository")
* @ORM\Table(name="comment")
* @ORM\HasLifecycleCallbacks
*/
class Comment
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @ORM\Column(type="string")
*/
protected $user;
/**
* @ORM\Column(type="text")
*/
protected $comment;
/**
* @ORM\Column(type="boolean")
*/
protected $approved;
/**
* @ORM\ManyToOne(targetEntity="Blog", inversedBy="comments")
* @ORM\JoinColumn(name="blog_id", referencedColumnName="id")
*/
protected $blog;
/**
* @ORM\Column(type="datetime")
*/
protected $created;
/**
* @ORM\Column(type="datetime")
*/
protected $updated;
public function __construct()
{
$this->setCreated(new \DateTime());
$this->setUpdated(new \DateTime());
$this->setApproved(true);
}
/**
* @ORM\preUpdate
*/
public function setUpdatedValue()
{
$this->setUpdated(new \DateTime());
}
}
Большую часть того, что вы здесь видите, мы уже рассмотрели в предыдущей части, однако мы использовали метаданные для создания ссылки на сущность Blog. Так как комментарий относится к записи, мы установили ссылку в сущности Comment к сущности Blog к которой она принадлежит. Мы сделали это указав ссылку ManyToOne к сущности Blog. Мы также указали, что обратная связь для этой ссылки будет доступна через комментарии. Чтобы инвертировать, нам нужно обновить сущность Blog так Doctrine 2 будет знать, что запись может содержать много комментариев. Обновите сущность Blog
src/Blogger/BlogBundle/Entity/Blog.php
<?php
// src/Blogger/BlogBundle/Entity/Blog.php
namespace Blogger\BlogBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* @ORM\Entity(repositoryClass="Blogger\BlogBundle\Entity\Repository\BlogRepository")
* @ORM\Table(name="blog")
* @ORM\HasLifecycleCallbacks
*/
class Blog
{
// ..
/**
* @ORM\OneToMany(targetEntity="Comment", mappedBy="blog")
*/
protected $comments;
// ..
public function __construct()
{
$this->comments = new ArrayCollection();
$this->setCreated(new \DateTime());
$this->setUpdated(new \DateTime());
}
// ..
}
Есть несколько изменений на которые нужно указать. Во-первых, мы добавили метаданные к объекту $comments. Помните, в предыдущей главе мы не добавляли метаданные для этого объекта, потому что мы не хотели, чтобы Doctrine 2 его сохраняла? Это по-прежнему так, однако, мы хотим, чтобы Doctrine 2, имела возможность заполнить этот объект соответствующими записями Comment. То есть то, что позволяют достичь метаданные. Во-вторых, Doctrine 2 требует, чтобы мы установили значение по умолчанию для объекта $comments в ArrayCollection. Мы сделаем это в конструкторе. Кроме того, обратите внимание на заявление use импортирующее класс ArrayCollection.
Так как мы создали сущность Comment и обновили сущность Blog, давайте создадим методы доступа. Выполните следующую команду Doctrine 2.
$ php app/console doctrine:generate:entities Blogger\BlogBundle
Обе сущности будут обновлены с корректными методами доступа. Вы также заметите, что в
src/Blogger/BlogBundle/Entity/Repository/CommentRepository.php
был создан класс CommentRepository, так как мы указали это в метаданных.
Наконец, мы должны обновить базу данных, чтобы отразить изменения в наших сущностях. Мы могли бы воспользоваться командой doctrine:schema:update которая показана ниже, чтобы сделать это, но вместо этого мы расскажем о Миграциях Doctrine 2.
$ php app/console doctrine:schema:update --force
Миграции Doctrine 2
Расширение Миграций Doctrine 2 и бандл не поставляется с Symfony2, мы должны вручную их установить. Откройте файл composer.json расположенный в корне проекта и вставьте зависимости Миграций Doctrine 2 и бандл как показано ниже.
"require": {
// ...
"doctrine/doctrine-migrations-bundle": "dev-master",
"doctrine/migrations": "dev-master"
}
Далее обновите библиотеки командой.
$ composer update
Это обновит все библиотеки с Github и установит их в необходимые директории.
Теперь давайте зарегистрируем бандл в kernel расположенного в app/AppKernel.php
// app/AppKernel.php
public function registerBundles()
{
$bundles = array(
// ...
new Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle(),
// ...
);
// ...
}
Теперь мы готовы обновить базу данных чтобы отразить изменения в сущности, этот процесс, пройдёт в 2 этапа. Во-первых, мы должны поручить Миграциям Doctrine 2 поработать с различиями между сущностями и текущей схемой базы данных. Это делается командой doctrine:migrations:diff. Во-вторых, мы должны выполнить миграцию, основанную на данных созданных первой командой. Это делается командой doctrine:migrations:migrate.
Выполните следующие 2 команды для того чтобы обновить схему базы данных.
$ php app/console doctrine:migrations:diff
$ php app/console doctrine:migrations:migrate
На предупреждение, показанное ниже отвечаем yes.
WARNING! You are about to execute a database migration that could result in schema changes and data lost. Are you sure you wish to continue? (y/n): yes
Теперь ваша база данных будет отражать последние изменения сущностей и содержать новую таблицу комментариев.
Заметка
Вы также увидите новую таблицу в базе данных под названием migration_versions. Она хранит номера версий миграций поэтому есть команда, с помощью которой можно узнать какая на данный момент версия базы данных.
Совет
Миграции Doctrine 2 являются отличным способом для обновления базы данных на production, поскольку изменения можно сделать программно. Это означает, что мы можем интегрировать эту задачу в сценарий развертывания проекта, поэтому база данных обновится автоматически при развертывании новой версии приложения. Миграции Doctrine 2 также позволяют откатить изменения, так как каждая миграция имеет up и down метод. Чтобы откатиться к предыдущей версии вам необходимо указать номер версии, на которую вы хотели бы вернуться, сделать это можно, так как показано ниже.
$ php app/console doctrine:migrations:migrate 20110806183439
Фикстуры данных
Теперь у нас есть сущность Comment, давайте добавим Фикстуры данных. Это хороший момент, в то время, когда вы создаете сущность. Мы знаем, что комментарий должен иметь связь с сущностью Blog, как мы указали это в метаданных, поэтому при создании фикстур для сущностей Comment нам нужно будет указать сущность Blog. Мы уже создали фикстуры для сущности Blog таким образом, мы могли бы просто обновить этот файл, чтобы добавить сущности Comment. Это может быть управляемым сейчас, но что произойдёт, когда мы позже добавим пользователей и другие сущности в наш бандл? Лучше всего будет создать новый файл для фикстур сущности Comment. Проблемой в этом подходе является то как мы получим доступ к записям Blog из фикстур блога.
К счастью, это может быть легко достигнуто путем добавления ссылки на объекты в файле фикстур к которому другие файлы фикстур имеют доступ. Обновите Фикстуры данных сущности Blog расположенной
src/Blogger/BlogBundle/DataFixtures/ORM/BlogFixtures.php
<?php
// src/Blogger/BlogBundle/DataFixtures/ORM/BlogFixtures.php
namespace Blogger\BlogBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Blogger\BlogBundle\Entity\Blog;
class BlogFixtures extends AbstractFixture implements OrderedFixtureInterface
{
public function load(ObjectManager $manager)
{
// ..
$manager->flush();
$this->addReference('blog-1', $blog1);
$this->addReference('blog-2', $blog2);
$this->addReference('blog-3', $blog3);
$this->addReference('blog-4', $blog4);
$this->addReference('blog-5', $blog5);
}
public function getOrder()
{
return 1;
}
}
Изменения, которые здесь стоит отметить, расширение класса AbstractFixture и реализация OrderedFixtureInterface. Также обратите внимание на 2 новых use оператора импортирующие эти классы.
Мы добавляем ссылки на сущности блог с помощью метода addReference(). Этот первый параметр является идентификатором ссылки, которую мы можем использовать для извлечения объекта позже. В конце мы должны реализовать метод getOrder() чтобы указать порядок загрузки фикстур. Записи должны быть загружены до комментариев поэтому мы возвращаем 1.
Фикстуры Comment
Теперь мы готовы определить фикстуры для сущности Comment. Создайте файл фикстур
src/Blogger/BlogBundle/DataFixtures/ORM/CommentFixtures.php
и вставьте
<?php
// src/Blogger/BlogBundle/DataFixtures/ORM/CommentFixtures.php
namespace Blogger\BlogBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Blogger\BlogBundle\Entity\Comment;
use Blogger\BlogBundle\Entity\Blog;
class CommentFixtures extends AbstractFixture implements OrderedFixtureInterface
{
public function load(ObjectManager $manager)
{
$comment = new Comment();
$comment->setUser('symfony');
$comment->setComment('To make a long story short. You can\'t go wrong by choosing Symfony! And no one has ever been fired for using Symfony.');
$comment->setBlog($manager->merge($this->getReference('blog-1')));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('David');
$comment->setComment('To make a long story short. Choosing a framework must not be taken lightly; it is a long-term commitment. Make sure that you make the right selection!');
$comment->setBlog($manager->merge($this->getReference('blog-1')));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Dade');
$comment->setComment('Anything else, mom? You want me to mow the lawn? Oops! I forgot, New York, No grass.');
$comment->setBlog($manager->merge($this->getReference('blog-2')));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Kate');
$comment->setComment('Are you challenging me? ');
$comment->setBlog($manager->merge($this->getReference('blog-2')));
$comment->setCreated(new \DateTime("2011-07-23 06:15:20"));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Dade');
$comment->setComment('Name your stakes.');
$comment->setBlog($manager->merge($this->getReference('blog-2')));
$comment->setCreated(new \DateTime("2011-07-23 06:18:35"));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Kate');
$comment->setComment('If I win, you become my slave.');
$comment->setBlog($manager->merge($this->getReference('blog-2')));
$comment->setCreated(new \DateTime("2011-07-23 06:22:53"));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Dade');
$comment->setComment('Your SLAVE?');
$comment->setBlog($manager->merge($this->getReference('blog-2')));
$comment->setCreated(new \DateTime("2011-07-23 06:25:15"));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Kate');
$comment->setComment('You wish! You\'ll do shitwork, scan, crack copyrights...');
$comment->setBlog($manager->merge($this->getReference('blog-2')));
$comment->setCreated(new \DateTime("2011-07-23 06:46:08"));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Dade');
$comment->setComment('And if I win?');
$comment->setBlog($manager->merge($this->getReference('blog-2')));
$comment->setCreated(new \DateTime("2011-07-23 10:22:46"));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Kate');
$comment->setComment('Make it my first-born!');
$comment->setBlog($manager->merge($this->getReference('blog-2')));
$comment->setCreated(new \DateTime("2011-07-23 11:08:08"));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Dade');
$comment->setComment('Make it our first-date!');
$comment->setBlog($manager->merge($this->getReference('blog-2')));
$comment->setCreated(new \DateTime("2011-07-24 18:56:01"));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Kate');
$comment->setComment('I don\'t DO dates. But I don\'t lose either, so you\'re on!');
$comment->setBlog($manager->merge($this->getReference('blog-2')));
$comment->setCreated(new \DateTime("2011-07-25 22:28:42"));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Stanley');
$comment->setComment('It\'s not gonna end like this.');
$comment->setBlog($manager->merge($this->getReference('blog-3')));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Gabriel');
$comment->setComment('Oh, come on, Stan. Not everything ends the way you think it should. Besides, audiences love happy endings.');
$comment->setBlog($manager->merge($this->getReference('blog-3')));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Mile');
$comment->setComment('Doesn\'t Bill Gates have something like that?');
$comment->setBlog($manager->merge($this->getReference('blog-5')));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Gary');
$comment->setComment('Bill Who?');
$comment->setBlog($manager->merge($this->getReference('blog-5')));
$manager->persist($comment);
$manager->flush();
}
public function getOrder()
{
return 2;
}
}
C изменениями которые мы сделали в классе BlogFixtures, класс CommentFixtures также расширяет класс AbstractFixture и реализует OrderedFixtureInterface. Это означает, что мы должны также реализовать метод getOrder(). На этот раз мы возвращаем значение 2, обеспечивая этим загрузку фикстур после фикстур записей.
Мы также можем увидеть, как используются ссылки на сущности Blog которые мы создали ранее.
$comment->setBlog($manager->merge($this->getReference('blog-2')));
Теперь мы готовы загрузить фикстуры в базу данных
$ php app/console doctrine:fixtures:load
На предупреждение отвечаем: yes
Careful, database will be purged. Do you want to continue y/N ? yes
Отображение Комментариев
Теперь мы можем отобразить комментарии, связанные с каждым сообщением в блоге. Мы начнём с обновления CommentRepository методом, который получает самые последние одобренные комментарии для блога.
Репозиторий Comment
Откройте класс CommentRepository расположенный
src/Blogger/BlogBundle/Entity/Repository/CommentRepository.php
и вставьте <?php
// src/Blogger/BlogBundle/Entity/Repository/CommentRepository.php
namespace Blogger\BlogBundle\Entity\Repository;
use Doctrine\ORM\EntityRepository;
/**
* CommentRepository
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class CommentRepository extends EntityRepository
{
public function getCommentsForBlog($blogId, $approved = true)
{
$qb = $this->createQueryBuilder('c')
->select('c')
->where('c.blog = :blog_id')
->addOrderBy('c.created')
->setParameter('blog_id', $blogId);
if (false === is_null($approved))
$qb->andWhere('c.approved = :approved')
->setParameter('approved', $approved);
return $qb->getQuery()
->getResult();
}
}
Метод, который мы создали будет получать комментарии к записи блога. Для этого нам нужно добавить where условие к нашему запросу. Условие where использует именованный параметр, который задается с помощью метода setParameter(). Вы должны всегда использовать параметры вместо того, чтобы устанавливать значения непосредственно в запросе
->where('c.blog = ' . blogId)
В этом примере значение $blogId не будет безопасно и может оставить запрос открытым для атаки SQL injection.
Контроллер Blog
Далее нам нужно обновить showAction метод контроллера Blog для извлечения комментариев. Обновите контроллер Blog
src/Blogger/BlogBundle/Controller/BlogController.php
// src/Blogger/BlogBundle/Controller/BlogController.php
public function showAction($id)
{
// ..
if (!$blog) {
throw $this->createNotFoundException('Unable to find Blog post.');
}
$comments = $em->getRepository('BloggerBlogBundle:Comment')
->getCommentsForBlog($blog->getId());
return $this->render('BloggerBlogBundle:Blog:show.html.twig', array(
'blog' => $blog,
'comments' => $comments
));
}
Мы используем новый метод в CommentRepository для получения одобренных комментариев. Коллекция $comments также передается в шаблон.
Шаблон Blog show
Теперь у нас есть список комментариев для блога мы можем обновить шаблон Blog show для показа комментариев. Мы могли бы просто поместить вывод комментариев непосредственно в шаблон Blog show, но так как комментарии имеют свою собственную сущность, было бы лучше, отделить отображение в другой шаблон, и включить в него этот. Это позволит нам повторно использовать шаблон вывода комментариев в любом месте приложения. Обновите шаблон Blog
show src/Blogger/BlogBundle/Resources/views/Blog/show.html.twig
{# src/Blogger/BlogBundle/Resources/views/Blog/show.html.twig #}
{# .. #}
{% block body %}
{# .. #}
<section class="comments" id="comments">
<section class="previous-comments">
<h3>Comments</h3>
{% include 'BloggerBlogBundle:Comment:index.html.twig' with { 'comments': comments } %}
</section>
</section>
{% endblock %}
Вы можете увидеть новый тег Twig include. Он будет включать содержимое шаблона BloggerBlogBundle:Comment:index.html.twig. Мы также можем передать любое количество аргументов в шаблон. В этом случае нам нужно пройти через коллекцию Comment сущностей для визуализации.
Шаблон Comment show
BloggerBlogBundle:Comment:index.html.twig который мы включили выше пока не существует, поэтому мы должны создать его. Так как это просто шаблон, нам не нужно, создавать маршрут или контроллер для этого нам нужен только файл шаблона. Создайте новый файл
src/Blogger/BlogBundle/Resources/views/Comment/index.html.twig
и вставьте
{# src/Blogger/BlogBundle/Resources/views/Comment/index.html.twig #}
{% for comment in comments %}
<article class="comment {{ cycle(['odd', 'even'], loop.index0) }}" id="comment-{{ comment.id }}">
<header>
<p><span class="highlight">{{ comment.user }}</span> commented <time datetime="{{ comment.created|date('c') }}">{{ comment.created|date('l, F j, Y') }}</time></p>
</header>
<p>{{ comment.comment }}</p>
</article>
{% else %}
<p>There are no comments for this post. Be the first to comment...</p>
{% endfor %}
Как вы можете видеть, мы итерируем коллекцию сущностей Comment и выводим комментарии. Расскажем и о ещё одной хорошей функции Twig, cycle. Эта функция будет перебирать значения в массиве, который вы передаете, во время каждой итерации цикла. Текущее значение итерации цикла получается через специальную переменную loop.index0. Это ведет подсчет итераций цикла, начиная с 0. Есть целый ряд других доступных специальных переменных, когда мы находимся в пределах цикла. Вы можете также заметить установку HTML-ID к article элементу. Это позволит нам позже создать ссылки на созданные комментарии.
Comment show CSS
И наконец, давайте добавим немного CSS, чтобы комментарии выглядели стильно. Обновите стили, расположенные в
src/Blogger/BlogBundle/Resouces/public/css/blog.css
/** src/Blogger/BlogBundle/Resorces/public/css/blog.css **/
.comments { clear: both; }
.comments .odd { background: #eee; }
.comments .comment { padding: 20px; }
.comments .comment p { margin-bottom: 0; }
.comments h3 { background: #eee; padding: 10px; font-size: 20px; margin-bottom: 20px; clear: both; }
.comments .previous-comments { margin-bottom: 20px; }
Заметка
Если вы не используете метод символических ссылок для обращения к assets бандла в папке web, вы должны повторно запустить команду установки assets чтобы скопировать изменения.
$ php app/console assets:install web
Если теперь посмотрим на одну из show pages, например, http://localhost:8000/2 вы должны увидеть вывод комментариев к записи.

Добавление комментариев
Последняя часть этой главы будет посвящена расширению функциональности для пользователей, добавление комментариев к записи в блоге. Это станет возможным благодаря форме на странице blog show. Мы уже говорили о создании форм в Symfony 2 когда создавали форму на странице контактов. Вместо того чтобы создавать форму комментария вручную, мы можем использовать Symfony2, чтобы он сделал это за нас. Запустите следующую команду для генерации класса CommentType для сущности Comment.
$ php app/console generate:doctrine:form BloggerBlogBundle:Comment
Вы снова заметите использование сокращения чтобы определить сущность Comment.
Подсказка
Вы возможно, заметили что также доступна команда doctrine:generate:form. Это та же команда названая по-другому.
Команда создала класс CommentType расположенный
src/Blogger/BlogBundle/Form/CommentType.php
<?php
namespace Blogger\BlogBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class CommentType extends AbstractType
{
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('user')
->add('comment')
->add('approved')
->add('created', 'datetime')
->add('updated', 'datetime')
->add('blog')
;
}
/**
* @param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Blogger\BlogBundle\Entity\Comment'
));
}
}
Мы уже изучили, что происходит здесь, в предыдущем классе Enquiry Type. Мы могли бы начать с настройки этого класса сейчас, но давайте займемся сначала отображением формы.
Отображение формы комментариев.
Так как мы хотим, чтобы пользователь добавлял комментарии со страницы blog show, мы могли бы создать форму в методе showAction контроллера Blog и вывести форму непосредственно в шаблоне show. Однако было бы лучше отделить этот код, как мы это делали с отображением комментариев. Разница между отображением комментариев и отображением формы комментариев в том, что форма комментария нуждается в обработке, поэтому требуется контроллер.
Маршрут
Нам нужно создать новый маршрут для обработки форм. Добавьте новый маршрут, расположенный
src/Blogger/BlogBundle/Resources/config/routing.yml
BloggerBlogBundle_comment_create:
path: /comment/{blog_id}
defaults: { _controller: "BloggerBlogBundle:Comment:create" }
requirements:
methods: POST
blog_id: \d+
Контроллер
Далее, нам необходимо создать новый CommentControler который мы упомянули выше. Создайте новый файл, расположенный в src/Blogger/BlogBundle/Controller/CommentController.php и вставьте
<?php
// src/Blogger/BlogBundle/Controller/CommentController.php
namespace Blogger\BlogBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Blogger\BlogBundle\Entity\Comment;
use Blogger\BlogBundle\Form\CommentType;
use Symfony\Component\HttpFoundation\Request;
/**
* Comment controller.
*/
class CommentController extends Controller
{
public function newAction($blog_id)
{
$blog = $this->getBlog($blog_id);
$comment = new Comment();
$comment->setBlog($blog);
$form = $this->createForm(CommentType::class, $comment);
return $this->render('BloggerBlogBundle:Comment:form.html.twig', array(
'comment' => $comment,
'form' => $form->createView()
));
}
public function createAction(Request $request, $blog_id)
{
$blog = $this->getBlog($blog_id);
$comment = new Comment();
$comment->setBlog($blog);
$form = $this->createForm(CommentType::class, $comment);
$form->handleRequest($request);
if ($form->isValid()) {
// TODO: Persist the comment entity
return $this->redirect($this->generateUrl('BloggerBlogBundle_blog_show', array(
'id' => $comment->getBlog()->getId())) .
'#comment-' . $comment->getId()
);
}
return $this->render('BloggerBlogBundle:Comment:create.html.twig', array(
'comment' => $comment,
'form' => $form->createView()
));
}
protected function getBlog($blog_id)
{
$em = $this->getDoctrine()
->getManager();
$blog = $em->getRepository('BloggerBlogBundle:Blog')->find($blog_id);
if (!$blog) {
throw $this->createNotFoundException('Unable to find Blog post.');
}
return $blog;
}
}
Мы создали 2 метода в контроллере Comment, один для new и один для create. Метод new связан с отображением формы для комментария, метод create связан с обработкой формы комментария. Хотя это может показаться большим куском кода, здесь нет ничего нового, все было рассказано во второй части, когда мы создавали контактную форму. Однако, прежде чем пойти дальше убедитесь, что вы в полной мере поняли, что происходит в контроллере Comment.
Валидация Формы
Мы не хотим, чтобы у пользователей была возможность оставлять комментарии с пустыми значениями параметров user и comment. Для достижения этого, вспомним Валидацию которую мы рассматривали во второй части при создании формы запроса. Обновите сущность Comment расположенную
src/Blogger/BlogBundle/Entity/Comment.php
<?php
// src/Blogger/BlogBundle/Entity/Comment.php
// ..
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraints\NotBlank;
// ..
class Comment
{
// ..
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
$metadata->addPropertyConstraint('user', new NotBlank(array(
'message' => 'You must enter your name'
)));
$metadata->addPropertyConstraint('comment', new NotBlank(array(
'message' => 'You must enter a comment'
)));
}
// ..
}
Здесь проверяется заполнены ли поля user и comment. Также мы переопределили сообщения по умолчанию. Не забудьте добавить пространство имен ClassMetadata и NotBlank, как показано выше.
Отображение
Далее нам нужно создать 2 шаблона для методов new и create контроллера. Создайте новый файл, расположенный в
src/Blogger/BlogBundle/Resources/views/Comment/form.html.twig
и вставьте {# src/Blogger/BlogBundle/Resources/views/Comment/form.html.twig #}
{{ form_start(form, { 'action': path('BloggerBlogBundle_comment_create' , { 'blog_id' : comment.blog.id }), 'method': 'POST', 'attr': {'class': 'blogger'} }) }}
{{ form_widget(form) }}
<p>
<input type="submit" value="Submit">
</p>
Цель этого шаблона довольно простая, он просто отображает форму комментария. Вы также заметите, что метод action формы является POST и относится к новому маршруту, который мы создали BloggerBlogBundle_comment_create.
Теперь давайте создадим шаблон для create метода. Создайте новый файл, расположенный в
src/Blogger/BlogBundle/Resources/views/Comment/create.html.twig
и вставьте {% extends 'BloggerBlogBundle::layout.html.twig' %}
{% block title %}Add Comment{% endblock%}
{% block body %}
<h1>Add comment for blog post "{{ comment.blog.title }}"</h1>
{% include 'BloggerBlogBundle:Comment:form.html.twig' with { 'form': form } %}
{% endblock %}
Так как метод createAction контроллера Comment имеет дело с обработкой формы, он также должен быть в состоянии отображать ее, так как там могут возникнуть ошибки. Мы повторно воспользуемся BloggerBlogBundle:Comment:form.html.twig для отображения формы чтобы не дублировать код.
Давайте теперь обновим шаблон blog show для отображения формы. Обновите шаблон
src/Blogger/BlogBundle/Resources/views/Blog/show.html
{# src/Blogger/BlogBundle/Resources/views/Blog/show.html.twig #}
{# .. #}
{% block body %}
{# .. #}
<section class="comments" id="comments">
{# .. #}
<h3>Add Comment</h3>
{{ render(controller('BloggerBlogBundle:Comment:new',{ 'blog_id': blog.id })) }}
</section>
{% endblock %}
Мы использовали здесь другой тег Twig, render. Этот тег выводит содержимое контроллера в шаблон. В нашем случае мы выводим содержимое BloggerBlogBundle:Comment:new
Если мы посмотрим теперь на одну из страниц блога, такую как http://localhost:8000/2 вы увидите уведомление, показанное ниже.

Это сообщение вызвано шаблоном BloggerBlogBundle:Blog:show.html.twig. Если мы посмотрим на строку 23 шаблона BloggerBlogBundle:Blog:show.html.twig мы увидим, что эта строка показывает, что проблема на самом деле в процессе встраивания BloggerBlogBundle:Comment:create контроллера.
{{ render(controller('BloggerBlogBundle:Comment:new',{ 'blog_id': blog.id })) }}
Если мы посмотрим на сообщение об ошибке внимательнее это даст нам больше информации о причине, почему ошибка была вызвана.
Она говорит нам о том, что поле, которое мы пытаемся вызвать не имеет метода __toString () для сущности, связанной с ним. Поле выбора является элементом формы, которое дает пользователю выбор нескольких вариантов, например, элемент select (выпадающий список). Вы можете быть удивлены, где мы выводим такое поле в форме комментария? Если вы посмотрите на шаблон формы комментария снова, вы заметите, что мы выводим форму с помощью функции Twig {{form_widget(form)}}. Эта функция выводит всю форму. Давайте вернемся к классу формы созданную из класса CommentType. Мы можем видеть, что ряд полей добавляются в форму с помощью объекта FormBuilder. В частности, мы добавляем поле blog.
Если вы помните, во второй части руководства, мы говорили о том, как FormBuilder будет пытаться угадать тип поля для вывода на основе метаданных, относящихся к полю. Так как мы установили связь между сущностями Comment и Blog, FormBuilder предположил, что комментарий должен быть choice полем, которое позволит пользователю указать запись, к которой надо прикрепить комментарий. Вот почему у нас есть choice поле в форме и вот почему была вызвана ошибка Symfony 2. Мы можем решить эту проблему путем реализации __toString() метода в сущности Blog.
// src/Blogger/BlogBundle/Entity/Blog.php
public function __toString()
{
return $this->getTitle();
}
Подсказка
Сообщения об ошибках Symfony2 очень информативны при описании проблемы, которая произошла. Всегда читайте сообщения об ошибках, так как они обычно делают процесс отладки намного проще. Сообщения об ошибках также показывают полный путь, так что вы можете увидеть шаги, которые были предприняты, чтобы вызвать ошибку.
Теперь, когда вы обновите страницу, вы должны увидеть вывод формы комментария. Вы также заметите, что некоторые нежелательные поля были выведены такие как approved, created, updated и blog. Это происходит потому, что мы не настроили сгенерированный класс CommentType ранее.
Подсказка
Все поля, которые были выведены имеют корректный тип. Поле пользователя text, поле комментария textarea, 2 поля DateTime позволяют указать время, и т.д
Это происходит из-за способности FormBuilder угадывать тип поля, которое должно быть выведено. Он способен делать это на основе метаданных, которые вы предоставляете. Так как мы определили вполне конкретные метаданные для сущности Comment, то FormBuilder способен делать точные предположения о типах полей.
Давайте теперь обновим класс, расположенный в src/Blogger/BlogBundle/Form/CommentType.php для вывода только тех полей, которые нам нужны,
<?php
namespace Blogger\BlogBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class CommentType extends AbstractType
{
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('user');
$builder->add('comment');
}
/**
* @param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Blogger\BlogBundle\Entity\Comment'
));
}
public function getBlockPrefix()
{
return 'blogger_blogbundle_commenttype';
}
}
Теперь, когда вы обновите страницу будут выведены только поле для имени пользователя и поле для комментариев. Если вы отправите форму сейчас, комментарий не будет сохранен в базе данных. Потому, что контроллер формы ничего не делает с сущностью комментария, если форма проходит проверку. Так как же мы сохраним комментарий в базу данных? Вы уже видели, как это делается при создании Фикстур данных. Обновите метод createAction как показано ниже.
// src/Blogger/BlogBundle/Controller/CommentController.php
public function createAction(Request $request, $blog_id)
{
//..
if ($form->isValid()) {
$em = $this->getDoctrine()
->getManager();
$em->persist($comment);
$em->flush();
return $this->redirect($this->generateUrl('BloggerBlogBundle_blog_show', array(
'id' => $comment->getBlog()->getId())) .
'#comment-' . $comment->getId()
);
}
//..
}
Сохранение сущности Comment происходит благодаря вызову методов persist() и flush(). Помните, что форма имеет дело с PHP-объектами, а Doctrine 2 управляет и сохраняет эти объекты. Там нет прямой связи между отправкой формы и сохранением представленных данных в базе.
Теперь вы должны иметь возможность добавлять комментарии к сообщениям в блоге.

Вывод
Мы добились значительного прогресса в этой части. Наш блог начинает функционировать так как мы и ожидали. Теперь пользователи могут оставлять комментарии к записям блога и читать комментарии, оставленные другими пользователями. Мы увидели, как создать фикстуры, которые могут ссылаться на несколько файлов фикстур и использовали Doctrine 2 Миграции чтобы сохранить встроенную схему базы данных при изменении сущностей.
Далее мы рассмотрим создание боковой панели (sidebar), чтобы поместить в неё облако тегов и недавние комментарии. Мы также расширим наши знания в Twig и увидим как с помощью него делать пользовательские фильтры. В заключение мы рассмотрим использование Assetic библиотеку, которая поможет нам в управлении нашими assets.
Источники и вспомогательные материалы:
https://symfony.com/
http://tutorial.symblog.co.uk/
http://twig.sensiolabs.org/
http://www.doctrine-project.org/
http://odiszapc.ru/doctrine/
Всем спасибо за внимание и замечания сделанные по проекту, если у вас возникли сложности или вопросы, отписывайтесь в комментарии или личные сообщения, добавляйтесь в друзья.
Часть 1 — Конфигурация Symfony2 и шаблонов
Часть 2 — Страница с контактной информацией: валидаторы, формы и электронная почта
Часть 3 — Doctrine 2 и Фикстуры данных
Часть 5 — Twig расширения, Боковая панель(sidebar) и Assetic
Также, если Вам понравилось руководство вы можете поставить звезду репозиторию проекта или подписаться. Спасибо.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Комментарии (113)
M-A-XG
05.06.2016 10:23-3>Чтобы инвертировать, нам нужно обновить сущность Blog так Doctrine 2 будет знать, что запись может содержать много комментариев. Обновите сущность Blog src/Blogger/BlogBundle/Entity/Blog.php
Хм, а если нам нужно привязаться к сторонней сущности, то нужно править исходники, которые перезатрутся от обновления?Fesor
05.06.2016 13:21+2А теперь давайте рассуждать… С какой стати сторонняя сущность должна что-то знать о вашей? Она же на то и сторонняя что бы ничего этого не знать.
Ну и потом, как правило сторонние пакеты предоставляют базовую реализацию сущности, оставляя возможность заменить своей (сущности — это бизнес объекты, а бизнес объекты не должны зависить от внешних зависимостей) или расширять.
M-A-XG
05.06.2016 15:01В моем ядре сущности не знают друг о друге абсолютно ничего, но это не мешает делать между ними связи.
А на фреймворках такая печальная ситуация, что связи со сторонними сущности не сделаешь :)
Вообще наличие этих прописанных связей плохо пахнет.
Комментарии ничего не должны знать о постах и наоборот.
>а бизнес объекты не должны зависеть от внешних зависимостей
Я не понимаю этой фразы… :)
У фреймворков есть маркетплейс, например, как в Битриксе?
>оставляя возможность заменить своей
Написать с нуля? Или отнаследоваться?
С нуля писать наверно плохо. Это потом придется поддерживать актуальность. Это дублирование кода. И есть заповедь: Не плоди сущностей. :)
Наследование? Где будем сохранять ее? В своем бандле? Аннотации (комментарии) не наследуются.Fesor
05.06.2016 16:42+1В моем ядре сущности не знают друг о друге абсолютно ничего, но это не мешает делать между ними связи.
В "ядре" не должно быть сущностей. Ну и если у вас есть связи, то они должны знать друг о друге либо сверзу будет объект хранящий свящи и управляющий этим всем.
А на фреймворках такая печальная ситуация, что связи со сторонними сущности не сделаешь :)
Вы можете указать связь вашей сущности со сторонней, но сторонняя не должна ничего знать о вашей сущности. И с этим в "фреймворках" проблем нет. Ну и опять же — мы сейчас говорим даже не о фреймворках а о ORM, которые нужны даже не в 90% случаев.
Вообще наличие этих прописанных связей плохо пахнет.
Давайте отстранимся от фреймворков и PHP на секундочку и вспомним о такой забытой штуке как UML. И представим что мы не проектируем "архитектуру" а просто сидим с клиентом и он объясняет как у него сейчас построен процесс или как он хочет что бы оно работало. Он вам рассказывает:
Ну… у меня есть товары, которые хранятся на складе. Складов у нас несколько, и нужно знать какой товар на каком складе. Причем товары могут перемещать со склада на склад (логистические загоны)… Этим занимаются управляющие складом.
То есть мы видим что склад должен знать о том, какие продукты в нем хранятся. И следовательно у сущности склада будет свойство
products
в котором будет храниться коллекция всех продуктов на этом складе. Логично? Так мы можем прямо в сущности реализовать метод для проверки есть ли на складе столько продуктов, можно организовать резервирование оных и т.д.
Я не понимаю этой фразы… :)
Есть такой подход к проектированию, который собственно исповедует Doctrine. Наши сущности — это просто PHP объекты, которые ничерта не знают ни о доктрине, ни о симфони… Они просто хранят какое-то состояние и поведение. Объекты предметной области. Например объект склада умеет хранить продукты, умеет их резервировать и все такое. Но трансфер между складами уже за рамками обязанностей одного склада и подобную логику нужно выносить на уровень выше.
А вся инфраструктура — фреймворки, работа с базой данных, весь технический булшит, оно все в сторонке. Этот подход выгодно использовть тогда, когда у вас предметная область не тривиальна как например с бложиком. Что-то уровня интернет магазинчика или там логистический центр.
К сожалению подавляющее большинство разработчиков выносит поведение из сущностей в менеджеры, и сущности превращатюся в тупые хранилища данных. Тем самым мы снижаем зацепление наших объектов, ломаем принцип единой ответственности и усложняем взаимодействия, как минимум нарушаем инкапсуляцию частенько. Просто так, на ровном месте. Но это проблема не фреймворков а людей.
Написать с нуля? Или отнаследоваться?
it depends. Например взять FosUserBundle. Для простеньких проектов можно отнаследоваться, а для проектов посложнее можно написать полностью свою реализацию (когда переопределение выходит не так гибко). Сам же FosUserBundle ожидает любую сущность имплементирующую
AdvancedUserInterface
так что никаких проблем с поддержкой.
Аннотации (комментарии) не наследуются.
наследуются свойства. А стало быть если вы не переопределяли свойства — то наследуются и аннотации.
M-A-XG
05.06.2016 18:14>В «ядре» не должно быть сущностей.
Они и не в ядре. Просто ядру не нужно описание связей.
>ORM, которые нужны даже не в 90% случаев
Может не нужны в 90% случаев. У меня нету ОРМ.
>То есть мы видим что склад должен знать о том, какие продукты в нем хранятся.
Если не использовать ОРМ, то никто ничего не должен знать :)
>К сожалению подавляющее большинство разработчиков выносит поведение из сущностей в менеджеры, и сущности превращатюся в тупые хранилища данных.
В мире веб у сущностей и не должно быть поведения. Есть только выбор элементов, но и его пихать в сущность не стоит. Я за тупые хранилища данных.
И многие разработчики держат выбор элементов в сущности (Yii это даже считает лучшей практикой :) Не знаю, как другие), ну или создают одноразово используемые тупорылые методы вроде get10LastNews() например в контроллере.
>А стало быть если вы не переопределяли свойства — то наследуются и аннотации.
А как мы добавим связь, не переопределив свойство?
Fesor
05.06.2016 18:37+1В мире веб у сущностей и не должно быть поведения.
Нет никакого мира WEB. Мы сейчас говорим про серверную часть в контексте клиент-серверной архитектуры. Это одно приложение. Оно может даже ничего не знать о WEB (представьте что у вас просто CRON-ом дергаются скрипты). Для приложения нет разницы запускаются ли команды через cron, выполняются из очередей или же по HTTP запросу. Как только мы перестаем думать мысли в духе "это web тут наверное по другому", все становится намного проще.
Ну и собственно когда у сущностей нет поведения, это называется анемичной моделью и признано антипаттерном. Почему антипаттерном? Потому что вам все еще нужно описывать эту модель но она никакой пользы вам не дает. Тот код который можно было писать прямо внутри сущности мы выносим в какие-то другие вещи, и при этом дергаем кучи сеттеров.
Как итог — читая код вы не сможете быстро понять какие бизнес-ограничения налагаются на ваши сущности. То есть вам придется в разы больше времени потратить что бы разобраться в предметной области по коду (например когда вы вводите нового разработчика в проект или просто работаете с проектом который писали год назад).
Есть только выбор элементов, но и его пихать в сущность не стоит. Я за тупые хранилища данных.
А что такое элементы в контексте вопроса? Я правильно понимаю что вы про такие штуки (в контексте Yii):
Something::findStuff(); // так?
Это просто для удобства. Этот метод на самом деле не пренадлежит сущностям/моделям — он же статический, то есть это просто функция закрепленная за каким-то типом. Внутри оно дергает глобально доступные штуки и т.д. но вам никто не мешает делать отдельный объекты, которые занимаются выборками. Это более правильный подход просто в большинстве случаев это оверхэд. Но знать что так можно несомненно надо.
Так же между AR в Yii (и другие реализации) и Doctrine есть одна огромная разница:
Doctrine оперирует объектами, и мэпит их на базу данных. То есть один объект можно замэпить хоть на 10 таблиц или 10 объектов замэпить на одну таблицу. Тогда как в случае с AR наши "модельки" это прямая проэкция рядов таблиц.
В случае AR ответственность за управление рядом таблицы лежит на самой моделе. В случае Doctrine "модельки" ничерта не знают о базе данных и вводится объект-репозиторий, который и является "тупым хранилищем". В нем максимум можно расположить логику, ограничивающую можно ли ложить объект в репозиторий или нет.
ну или создают одноразово используемые тупорылые методы вроде get10LastNews() например в контроллере.
Ну печально. У меня в репозиториях будет метод
getLatestNews($limit = 10)
А как мы добавим связь, не переопределив свойство?
Повторюсь еще раз. Если мы наследуемся от сторонней сущности, там нет связей с нашими сущностями потому что… ну сторонний пакет понятия не имеет о том кто и как будет его использовать.
А если связи нет и хотим ее добавить, то мы добавляем свойство а не "переопределяем".
M-A-XG
05.06.2016 19:17>признано антипаттерном
Божественный объект тож признан антипаттерном, но jQuery это не мешает :)
>Потому что вам все еще нужно описывать эту модель но она никакой пользы вам не дает.
А я у себя ее и не описываю. 3 строчки кода для наследования базового класса, где указана таблица, с которой работать и все :)
Я не разделяю жирных моделей. Все должно быть просто. Какое поведение в вебе? Выбрать из базы и записать в базу. Неоходимо грамотное АПИ для этого, а не тонны лишнего кода.
Или письма отправлять из сущностей? :)
>А что такое элементы в контексте вопроса?
Записи в БД (результаты запросов).
Я против методов get10LastNews и get10FirstComments (ну или без 10, например, getLastNewsByAuthor)
>Если мы наследуемся от сторонней сущности, там нет связей с нашими сущностями потому что…
У стороннего пакета могут быть связи сущности с его сущностями.IncorrecTSW
05.06.2016 19:27+2jQuery совсем из другой области. Да и по большей части это неймспейс уже.
Fesor
05.06.2016 20:02Божественный объект тож признан антипаттерном, но jQuery это не мешает :)
а где там god object? Там все весьма грамотно.
3 строчки кода для наследования базового класса, где указана таблица, с которой работать и все :)
И тогда мы говорим не о "моделях", "сущностях" и т.д. а о старом добром row data gateway.
Я не разделяю жирных моделей. Все должно быть просто.
Все должно быть в меру. Тощие модели тоже никто не любит. Они должны быть сочными но не жирными. Что бы было за что ухватиться, но что бы проходили в дверь.
Если модель слишком жирная — значит декомпозиция системы проведена плохо и "модель" нужно дробить.
Или письма отправлять из сущностей? :)
Это другая крайность. Чисто теоритически через дабл-диспатч и свои интерфейсы (то есть мы передаем сервис реализующий интерфейс, лежащий рядом с сущностью, в качестве аргумента какого-то метода этой сущности) в этом даже не будет ничего плохого. Но мне больше нравится комбинация domain events + event dispatcher для подобного либо просто явно задавать эти действия в сервисах уровня приложения.
Записи в БД (результаты запросов).
конкретно вы говорили о чтении из БД. Запись в БД в контексте active record это как раз таки дело самой модели. Если вам не нравится и нужен именно ORM — то доктрина (или любой датамэппер) хороший выбор. А если не нужен ORM — лучше использовать table gateway.
Я против методов get10LastNews и get10FirstComments (ну или без 10, например, getLastNewsByAuthor)
размажем SQL повсюду? Где-то же это должно быть изолировано? А стало быть и методы такие появятся. Вопрос только в том, где они будут расположены и насколько нам будет удобно это тестировать и использовать.
У стороннего пакета могут быть связи сущности с его сущностями.
Могут быть конечно. Но зачем вам их переопределять? В любом случае доктрина предоставляет возможность переопределять мэппинги так гибко.
M-A-XG
05.06.2016 21:48>конкретно вы говорили о чтении из БД
«Записи» — не процесс записи, а строчки в таблице. :)
>row data gateway, table gateway
Тут описание какое-то унылое: :)
http://www.design-pattern.ru/patterns/table-data-gateway.html
http://www.design-pattern.ru/patterns/row-data-gateway.html
У меня же нормальное АПИ для работы с БД, ну и пофиг, что для него нету умного термина :)
Ну или термин есть, но у них реализации унылые, а у меня норм :)
>размажем SQL повсюду?
Изолировано должно быть, но без таких методов :)
>Но зачем вам их переопределять?
Не переопределять, а добавить. Связывание не по первичному ключу, а по двум вторичным. Если предоставляет, то ок :)Fesor
05.06.2016 22:41+2У меня же нормальное АПИ для работы с БД, ну и пофиг, что для него нету умного термина :)
Покажите. Ибо я могу судить только по этому примеру: http://govnokod.ru/19878#comment323654
И называется это процедурное программирование без явного разделения ответственности. Просто говнокод. Вполне себе академический термин :)
В целом же подозреваю что вы организуете DAO или другими словами Table Gateway + кастыли.
VolCh
06.06.2016 07:33К сожалению подавляющее большинство разработчиков выносит поведение из сущностей в менеджеры, и сущности превращатюся в тупые хранилища данных.
Отчасти это связано с некоторыми особенностями Doctrine. Например, в сущность сложно заинжектить сервис, особенно лениво.Fesor
06.06.2016 10:02в сущности не стоит инджектить сервисы, как по мне. А передать сервис через дабл диспатч — без проблем.
VolCh
06.06.2016 08:32+3Не вижу смысла для данной задачи ни создавать CommentRepository, ни получать их отдельно в контроллере, ни передавать их отдельно в шаблон. По данной задаче комментарии не являются самостоятельными сущностями, они лишь агрегат в блоге. Наивная (и академически чистая :) реализация могла бы сводиться к методу getApprovedComments в Blog:
, который вызывался бы непосредственно в шаблоне, а потом, при оптимизации создать метод в репозитории блога (как корня агрегата), который получал бы из базы блоги только с утвержденными комментариями.public function getApprovedComments() { return $this->comments->filter(function (Comment $comment) {return $comment->getApproved()}) }
Кстати о комментариях. Имена типа $comment->getComment() обычно неудобны и сбивают с толку и в коде, и в базе (comment.comment). Лучше использовать text, body, content, в конце-концов просто value. А булевы поля для статуса записей быстро себя исчёрпывают, в частности уже сейчас если поле approved === FALSE, то нельзя однозначно сказать, то ли уже одобренная запись была разодобрена, то ли это новая, которую ещё не просматривали, то ли просмотрели и отклонили.
Да, даже если остановиться на булевом поле, то по соглашениям для таких полей имя isApproved, а не approved, что гораздо понятнее. Кстати, тогда Doctrine и метод сгенерирует не getIsApproved, а isApproved, что удобнее. И если потом перейти к каким-то другим статусам, то можно просто переписать код иззера isApproved() не изменяя клиентского кода. Кстати, ещё по замечание по именованию — принято, что тайстамп поля именуются в мире доктрин/симфони как createdAt и updatedAt, более чётко определяя сущность поля.Fesor
06.06.2016 10:03+1маленькая поправка. Можно сделать extra-lazy связь и вместо
filter
использоватьmatching
с критерией. Тогда сформируется правильный SQL запрос и не придется грузить всю коллекцию. Хотя это мелочи.tatenen
08.06.2016 23:54можете показать пример как надо правильно?
SiDz
09.06.2016 08:41+2http://doctrine-orm.readthedocs.io/projects/doctrine-orm/en/latest/tutorials/extra-lazy-associations.html — про ExtraLazy
http://doctrine-orm.readthedocs.io/projects/doctrine-orm/en/latest/reference/working-with-associations.html#filtering-collections — про matching.
Очень удобная и крутая штука. Рекомендую.
ju5tify
Вся суть Symfony.
4 (четыре, ЧЕТЫРЕ) статьи, чтобы сделать один бложик. Поистине, фреймворк для мазохистов.
Давайте теперь все возьмемся за руки, прочтем несколько мантр про энтерпрайз, бандлы и аннотации и дружно пройдем сертификацию у св. Фабьена.
IncorrecTSW
Не вижу проблемы в том, что автор с излишней подробностью расписал для новичков на 4 статьи. При этом затрагивая далеко не только фреймворк.
M-A-XG
Говорят, фреймворки ускоряют разработку.
Но с таким фреймворком, как Симфони приходится долго мудохатся даже с простым блогом.
Вот трезвый взгляд на фрейморки:
http://blog.kpitv.net/article/frameworks-1/ (статья обновлена)
Местная публика явно враждебно настроена против альтернативной точки зрения.
Fesor
я тут хотел опять написать что вы не компетентны говорить об "алтернативных" точках зрения, но потом подумал… а какая разница. Вы только больше расшатываете разницу между разработчиками на рынке труда и мне в принципе это даже в каком-то смысле выгодно. Вопервых адекватные люди не купят этот булшит, а меня интересуют только они, а во вторых за счет больешго количества новичков которые "не любят фреймворки" адекватные люди будут более ценны на рынке труда.
Mariik
Присоединяюсь к Вашему мнению. Очень здравая точка зрения.
AmdY
Не знаю, к счастью или к сожалению, но фреймворки лишь маскируют это отличие. Как только автор вышел за пределы фреймворка и начал писать на php, так сразу вылезла substr, которая не дружит с юникодом. Да и truncate для твига есть в экстеншинах, не знаю включён ли он по дефолту в симфони. И я бы счёл это за ошибку в спешке, если бы не пример с php-шаблоном, где автор проигнорировал экранирование выводимых данных, что является смертным грехом и едва ли не главной фичей твига в сравнении с нативными шаблонами.
norgen
В Symfony еще с версии 2.3 в Twig по умолчанию включено автоматическое экранирование.
Miraage
А для кого сделаны mb_* функции?
AmdY
norgen, miraage так я и писал, что не хватает экранирование в примере с нативным шаблоном, и что substr использовать в эпоху юникода нельзя.
M-A-XG
1. Если фреймворки так просты, то почему на них выше ЗП?
2. Фреймворки и так забиты разработчиками, которые без фреймворка не умеют думать.
3. Адекватные люди — это люди с баблом? Да, у них часто логика типа: вот отвалим дохрена бабла, получим качественный продукт.
Но часто получаем велосипеды на фреймворках.
В конторе, в которой я работал с самописной ЦМС, гребут бабло лопатой, так как хорошее портфолио.
И пофиг, что та ЦМС необновляемая, и пофиг, что с той ЦМС работает только эта студия, и пофиг, что SQL запросы на ней все написаны руками.
Главное навешать клиенту. :)
Кстати да, чем больше людей будет на фреймворках, тем более будут ценны настоящие специалисты.
Я писал и на фреймворках, и на CMS, и на самописи. :)
AmdY
Потому что фреймворки позволяют писать быстро и качественно, при этом и поддержка обходится значительно дешевле.
Вот смотрите в статье есть отсылка емейла, без фреймворка вы бы накодили функцию mail и и боролись бы с кодировками и спамом, а наличие фреймворка позволит вам забыть о таких вещах, при этом без лишних усилий настроите чтобы письмо слалось не сразу, а сервером очередей и не вызывало дедлок под нагрузкой, и не через mail слалось, а через сервисы вроде мейлчип. А ведь плохое письмо убивает конверсию и бизнес несёт большие убытки, не говоря уже о лежащем сайте. Бизнесу срать на фреймворки и cms, но деньги они считать умеют и знают, что лучше брать готовое, чем верить фантазиям велосипедостроителей.
DCrystal
Вот смотрите в статье есть отсылка емейла, без фреймворка
Справедливости ради, отправкой имейлов занимается не фреймворк, а библиотека.
письмо слалось не сразу, а сервером очередей и не вызывало дедлок под нагрузкой
Простите, а как отправка писем может вызывать дедлок?
M-A-XG
Я использую внешнюю библиотеку для почты.
Я ж не против стороннего кода…
Я против глупостей фреймворков.
Fesor
и с такой мотивацией мастерите свои велосипеды? Это как-то нелогично. Ладно бы использовали компоненты и на них строили все.
VolCh
Что вы считаете глупостями фреймворков? В частности глупостью Symfony?
AmdY
Ну так современный фреймворк вроде symfony это и есть набор библиотек. Так что вы занимаетесь самообманом.
aktuba
>1. Если фреймворки так просты, то почему на них выше ЗП?
Может потому, что с фреймворками работают люди чуть умнее тех, кто всегда пишет с нуля? Или потому, что эти люди быстрее решают задачи?
>2. Фреймворки и так забиты разработчиками, которые без фреймворка не умеют думать.
Ну да… Назовите plz пару известных людей, которые ПРОТИВ фреймворков. Реально интересно…
>3. Адекватные люди — это люди с баблом? Да, у них часто логика типа: вот отвалим дохрена бабла, получим качественный продукт.
Нет. «Люди с баблом» — это «люди с баблом». Адекватность и наличие «бабла» никак не пересекаются.
>Кстати да, чем больше людей будет на фреймворках, тем более будут ценны настоящие специалисты.
Серьезно?! ))) Расскажите это людям из мира 1С или Java ;)
>Я писал и на фреймворках, и на CMS, и на самописи. :)
Не верю! (с) Станиславский. Покажите plz, что вы написали на популярном фреймворке. Судя по вашему посту — вы даже результирующий sql-запрос из AR получить не можете, не то чтобы что-то написать))))
Fesor
Это насамом деле очень интересная штука. Допустим разработчик который хорошо знает фреймворк напишет продукт в 3 раза быстрее и за в 2 раза большие деньги, в итоге все в профите. И бизнесу это выгоднее, и продукт раньше выйдет, и разработчик получил больше.
M-A-XG
Но на фреймворках продукты стоят дороже.
VolCh
Не факт. Один и тот же продукт (пускай тот же блог с общепринятыми функциональными возможностями и требованиями к безопасности) я напишу быстрее на фреймворке типа Symfony, чем с нуля, поскольку буду тратить значительно меньше времени на такие велосипеды как роутинг, формы и их валидацию, маппинг программных сущностей на базу, аутентификацию и авторизацию, защиту от популярных видов атак и т. п., сосредотачиваясь только на специфичных требованиях заказчика. А быстрее для заказчика значит дешевле. И то, могу повысить ставку за то, что придётся заниматься неинтересными делами.
Единственный вариант, по-моему, когда написанный с нуля продукт стоит дешевле — очень низкие требования к качеству кода, включая требования по безопасности, допускающие привлечение очень низкоквалифицированных разработчиков, пользующихся некомпетентностью заказчика в вопросах разработки, когда он может сформулировать, что должно быть в продукте, но не может сформулировать чего быть не должно, а во фреймворках большинство «не должно» из коробки.
M-A-XG
1. Самопись не значит, что каждый раз все пишут с нуля…
2. У меня нету роутинга, маппинга. Проще код — все довольны.
3. От атак фреймворк не защищает.
4. Во фреймворках нету функционала в коробке, это не ЦМС.
Fesor
M-A-XG
1. Не факт.
2. Это Ваша выдумка.
3. Никак не защищает. Возможно в шаблонизаторе есть htmlspecialchars по умолчанию.
Fesor
Факт. В 99.9% случаев самопись не оправдана ничем кроме как желанием разработчиков поразвлекаться. Это не означает что в ситуациях с фреймворками поддержка всегда будет дешевле — там можно тоже натворить дел, но в среднем стоимость и главное скорость разработки обычно выше, меньше рисков и т.д. У вас же может быть просто не репрезентативная выборка (я полагаю вы работали только с yii причем старым который сейчас смешно рассматривать как достойное решение).
Простой вопрос. Как вы генерируете ссылки на другие маршруты?
M-A-XG
2. В конфигах прописаны пути.
В Yii 1.1 пути хардкодятся :)
zelenin
что значит «хардкодятся»? в yii1/2 как и в симфони и зенде, ларавеле и фалконе генерация урлов работает идентично — generateUrl($route, $params)
M-A-XG
А не, работает. Сорри.
Это у нас аутсорсеры наговнокодили :)
Только контроллер не знает какие пути ему генерировать :)
Fesor
они точно так же на вашем решении наговнокодят, это будет даже в разы проще. Вы же понимаете что проблема в головах а не в инструментах?
VolCh
Если структуру приложения не пишут каждый раз с нуля, то значит просто используют самописный фреймворк
в корне сайта 100500 *.php файлов, ссылки на которые в шаблоне руками пишутся?
Прямо в базу с браузера стучитесь? Клиент mysql на javascript?
От всех — нет. От распространенных типа csrf или sqlj — да, если не пытаться эту защиту отключать.
Во фреймворках есть функциональность в коробке, нет готовой функциональности по управлению контентом.
aktuba
А есть такое сравнение? Покажите plz.
calg0n
1. Потому что фреймворки — узкоспециализированная вещь. Найти толкового разработчика с опытом не так просто.
2. И что в этом плохого? Больше разработчиков, больший выбор для работодателя.
3. Люди с баблом разные бывают, как тражиры так и скряги.
VolCh
1. Полностью кастомные решения куда более узкоспециализрованная вещь, чем решения на сколь-нибудь популярных фреймворках.
calg0n
С кастомными решениями проблема в том что нужно не только найти специалиста, но и научить его. На это тратится больше времени и средств, чем если бы в продукте использовался популярный фреймворк.
tatenen
Чем менее компетентен он (м-а-xg), тем больше зп у меня
SiDz
Жаль автор не показал, своё «самое лучшее» творение, например на гитхабе.
тот самый код, который, по его словам, может понять каждый разработчик.
P.s. согласен с Fesor.
Fesor
К слову да. M-A-XG, выложите на гитхаб как время найдется. Хоть спорить потом можно будет более предметно.
M-A-XG
http://blog.kpitv.net/article/когда-будет-публичный-релиз-ядра-15373/
Fesor
Так в том же и соль. Сразу указать на недостатки. Один человек не в состоянии продумывать такие вещи.
От вас требуется лишь пример а не готовый релиз очередного фреймворка
не беспокойтесь. Ваш код никому не нужен.
так же не беспокойтесь
и так сойдет
напишите в ридми что это только пример "ноу хау" и что бы не пользовались
izac
А что вы там хотите увидеть если автор сам пишет
?MetaDone
http://govnokod.ru/19878#comment323654
автор показал его здесь
izac
да есть и по свежее
http://blog.kpitv.net/article/how-to-set-title/
LeGront
Не к альтернативной, а к специальному разжиганию срача. Как я вижу это уже не первый аккаунт тут. Надо бы как-то банить за мультиводство.
zelenin
А, знаете, я прочел статью. Могу сказать, что альтернативной версии там ноль (хотя честно я ожидал такой версии). Но есть очень мало знаний, подмены понятий и обоснования своей т.з. на примере легаси-фреймворка 2008 года.
aktuba
Ну в вашем посте бреда не меньше, а то и больше… Особенно понравилось вот это:
>Отстутствие нужных фич, которые можно легко реализовать в самописи.
Т.е. написать фичу на фреймворке — это плохо, написать без фреймворка — это хорошо)
M-A-XG
Отстутствие нужных фич, которые можно легко реализовать в самописи, но сложно на фреймворке из-за его ограничений.
aktuba
Эмм… Например?
M-A-XG
Я так и не помню все.
Не записывал. :)
Нормальные выборки из БД.
Фреймворковские конструкторы запросов — УГ.
Ну и ещке, к примеру, мой старший товарищ на прошлой работе зачем-то часть шаблона контроллера вынес в виджет, ибо оно должно выводится выше вызова контроллера.
А в виджете при этом было типа такого:
echo SOME_HTML // быдлопарсер не пропускает;
Говорю, кто эту херню написал?
Он обидился, говорит, это не херня, просто Yii что-то там не позволяет :)
aktuba
>Нормальные выборки из БД.
Абсолютно во всех фреймворках, с которыми я работал, можно использовать plain-sql. Даже если взять в пример не любимый вами yii 1, насколько я помню, можно сделать так: $list= Yii::app()->db->createCommand('select * from post')->queryAll();
Так, для информации, я сам не использую AR/ORM, но с удовольствием пользуюсь фреймворками.
>Он обидился, говорит, это не херня, просто Yii что-то там не позволяет :)
Ну т.е. отваришь ваш мудак, но фреймворки — это плохо)))).
M-A-XG
>можно использовать plain-sql
1. И привет sql-инъекции. Толковые разработчик заискейпит параметры или использует плейсхолдеры, но часто замечаю, что программисты на фреймворках уверены, что фреймворк каким-то магическим способом защищает от инъекций и допускают ошибки. :)
2. Хотелось бы, чтобы sql-запросы писались не вручную, хотелось бы большей гибкости.
Вручную замахаешься писать ифы при построении сложного запроса, расставлять пробелы, запятые и т.д.
Fesor
M-A-XG
1. Ну так большинство разрабов не применяет подготовленные выражения.
2. Вскоре наши сайты переедут на Symfony, посмотрим. :)
Fesor
MetaDone
мне кажется все ооочень понятно — что и откуда берем.
а для случаев сложнее никто не мешает сделать plain-sql и передать туда нужные параметры. Если учесть что в типичном приложении наверно 80% простых запросов — такие штуки увеличивают скорость работы и повышают читаемость.
Ну а наговнокодить можно в чем угодно, все ограничено только кривизной рук. В самописном движке шансы на получение неструктурированной нечитабельной хрени резко растут.
M-A-XG
1. Такой конструктор запроса неудобен, как раз о нем в статье написано.
2. Есть потенциальная возможность инъекций.
>а для случаев сложнее никто не мешает сделать plain-sql и передать туда нужные параметры
Это примерно как жирные модели.
Я против такого. Это псевдопрограммирование.
Средний уровень говнокода на самописи скорее всего буде выше, так как выше свобода.
Но могут существовать конкретно взятые качественные самописи.
Фреймворк только маскирует говнокодерство разработчика.
MetaDone
1. Удобство — понятие субъективное, если не умеете пользоваться — что угодно будет неудобным
2. Используйте prepared statement, а не вручную склеивайте запрос как строки
Ваша «конкретно взятая качественная самопись» лежит здесь. Если бы вы предложили что-то вменяемое, то диалог имел бы смысл. Конкретно ваша ересь не ускорит разработку проекта, не сделает его более структурированным и легким для входа, поэтому ваше мнение по этому вопросу не следует воспринимать серьезно. Если только вы в таком копаетесь — то пишите что угодно, главное коммерческие проекты не делайте
Ваше незамаскировано, в чистом виде — ни структуры, ни стандартов, в контроллере сборщик запросов. Уж не с таким кодом говорить о правильности подходов и архитектуре.
M-A-XG
1. Вы даже не способны заценить красоту игры. :) Мудохаетесь с такими неконструкторами и думаете, что это вершина технической мысли. :)
2. Я не о себе говорю, а вообще. Большинство разрабов об этом даже не знает.
Приводите ссылку не на коммент, а на сам код, чтобы больше людей могло заценить красоту игры.
Любому битриксоиду данный код понятен и он должен заценить.
Я на заказ пока и не пишу. Только для себя. А то жалко отдавать ядро за бесцень. :)
И это только кусок контроллера.
В контроллере не сборщик запросов, а АПИ запрос.
Но вы просто задурманены фреймворками и не способны оценить красоту игры.
Только прикрываетесь фреймворками, а сами продумать и реализовать архитектуру неспособны.
Fesor
вы даже не способы сформулировать четкий список минусов.
Большинство не знают что используют prepared statements когда работают с query builder-ами. Они дают возможность сэтить параметры к запросам и люди просто этим пользуются. В этом собственно соль, для этого абстракции и вводятся. Что бы меньше думали о всяких там мелочах, о которых кто-то за нас уже подумал.
Жалко, но придется. Вы впустую тратите свое время.
Задача контроллера — дернуть один метод приложения (или два, один на запись и один на чтение). Ну и соответственно подготовить данные на вход и результат для вывода. Все. Никаких запросов в базу оно делать не должно.
Какой игры?
Дайте определение термина "архитектура" в контексте программирования.
M-A-XG
>Большинство не знают что используют prepared statements когда работают с query builder-ами.
Я об этом и говорю. Когда они попадают в чистое поле, то творят дыры, ибо сами думать не способны.
>Задача контроллера — дернуть один метод приложения
Это извращенное понимание MVC. А конкретно — жирные модели.
VolCh
Жирность модели определяется предметной областью приложения (при правильном применении MVC). Есть приложения, где бизнес-логика заключается в тупом CRUD, то есть отсутствует по сути, а есть приложения, где очень сложная инфраструктура и большое множество сложных представлений составляют лишь малую часть от кода бизнес-логики.
M-A-XG
Согласен.
Но в примере на говнокоде — тупой одноразовый CRUD.
Fesor
тупой одноразовый часто становится не таким тупым. А сделать минимальное разделение ответственности не сильно усложняет код, даже наоборот.
p.s. для такого тупого CRUD-а есть кодогенераторы вообще.
M-A-XG
Когда станет не тупым, тогда и будем думать.
Вы просто не понимайте красоты игры, поэтому вам кажется, что должно быть разделение.
В данном случае разделения быть не должно. :)
MetaDone
маньяк, режущий жертве лицо осколком стекла, тоже может думать, что украшает ее. странные в общем у вас представления о красоте
Fesor
Просто жертва не понимает красоты игры.
p.s. Меня одного настараживают подобные фразы?)
MetaDone
мне тоже кажется странным, вспоминается «let's play a game» от пилы
M-A-XG
>маньяк
Оу-оу-оу, палехче.
Тебя, Остап, не в ту степь занесло.
VolCh
Так для тупого CRUD и модель не нужна, максимум DTO.
MetaDone
если это красиво — то боюсь представить как выглядит корявый код по вашим понятиям. когда излечитесь от синдрома, тогда и продолжим диалог
Fesor
Чем он не удобен?
Нет.
tatenen
а мне надоело уже в каментах к каждой статье видеть эти ссылки на блоги и неадекватные доводы.
сказал а, говори б. покажи уже свое детище — не стыдись.
M-A-XG
Мне тоже много чего надоело. :)
Я критикую фреймворки.
И я не обещал показать свое детище.
Бисер перед свиньями метать не собираюсь.
Может это ты неадекват?
Fesor
вы с ними так много работали, я смотрю. Вы критикуете фреймворки на примере только одного, судя по всему с которым пришлось поработать. И вам уже объяснили что Yii это в принципе трэйдоф между качеством и скоростью разработки. Так вы еще и 1-ый Yii смотрите который уже давно устарел.
Fesor
Почитал ваш бред про скрам, печально.
M-A-XG
Хотите обсудить скрам, пишите у меня на сайте, а не сюда тащите… :)
tatenen
в каждом твоем каменте, когда ты пытаешься что-то оспорить то приводишь пример свое детище. а у меня не так а у меня вот так.
я тоже могу сказать вы все говно — я один дантарьян. но мои слова все адекватные люди пропустят мимо ушей потому что нет как говорится пруфов. я читал что ты не хочешь открывать свое детище, хочешь на нем заработать и тд. но ведь ты можешь для подкрепления своих слов сделать гист конкретного куска кода и показать в качестве аргументов. что тебе мешает?
Я считаю что фреймворк это склад кирпичей. складывай дом сам.
Самопись это отливание кирпичей с нуля. дольше, на первый взгляд дешевле обходится кирпич, но в итоге будет дороже (поддержка, порог входа)
Я не против самописи, но если уже есть готовый компонент — почему бы его не использовать? не нравится пиши свой — трать время.
Мне как-то техдир запретил использовать pear библиотеки. Типа он напишет свое лучше и тд. в итоге куча дыр, багов, негатива и куча времени на их исправление, пока не взял инициативу в свои руки не начал использовать pear ( было еще до композера).
Половина недостатков фремворков по твоей версии — это неудобно, непонятно, сложно.
Все это субъективно.
Мне допустим все удобно, понятно, и дебажить не сложно — есть xdebug.
Некоторые пункты вообще смутили:
— Нету добавления хлебных крошек.
— У добавленных цсс/жс файлов нету признака, что файл изменился
— Нету нормальных готовых компонентов, например меню. Меню каждый обязан написать сам
На мой взгляд это вообще не должно обсужаться. Это больше относится к cms. cms != framework. Да их нет, и быть не должно. Напиши сам. Или посмотри мб кто нить уже написал похожее и выложил на гитхаб/композер.
M-A-XG
А зачем примеры кода?
Вам же даже сам подход не нравится…
>Я не против самописи, но если уже есть готовый компонент — почему бы его не использовать?
Допустим я не против фреймворков. Но когда у меня все написано и все сайты на самописи, то смысл в них? :)
А также у них есть фатальные недостатки. :)
Если у вас нету своего ядра, то пользуйтесь фреймворками дальше. :)
>Половина недостатков фремворков по твоей версии — это неудобно, непонятно, сложно.
Просто есть куда стремиться…
>Некоторые пункты вообще смутили
Не знаю, почему смутили. О хлебных крошках и меню я говорю не о шаблонах, а скорее о едином интерфейсе у всех на одном фреймворке.
padlyuck
>А зачем примеры кода?
За тем, что Вы в каждом посте, затрагивающем фреймворки, диким ором орёте о том, что фреймворки гуано, а Вы такой замечательный и у Вас есть своё собственное МЕГАЯДРИЩЕ холеное и лелеяное с 2000какогото года, но Вы нам его не покажете, потому что мы все тут тупорылые снобы, не способные осознать всю красоту и элегантность Ваших решений. А те из присутствующих, кто проникнется Вашими идеями, обязательно бросят свой говёный %framework_name% и побегут пользоваться Вашим божественным продуктом, тем самым лишив Вас чудовищных прибылей.
Вы постоянно упоминаете какое-то свое ядро абстрактное в вакууме, но пока никто не видил пруфов о том, что оно действительно такое удобное, как Вы его описываете. Соотвественно все упоминания о том что «все фреймворки гавно, у меня свое удобное и правильное ядро» воспринимаются как «я не осилил yii, поэтому написал своё»
Иметь собственное мнение — это хорошо, но выпячивать его так, как это делаете Вы — абсолютно не обязательно. Тем более что, повторюсь, никто не видел Вашего ядра и о том как всё в нем правильно и замечательно мы можем судить только с Ваших слов.
M-A-XG
Я ж спорю не с кодом фреймворков, а с самим подходом…
Зачем тут мой код? :)
А мое ядро не публикуется не только потому, что я боюсь потерят мегаприбыли.
Там и другие причины есть. А платным оно вряд ли будет. Будут другие способы монетизации.
>я не осилил yii, поэтому написал своё
Ядро было написано до того, как я сел за Yii.
В Украине очень популярный Yii, и на 3 работах доводилось работать именно с дремучим 1.1 :)
Он везде был приготовлен некачественно. Но мою критику Yii никто не воспринимал тоже.
padlyuck
>Я ж спорю не с кодом фреймворков, а с самим подходом…
Я так и сказал. Знаете выражение «характер как и член должен быть твердым, и характер так же как и член не стоит выпячивать»? Вот вы именно этим и занимаетесь, выпячиваете свою альтернативную точку зрения. Это было бы хоть сколько-нибудь обоснованным, если бы вы могли привести хотябы куски кода, чтобы не быть голословным, возможно вашу точку зрения и приняли бы как-то. Но вы свои слова о том, что фреймворки говно, а у вас все круто, подтверждаете только своими же словами. Вы не википедия, на слово вам никто не поверит.
>В Украине очень популярный Yii, и на 3 работах доводилось работать именно с дремучим 1.1
ок, не спорю, yii популярен, но то что вы работали с дремучей её версией — это еще не означает, что все фреймворки говно. Это даже не означает, что yii2 говно. Но вы же наверное даже и не пробовали заглянуть в yii2?
Вам не хватает каких-то хлебных крошек, меню и еще какой-то фигни которая не относится к фреймворку. Кто мешает вам написать средствами фреймворка компонент или модуль который реализует нужный вам функционал и тягать его по всем проектам на этом фреймворке? Когда мне чего-то не хватает в юи я просто открываю интересующий компонент юи, полчаса трачу на то, чтобы изучить его код и пишу нужный мне функционал с использованием кода фреймворка(или без использования если нужна универсальность). Дальше этот функционал упаковывается в композер пакет и я могу к любому проекту его прикрутить.
>Он везде был приготовлен некачественно.
Будьте уверенны наговнокодить можно на чем угодно. Ваша самопись не исключение. Человек не знакомый с деталями вашей реализации с тем же успехом наговнокодит и на вашем ядре. Тут вопрос опять же не к фреймворку, а к прямоте рук которые на этом фреймворке что-то готовят.
Опять же, мы почему-то допускаем, что ваше ядро если не идеально, то как минимум написано хорошо, но пруфов никаких и мы вынуждены верить вам на слово. На поверке то может оказаться велосипед на костылях? :)
Я собственно говоря к чему растекся мыслью по древу? Обосновывайте свою точку зрения какими-то более авторитетными источниками или примерами своих реализаций. В противном случае — все что вы делаете — просто захламляете темы неподтвержденным мракобесием.
M-A-XG
Смысл в кусках кода, если всех устраивает подход фреймворков?
Я не говорю, что у меня все круто.
У меня нет описанных недостатков.
>это еще не означает, что все фреймворки говно
Во всех фреймворках похожие подходы…
>Кто мешает вам написать средствами фреймворка компонент или модуль который реализует нужный вам функционал и тягать его по всем проектам на этом фреймворке?
Собственно поэтому меня устраивает моя самопись, которую, как вы наконец-то догадались, я не пишу каждый раз с нуля, а тягаю с проекта в проект.
>Будьте уверенны наговнокодить можно на чем угодно.
Просто фреймворки приподносятся как нечто такое, на котором будет писаться только качественный код, типа приучает к чему-то :)
padlyuck
>Смысл в кусках кода, если всех устраивает подход фреймворков?
>Я не говорю, что у меня все круто.
тогда зачем вы так яро пропагандируете свою альтернативно-ориентированную точку зрения? Или вы думаете, что после разговора с вами тот же господин fesor к буям выкинет свой многолетний опыт работы с фреймворками и побежит строгать новый идеальный велосипед? Если уж говорить о своих идеальных велосипедах и конвеере работ, то нормальные люди выбирают фреймворк, на нем пишут свою цмс, к ней пишут кучу всяких модулей которые могут понадобиться, и вот на этой цмс уже клепают сайты. А написать ядро с нуля(пусть и один раз), потом его поддерживать и отлавливать баги и дыры — вы уж меня извините — это дурость и сизифов трудю Как по моему мнению — лучше уж переложить этот труд на разработчиков и комьюнити. Хотя если у вас вагон времени и с десяток сертификатов спеца по компьютерной безопасности — вперед на баррикады
>Во всех фреймворках похожие подходы…
может быть это чего-то да значит, не задумывались? ;)
>как вы наконец-то догадались
то что вы её тягаете из проекта в проект все знали изначально, вам скорее указывали на то, что вашу самопись в случае каких-то изменений может быть проблематично обновить на тех проектах которые вы уже реализовали с её помощью(это нужно не всегда, но тем не менее)
>Просто фреймворки приподносятся как нечто такое, на котором будет писаться только качественный код
Вы свою точку зрения преподносите так же. «Я не использую фреймворки, я крут. А все кто используют их — тупые макаки за компьютерами»
M-A-XG
Я не призываю никого выбрасывать что-то.
Читайте выше.
>нормальные люди выбирают фреймворк, на нем пишут свою цмс, к ней пишут кучу всяких модулей которые могут понадобиться, и вот на этой цмс уже клепают сайты
А не проще взять готовую CMS?
>А написать ядро с нуля(пусть и один раз)
Это не так и сложно.
Мое ядро всего 50 КБ.
>может быть это чего-то да значит, не задумывались? ;)
Они все пошли по ложной дороге.
>вашу самопись в случае каких-то изменений может быть проблематично обновить на тех проектах которые вы уже реализовали с её помощью(это нужно не всегда, но тем не менее)
С обновлениями моей самописи проблем нету.
На всех сайтах всегда последняя версия, правда обновления не автоматические, а полуавтоматические.
С обновлениями самописей вообще могут быть проблемы, как у меня на одной работе, где у каждого клиента было свое ядро.
>Вы свою точку зрения преподносите так же.
Это только из-за того, как преподносятся фреймворки.
Я не говорю, что на самописях только качественный код, я говорю, что качественный код возможен не только на фреймворках, но и на самописях.
И я говорю не столько о коде, сколько о принципах.
И не столько о самописи, сколько о глупых принципах фреймворков.
Самопись выступает как альтернатива, когда все вокруг сошли с ума.
Fesor
А как они преподносятся? Они преподносятся как инструменты для решения проблем. То что люди начинают зацикливаться на них это проблема людей. Это как с языками программирования, есть фанаты и все тут. Рассматривать другие языки они даже не будут, хотя для профессианального развития нужно знать хотя бы парочку очень разных языков с разными подходами.
У вас есть опыт работы только с Yii. А там большая часть "принципов" программирования нарушены в угоду скорости разработки. О каких тогда принципах мы говорим? SOLID дурацкая идея? GRASP?
M-A-XG
А где я говорил, что SOLID дурацкая идея?
Fesor
ну фреймворки вроде симфони или zend же дурацкие, стало быть и SOLID дурацкая концепция.
zelenin
>> Во всех фреймворках похожие подходы…
https://github.com/zendframework/zend-expressive
тут вас что например не устраивает для инфо?
padlyuck
<sarcasm>а там есть меню и хлебные крошки? без них любой фреймворк гуано и пофигу какая у него история и кто его разрабатывает.</sarcasm>
zelenin
это вы верно говорите, но все же хотелось бы услышать более компетентное мнение )
Fesor
ядро означает тот факт, что оно внутри вашего приложения. То есть никакого Inversion of Control. Фреймворки же обычно сверху, снаружи приложения.
Что будет с вашим приложением через 10 лет?
Вам?
Что если я вам скажу что тенденция выносить графический интерфейс полностью отдельно в виде SPA только продолжит набирать популярность? И в этом случае серверу об этом париться уже не нужно? Например у меня все проекты за последние 2.5 года с SPA или мобильными приложениями, "хлебные крошки" и любая навигация реализуются уже на клиенте.
M-A-XG
>ядро означает тот факт, что оно внутри вашего приложения. То есть никакого Inversion of Control.
Ядро отдельно, пользовательский код отдельно…
>Что будет с вашим приложением через 10 лет?
Одному сайту уже 8 лет.
>Вам?
Создателям фреймворков
Но они и их адепты считают, что все норм :)
>Что если я вам скажу что тенденция выносить графический интерфейс полностью отдельно в виде SPA только продолжит набирать популярность?
Не знаю, что такое SPA.
Но я говорил не о графическом интерфейсе, а об АПИ.
tatenen
4 статьи в которых все разжевано до мелочей. мне очень понравилось знакомиться с фреймворком