oliverdavies.uk/source/_pages/atdc/4.md

177 lines
5.1 KiB
Markdown
Raw Normal View History

2024-02-07 22:25:53 +00:00
---
2024-02-08 00:00:16 +00:00
title: 'ATDC: Lesson 4 - Refactoring to a Repository'
2024-02-07 22:25:53 +00:00
permalink: /atdc/4-refactoring-repository
---
{% block head_meta %}
<meta name="robots" content="noindex">
{% 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:
```language-php
2024-02-07 22:25:53 +00:00
<?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`:
```diff
$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:
```language-php
2024-02-07 22:25:53 +00:00
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:
```language-php
2024-02-07 22:25:53 +00:00
public function __construct(
private PostNodeRepository $postNodeRepository,
) {
}
```
Add `use Drupal\atdc\Repository\PostNodeRepository;` if needed, and use it to load the post nodes:
```language-php
2024-02-07 22:25:53 +00:00
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.
```language-php
2024-02-07 22:25:53 +00:00
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:
```language-yaml
2024-02-07 22:25:53 +00:00
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`:
```language-php
2024-02-07 22:25:53 +00:00
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:
```language-yaml
2024-02-07 22:25:53 +00:00
services:
Drupal\atdc\Repository\PostNodeRepository:
arguments:
- '@entity_type.manager'
```
Finally, update the code in the `findAll()` method:
```diff
- $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 %}