Re-add syntax highlighting to daily emails and

...ATDC lessons
This commit is contained in:
Oliver Davies 2024-02-18 01:35:59 +00:00
parent 0d9bb37503
commit 5fbf48d9ac
48 changed files with 186 additions and 165 deletions

View file

@ -64,7 +64,7 @@ You can also use the `COMPOSE_PROJECT_NAME` variable inside Docker Compose files
For example, if you use Traefik and needed to override the host URL for a service, the string will be interpolated and the project name would be injected as you'd expect. For example, if you use Traefik and needed to override the host URL for a service, the string will be interpolated and the project name would be injected as you'd expect.
```yaml ```language-yaml
labels: labels:
- "traefik.http.routers.${COMPOSE_PROJECT_NAME}.rule=Host( - "traefik.http.routers.${COMPOSE_PROJECT_NAME}.rule=Host(
`${COMPOSE_PROJECT_NAME}.docker.localhost`, `${COMPOSE_PROJECT_NAME}.docker.localhost`,

View file

@ -15,7 +15,7 @@ A Makefile contains a number of named targets that you can reference, and each h
For example: For example:
```yaml ```language-language-yaml
# Start the project. # Start the project.
start: start:
docker-compose up -d docker-compose up -d

View file

@ -15,7 +15,7 @@ By using one of these tools, you can programatically provision a new, blank serv
For example, to [create a DigitalOcean droplet](https://docs.ansible.com/ansible/latest/collections/community/digitalocean/digital_ocean_module.htm): For example, to [create a DigitalOcean droplet](https://docs.ansible.com/ansible/latest/collections/community/digitalocean/digital_ocean_module.htm):
```yaml ```language-yaml
--- ---
- community.digitalocean.digital_ocean_droplet: - community.digitalocean.digital_ocean_droplet:
image: ubuntu-20-04-x64 image: ubuntu-20-04-x64
@ -35,7 +35,7 @@ If you needed to create a separate database server or another server for a new e
[Creating an Amazon EC2 instance](https://docs.ansible.com/ansible/latest/collections/amazon/aws/ec2_instance_module.html#ansible-collections-amazon-aws-ec2-instance-module) looks very similar: [Creating an Amazon EC2 instance](https://docs.ansible.com/ansible/latest/collections/amazon/aws/ec2_instance_module.html#ansible-collections-amazon-aws-ec2-instance-module) looks very similar:
```yaml ```language-yaml
--- ---
- amazon.aws.ec2_instance: - amazon.aws.ec2_instance:
image_id: ami-123456 image_id: ami-123456

View file

@ -10,7 +10,7 @@ I cloned a fresh version of my [Docker Examples repository](https://github.com/o
I ran `mkdir -p web/modules/custom/example/tests/src/Functional` to create the directory structure that I needed, and then `touch web/modules/custom/example/tests/src/Functional/ExampleTest.php` to create a new test file and populated it with some initial code: I ran `mkdir -p web/modules/custom/example/tests/src/Functional` to create the directory structure that I needed, and then `touch web/modules/custom/example/tests/src/Functional/ExampleTest.php` to create a new test file and populated it with some initial code:
```php ```language-php
<?php <?php
namespace Drupal\Tests\example\Functional; namespace Drupal\Tests\example\Functional;
@ -27,7 +27,7 @@ class ExampleTest extends BrowserTestBase {
For the simplest test, I decided to test some existing Drupal core functionality - that an anonymous user can view the front page: For the simplest test, I decided to test some existing Drupal core functionality - that an anonymous user can view the front page:
```php ```language-php
/** @test */ /** @test */
public function the_front_page_loads_for_anonymous_users() { public function the_front_page_loads_for_anonymous_users() {
$this->drupalGet('<front>'); $this->drupalGet('<front>');
@ -42,7 +42,7 @@ As this is existing functionalty, the test passes. I can change either the path
With the first test working, it's easy to add more for other functionality, such as whether different users should be able to access administration pages: With the first test working, it's easy to add more for other functionality, such as whether different users should be able to access administration pages:
```php ```language-php
/** @test */ /** @test */
public function the_admin_page_is_not_accessible_to_anonymous_users() { public function the_admin_page_is_not_accessible_to_anonymous_users() {
$this->drupalGet('admin'); $this->drupalGet('admin');

View file

@ -15,7 +15,7 @@ I find that these higher-level types of tests are easier and quicker to set up c
For example, this `Device` class which is a data transfer object around Drupal's `NodeInterface`. It ensures that the correct type of node is provided, and includes a named constructor and a helper method to retrieve a device's asset ID from a field: For example, this `Device` class which is a data transfer object around Drupal's `NodeInterface`. It ensures that the correct type of node is provided, and includes a named constructor and a helper method to retrieve a device's asset ID from a field:
```php ```language-php
final class Device { final class Device {
private NodeInterface $node; private NodeInterface $node;
@ -47,7 +47,7 @@ I need to specify what value is returned from the `bundle()` method as well as g
I need to mock the `get()` method and specify the field name that I'm getting the value for, which also returns it's own mock for `FieldItemListInterface` with a value set for the `getString()` method. I need to mock the `get()` method and specify the field name that I'm getting the value for, which also returns it's own mock for `FieldItemListInterface` with a value set for the `getString()` method.
```php ```language-php
/** @test */ /** @test */
public function should_return_an_asset_id(): void { public function should_return_an_asset_id(): void {
// Arrange. // Arrange.
@ -84,7 +84,7 @@ If I was to refactor from using the `get()` and `getString()` methods to a diffe
This is how I could write the same test using a kernel (integration) test: This is how I could write the same test using a kernel (integration) test:
```php ```language-php
/** @test */ /** @test */
public function should_return_an_asset_id(): void { public function should_return_an_asset_id(): void {
// Arrange. // Arrange.

View file

@ -8,7 +8,7 @@ tags: [php]
Here's a snippet of some Drupal code that I wrote last week. It's responsible for converting an array of nodes into a Collection of one of it's field values. Here's a snippet of some Drupal code that I wrote last week. It's responsible for converting an array of nodes into a Collection of one of it's field values.
```php ```language-php
return Collection::make($stationNodes) return Collection::make($stationNodes)
->map(fn (NodeInterface $station): string => $station->get('field_station_code')->getString()) ->map(fn (NodeInterface $station): string => $station->get('field_station_code')->getString())
->values(); ->values();
@ -28,7 +28,7 @@ It accepts the original node but checks to ensure that the node is the correct t
I've added a helper method to get the field value, encapsulating that logic in a reusable function whilst making the code easier to read and its intent clearer. I've added a helper method to get the field value, encapsulating that logic in a reusable function whilst making the code easier to read and its intent clearer.
```php ```language-php
namespace Drupal\mymodule\ValueObject; namespace Drupal\mymodule\ValueObject;
use Drupal\node\NodeInterface; use Drupal\node\NodeInterface;
@ -60,7 +60,7 @@ final class Station implements StationInterface {
This is what my code now looks like: This is what my code now looks like:
```php ```language-php
return Collection::make($stationNodes) return Collection::make($stationNodes)
->map(fn (NodeInterface $node): StationInterface => Station::fromNode($node)) ->map(fn (NodeInterface $node): StationInterface => Station::fromNode($node))
->map(fn (StationInterface $station): string => $station->getStationCode()) ->map(fn (StationInterface $station): string => $station->getStationCode())

View file

@ -37,7 +37,7 @@ It also makes it more obvious to anyone reading the code that these values are b
It's similar to having this example PHP code: It's similar to having this example PHP code:
```php ```language-php
function __invoke(string $type, int $limit): void {}; function __invoke(string $type, int $limit): void {};
``` ```

View file

@ -10,7 +10,7 @@ tags:
When writing object-orientated code, particularly in PHP, you usually write method names using camel-case letters - such as: When writing object-orientated code, particularly in PHP, you usually write method names using camel-case letters - such as:
```php ```language-php
public function doSomething(): void { public function doSomething(): void {
// ... // ...
} }
@ -18,7 +18,7 @@ public function doSomething(): void {
This is also true when writing methods within a test class - only that the method name is prefixed with the word `test`: This is also true when writing methods within a test class - only that the method name is prefixed with the word `test`:
```php ```language-php
public function testSomething(): void { public function testSomething(): void {
} }
``` ```
@ -27,7 +27,7 @@ This is probably expected and complies with the PSR code style standards like PS
Something that I've seen some PHP developers and some frameworks prefer is to write their test methods using snake-case letters and commonly removing the `test` prefix in favour of using an annotation: Something that I've seen some PHP developers and some frameworks prefer is to write their test methods using snake-case letters and commonly removing the `test` prefix in favour of using an annotation:
```php ```language-php
/** @test */ /** @test */
public function the_api_should_return_a_200_response_code_if_everything_is_ok(): void { public function the_api_should_return_a_200_response_code_if_everything_is_ok(): void {
// ... // ...

View file

@ -14,7 +14,7 @@ As long as a class implements an Interface, it can be decorated.
For example, if I have this PHP interface: For example, if I have this PHP interface:
```php ```language-php
interface DoesSomething interface DoesSomething
{ {
public function doSomething(): void; public function doSomething(): void;
@ -23,7 +23,7 @@ interface DoesSomething
I could have this class that does something: I could have this class that does something:
```php ```language-php
final class FirstClass implements DoesSomething final class FirstClass implements DoesSomething
{ {
public function doSomething(): void public function doSomething(): void
@ -37,7 +37,7 @@ If I need to do something else, like caching or logging the result, I can decora
To do this, I need another class that implements the same interface and inject the original version. To do this, I need another class that implements the same interface and inject the original version.
```php ```language-php
final class SecondClass implements DoesSomething final class SecondClass implements DoesSomething
{ {
public function __constuct( public function __constuct(

View file

@ -19,7 +19,7 @@ A local port needs to be exposed from the database container that Neovim can con
I usually do this with a `docker-compose.override.yaml` file: I usually do this with a `docker-compose.override.yaml` file:
```yaml ```language-yaml
services: services:
database: database:
ports: ports:

View file

@ -45,7 +45,7 @@ Now I can run `make composer COMPOSER_ARGS="info drupal/core"` and see what I wa
`just`, on the other hand, allows for defining parameters to its recipes: `just`, on the other hand, allows for defining parameters to its recipes:
```yaml ```language-yaml
composer *args: composer *args:
  docker compose exec php composer {{ args }}   docker compose exec php composer {{ args }}
``` ```

View file

@ -14,7 +14,7 @@ If I'm passing arguments into a constructor, I can declare a visibility and it w
Here's an example of a value of a data transfer object that accepts a sort code and account number as strings: Here's an example of a value of a data transfer object that accepts a sort code and account number as strings:
```php ```language-php
class AccountDetails { class AccountDetails {
public function __construct( public function __construct(
@ -27,7 +27,7 @@ class AccountDetails {
Without promoted constructor properties, I'd need to create the properties and assign them manually, and I'd have this: Without promoted constructor properties, I'd need to create the properties and assign them manually, and I'd have this:
```php ```language-php
class AccountDetails { class AccountDetails {
public string $accountNumber; public string $accountNumber;

View file

@ -10,7 +10,7 @@ tags:
Continuing with yesterday's data transfer object (DTO) example, something that can be done since PHP 8.1 is to make properties read-only: Continuing with yesterday's data transfer object (DTO) example, something that can be done since PHP 8.1 is to make properties read-only:
```php ```language-php
class AccountDetails { class AccountDetails {
public function __construct( public function __construct(
@ -25,7 +25,7 @@ This means the public properties can be read and used without the need for gette
Without `readonly`, a DTO can be created and the property values can be changed: Without `readonly`, a DTO can be created and the property values can be changed:
```php ```language-php
$accountDetails = new AccountDetails('12345678', '00-00-00'); $accountDetails = new AccountDetails('12345678', '00-00-00');
$accountDetails->accountNumber = 'banana'; $accountDetails->accountNumber = 'banana';
``` ```

View file

@ -13,7 +13,7 @@ A data transfer object only stores data but we could also use a value object tha
For example, we could validate that the account number is the correct length and the sort code is the correct format: For example, we could validate that the account number is the correct length and the sort code is the correct format:
```php ```language-php
class AccountDetails { class AccountDetails {
public function __construct( public function __construct(

View file

@ -12,7 +12,7 @@ Marian Kostadinov ([stochnagara on Twitter](https://twitter.com/stochnagara)) re
Looking at the previous class: Looking at the previous class:
```php ```language-php
class AccountDetails { class AccountDetails {
public function __construct( public function __construct(
@ -25,7 +25,7 @@ class AccountDetails {
Instead of setting each property as `readonly`, the whole class can instead be marked as `readonly`: Instead of setting each property as `readonly`, the whole class can instead be marked as `readonly`:
```php ```language-php
readonly class AccountDetails { readonly class AccountDetails {
public function __construct( public function __construct(

View file

@ -17,7 +17,7 @@ Why? I didn't like the inconsistency of using one approach for variable names an
I'd have had code like this with a mixture of both: I'd have had code like this with a mixture of both:
```php ```language-php
class MyClass { class MyClass {
private EntityTypeManagerInterface $entityTypeManager; private EntityTypeManagerInterface $entityTypeManager;
@ -31,7 +31,7 @@ class MyClass {
Or even more simply: Or even more simply:
```php ```language-php
$entity_type_manager = \Drupal::entityTypeManager(); $entity_type_manager = \Drupal::entityTypeManager();
``` ```

View file

@ -13,7 +13,7 @@ I've seen a lot on social media and posts and videos recently about Laravel Pipe
This is an example from the new documentation: This is an example from the new documentation:
```php ```language-php
$user = Pipeline::send($user) $user = Pipeline::send($user)
->through([ ->through([
GenerateProfilePhoto::class, GenerateProfilePhoto::class,

View file

@ -21,7 +21,7 @@ Maybe a user was pending initially, and they're active after running a command o
To help me get started, I'll sometimes write a test like this with placeholders to separate the test into its separate stages: To help me get started, I'll sometimes write a test like this with placeholders to separate the test into its separate stages:
```php ```language-php
/** @test */ /** @test */
function should_activate_a_pending_user(): void { function should_activate_a_pending_user(): void {
// Arrange. // Arrange.
@ -38,7 +38,7 @@ This makes me think about the different stages and what each might need to conta
Or I might write it out in the "Given, When, Then" format: Or I might write it out in the "Given, When, Then" format:
```php ```language-php
/** @test */ /** @test */
function should_activate_a_pending_user(): void { function should_activate_a_pending_user(): void {
// Given I have a user. // Given I have a user.

View file

@ -16,7 +16,7 @@ For example, the `drupal_set_message()` function was deprecated in Drupal 8.5 an
Once it was deprecated, the function was changed to use the new service to avoid duplicating code and a message was added to notify Developers: Once it was deprecated, the function was changed to use the new service to avoid duplicating code and a message was added to notify Developers:
```php ```language-php
function drupal_set_message($message = NULL, $type = 'status', $repeat = FALSE) { function drupal_set_message($message = NULL, $type = 'status', $repeat = FALSE) {
@trigger_error('drupal_set_message() is deprecated in Drupal 8.5.0 and will be removed before Drupal 9.0.0. Use \Drupal\Core\Messenger\MessengerInterface::addMessage() instead. See https://www.drupal.org/node/2774931', E_USER_DEPRECATED); @trigger_error('drupal_set_message() is deprecated in Drupal 8.5.0 and will be removed before Drupal 9.0.0. Use \Drupal\Core\Messenger\MessengerInterface::addMessage() instead. See https://www.drupal.org/node/2774931', E_USER_DEPRECATED);

View file

@ -18,7 +18,7 @@ I'm using PHPUnit's native assertions to check it returns a Collection (I regula
My initial implementation was to loop over each node and use `assertSame` on its bundle before refactoring to create an array of unique bundle names and comparing it to my expected names: My initial implementation was to loop over each node and use `assertSame` on its bundle before refactoring to create an array of unique bundle names and comparing it to my expected names:
```php ```language-php
self::assertSame( self::assertSame(
expected: [$nodeType], expected: [$nodeType],
actual: $haystack actual: $haystack

View file

@ -13,7 +13,7 @@ Writing custom assertions is a great way to clean up your test code.
Here's an example from one of my client Drupal projects: Here's an example from one of my client Drupal projects:
```php ```language-php
private static function assertProductVariationHasPrice(ProductVariationInterface $productVariation, string $expectedPrice): void { private static function assertProductVariationHasPrice(ProductVariationInterface $productVariation, string $expectedPrice): void {
self::assertSame( self::assertSame(
actual: $productVariation->getPrice()->getNumber(), actual: $productVariation->getPrice()->getNumber(),

View file

@ -14,7 +14,7 @@ In PHP, this is done by throwing an Exception if a condition is met.
For example: For example:
```php ```language-php
if (!is_array(false)) { if (!is_array(false)) {
throw new \Exception('Not an array'); throw new \Exception('Not an array');
} }
@ -22,13 +22,13 @@ if (!is_array(false)) {
There's also the `assert` construct which, since PHP 8.0, throws an Exception by default: There's also the `assert` construct which, since PHP 8.0, throws an Exception by default:
```php ```language-php
assert(is_array(false)); assert(is_array(false));
``` ```
You can also use an assertion library, such as `webmozart/assert` or `beberlei/assert` which provide assertions and guard methods: You can also use an assertion library, such as `webmozart/assert` or `beberlei/assert` which provide assertions and guard methods:
```php ```language-php
use Webmozart\Assert\Assert; use Webmozart\Assert\Assert;
Assert::isArray(false); Assert::isArray(false);

View file

@ -12,7 +12,7 @@ Following yesterday's email about input validation, guard clauses and assertion
For example: For example:
```php ```language-php
function createJourney(string $from, string $to, int $duration): void { function createJourney(string $from, string $to, int $duration): void {
var_dump($from, $to, $duration); var_dump($from, $to, $duration);
} }
@ -22,13 +22,13 @@ In this code, each parameter has a type, but there's no validation on the values
If I run this: If I run this:
```plain ```language-plain
createJourney('', '', -10); createJourney('', '', -10);
``` ```
I would get this output: I would get this output:
```plain ```language-plain
string(0) "" string(0) ""
string(0) "" string(0) ""
int(-10) int(-10)
@ -44,7 +44,7 @@ I can use an assertion library or throw my own Exceptions if the values pass the
For example: For example:
```php ```language-php
function createJourney(string $from, string $to, int $duration): void { function createJourney(string $from, string $to, int $duration): void {
Assert::stringNotEmpty($from); Assert::stringNotEmpty($from);
Assert::stringNotEmpty($to); Assert::stringNotEmpty($to);

View file

@ -26,7 +26,7 @@ To start, we wrote a test for existing functionality within Drupal core - anonym
This is the whole test: This is the whole test:
```php ```language-php
<?php <?php
namespace Drupal\Tests\my_module\Functional; namespace Drupal\Tests\my_module\Functional;

View file

@ -18,7 +18,7 @@ If I look at some code, I want to see as few indentation levels as possible, mak
Instead, you check for a condition; if that isn't met, you return early. Instead, you check for a condition; if that isn't met, you return early.
For example, here is some code I saw recently during a meetup talk: For example, here is some code I saw recently during a meetup talk:
```php ```language-php
$callingClass = $scope->getClassReflection()->getName(); $callingClass = $scope->getClassReflection()->getName();
if ($callingClass === TextMessageQueueProcessor::class) { if ($callingClass === TextMessageQueueProcessor::class) {

View file

@ -23,7 +23,7 @@ Only write comments that add value, and if there are fewer comments, they are ea
This is an example from an open source pull request: This is an example from an open source pull request:
```php ```language-php
/** /**
* An interface for field access override. * An interface for field access override.
*/ */

View file

@ -38,7 +38,7 @@ For example, to install Pathauto, you run `composer require drupal/pathauto`.
Within its output, you'll see this: Within its output, you'll see this:
```plain ```language-plain
Package operations: 3 installs, 0 updates, 0 removals Package operations: 3 installs, 0 updates, 0 removals
- Downloading drupal/token (1.12.0) - Downloading drupal/token (1.12.0)
- Downloading drupal/ctools (4.0.4) - Downloading drupal/ctools (4.0.4)

View file

@ -16,7 +16,7 @@ Here's an example (thanks, ChatGPT, for the code).
## The Class to be tested (MyClass.php) ## The Class to be tested (MyClass.php)
```php ```language-php
<?php <?php
class MyClass { class MyClass {
@ -36,7 +36,7 @@ class MyClass {
## Dependency Interface (DependencyInterface.php) ## Dependency Interface (DependencyInterface.php)
```php ```language-php
<?php <?php
interface DependencyInterface { interface DependencyInterface {
@ -48,7 +48,7 @@ interface DependencyInterface {
## A test class that ends up testing mocks (MyClassTest.php) ## A test class that ends up testing mocks (MyClassTest.php)
```php ```language-php
<?php <?php
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;

View file

@ -16,7 +16,7 @@ In PHPUnit, there are different ways to write test methods.
The standard way is to use camel-case method names with a `test` prefix, for example: The standard way is to use camel-case method names with a `test` prefix, for example:
```php ```language-php
public function testTheProjectNameShouldBeAString(): void public function testTheProjectNameShouldBeAString(): void
{ {
// ... // ...
@ -25,7 +25,7 @@ public function testTheProjectNameShouldBeAString(): void
Another popular way, particularly in some frameworks, is to use snake-case method names: Another popular way, particularly in some frameworks, is to use snake-case method names:
```php ```language-php
/** @test */ /** @test */
public function the_project_name_should_be_a_string(): void public function the_project_name_should_be_a_string(): void
{ {

View file

@ -19,7 +19,7 @@ Unfortunately, they're often overused and are outdated compared to the code they
For example, here's some PHP code: For example, here's some PHP code:
```php ```language-php
function sayHello(string $name, array $options = ['shout' => false]): void { function sayHello(string $name, array $options = ['shout' => false]): void {
     echo 'Hello, ' . $name;      echo 'Hello, ' . $name;
} }
@ -53,7 +53,7 @@ Then you'll get this error:
Now, we can use a docblock to provide more information to PHPStan to describe the structure of the `$options` array and that it has strings for keys and boolean values. Now, we can use a docblock to provide more information to PHPStan to describe the structure of the `$options` array and that it has strings for keys and boolean values.
```php ```language-php
/** /**
 * @param array<string, bool> $options  * @param array<string, bool> $options
 */  */
@ -61,7 +61,7 @@ Now, we can use a docblock to provide more information to PHPStan to describe th
Although it's not erroring, we can add the `$name` parameter and declare it as a `string`. Although it's not erroring, we can add the `$name` parameter and declare it as a `string`.
```php ```language-php
/** /**
 * @param string $name  * @param string $name
 * @param array<string, bool> $options  * @param array<string, bool> $options
@ -78,7 +78,7 @@ We don't want an empty string for a name and want the correct options and values
Let's change the docblock to this: Let's change the docblock to this:
```php ```language-php
/** /**
 * @param non-empty-string $name  * @param non-empty-string $name
 * @param array{shout: bool} $options  * @param array{shout: bool} $options

View file

@ -16,7 +16,7 @@ As well as different base classes for types of tests - i.e. functional, kernel a
For example, if we have this test: For example, if we have this test:
```php ```language-php
<?php <?php
namespace Drupal\Tests\example\Kernel; namespace Drupal\Tests\example\Kernel;
@ -62,7 +62,7 @@ When writing a lot of tests, this can result in duplication and more complex tes
This can be simplified using `EntityKernelTestBase` instead of `KernelTestBase`: This can be simplified using `EntityKernelTestBase` instead of `KernelTestBase`:
```php ```language-php
<?php <?php
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase; use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;

View file

@ -41,7 +41,7 @@ Before adding tests, you must create a module to place them in.
Run `mkdir -p web/modules/custom/atdc` to create an empty module directory, and create an `atdc.info.yml` file within it with this content: Run `mkdir -p web/modules/custom/atdc` to create an empty module directory, and create an `atdc.info.yml` file within it with this content:
```yaml ```language-yaml
name: Example name: Example
type: module type: module
core_version_requirement: ^10 core_version_requirement: ^10
@ -58,7 +58,7 @@ Run `mkdir -p web/modules/custom/atdc/tests/src/Functional && touch web/modules/
Then, add this content. Then, add this content.
```php ```language-php
<?php <?php
namespace Drupal\Tests\atdc\Functional; namespace Drupal\Tests\atdc\Functional;
@ -77,7 +77,7 @@ Note: within a test class, the namespace is `Drupal\Tests\{module_name}` instead
With the boilerplate class added, create a test method within it: With the boilerplate class added, create a test method within it:
```php ```language-php
public function testBasic(): void { public function testBasic(): void {
  self::assertTrue(FALSE);   self::assertTrue(FALSE);
} }
@ -148,7 +148,7 @@ As you're writing functional tests by extending `BrowserTestBase`, you can make
Replace the `testBasic` test method with the following: Replace the `testBasic` test method with the following:
```php ```language-php
public function testFrontPage(): void { public function testFrontPage(): void {
$this->drupalGet('/'); $this->drupalGet('/');

View file

@ -15,7 +15,7 @@ In some instances, setting zero will return all items - essentially, an 'unlimit
I imagine the code looks something like this: I imagine the code looks something like this:
```php ```language-php
if ($limit > 0) { if ($limit > 0) {
$query->range(0, $limit); $query->range(0, $limit);
} }
@ -31,7 +31,7 @@ It means the value could be either an integer or null, but I think the intent of
This would make the code look like this: This would make the code look like this:
```php ```language-php
if (is_int($limit)) { if (is_int($limit)) {
$query->range(0, $limit); $query->range(0, $limit);
} }
@ -45,7 +45,7 @@ It wouldn't make sense to set a negative number as the limit or, as the unlimite
This is the end code I'd likely write: This is the end code I'd likely write:
```php ```language-php
if (is_int($limit)) { if (is_int($limit)) {
if ($limit < 1) { if ($limit < 1) {
throw new InvalidArgumentException('A limit must be a positive integer.'); throw new InvalidArgumentException('A limit must be a positive integer.');

View file

@ -17,14 +17,14 @@ The default approach is that all files can be added, and you specify the files a
For example, if my `.gitignore` file was this, these two directories would be ignored: For example, if my `.gitignore` file was this, these two directories would be ignored:
```plain ```language-plain
vendor vendor
web web
``` ```
The other approach is to ignore everything and unignore the things to add. For example: The other approach is to ignore everything and unignore the things to add. For example:
```plain ```language-plain
* *
!build.yaml !build.yaml
!Dockerfile !Dockerfile

View file

@ -27,7 +27,7 @@ Anything that affects multiple users - such as ignoring `vendor` or `node_module
Add this to your `~/.gitconfig` or `~/.config/git/config` file to set the path for your global ignore file: Add this to your `~/.gitconfig` or `~/.config/git/config` file to set the path for your global ignore file:
```plain ```language-plain
[core] [core]
excludesFile = "~/.config/git/ignore" excludesFile = "~/.config/git/ignore"
``` ```

View file

@ -23,7 +23,7 @@ Going forward, I'd like to ensure that each Drupal module only uses its own clas
Here's what I have so far for my [testing course codebase][atdc]: Here's what I have so far for my [testing course codebase][atdc]:
```php ```language-php
final class ArchitectureTest { final class ArchitectureTest {
public function test_classes_should_be_final(): Rule { public function test_classes_should_be_final(): Rule {

View file

@ -37,8 +37,12 @@
<meta property="twitter:title" content="{{ page.title }} | {{ site.name }}" /> <meta property="twitter:title" content="{{ page.title }} | {{ site.name }}" />
{% endif %} {% endif %}
{% endblock %} {% endblock %}
{% block styles %}{% endblock %}
</head> </head>
<body> <body>
{% block body %}{% endblock %} {% block body %}{% endblock %}
{% block scripts %}{% endblock %}
</body> </body>
</html> </html>

View file

@ -3,3 +3,20 @@
{% block content_bottom %} {% block content_bottom %}
{% include 'about-me.html.twig' %} {% include 'about-me.html.twig' %}
{% endblock %} {% endblock %}
{% block styles %}
<link rel="stylesheet" href="https://unpkg.com/@highlightjs/cdn-assets@11.9.0/styles/github-dark.min.css">
<style type="text/css">
.hljs {
background: transparent;
}
</style>
{% endblock %}
{% block scripts %}
<script src="https://unpkg.com/@highlightjs/cdn-assets@11.9.0/highlight.min.js"></script>
<script src="https://unpkg.com/@highlightjs/cdn-assets@11.9.0/languages/php.min.js"></script>
<script>hljs.highlightAll();</script>
{% endblock %}

View file

@ -30,7 +30,7 @@ Before adding tests, you must create a module to place them in.
Run `mkdir -p web/modules/custom/atdc` to create an empty module directory and create an `atdc.info.yml` file within it with this content: Run `mkdir -p web/modules/custom/atdc` to create an empty module directory and create an `atdc.info.yml` file within it with this content:
```yaml ```language-yaml
name: ATDC name: ATDC
type: module type: module
core_version_requirement: ^10 core_version_requirement: ^10
@ -47,7 +47,7 @@ Run `mkdir -p web/modules/custom/atdc/tests/src/Functional && touch web/modules/
Then, add this content. Then, add this content.
```php ```language-php
<?php <?php
namespace Drupal\Tests\atdc\Functional; namespace Drupal\Tests\atdc\Functional;
@ -66,7 +66,7 @@ Note: within a test class, the namespace is `Drupal\Tests\{module_name}` instead
With the boilerplate class added, create a test method within it: With the boilerplate class added, create a test method within it:
```php ```language-php
public function testBasic(): void { public function testBasic(): void {
  self::assertTrue(FALSE);   self::assertTrue(FALSE);
} }
@ -137,7 +137,7 @@ As you're writing functional tests by extending `BrowserTestBase`, you can make
Replace the `testBasic` test method with the following: Replace the `testBasic` test method with the following:
```php ```language-php
public function testFrontPage(): void { public function testFrontPage(): void {
$this->drupalGet('/'); $this->drupalGet('/');

View file

@ -18,7 +18,7 @@ Let's see how that would look as a unit test.
Create a new test, `PostNodeRepositoryUnitTest` and, for now, just create a new `PostNodeRepository`: Create a new test, `PostNodeRepositoryUnitTest` and, for now, just create a new `PostNodeRepository`:
```php ```language-php
<?php <?php
// web/modules/custom/atdc/tests/src/Unit/PostNodeRepositoryUnitTest.php // web/modules/custom/atdc/tests/src/Unit/PostNodeRepositoryUnitTest.php
@ -43,7 +43,7 @@ But, as this is a unit test, you can't get the Repository from the service conta
Try to fix this by creating a new `EntityTypeManager` and injecting it into the constructor: Try to fix this by creating a new `EntityTypeManager` and injecting it into the constructor:
```php ```language-php
$repository = new PostNodeRepository( $repository = new PostNodeRepository(
new EntityTypeManager(), new EntityTypeManager(),
); );
@ -61,7 +61,7 @@ Instead of doing this manually, let's start using mocks.
Add `use Drupal\Core\Entity\EntityTypeManagerInterface;` and create a mock to use instead of the manually created version. Add `use Drupal\Core\Entity\EntityTypeManagerInterface;` and create a mock to use instead of the manually created version.
```php ```language-php
$repository = new PostNodeRepository( $repository = new PostNodeRepository(
$this->createMock(EntityTypeManagerInterface::class), $this->createMock(EntityTypeManagerInterface::class),
); );
@ -73,7 +73,7 @@ As the mock implements `EntityTypeManagerInterface`, this will fix the failure,
Next, try to get the posts from the Repository: Next, try to get the posts from the Repository:
```php ```language-php
$repository->findAll(); $repository->findAll();
``` ```
@ -87,7 +87,7 @@ For the test to work, this needs to be mocked too and returned from the `getStor
Create a mock of `EntityStorageInterface`, which will be used as the node storage: Create a mock of `EntityStorageInterface`, which will be used as the node storage:
```php ```language-php
$nodeStorage = $this->createMock(EntityStorageInterface::class); $nodeStorage = $this->createMock(EntityStorageInterface::class);
``` ```
@ -95,7 +95,7 @@ Next, this needs to be returns from the mock `EntityTypeManager`.
To do this, specify that the `getStorage()` method when called with the value `node`, will return the mocked node storage: To do this, specify that the `getStorage()` method when called with the value `node`, will return the mocked node storage:
```php ```language-php
$entityTypeManager = $this->createMock(EntityTypeManagerInterface::class); $entityTypeManager = $this->createMock(EntityTypeManagerInterface::class);
$entityTypeManager->method('getStorage')->with('node')->willReturn($nodeStorage); $entityTypeManager->method('getStorage')->with('node')->willReturn($nodeStorage);
@ -112,7 +112,7 @@ You'll need to use a mock for each node and set what each method needs to return
The same as the Kernel test, set a title for each post with different created times. The same as the Kernel test, set a title for each post with different created times.
```php ```language-php
$node1 = $this->createMock(NodeInterface::class); $node1 = $this->createMock(NodeInterface::class);
$node1->method('bundle')->willReturn('post'); $node1->method('bundle')->willReturn('post');
$node1->method('getCreatedTime')->willReturn(strtotime('-1 week')); $node1->method('getCreatedTime')->willReturn(strtotime('-1 week'));
@ -131,7 +131,7 @@ $node3->method('label')->willReturn('Post three');
Then, specify the `loadByProperties` method should return the posts. Then, specify the `loadByProperties` method should return the posts.
```php ```language-php
$nodeStorage->method('loadByProperties')->willReturn([ $nodeStorage->method('loadByProperties')->willReturn([
$node1, $node1,
$node2, $node2,
@ -141,7 +141,7 @@ $nodeStorage->method('loadByProperties')->willReturn([
Finally, add some assertions that the nodes returned are the correct ones and in the correct order: Finally, add some assertions that the nodes returned are the correct ones and in the correct order:
```php ```language-php
$posts = $repository->findAll(); $posts = $repository->findAll();
self::assertContainsOnlyInstancesOf(NodeInterface::class, $posts); self::assertContainsOnlyInstancesOf(NodeInterface::class, $posts);
@ -166,7 +166,7 @@ This is testing the same thing as the kernel test, but it's your preference whic
Hopefully, if you run your whole testsuite, you should see output like this: Hopefully, if you run your whole testsuite, you should see output like this:
```plain ```language-plain
PHPUnit 9.6.15 by Sebastian Bergmann and contributors. PHPUnit 9.6.15 by Sebastian Bergmann and contributors.
......... 9 / 9 (100%) ......... 9 / 9 (100%)
@ -176,7 +176,7 @@ Time: 00:07.676, Memory: 10.00 MB
Or, if you use `--testdox`, output like this: Or, if you use `--testdox`, output like this:
```plain ```language-plain
PHPUnit 9.6.15 by Sebastian Bergmann and contributors. PHPUnit 9.6.15 by Sebastian Bergmann and contributors.
Blog Page (Drupal\Tests\atdc\Functional\BlogPage) Blog Page (Drupal\Tests\atdc\Functional\BlogPage)

View file

@ -22,7 +22,7 @@ For example, how do we test the administration pages to see if they work for a u
Let's start with a new test: Let's start with a new test:
```php ```language-php
public function testAdminPageLoggedIn(): void { public function testAdminPageLoggedIn(): void {
$this->drupalGet('/admin'); $this->drupalGet('/admin');
@ -47,7 +47,7 @@ This is commonly known as the **Arrange** step of the test.
To create a user, use `$this->drupalCreateUser()` and `$this->drupalLogin()` to log in as that user. To create a user, use `$this->drupalCreateUser()` and `$this->drupalLogin()` to log in as that user.
```php ```language-php
$user = $this->drupalCreateUser(); $user = $this->drupalCreateUser();
$this->drupalLogin($user); $this->drupalLogin($user);
@ -61,7 +61,7 @@ As we're testing against a temporary Drupal installation, we don't have access t
To do this, when creating the user, include an array of permissions to add to it: To do this, when creating the user, include an array of permissions to add to it:
```php ```language-php
$user = $this->createUser(permissions: [ $user = $this->createUser(permissions: [
'access administration pages', 'access administration pages',
'administer site configuration', 'administer site configuration',
@ -80,7 +80,7 @@ Let's create a page and test we can view it.
Firstly, let's ensure the page is not found: Firstly, let's ensure the page is not found:
```php ```language-php
public function testContent(): void { public function testContent(): void {
$this->drupalGet('/node/1'); $this->drupalGet('/node/1');
$this->assertSession()->statusCodeEquals(Response::HTTP_NOT_FOUND); $this->assertSession()->statusCodeEquals(Response::HTTP_NOT_FOUND);
@ -91,7 +91,7 @@ Similar to `$this->createUser()`, there are similar methods to create content ty
Again, as there are no existing content or content types, we need to create them and add the follow-up assertions: Again, as there are no existing content or content types, we need to create them and add the follow-up assertions:
```php ```language-php
public function testContent(): void { public function testContent(): void {
// ... // ...
@ -111,7 +111,7 @@ You're probably expecting the test to pass now, but you'll likely get an error l
To fix this, we need to tell Drupal to enable the `node` module within the test by adding this within the test class: To fix this, we need to tell Drupal to enable the `node` module within the test by adding this within the test class:
```php ```language-php
protected static $modules = ['node']; protected static $modules = ['node'];
``` ```
@ -123,7 +123,7 @@ Here's a tip for today: if you're getting an unexpected status code or another e
To do that, add this to your test, and it will output the page content: To do that, add this to your test, and it will output the page content:
```php ```language-php
var_dump($this->getSession()->getPage()->getContent()); var_dump($this->getSession()->getPage()->getContent());
``` ```

View file

@ -20,7 +20,7 @@ Create a new `BlogPageTest` and have it extend `BrowserTestBase`.
Let's assert that a page should exist at `/blog` by returning a `200` status code, as this should be accessible by anonymous users. Let's assert that a page should exist at `/blog` by returning a `200` status code, as this should be accessible by anonymous users.
```php ```language-php
<?php <?php
namespace Drupal\Tests\atdc\Functional; namespace Drupal\Tests\atdc\Functional;
@ -49,7 +49,7 @@ Whilst you could create the page using the Views module, let's create a custom r
Create an `atdc.routing.yml` file: Create an `atdc.routing.yml` file:
```yaml ```language-yaml
# web/modules/custom/atdc/atdc.routing.yml # web/modules/custom/atdc/atdc.routing.yml
atdc.blog: atdc.blog:
@ -65,13 +65,13 @@ With this added, the status code doesn't change and is a `404`.
Like in the previous lesson, you need to enable the `atdc` module by setting `$modules` in your test: Like in the previous lesson, you need to enable the `atdc` module by setting `$modules` in your test:
```php ```language-php
protected static $modules = ['atdc']; protected static $modules = ['atdc'];
``` ```
You'll also need to create an `atdc.info.yml` file so the module can be installed: You'll also need to create an `atdc.info.yml` file so the module can be installed:
```yaml ```language-yaml
# web/modules/custom/atdc/atdc.info.yml # web/modules/custom/atdc/atdc.info.yml
name: ATDC name: ATDC
@ -81,7 +81,7 @@ core_version_requirement: ^10
This should change the status code to a `403`, as you also need the `node` module for the `access content` permission: This should change the status code to a `403`, as you also need the `node` module for the `access content` permission:
```php ```language-php
protected static $modules = ['node', 'atdc']; protected static $modules = ['node', 'atdc'];
``` ```
@ -97,7 +97,7 @@ Let's do that next.
Create the expected Controller class within a `src/Controller` directory: Create the expected Controller class within a `src/Controller` directory:
```php ```language-php
<?php <?php
// web/modules/custom/atdc/src/Controller/BlogPageController.php // web/modules/custom/atdc/src/Controller/BlogPageController.php
@ -115,7 +115,7 @@ For this step, the simplest thing you can do to get a passing test is to return
As long as it's an array, even an empty one, the test should pass: As long as it's an array, even an empty one, the test should pass:
```php ```language-php
public function __invoke(): array { public function __invoke(): array {
return []; return [];
} }
@ -131,7 +131,7 @@ This is also a good time to do a `git commit`.
Again, let's start with a new test: Again, let's start with a new test:
```php ```language-php
public function testPostsAreVisible(): void { public function testPostsAreVisible(): void {
// Arrange. // Arrange.
$this->createNode(['type' => 'post', 'title' => 'First post']); $this->createNode(['type' => 'post', 'title' => 'First post']);
@ -165,7 +165,7 @@ Start by extending the `ControllerBase` base class within your Controller:
Now, within the `__invoke` method, add this to return a list of each node title: Now, within the `__invoke` method, add this to return a list of each node title:
```php ```language-php
public function __invoke(): array { public function __invoke(): array {
$nodeStorage = $this->entityTypeManager()->getStorage('node'); $nodeStorage = $this->entityTypeManager()->getStorage('node');
$nodes = $nodeStorage->loadMultiple(); $nodes = $nodeStorage->loadMultiple();

View file

@ -26,7 +26,7 @@ This will contain the `PostNodeRepository` class that will be responsible for lo
Add this as the initial content: Add this as the initial content:
```php ```language-php
<?php <?php
namespace Drupal\atdc\Repository; namespace Drupal\atdc\Repository;
@ -62,7 +62,7 @@ $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: Add them to the `findAll()` method, alter the first line that gets the `EntityTypeManager` (we'll refactor this later) and return the loaded nodes:
```php ```language-php
public function findAll(): array { public function findAll(): array {
$nodeStorage = \Drupal::entityTypeManager()->getStorage('node'); $nodeStorage = \Drupal::entityTypeManager()->getStorage('node');
$nodes = $nodeStorage->loadMultiple(); $nodes = $nodeStorage->loadMultiple();
@ -73,7 +73,7 @@ public function findAll(): array {
Within the `BlogPageController`, create a constructor method and inject the Repository using constructor property promotion: Within the `BlogPageController`, create a constructor method and inject the Repository using constructor property promotion:
```php ```language-php
public function __construct( public function __construct(
private PostNodeRepository $postNodeRepository, private PostNodeRepository $postNodeRepository,
) { ) {
@ -82,7 +82,7 @@ public function __construct(
Add `use Drupal\atdc\Repository\PostNodeRepository;` if needed, and use it to load the post nodes: Add `use Drupal\atdc\Repository\PostNodeRepository;` if needed, and use it to load the post nodes:
```php ```language-php
public function __invoke(): array { public function __invoke(): array {
$nodes = $this->postNodeRepository->findAll(); $nodes = $this->postNodeRepository->findAll();
@ -105,7 +105,7 @@ Currently, the test is failing, as the response code is a `500` status because t
It's expected within the constructor, but you must add a `create` method to inject it. It's expected within the constructor, but you must add a `create` method to inject it.
```php ```language-php
public static function create(ContainerInterface $container): self { public static function create(ContainerInterface $container): self {
return new self( return new self(
$container->get(PostNodeRepository::class), $container->get(PostNodeRepository::class),
@ -123,7 +123,7 @@ To do this, create an `atdc.services.yml` file within your module.
Add `PostNodeRepository` using the fully-qualified class name as the service name: Add `PostNodeRepository` using the fully-qualified class name as the service name:
```yaml ```language-yaml
services: services:
Drupal\atdc\Repository\PostNodeRepository: Drupal\atdc\Repository\PostNodeRepository:
arguments: [] arguments: []
@ -139,7 +139,7 @@ Before moving on, let's refactor the `PostNodeRepository` and inject the `Entity
The same as the `BlogPageController`, create a constructor method and inject the `EntityTypeManagerInterface`: The same as the `BlogPageController`, create a constructor method and inject the `EntityTypeManagerInterface`:
```php ```language-php
public function __construct( public function __construct(
private EntityTypeManagerInterface $entityTypeManager, private EntityTypeManagerInterface $entityTypeManager,
) { ) {
@ -148,7 +148,7 @@ public function __construct(
Add the `use Drupal\Core\Entity\EntityTypeManagerInterface;` if needed, and specify it as an argument so it's injected into the constructor: Add the `use Drupal\Core\Entity\EntityTypeManagerInterface;` if needed, and specify it as an argument so it's injected into the constructor:
```yaml ```language-yaml
services: services:
Drupal\atdc\Repository\PostNodeRepository: Drupal\atdc\Repository\PostNodeRepository:
arguments: arguments:

View file

@ -28,7 +28,7 @@ Instead of making HTTP requests and checking the responses, we can test the resu
Let's create a new test that uses the `PostNodeRepository` to find the nodes and assert we get an expected number returned. Let's create a new test that uses the `PostNodeRepository` to find the nodes and assert we get an expected number returned.
```php ```language-php
<?php <?php
namespace Drupal\Tests\atdc\Kernel; namespace Drupal\Tests\atdc\Kernel;
@ -73,7 +73,7 @@ It's defined within `atdc.services.yml`, but we need to be explicit about which
Create a `$modules` array within the test class and add `atdc`: Create a `$modules` array within the test class and add `atdc`:
```php ```language-php
protected static $modules = ['atdc']; protected static $modules = ['atdc'];
``` ```
@ -83,7 +83,7 @@ Run the tests again, and you should get a different error:
As well as `atdc`, you must enable the `node` module. You can do so by adding it to the `$modules` array: As well as `atdc`, you must enable the `node` module. You can do so by adding it to the `$modules` array:
```php ```language-php
protected static $modules = ['node', 'atdc']; protected static $modules = ['node', 'atdc'];
``` ```
@ -111,7 +111,7 @@ Within the test, you can use `$this->createNode()` to create posts.
Create the three posts the test is expecting: Create the three posts the test is expecting:
```php ```language-php
// Arrange. // Arrange.
$this->createNode(['type' => 'post']); $this->createNode(['type' => 'post']);
$this->createNode(['type' => 'post']); $this->createNode(['type' => 'post']);
@ -126,7 +126,7 @@ Next, let's assert they're returned in a specific order.
Update the posts to have a specific title and created date so we can specify which order we expect them to be returned in and which titles they should have: Update the posts to have a specific title and created date so we can specify which order we expect them to be returned in and which titles they should have:
```php ```language-php
// Arrange. // Arrange.
$this->createNode([ $this->createNode([
'created' => (new DrupalDateTime('-1 week'))->getTimestamp(), 'created' => (new DrupalDateTime('-1 week'))->getTimestamp(),
@ -151,7 +151,7 @@ Note we're intentionally setting them to be in an incorrect order, to begin with
Next, assert that the titles are returned in the correct order. Next, assert that the titles are returned in the correct order.
```php ```language-php
self::assertSame( self::assertSame(
['Post two', 'Post one', 'Post three'], ['Post two', 'Post one', 'Post three'],
array_map( array_map(
@ -165,7 +165,7 @@ For each node in `$nodes`, get its label (title) and compare them with the title
As expected, the test fails: As expected, the test fails:
```plain ```language-plain
1) Drupal\Tests\atdc\Kernel\PostNodeRepositoryTest::testPostsAreReturnedByCreatedDate 1) Drupal\Tests\atdc\Kernel\PostNodeRepositoryTest::testPostsAreReturnedByCreatedDate
Failed asserting that two arrays are identical. Failed asserting that two arrays are identical.
--- Expected --- Expected
@ -187,7 +187,7 @@ We need to update the code within `PostNodeRepository` to fix the ordering.
After loading the nodes, we need to sort them. After loading the nodes, we need to sort them.
```php ```language-php
public function findAll(): array { public function findAll(): array {
$nodeStorage = $this->entityTypeManager->getStorage('node'); $nodeStorage = $this->entityTypeManager->getStorage('node');
$nodes = $nodeStorage->loadMultiple(); $nodes = $nodeStorage->loadMultiple();
@ -206,7 +206,7 @@ This gets us further, but the test is still failing.
Whilst the order is correct, the array keys don't match what we expect: Whilst the order is correct, the array keys don't match what we expect:
```plain ```language-plain
1) Drupal\Tests\atdc\Kernel\PostNodeRepositoryTest::testPostsAreReturnedByCreatedDate 1) Drupal\Tests\atdc\Kernel\PostNodeRepositoryTest::testPostsAreReturnedByCreatedDate
Failed asserting that two arrays are identical. Failed asserting that two arrays are identical.
--- Expected --- Expected
@ -233,7 +233,7 @@ And, because the correct titles are still being shown, our original Functional t
Tip: to see the names of the tests in your output, add the `--testdox` flag to the `phpunit` command: Tip: to see the names of the tests in your output, add the `--testdox` flag to the `phpunit` command:
```plain ```language-plain
Blog Page (Drupal\Tests\atdc\Functional\BlogPage) Blog Page (Drupal\Tests\atdc\Functional\BlogPage)
✔ Blog page ✔ Blog page
✔ Posts are visible ✔ Posts are visible

View file

@ -12,7 +12,7 @@ In yesterday's lesson, you created your first Kernel test and used it to ensure
This is how we're creating the posts currently: This is how we're creating the posts currently:
```php ```language-php
$this->createNode([ $this->createNode([
'created' => (new DrupalDateTime('-1 week'))->getTimestamp(), 'created' => (new DrupalDateTime('-1 week'))->getTimestamp(),
'title' => 'Post one', 'title' => 'Post one',
@ -40,7 +40,7 @@ Let's create a Builder class to create the posts.
This is how I'd like to create a post using a `PostBuilder`: This is how I'd like to create a post using a `PostBuilder`:
```php ```language-php
PostBuilder::create() PostBuilder::create()
->setCreatedDate('-1 week') ->setCreatedDate('-1 week')
->setTitle('Post one') ->setTitle('Post one')
@ -51,7 +51,7 @@ This makes it easier to do by creating named methods for each value we want to s
To do this, create a new class at `src/Builder/PostBuilder.php`: To do this, create a new class at `src/Builder/PostBuilder.php`:
```php ```language-php
<?php <?php
// web/modules/custom/atdc/src/Builder/PostBuilder.php // web/modules/custom/atdc/src/Builder/PostBuilder.php
@ -73,7 +73,7 @@ As it returns a new version of `self`, you can also chain methods onto it.
Add the additional methods and properties: Add the additional methods and properties:
```php ```language-php
private ?DrupalDateTime $created = NULL; private ?DrupalDateTime $created = NULL;
private string $title; private string $title;
@ -95,7 +95,7 @@ Again, by returning `$this`, we can keep chaining methods.
Finally, create the `getPost()` method that creates the node based on the property values, saves it, and returns it. Finally, create the `getPost()` method that creates the node based on the property values, saves it, and returns it.
```php ```language-php
public function getPost(): NodeInterface { public function getPost(): NodeInterface {
$post = Node::create([ $post = Node::create([
'created' => $this->created?->getTimestamp(), 'created' => $this->created?->getTimestamp(),
@ -111,7 +111,7 @@ public function getPost(): NodeInterface {
Now, refactor the test to use the `PostBuilder`: Now, refactor the test to use the `PostBuilder`:
```php ```language-php
PostBuilder::create() PostBuilder::create()
->setCreatedDate('-1 week') ->setCreatedDate('-1 week')
->setTitle('Post one') ->setTitle('Post one')
@ -136,7 +136,7 @@ Finally, for today, let's refactor the assertion that verifies the titles are re
This is the current assertion: This is the current assertion:
```php ```language-php
self::assertSame( self::assertSame(
['Post two', 'Post one', 'Post three'], ['Post two', 'Post one', 'Post three'],
array_map( array_map(
@ -152,7 +152,7 @@ We can make this more reusable and readable by extracting this into a new custom
Create a new static function at the bottom of the class with a name that describes what it's asserting: Create a new static function at the bottom of the class with a name that describes what it's asserting:
```php ```language-php
/** /**
* @param array<int, string> $expectedTitles * @param array<int, string> $expectedTitles
* @param array<int, NodeInterface> $nodes * @param array<int, NodeInterface> $nodes
@ -179,7 +179,7 @@ The benefits are that this now has a name that describes what we're asserting, a
Finally, refactor the test to use the new assertion: Finally, refactor the test to use the new assertion:
```php ```language-php
self::assertNodeTitlesAreSame( self::assertNodeTitlesAreSame(
['Post two', 'Post one', 'Post three'], ['Post two', 'Post one', 'Post three'],
$nodes, $nodes,

View file

@ -20,7 +20,7 @@ First, let's ensure that only published nodes are returned and displayed on the
We can do this easily with a functional test, so add a new test method to `BlogPostTest`: We can do this easily with a functional test, so add a new test method to `BlogPostTest`:
```php ```language-php
public function testOnlyPublishedNodesAreShown(): void { public function testOnlyPublishedNodesAreShown(): void {
PostBuilder::create() PostBuilder::create()
->setTitle('Post one') ->setTitle('Post one')
@ -54,7 +54,7 @@ In this test, we want to create some published and unpublished posts and assert
To fix the error, add this function so it exists: To fix the error, add this function so it exists:
```php ```language-php
public function isPublished(): self { public function isPublished(): self {
return $this; return $this;
} }
@ -70,7 +70,7 @@ When using `PostBuilder` in the previous lesson, we were always providing a crea
Update the `getPost()` method to only set the created time if the `created` property has a value. Update the `getPost()` method to only set the created time if the `created` property has a value.
```php ```language-php
public function getPost(): NodeInterface { public function getPost(): NodeInterface {
$post = Node::create([ $post = Node::create([
'title' => $this->title, 'title' => $this->title,
@ -93,7 +93,7 @@ Now, we can see a similar error to the one before for `isNotPublished()`.
Again, create the simplest version of the method so the test can progress: Again, create the simplest version of the method so the test can progress:
```php ```language-php
public function isNotPublished(): self { public function isNotPublished(): self {
return $this; return $this;
} }
@ -112,13 +112,13 @@ Within `PostBuilder`, we need to use the `isPublished` and `isNotPublished` meth
First, add an `isPublished` property to the class and set it to be `TRUE` by default: First, add an `isPublished` property to the class and set it to be `TRUE` by default:
```php ```language-php
private bool $isPublished = TRUE; private bool $isPublished = TRUE;
``` ```
Next, update the `isPublished()` and `isNotPublished()` methods to set the value appropriately: Next, update the `isPublished()` and `isNotPublished()` methods to set the value appropriately:
```php ```language-php
public function isNotPublished(): self { public function isNotPublished(): self {
$this->isPublished = FALSE; $this->isPublished = FALSE;
@ -136,7 +136,7 @@ Even though `isPublished` is already true by default, doing this makes it explic
Finally, within `getPost()`, update the code that creates the node to set the `status` property accordingly. Finally, within `getPost()`, update the code that creates the node to set the `status` property accordingly.
```php ```language-php
$post = Node::create([ $post = Node::create([
'status' => $this->isPublished, 'status' => $this->isPublished,
'title' => $this->title, 'title' => $this->title,
@ -152,7 +152,7 @@ We also need to update the `PostNodeRepository` as that is responsible for loadi
Currently, all we're doing is this: Currently, all we're doing is this:
```php ```language-php
$nodes = $nodeStorage->loadMultiple(); $nodes = $nodeStorage->loadMultiple();
``` ```
@ -160,7 +160,7 @@ This will load all nodes, regardless of their type or status.
To fix this, change this to use `loadByProperties()` instead: To fix this, change this to use `loadByProperties()` instead:
```php ```language-php
$nodes = $nodeStorage->loadByProperties(); $nodes = $nodeStorage->loadByProperties();
``` ```
@ -170,7 +170,7 @@ Note: you can also use `->getQuery()` if you prefer and write the query yourself
For this case, let's add a property for `status` and its value to be `TRUE`: For this case, let's add a property for `status` and its value to be `TRUE`:
```php ```language-php
$nodes = $nodeStorage->loadByProperties([ $nodes = $nodeStorage->loadByProperties([
'status' => TRUE, 'status' => TRUE,
]); ]);
@ -184,7 +184,7 @@ The other issue is all published nodes are returned, even if they aren't posts.
Before adding this to `PostNodeRepository`, create a new failing test for it: Before adding this to `PostNodeRepository`, create a new failing test for it:
```php ```language-php
public function testOnlyPostNodesAreShown(): void { public function testOnlyPostNodesAreShown(): void {
PostBuilder::create()->setTitle('Post one')->getPost(); PostBuilder::create()->setTitle('Post one')->getPost();
PostBuilder::create()->setTitle('Post two')->getPost(); PostBuilder::create()->setTitle('Post two')->getPost();
@ -213,7 +213,7 @@ If you run the test, it should fail as expected:
Now we have a failing test, let's add the extra condition to `PostNodeRepository`: Now we have a failing test, let's add the extra condition to `PostNodeRepository`:
```php ```language-php
$nodes = $nodeStorage->loadByProperties([ $nodes = $nodeStorage->loadByProperties([
'status' => TRUE, 'status' => TRUE,
'type' => 'post', 'type' => 'post',

View file

@ -12,7 +12,7 @@ In this lesson, let's add tags to our posts using the `PostBuilder`.
As we're doing test-driven development, start by creating a new `PostBuilderTest`: As we're doing test-driven development, start by creating a new `PostBuilderTest`:
```php ```language-php
<?php <?php
// web/modules/custom/atdc/tests/src/Kernel/Builder/PostBuilderTest.php // web/modules/custom/atdc/tests/src/Kernel/Builder/PostBuilderTest.php
@ -37,7 +37,7 @@ Let's start by testing the existing functionality within `PostBuilder` by verify
Create these tests, which should pass by default as the code is already written: Create these tests, which should pass by default as the code is already written:
```php ```language-php
/** @test */ /** @test */
public function it_returns_a_published_post(): void { public function it_returns_a_published_post(): void {
$node = PostBuilder::create() $node = PostBuilder::create()
@ -73,7 +73,7 @@ Next, create a test for adding tags to a post.
It should be mostly the same as the others, but instead of an assertion for the published status, try to use `var_dump()` to see the value of `field_tags`: It should be mostly the same as the others, but instead of an assertion for the published status, try to use `var_dump()` to see the value of `field_tags`:
```php ```language-php
/** @test */ /** @test */
public function it_returns_a_post_with_tags(): void { public function it_returns_a_post_with_tags(): void {
$node = PostBuilder::create() $node = PostBuilder::create()
@ -104,7 +104,7 @@ The convention is to create a test module that will only be required within the
To do this, create a `web/modules/custom/atdc/modules/atdc_test` directory and an `atdc_test.info.yml` file with this content: To do this, create a `web/modules/custom/atdc/modules/atdc_test` directory and an `atdc_test.info.yml` file with this content:
```yaml ```language-yaml
name: ATDC Test name: ATDC Test
type: module type: module
core_version_requirement: ^10 core_version_requirement: ^10
@ -123,7 +123,7 @@ Rather than trying to write them by hand, I create the configuration I need, suc
To do that for this project, as I'm using the PHP built-in web server, I can use Drush to install Drupal using an SQLite database: To do that for this project, as I'm using the PHP built-in web server, I can use Drush to install Drupal using an SQLite database:
```plain ```language-plain
./vendor/bin/drush site:install --db-url sqlite://localhost/atdc.sqlite ./vendor/bin/drush site:install --db-url sqlite://localhost/atdc.sqlite
``` ```
@ -143,7 +143,7 @@ These are the files that I created in my module based on the field I created.
`field.field.node.post.field_tags.yml`: `field.field.node.post.field_tags.yml`:
```yaml ```language-yaml
langcode: en langcode: en
status: true status: true
dependencies: dependencies:
@ -174,7 +174,7 @@ field_type: entity_reference
`field.storage.node.field_tags.yml`: `field.storage.node.field_tags.yml`:
```yaml ```language-yaml
langcode: en langcode: en
status: true status: true
dependencies: dependencies:
@ -198,7 +198,7 @@ custom_storage: false
Then, enable the module within `PostBuilderTest`: Then, enable the module within `PostBuilderTest`:
```php ```language-php
protected static $modules = [ protected static $modules = [
// Core. // Core.
'node', 'node',
@ -210,7 +210,7 @@ protected static $modules = [
Finally, install the configuration to create the field. Add this within the test: Finally, install the configuration to create the field. Add this within the test:
```php ```language-php
$this->installConfig(modules: [ $this->installConfig(modules: [
'atdc_test', 'atdc_test',
]); ]);
@ -218,7 +218,7 @@ $this->installConfig(modules: [
After adding this and attempting to install the configuration to add the field, you'll get an error: After adding this and attempting to install the configuration to add the field, you'll get an error:
```plain ```language-plain
Exception when installing config for module atdc_test, the message was: Field 'field_tags' on entity type 'node' references a target entity type 'taxonomy_term', which does not exist. Exception when installing config for module atdc_test, the message was: Field 'field_tags' on entity type 'node' references a target entity type 'taxonomy_term', which does not exist.
``` ```
@ -234,7 +234,7 @@ As well as the field configuration, we also need to create the Post content type
This can be done by creating a `node.type.post.yml` file: This can be done by creating a `node.type.post.yml` file:
```yaml ```language-yaml
langcode: en langcode: en
status: true status: true
dependencies: { } dependencies: { }
@ -255,7 +255,7 @@ Let's update the test and add assertions about the tags being saved and returned
Get the tags from the post and assert that three tags are returned: Get the tags from the post and assert that three tags are returned:
```php ```language-php
$tags = $node->get('field_tags')->referencedEntities(); $tags = $node->get('field_tags')->referencedEntities();
self::assertCount(3, $tags); self::assertCount(3, $tags);
``` ```
@ -264,7 +264,7 @@ As none have been added, this would fail the test.
Update the test to use a `setTags()` method that you haven't created yet: Update the test to use a `setTags()` method that you haven't created yet:
```php ```language-php
$node = PostBuilder::create() $node = PostBuilder::create()
->setTitle('test') ->setTitle('test')
->setTags(['Drupal', 'PHP', 'Testing']) ->setTags(['Drupal', 'PHP', 'Testing'])
@ -277,7 +277,7 @@ You should get an error confirming the method is undefined:
To fix this, add the `tags` property and `setTags()` method to `PostBuilder`: To fix this, add the `tags` property and `setTags()` method to `PostBuilder`:
```php ```language-php
/** /**
* @var string[] * @var string[]
*/ */
@ -297,7 +297,7 @@ Tags will be an array of strings, and `setTags()` should set the tags to the `ta
Next, add the logic to `getPost()` to create a taxonomy term for each tag name. Next, add the logic to `getPost()` to create a taxonomy term for each tag name.
```php ```language-php
$tagTerms = []; $tagTerms = [];
if ($this->tags !== []) { if ($this->tags !== []) {
@ -322,7 +322,7 @@ If `$this->tags` is not empty, create a new taxonomy term for each one and save
As well as asserting we have the correct number of tags, let's also assert that the correct tag names are returned and that they are the correct type of term. As well as asserting we have the correct number of tags, let's also assert that the correct tag names are returned and that they are the correct type of term.
```php ```language-php
self::assertContainsOnlyInstancesOf(TermInterface::class, $tags); self::assertContainsOnlyInstancesOf(TermInterface::class, $tags);
foreach ($tags as $tag) { foreach ($tags as $tag) {
self::assertSame('tags', $tag->bundle()); self::assertSame('tags', $tag->bundle());
@ -333,7 +333,7 @@ To assert the tags array only includes taxonomy terms, use `self::assertContains
Next, add some new assertions to the test to check the tag names match the specified tags. Next, add some new assertions to the test to check the tag names match the specified tags.
```php ```language-php
self::assertSame('Drupal', $tags[0]->label()); self::assertSame('Drupal', $tags[0]->label());
self::assertSame('PHP', $tags[1]->label()); self::assertSame('PHP', $tags[1]->label());
self::assertSame('Testing', $tags[2]->label()); self::assertSame('Testing', $tags[2]->label());

View file

@ -22,7 +22,7 @@ I've also seen Unit tests that are very tightly coupled to the implementation, s
Based on what you've learned so far, let's write a Unit test that we'd expect to pass: Based on what you've learned so far, let's write a Unit test that we'd expect to pass:
```php ```language-php
<?php <?php
namespace Drupal\Tests\atdc\Unit; namespace Drupal\Tests\atdc\Unit;
@ -58,7 +58,7 @@ Update the test to create a mock version of `NodeInterface` instead.
As the mock an instance of `NodeInterface`, it satisfies the assertion and the test passes. As the mock an instance of `NodeInterface`, it satisfies the assertion and the test passes.
```php ```language-php
/** @test */ /** @test */
public function it_wraps_a_post(): void { public function it_wraps_a_post(): void {
$node = $this->createMock(NodeInterface::class); $node = $this->createMock(NodeInterface::class);
@ -69,7 +69,7 @@ public function it_wraps_a_post(): void {
Next, add an assertion to ensure the bundle is correct: Next, add an assertion to ensure the bundle is correct:
```php ```language-php
self::assertSame('post', $node->bundle()); self::assertSame('post', $node->bundle());
``` ```
@ -81,7 +81,7 @@ Because you're using a mock, all methods will return `NULL`.
To get this to pass, you need to define what `$this->bundle()` will return: To get this to pass, you need to define what `$this->bundle()` will return:
```php ```language-php
$node->method('bundle')->willReturn('post'); $node->method('bundle')->willReturn('post');
``` ```
@ -97,7 +97,7 @@ Within the test, instantiate a new `Postwrapper` class that takes the node as an
Then, add an assertion that a `getType()` method should return `post`. Then, add an assertion that a `getType()` method should return `post`.
```php ```language-php
$wrapper = new PostWrapper($node); $wrapper = new PostWrapper($node);
self::assertSame('post', $wrapper->getType()); self::assertSame('post', $wrapper->getType());
@ -105,7 +105,7 @@ self::assertSame('post', $wrapper->getType());
Next, create a `PostWrapper` class with the `getType()` method: Next, create a `PostWrapper` class with the `getType()` method:
```php ```language-php
<?php <?php
namespace Drupal\atdc; namespace Drupal\atdc;
@ -132,7 +132,7 @@ We've tested that the `PostWrapper` works with post nodes, but let's also ensure
Create a new test that creates a mock node and returns `page` as the bundle: Create a new test that creates a mock node and returns `page` as the bundle:
```php ```language-php
/** /**
* @test * @test
* @testdox It can't wrap a page * @testdox It can't wrap a page
@ -153,7 +153,7 @@ Before creating a new `PostWrapper`, assert that an `InvalidArgumentException` s
To fix it, within the constructor for `PostWrapper`, check the bundle and throw the expected Exception if the bundle is not `post`: To fix it, within the constructor for `PostWrapper`, check the bundle and throw the expected Exception if the bundle is not `post`:
```php ```language-php
/** /**
* @throws \InvalidArgumentException * @throws \InvalidArgumentException
*/ */