Login to bookmark this video
Buy Access to Course
09.

All about Entity Repositories

Share this awesome video!

|

Keep on Learning!

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

With the article show page now dynamic, let's turn to the homepage... cause these news stories are totally still hardcoded. Open ArticleController and find the homepage() action:

76 lines | src/Controller/ArticleController.php
// ... lines 1 - 15
class ArticleController extends AbstractController
{
// ... lines 18 - 30
public function homepage()
{
return $this->render('article/homepage.html.twig');
}
// ... lines 35 - 74
}

Perfect. Just like before, we need to query for the articles. This means that we need an EntityManagerInterface $em argument:

81 lines | src/Controller/ArticleController.php
// ... lines 1 - 7
use Doctrine\ORM\EntityManagerInterface;
// ... lines 9 - 15
class ArticleController extends AbstractController
{
// ... lines 18 - 30
public function homepage(EntityManagerInterface $em)
{
// ... lines 33 - 38
}
// ... lines 40 - 79
}

Next, we get the repository for the class: $repository = $em->getRepository(Article::class). And then we can say, $articles = $repository->findAll():

81 lines | src/Controller/ArticleController.php
// ... lines 1 - 15
class ArticleController extends AbstractController
{
// ... lines 18 - 30
public function homepage(EntityManagerInterface $em)
{
$repository = $em->getRepository(Article::class);
$articles = $repository->findAll();
// ... lines 35 - 38
}
// ... lines 40 - 79
}

Nice! With this array of Article objects in hand, let's pass those into the template as a new articles variable:

81 lines | src/Controller/ArticleController.php
// ... lines 1 - 15
class ArticleController extends AbstractController
{
// ... lines 18 - 30
public function homepage(EntityManagerInterface $em)
{
$repository = $em->getRepository(Article::class);
$articles = $repository->findAll();
return $this->render('article/homepage.html.twig', [
'articles' => $articles,
]);
}
// ... lines 40 - 79
}

Now, to the template! Open homepage.html.twig and scroll down just a little bit. Yes: here is the article list:

81 lines | templates/article/homepage.html.twig
// ... lines 1 - 2
{% block body %}
<div class="container">
<div class="row">
<!-- Article List -->
<div class="col-sm-12 col-md-8">
// ... lines 10 - 18
<!-- Supporting Articles -->
<div class="article-container my-1">
<a href="{{ path('article_show', {slug: 'why-asteroids-taste-like-bacon'}) }}">
<img class="article-img" src="{{ asset('images/asteroid.jpeg') }}">
<div class="article-title d-inline-block pl-3 align-middle">
<span>Why do Asteroids Taste Like Bacon?</span>
<br>
<span class="align-left article-details"><img class="article-author-img rounded-circle" src="{{ asset('images/alien-profile.png') }}"> Mike Ferengi </span>
<span class="pl-5 article-details float-right"> 3 hours ago</span>
</div>
</a>
</div>
// ... lines 32 - 56
</div>
// ... lines 58 - 77
</div>
</div>
{% endblock %}

Well, there's a "main" article on top, but I'm going to ignore that for now. Down below, add for article in articles with, at the bottom, endfor:

58 lines | templates/article/homepage.html.twig
// ... lines 1 - 2
{% block body %}
<div class="container">
<div class="row">
<!-- Article List -->
<div class="col-sm-12 col-md-8">
// ... lines 10 - 18
<!-- Supporting Articles -->
{% for article in articles %}
<div class="article-container my-1">
// ... lines 23 - 31
</div>
{% endfor %}
</div>
// ... lines 35 - 54
</div>
</div>
{% endblock %}

Then... just make things dynamic: article.slug, article.title, and for the three hours ago, if article.publishedAt is not null, print article.publishedAt|ago. If it's not published, do nothing. With this in place, delete the last two hardcoded articles:

58 lines | templates/article/homepage.html.twig
// ... lines 1 - 2
{% block body %}
<div class="container">
<div class="row">
<!-- Article List -->
<div class="col-sm-12 col-md-8">
// ... lines 10 - 18
<!-- Supporting Articles -->
{% for article in articles %}
<div class="article-container my-1">
<a href="{{ path('article_show', {slug: article.slug}) }}">
<img class="article-img" src="{{ asset('images/asteroid.jpeg') }}">
<div class="article-title d-inline-block pl-3 align-middle">
<span>{{ article.title }}</span>
<br>
<span class="align-left article-details"><img class="article-author-img rounded-circle" src="{{ asset('images/alien-profile.png') }}"> Mike Ferengi </span>
<span class="pl-5 article-details float-right"> {{ article.publishedAt ? article.publishedAt|ago }}</span>
</div>
</a>
</div>
{% endfor %}
</div>
// ... lines 35 - 54
</div>
</div>
{% endblock %}

Controlling the ORDER BY

Let's give it a try: find your browser and, refresh! Nice! You can see a mixture of published and unpublished articles. But... you can also see that the articles just printed out in whatever order they were created, regardless of the publish date. Space travellers demand fresh content! So let's print the newest articles first.

Head back to ArticleController. Hmm... the findAll() methods gives us everything... but it's pretty limited. In fact, it takes zero arguments: you can't control it at all:

81 lines | src/Controller/ArticleController.php
// ... lines 1 - 15
class ArticleController extends AbstractController
{
// ... lines 18 - 30
public function homepage(EntityManagerInterface $em)
{
// ... line 33
$articles = $repository->findAll();
// ... lines 35 - 38
}
// ... lines 40 - 79
}

But, some of the other methods are just a little bit more flexible. To control the order, use findBy() instead, pass this an empty array, and then another array with publishedAt set to DESC:

81 lines | src/Controller/ArticleController.php
// ... lines 1 - 15
class ArticleController extends AbstractController
{
// ... lines 18 - 30
public function homepage(EntityManagerInterface $em)
{
// ... line 33
$articles = $repository->findBy([], ['publishedAt' => 'DESC']);
// ... lines 35 - 38
}
// ... lines 40 - 79
}

The first array is where you would normally pass some criteria for a WHERE clause. If we pass nothing, we get everything!

Try it - refresh! Much better!

Hello ArticleRepository

Except... hmm... it probably does not make sense to show the unpublished articles on the homepage. And this is when things get a bit more interesting. Sure, you can pass simple criteria to findBy(), like slug equal to some value. But, in this case, we need a query that says WHERE publishedAt IS NOT NULL. That's just not possible with findBy()!

And so... for the first time, we're going to write - drumroll - a custom query!

Let me show you something cool: when we originally generated our entity, the command created the Article class, but it also created an ArticleRepository class in the Repository directory. Try this: dump($repository) and, refresh. Guess what? This is an instance of that ArticleRepository!

Yes, there is a connection between the Article and ArticleRepository classes. In fact, that connection is explicitly configured right at the top of your Article class:

93 lines | src/Entity/Article.php
// ... lines 1 - 6
/**
* @ORM\Entity(repositoryClass="App\Repository\ArticleRepository")
*/
class Article
{
// ... lines 12 - 91
}

This says: when we ask for the Article class's repository, Doctrine should give us an instance of this ArticleRepository class:

51 lines | src/Repository/ArticleRepository.php
// ... lines 1 - 5
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
// ... lines 7 - 14
class ArticleRepository extends ServiceEntityRepository
{
// ... lines 17 - 49
}

Oh, and the built-in find*() methods actually come from one of the parent classes of ArticleRepository.

So... why the heck are we talking about this? Because, if you want to create a custom query, you can do that by creating a custom method inside of this class. And, hey! It even has a couple of examples:

51 lines | src/Repository/ArticleRepository.php
// ... lines 1 - 14
class ArticleRepository extends ServiceEntityRepository
{
// ... lines 17 - 21
// /**
// * @return Article[] Returns an array of Article objects
// */
/*
public function findByExampleField($value)
{
return $this->createQueryBuilder('a')
->andWhere('a.exampleField = :val')
->setParameter('val', $value)
->orderBy('a.id', 'ASC')
->setMaxResults(10)
->getQuery()
->getResult()
;
}
*/
/*
public function findOneBySomeField($value): ?Article
{
return $this->createQueryBuilder('a')
->andWhere('a.exampleField = :val')
->setParameter('val', $value)
->getQuery()
->getOneOrNullResult()
;
}
*/
}

Uncomment the first example, and rename it to findAllPublishedOrderedByNewest():

49 lines | src/Repository/ArticleRepository.php
// ... lines 1 - 14
class ArticleRepository extends ServiceEntityRepository
{
// ... lines 17 - 21
/**
* @return Article[]
*/
public function findAllPublishedOrderedByNewest()
{
// ... lines 27 - 34
}
// ... lines 36 - 47
}

I love descriptive names... or maybe I love long names... not sure.

Anyways, it's time to talk about how you actually write custom queries in Doctrine. Let's do that next!