5.1 KiB
title | permalink |
---|---|
Lesson 4 - Refactoring to a Repository | /atdc/4-refactoring-repository |
{% block head_meta %}
{% endblock %}{% block content %}
While the existing tests are passing, let's refactor the Controller and move the logic for loading posts into a new PostNodeRepository
class.
Doing this will make the Controller simpler and cleaner and make it easier to test that posts are returned in the correct order.
Creating a PostNodeRepository
I like the Repository design pattern.
It's much better to have all logic to find and load nodes in one place instead of duplicating them across an application.
It also makes it easier to test.
To start, within your atdc
module, create an src/Repository
directory and a PostNodeRepository.php
file inside it.
This will contain the PostNodeRepository
class that will be responsible for loading the post nodes from the database instead of within BlogPageController
.
Add this as the initial content:
<?php
namespace Drupal\atdc\Repository;
use Drupal\node\NodeInterface;
final class PostNodeRepository {
/**
* @return array<int, NodeInterface>
*/
public function findAll(): array {
return [];
}
}
I like to make my classes final
, but this is optional and, by adding this docblock, we can specify the findAll()
method should return an array of NodeInterface
objects - making the code easier to read and providing better completion.
So far, you haven't changed BlogPageController
, so the tests should still pass.
Next, let's move the logic for loading nodes into the Repository.
Moving the logic
Remove these lines from BlogPageController
:
$nodeStorage = $this->entityTypeManager()->getStorage('node');
$nodes = $nodeStorage->loadMultiple();
Add them to the findAll()
method, alter the first line that gets the EntityTypeManager
(we'll refactor this later) and return the loaded nodes:
public function findAll(): array {
$nodeStorage = \Drupal::entityTypeManager()->getStorage('node');
$nodes = $nodeStorage->loadMultiple();
return $nodes;
}
Within the BlogPageController
, create a constructor method and inject the Repository using constructor property promotion:
public function __construct(
private PostNodeRepository $postNodeRepository,
) {
}
Add use Drupal\atdc\Repository\PostNodeRepository;
if needed, and use it to load the post nodes:
public function __invoke(): array {
$nodes = $this->postNodeRepository->findAll();
$build = [];
$build['content']['#theme'] = 'item_list';
foreach ($nodes as $node) {
$build['content']['#items'][] = $node->label();
}
return $build;
}
We're almost back to a passing test, but there's more to do.
Getting back to green
Currently, the test is failing, as the response code is a 500
status because the PostNodeRepository
isn't being injected into the Controller.
It's expected within the constructor, but you must add a create
method to inject it.
public static function create(ContainerInterface $container): self {
return new self(
$container->get(PostNodeRepository::class),
);
}
You may also need to add use Symfony\Component\DependencyInjection\ContainerInterface;
at the top of the file for the correct ContainerInterface
to be used.
Creating a service
$container->get()
uses a service's ID to retrieve it from the container, but PostNodeRepository
isn't in the service container.
To do this, create an atdc.services.yml
file within your module.
Add PostNodeRepository
using the fully-qualified class name as the service name:
services:
Drupal\atdc\Repository\PostNodeRepository:
arguments: []
For now, include no arguments.
This should be enough to get the tests passing and back to green.
Injecting more dependencies
Before moving on, let's refactor the PostNodeRepository
and inject the EntityTypeManager
as a dependency.
The same as the BlogPageController
, create a constructor method and inject the EntityTypeManagerInterface
:
public function __construct(
private EntityTypeManagerInterface $entityTypeManager,
) {
}
Add the use Drupal\Core\Entity\EntityTypeManagerInterface;
if needed, and specify it as an argument so it's injected into the constructor:
services:
Drupal\atdc\Repository\PostNodeRepository:
arguments:
- '@entity_type.manager'
Finally, update the code in the findAll()
method:
- $nodeStorage = \Drupal::entityTypeManager()->getStorage('node');
+ $nodeStorage = $this->entityTypeManager->getStorage('node');
If this refactor is successful, the test will still pass.
Conclusion
Whilst we haven't added any new tests in this lesson, we've been able to use the existing tests to ensure that the functionality still works.
If you make a mistake, the tests will fail, and you can revert the changes and try again.
If they pass, the functionality still works as expected.
Now that the PostNodeRepository
is in place, it'll be easier for us to test the order in which the posts are returned tomorrow.
{% endblock %}