176 lines
		
	
	
	
		
			5.1 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			176 lines
		
	
	
	
		
			5.1 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| ---
 | |
| title: 'ATDC: Lesson 4 - Refactoring to a Repository'
 | |
| 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
 | |
| <?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
 | |
| 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
 | |
| public function __construct(
 | |
|   private PostNodeRepository $postNodeRepository,
 | |
| ) {
 | |
| }
 | |
| ```
 | |
| 
 | |
| Add `use Drupal\atdc\Repository\PostNodeRepository;` if needed, and use it to load the post nodes:
 | |
| 
 | |
| ```language-php
 | |
| 
 | |
| 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
 | |
| 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
 | |
| 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
 | |
| 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
 | |
| 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 %}
 |