Githubプロジェクト
必要なマニュアルの一部をインストールする方法については、 リンクでリポジトリの説明を参照してください 。 (たとえば、前のレッスンを経ずにこのレッスンから始めたい場合)
ホームページ 投稿とコメント。
メインページに最近のエントリのリストが表示されるようになりましたが、これらのエントリのコメントに関する情報はありません。 エッセンスコメントがあります。この情報を提供するためにメインページに戻ることができます。 BlogエンティティとCommentエンティティの間に関係を確立したので、Doctrine 2は投稿に対するコメントを受信できることを知っています(
$ commentsオブジェクトをBlogエンティティに追加したことを思い出してください)。 ホームページテンプレートを更新しましょう
src / Blogger / BlogBundle / Resources / views / Page / index.html.twig{
{
<footer class="meta">
<p>Comments: {{ blog.comments|length }}</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>
{
comments getter ,
Twig length . ,
http://localhost:8000/ .
, Doctrine 2
$comments Blog Comment. Blog.
src/Blogger/BlogBundle/Entity/Blog.php , , Doctrine 2 ,
$comments Comment? BlogRepository, ( ) , Comment .
src/Blogger/BlogBundle/Repository/BlogRepository.php
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();
}
, Doctrine 2 , , Comment , ,
{{blog.comments | length}}. , . , , Doctrine 2 . Doctrine 2 , … , HTTP-.

Doctrine 2 , Doctrine 2 HTTP.

, . ,
getLatestBlogs() BlogRepository. , , .
WHERE t0.blog_id = ? ,
? (
id ).
{{blog.comments}} . , , Doctrine 2 (lazily) Comment, Blog.
, . Doctrine2 , . , Blog Comment . QueryBuilder BlogRepository.
src/Blogger/BlogBundle/Repository/BlogRepository.php
public function getLatestBlogs($limit = null)
{
$qb = $this->createQueryBuilder('b')
->select('b, c')
->leftJoin('b.comments', 'c')
->addOrderBy('b.created', 'DESC');
if (false === is_null($limit))
$qb->setMaxResults($limit);
return $qb->getQuery()
->getResult();
}
, Doctrine 2 , . , comment blog.
, . 2 , , . , . , , Doctrine 2. , .
, , .
src/Blogger/BlogBundle/Resources/views/Page/index.html.twig{
{
<footer class="meta">
<p>Comments: <a href="{{ path('BloggerBlogBundle_blog_show', { 'id': blog.id }) }}
<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>
{
.
(sidebar)
symblog .
.
, . . BlogRepository . BlogRepository
src/Blogger/BlogBundle/Repository/BlogRepository.php
public function getTags()
{
$blogTags = $this->createQueryBuilder('b')
->select('b.tags')
->getQuery()
->getResult();
$tags = array();
foreach ($blogTags as $blogTag)
{
$tags = array_merge(explode(",", $blogTag['tags']), $tags);
}
foreach ($tags as &$tag)
{
$tag = trim($tag);
}
return $tags;
}
public function getTagWeights($tags)
{
$tagWeights = array();
if (empty($tags))
return $tagWeights;
foreach ($tags as $tag)
{
$tagWeights[$tag] = (isset($tagWeights[$tag])) ? $tagWeights[$tag] + 1 : 1;
}
uksort($tagWeights, function() {
return rand() > rand();
});
$max = max($tagWeights);
$multiplier = ($max > 5) ? 5 / $max : 1;
foreach ($tagWeights as &$tag)
{
$tag = ceil($tag * $multiplier);
}
return $tagWeights;
}
, (CSV) , .
getTags().
getTagWeights() , «» , . , .
, .
src/Blogger/BlogBundle/Controller/PageController.php
public function sidebarAction()
{
$em = $this->getDoctrine()
->getManager();
$tags = $em->getRepository('BloggerBlogBundle:Blog')
->getTags();
$tagWeights = $em->getRepository('BloggerBlogBundle:Blog')
->getTagWeights($tags);
return $this->render('BloggerBlogBundle:Page:sidebar.html.twig', array(
'tags' => $tagWeights
));
}
, 2 BlogRepository , .
src/Blogger/BlogBundle/Resources/views/Page/sidebar.html.twig{
<section class="section">
<header>
<h3>Tag Cloud</h3>
</header>
<p class="tags">
{% for tag, weight in tags %}
<span class="weight-{{ weight }}">{{ tag }}</span>
{% else %}
<p>There are no tags</p>
{% endfor %}
</p>
</section>
. «» .
for , ,
tag ,
weight .
for,
Twig.
BloggerBlogBundle,
src/Blogger/BlogBundle/Resources/views/layout.html.twig , (placeholder) . . , Twig
render , sidebar Page.
{
{
{% block sidebar %}
{{ render(controller('BloggerBlogBundle:Page:sidebar' ))}}
{% endblock %}
.
src/Blogger/BlogBundle/Resources/public/css/sidebar.css.sidebar .section { margin-bottom: 20px; }
.sidebar h3 { line-height: 1.2em; font-size: 20px; margin-bottom: 10px; font-weight: normal; background: #eee; padding: 5px; }
.sidebar p { line-height: 1.5em; margin-bottom: 20px; }
.sidebar ul { list-style: none }
.sidebar ul li { line-height: 1.5em }
.sidebar .small { font-size: 12px; }
.sidebar .comment p { margin-bottom: 5px; }
.sidebar .comment { margin-bottom: 10px; padding-bottom: 10px; }
.sidebar .tags { font-weight: bold; }
.sidebar .tags span { color: #000; font-size: 12px; }
.sidebar .tags .weight-1 { font-size: 12px; }
.sidebar .tags .weight-2 { font-size: 15px; }
.sidebar .tags .weight-3 { font-size: 18px; }
.sidebar .tags .weight-4 { font-size: 21px; }
.sidebar .tags .weight-5 { font-size: 24px; }
, . BloggerBlogBundle
mainsrc/Blogger/BlogBundle/Resources/views/layout.html.twig{
{
{% block stylesheets %}
{{ parent() }}
<link href="{{ asset('bundles/bloggerblog/css/blog.css') }}" type="text/css" rel="stylesheet" />
<link href="{{ asset('bundles/bloggerblog/css/sidebar.css') }}" type="text/css" rel="stylesheet" />
{% endblock %}
{
assets web, assets.
$ php app/console assets:install web
. , «» (), .
,
.
-, . CommentRepository
src/Blogger/BlogBundle/Repository/CommentRepository.php<?php
public function getLatestComments($limit = 10)
{
$qb = $this->createQueryBuilder('c')
->select('c')
->addOrderBy('c.id', 'DESC');
if (false === is_null($limit))
$qb->setMaxResults($limit);
return $qb->getQuery()
->getResult();
}
sidebar
src/Blogger/BlogBundle/Controller/PageController.php
public function sidebarAction()
{
$commentLimit = $this->container
->getParameter('blogger_blog.comments.latest_comment_limit');
$latestComments = $em->getRepository('BloggerBlogBundle:Comment')
->getLatestComments($commentLimit);
return $this->render('BloggerBlogBundle:Page:sidebar.html.twig', array(
'latestComments' => $latestComments,
'tags' => $tagWeights
));
}
blogger_blog.comments.latest_comment_limit . ,
src/Blogger/BlogBundle/Resources/config/config.yml
parameters:
blogger_blog.comments.latest_comment_limit: 10
, .
src/Blogger/BlogBundle/Resources/views/Page/sidebar.html.twig{
{
<section class="section">
<header>
<h3>Latest Comments</h3>
</header>
{% for comment in latestComments %}
<article class="comment">
<header>
<p class="small"><span class="highlight">{{ comment.user }}</span> commented on
<a href="{{ path('BloggerBlogBundle_blog_show', { 'id': comment.blog.id }) }}#comment-{{ comment.id }}">
{{ comment.blog.title }}
</a>
[<em><time datetime="{{ comment.created|date('c') }}">{{ comment.created|date('Y-m-d h:iA') }}</time></em>]
</p>
</header>
<p>{{ comment.comment }}</p>
</p>
</article>
{% else %}
<p>There are no recent comments</p>
{% endfor %}
</section>
,
.

Twig
, ,
2011-04-21. , , , , ,
3 . Comment ,
{{comment.created|date (' h:iA Y-m-d')}}.
, Comment. , Twig. Twig , Extension.
extension Twig , . Twig , .
{{ comment.created|created_ago }}. ,
2 .
Twig
src/Blogger/BlogBundle/Twig/Extensions/BloggerBlogExtension.php<?php
namespace Blogger\BlogBundle\Twig\Extensions;
class BloggerBlogExtension extends \Twig_Extension
{
public function getFilters()
{
return array(
new \Twig_SimpleFilter('created_ago', array($this, 'createdAgo')),
);
}
public function createdAgo(\DateTime $dateTime)
{
$delta = time() - $dateTime->getTimestamp();
if ($delta < 0)
throw new \InvalidArgumentException("createdAgo is unable to handle dates in the future");
$duration = "";
if ($delta < 60)
{
$time = $delta;
$duration = $time . " second" . (($time > 1) ? "s" : "") . " ago";
}
else if ($delta <= 3600)
{
$time = floor($delta / 60);
$duration = $time . " minute" . (($time > 1) ? "s" : "") . " ago";
}
else if ($delta <= 86400)
{
$time = floor($delta / 3600);
$duration = $time . " hour" . (($time > 1) ? "s" : "") . " ago";
}
else
{
$time = floor($delta / 86400);
$duration = $time . " day" . (($time > 1) ? "s" : "") . " ago";
}
return $duration;
}
public function getName()
{
return 'blogger_blog_extension';
}
}
. getFilters() , .
created_ago.
createdAgo, DateTime , DateTime.
, Twig ,
src/Blogger/BlogBundle/Resources/config/services.ymlservices:
blogger_blog.twig.extension:
class: Blogger\BlogBundle\Twig\Extensions\BloggerBlogExtension
tags:
- { name: twig.extension }
BloggerBlogExtension Twig .
Twig .
created_at .
src/Blogger/BlogBundle/Resources/views/Page/sidebar.html.twig{
{
<section class="section">
<header>
<h3>Latest Comments</h3>
</header>
{% for comment in latestComments %}
{
<em><time datetime="{{ comment.created|date('c') }}">{{ comment.created|created_ago }}</time></em>
{
{% endfor %}
</section>
http://localhost:8000/ , 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|created_ago }}</time></p>
</header>
<p>{{ comment.comment }}</p>
</article>
{% else %}
<p>There are no comments for this post. Be the first to comment...</p>
{% endfor %}
Twig, Twig-Extensions GitHub. pull request , , .
Url
URL id . , SEO. , URL
http://localhost:8000/1 , -
http://localhost:8000/1/a-day-with-symfony2 . slug URL. slug , ASCII
— .
slug.
src/Blogger/BlogBundle/Resources/config/routing.yml
BloggerBlogBundle_blog_show:
path: /{id}/{slug}
defaults: { _controller: "BloggerBlogBundle:Blog:show" }
requirements:
methods: GET
id: \d+
id,
slug ,
src/Blogger/BlogBundle/Controller/BlogController.php
public function showAction($id, $slug)
{
}
.
, , . Symfony2 . , . defaults.
BloggerBlogBundle_blog_show:
path: /{id}/{slug}
defaults: { _controller: "BloggerBlogBundle:Blog:show", comments: true }
requirements:
methods: GET
id: \d+
public function showAction($id, $slug, $comments)
{
}
, http://localhost:8000/1/symfony2-blog $comments true showAction
Slug
, slug , slug. , slug Blog .
Blog
Blog slug.
src/Blogger/BlogBundle/Entity/Blog.php
class Blog
{
protected $slug;
}
$slug. .
$ php app/console doctrine:generate:entities Blogger
.
$ php app/console doctrine:migrations:diff
$ php app/console doctrine:migrations:migrate
slug slugify symfony1
Jobeet . slugify Blog
src/Blogger/BlogBundle/Entity/Blog.php
public function slugify($text)
{
$text = preg_replace('#[^\\pL\d]+#u', '-', $text);
$text = trim($text, '-');
if (function_exists('iconv'))
{
$text = iconv('utf-8', 'us-ascii//TRANSLIT', $text);
}
$text = strtolower($text);
$text = preg_replace('#[^-\w]+#', '', $text);
if (empty($text))
{
return 'n-a';
}
return $text;
}
, slug slug, .
setTitle slug. Blog
src/Blogger/BlogBundle/Entity/Blog.php
public function setTitle($title)
{
$this->title = $title;
$this->setSlug($this->title);
}
setSlug.
src/Blogger/BlogBundle/Entity/Blog.php
public function setSlug($slug)
{
$this->slug = $this->slugify($slug);
}
slug.
$ php app/console doctrine:fixtures:load
, . .
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, 'slug': blog.slug }) }}">{{ 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, 'slug': blog.slug }) }}">Continue reading...</a></p>
</div>
<footer class="meta">
<p>Comments: <a href="{{ path('BloggerBlogBundle_blog_show', { 'id': blog.id, 'slug': blog.slug }) }}
<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 %}
,
src/Blogger/BlogBundle/Resources/views/Page/sidebar.html.twig{
{
<a href="{{ path('BloggerBlogBundle_blog_show', { 'id': comment.blog.id, 'slug': comment.blog.slug }) }}#comment-{{ comment.id }}">
{{ comment.blog.title }}
</a>
{
createAction Comment . Comment
src/Blogger/BlogBundle/Controller/CommentController.php
public function createAction($blog_id)
{
if ($form->isValid()) {
return $this->redirect($this->generateUrl('BloggerBlogBundle_blog_show', array(
'id' => $comment->getBlog()->getId(),
'slug' => $comment->getBlog()->getSlug())) .
'#comment-' . $comment->getId()
);
}
}
,
http://localhost:8000/ , slug URL.
Symfony2. Symfony2 - . Symfony2 3- :
dev —
test —
prod —
, , - . , . , . , . , . — , ..
test . , unit . , …
Front
. - web/app_dev.php :
$kernel = new AppKernel('dev', true);
, - , web/app.php :
$kernel = new AppKernel('prod', false);
, AppKernel .
- app_test.php.
, - , . , .
app/config config.yml. ,
config.yml 3 ;
config_dev.yml, config_test.yml config_prod.yml. .
config_dev.yml .
imports:
- { resource: config.yml }
imports config.yml, imports 2 ,
config_test.yml config_prod.yml. , config.yml . development,
app/config/config_dev.yml , .
web_profiler:
toolbar: true
, .
, .
-, Symfony2.
$ php app/console cache:clear --env=prod
http://localhost:8000/app.php.
, , . ,
http://localhost:8000/app.php/999.

, . .
,
app/logs/prod.log . , , .
http://localhost:8000/app.php app.php? , , index.html index.php, , app.php ? RewriteRule web/.htaccess
RewriteRule ^(.*)$ app.php [QSA,L]
, , , ^(.*)$ app.php.
Apache, mod_rewrite.c, app.php URL, http://localhost:8000/app.php/.
Symfony2 , , , , . , .
, .
cookbook Symfony2, .
Assetic
Symfony2.8
Asseti , assetic.
composer.json"require": {
"symfony/assetic-bundle": "dev-master"
},
composer update
.
app/AppKernel.php
public function registerBundles()
{
$bundles = array(
new Symfony\Bundle\AsseticBundle\AsseticBundle(),
);
}
app/config/config.yml
assetic:
debug: '%kernel.debug%'
use_controller: '%kernel.debug%'
filters:
cssrewrite: ~
Kris Wallsmith Python webassets.
Assetic 2- assets, assets , , JavaScript , . , CSS JavaScript, CoffeeScript CoffeeScript assets , HTTP .
Twig asset assets .
<link href="{{ asset('bundles/bloggerblog/css/blog.css') }}" type="text/css" rel="stylesheet" />
Assets
Assetic asset :
Assetic asset - , . Asset , .
, assets , , .
Assetic BloggerBlogBundle
app/config/config.yml
assetic:
bundles: [BloggerBlogBundle]
Assetic BloggerBlogBundle , Assetic. bundles .
asset BloggerBlogBundle.
src/Blogger/BlogBundle/Resources/views/layout.html.twig{
{
{% block stylesheets %}
{{ parent () }}
{% stylesheets
'@BloggerBlogBundle/Resources/public/css/*'
%}
<link href="{{ asset_url }}" rel="stylesheet" media="screen" />
{% endstylesheets %}
{% endblock %}
{
2 CSS-, Assetic . Assetic , CSS
src/Blogger/BlogBundle/Resources/public/css 1 , . , . HTTP- .
* css .
{
{
{% block stylesheets %}
{{ parent () }}
{% stylesheets
'@BloggerBlogBundle/Resources/public/css/blog.css'
'@BloggerBlogBundle/Resources/public/css/sidebar.css'
%}
<link href="{{ asset_url }}" rel="stylesheet" media="screen" />
{% endstylesheets %}
{% endblock %}
{
.
* , , CSS , CSS Assetic. , .
HTML
http://localhost:8000/ , CSS (, ).
<link href="/css/d8f44a4_part_1_blog_1.css" rel="stylesheet" media="screen" />
<link href="/css/d8f44a4_part_1_sidebar_2.css" rel="stylesheet" media="screen" />
, 2 . , Assetic 1 CSS . , symblog . Assetic ,
false{
{
{% stylesheets
'@BloggerBlogBundle/Resources/public/css/*'
debug=false
%}
<link href="{{ asset_url }}" rel="stylesheet" media="screen" />
{% endstylesheets %}
{
, HTML - .
<link href="/css/3c7da45.css" rel="stylesheet" media="screen" />
, 2 CSS ,
blog.css sidebar.css 1 . CSS . ,
output{% stylesheets
'@BloggerBlogBundle/Resources/public/css/*'
output='css/blogger.css'
%}
<link href="{{ asset_url }}" rel="stylesheet" media="screen" />
{% endstylesheets %}
, , , assets.
app/Resources/views/base.html.twig{
{
{% block stylesheets %}
<link href='http://fonts.googleapis.com/css?family=Irish+Grover' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=La+Belle+Aurore' rel='stylesheet' type='text/css'>
{% stylesheets
'css/*'
%}
<link href="{{ asset_url }}" rel="stylesheet" media="screen" />
{% endstylesheets %}
{% endblock %}
{
JavaScripts
- JavaScript , Assetic ,
{% javascripts
'@BloggerBlogBundle/Resources/public/js/*'
%}
<script type="text/javascript" src="{{ asset_url }}"></script>
{% endjavascripts %}
Assetic — . assets assets. , , :
CssMinFilter: CSS
JpegoptimFilter: JPEG
Yui\CssCompressorFilter: CSS YUI
Yui\JsCompressorFilter: JavaScript YUI
CoffeeScriptFilter: CoffeeScript JavaScript
Assetic Readme.
, YUI , , , / , .
YUI 2.4.7 ( , , 2.4.8 ), yuicompressor-2.4.7.jar
build app/Resources/java/.
Java
Assetic CSS YUI .
app/config/config.yml
assetic:
filters:
cssrewrite: ~
yui_css:
jar: "%kernel.root_dir%/Resources/java/yuicompressor-2.4.7.jar"
yui_css, YUI Compressor Java . assets , . yui_css
src/Blogger/BlogBundle/Resources/views/layout.html.twig{
{
{% stylesheets
'@BloggerBlogBundle/Resources/public/css/*'
output='css/blogger.css'
filter='yui_css'
%}
<link href="{{ asset_url }}" rel="stylesheet" media="screen" />
{% endstylesheets %}
{
, Assetic , . , , JavaScript. ,
?
{% stylesheets
'@BloggerBlogBundle/Resources/public/css/*'
output='css/blogger.css'
filter='?yui_css'
%}
<link href="{{ asset_url }}" rel="stylesheet" media="screen" />
{% endstylesheets %}
assets
, Assetic css javascript . , , Symfony ( ).
assets , , assets . , . , .
, Assetic assets , .
asset.
$ php app/console assetic:dump
, CSS-
web/css blogger.css. , - symblog
http://localhost:8000/app.php .
asset , asset web/, Assetic .
Symfony2, Symfony2 Assetic asset . .
. unit . , Symfony2 , , -, , , .
:
https://symfony.com/http://tutorial.symblog.co.uk/http://twig.sensiolabs.org/How to Use Assetic for Asset ManagementHow to Minify JavaScripts and Stylesheets with YUI CompressorHow to Use Assetic For Image Optimization with Twig FunctionsHow to Apply an Assetic Filter to a Specific File Extension1 — Symfony2
2 — : ,
3 — Doctrine 2
4 — , Doctrine 2
6 —
,
. .