Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Shrek, Onions and Architecture - PHPUK 2025

Shrek, Onions and Architecture - PHPUK 2025

Have you ever encountered a codebase that’s ogreish and has so many layers of complexity that it makes you cry? It might actually be an onion!

In this humorous talk we’ll investigate the structure of large monolithic applications and how we can peel back layers to reveal greater understanding. Using the principles of hexagonal architecture and the science of onion growth, we can make monoliths great again.

Katy Ereira

February 19, 2025
Tweet

More Decks by Katy Ereira

Other Decks in Programming

Transcript

  1. Katy Ereira - #PHPUK25 - @[email protected] Itʼs only logical…. •

    There are so many layers. • It smells (or at least, engineers act like it does). • Biting into it without preparation leaves a bad taste in your mouth. • It makes you cry. Introducing: the monionlith
  2. Katy Ereira - #PHPUK25 - @[email protected] Agenda 01 Growing onions

    Learn the secrets to grow a robust monionlith, from the ground up. 02 Cooking onions Making the monionlith palatable - top tips for coding without tears. 00 Peeling onions Demystifying monionlith anatomy and architectural layers.
  3. Katy Ereira - #PHPUK25 - @[email protected] The layers of an

    onion can be separated one at a time, from the outside in. 00 Hexagonal architecture and dependency inversion
  4. Katy Ereira - #PHPUK25 - @[email protected] 1 An onion. Skin:

    application layer Flesh: business logic Base: core domain model Roots: infrastructure Leaves: presentation 2 3 4 5 1 2 3 4 5
  5. Katy Ereira - #PHPUK25 - @[email protected] Flowers and leaves •

    Concerned with visibility and accessibility from the outside. • Provides entry points for interacting with the internals. • Is attached to and aware of all inner layers. presentation 🧅 Fun onion fact: onions usually flower in the second year as they move between growth and reproductive phases.
  6. Katy Ereira - #PHPUK25 - @[email protected] ➔ HTTP routes ➔

    REST API entry points ➔ GraphQL schemas ➔ HTML templates / views Flowers and leaves presentation
  7. Katy Ereira - #PHPUK25 - @[email protected] Skin • Facilitates safe

    interactions with the system. • Itʼs thin; does not provide any business logic. • Receives and directs requests, and returns responses. application layer 🧅 Fun onion fact: dried leaves which form the skin of an onion are also known as the ‘tunicʼ, and it protects the onion from pests and disease.
  8. Katy Ereira - #PHPUK25 - @[email protected] Fleshy layers • Form

    the bulk of the system; provides business logic. • Are distinct and separate from each other. • Are implementations of core abstractions. business logic 🧅 Fun onion fact: the fleshy layers that form the bulk of an onion are a type of succulent leaf that stores water and nutrients.
  9. Katy Ereira - #PHPUK25 - @[email protected] ➔ Domain services ➔

    Helpers ➔ Processes / algorithms Fleshy layers business logic
  10. Katy Ereira - #PHPUK25 - @[email protected] Base • Contains the

    building blocks for all future growth. • Encapsulates core concepts. • Has no substance in and of itself. core domain model 🧅 Fun onion fact: the base of an onion is actually an extremely short and wide stem, and it contains stem cells.
  11. Katy Ereira - #PHPUK25 - @[email protected] Roots • Communicates outwards

    from the system. • Interacts with external infrastructure. • Is well hidden from the public. • Enriches the system internals. infrastructure 🧅 Fun onion fact: onion roots grow from the underside of the base stem in contrast to leaves and flowers which grow from the top.
  12. Katy Ereira - #PHPUK25 - @[email protected] ➔ SDKs ➔ Databases

    & ORMs ➔ Frameworks ➔ Adaptors Roots infrastructure
  13. Katy Ereira - #PHPUK25 - @[email protected] Onions grow from the

    basal stem whose cells contain the information for growth. 01 Domain Driven Design ⅹ Behaviour Driven Design
  14. Katy Ereira - #PHPUK25 - @[email protected] Growing onions A step

    by step guide Discovery Talk to Domain Experts and business stakeholders. Become fluent in Ubiquitous Language . Identify the demand and create User Stories . Groundwork Plant a seed Germinate Set root Develop a skin Bloom and harvest!
  15. Katy Ereira - #PHPUK25 - @[email protected] Growing onions A step

    by step guide Definition Identify growth requirements based on the Shared Understanding . Define API Schemas . Create Entities and Value Objects . The seed is your Core Domain Model . Groundwork Plant a seed Germinate Set root Develop a skin Bloom and harvest!
  16. Katy Ereira - #PHPUK25 - @[email protected] Growing onions A step

    by step guide Implementation Implement the Business Logic - the way domain objects transact. Remember! Onions grow in layers - these are Bounded Contexts . Bounded contexts are independent from each other. Groundwork Plant a seed Germinate Set root Develop a skin Bloom and harvest!
  17. Katy Ereira - #PHPUK25 - @[email protected] Growing onions A step

    by step guide Infrastructure External Infrastructure supports growth. Our core domain base provides Ports (interfaces) that are satisfied using Adaptors (implementations). Adaptors can be used for ORMs, messaging and event services, and logging. Groundwork Plant a seed Germinate Set root Develop a skin Bloom and harvest!
  18. Katy Ereira - #PHPUK25 - @[email protected] Growing onions A step

    by step guide Integration Plug adaptors into ports using Service Providers . User stories become Use Cases via controllers. Groundwork Plant a seed Germinate Set root Develop a skin Bloom and harvest!
  19. Katy Ereira - #PHPUK25 - @[email protected] Growing onions A step

    by step guide Delivery Expose Ingress points - use a router to handle HTTP requests, register commands. Implement a visual Frontend . Groundwork Plant a seed Germinate Set root Develop a skin Bloom and harvest!
  20. Katy Ereira - #PHPUK25 - @[email protected] One does not simply

    bite into an onion. One must master the elements of good cooking. 02 Coding without tears.
  21. Katy Ereira - #PHPUK25 - @[email protected] 🧂 Salt • Add

    comments when they improve clarity. • Avoid adding unnecessary detail. • Use Ubiquitous Language in comments and type references. add clarity 󰱜 Samin says, “The three basic decisions involving salt are: When? How much? In what form?ˮ
  22. Katy Ereira - #PHPUK25 - @[email protected] Donʼt do that Do

    this interface Basket { /** * @param Item $item */ public function addItem( Item $item, ): self; }
  23. Katy Ereira - #PHPUK25 - @[email protected] interface Basket { /**

    * @param Item $item */ public function addItem( Item $item, ): self; } interface Basket { /** * */ public function addItem( Item $item, ): self; } Donʼt do that Do this interface Basket { /** * @param Item $item */ public function addItem( Item $item, ): self; }
  24. Katy Ereira - #PHPUK25 - @[email protected] interface Basket { /**

    * */ public function addItem( Item $item, ): self; } interface Basket { /** * @throws OutOfStock */ public function addItem( Item $item, ): self; } Donʼt do that Do this interface Basket { /** * @param Item $item */ public function addItem( Item $item, ): self; }
  25. Katy Ereira - #PHPUK25 - @[email protected] Donʼt do that Do

    this /** * Creates a new order for the * basket, then emits an event and * emails the merchant. * * New Order returned if success. * * Else, returns an error string. */ public function placeOrder( Basket $basket ): mixed;
  26. Katy Ereira - #PHPUK25 - @[email protected] /** * Creates a

    new order for the * basket, then emits an event and * emails the merchant. * * New Order returned if success. * * Else, returns an error string. */ public function placeOrder( Basket $basket ): mixed; /** * Creates a new order for the * basket, then emits an event and * emails the merchant. * * * * @throws OrderPlacementFailed */ public function placeOrder( Basket $basket ): Order; Donʼt do that Do this /** * Creates a new order for the * basket, then emits an event and * emails the merchant. * * New Order returned if success. * * Else, returns an error string. */ public function placeOrder( Basket $basket ): mixed;
  27. Katy Ereira - #PHPUK25 - @[email protected] /** * Creates a

    new order for the * basket, then emits an event and * emails the merchant. * * * * @throws OrderPlacementFailed */ public function placeOrder( Basket $basket ): Order; /** * Attempt to place an order when * it has been indicated that * the items in the basket would * like to be purchased. * * * @throws OrderPlacementFailed */ public function placeOrder( Basket $basket ): Order; Donʼt do that Do this /** * Creates a new order for the * basket, then emits an event and * emails the merchant. * * * * @throws OrderPlacementFailed */ public function placeOrder( Basket $basket ): Order;
  28. Katy Ereira - #PHPUK25 - @[email protected] /** * Creates a

    new order for the * basket, then emits an event and * emails the merchant. * * * * @throws OrderPlacementFailed */ public function placeOrder( Basket $basket ): Order; Donʼt do that Do this
  29. Katy Ereira - #PHPUK25 - @[email protected] Donʼt do that Do

    this // E-commerce checkout process. interface Handler { // Submit an order for processing public function process( Record $arg, ); } interface Record { // Customer details. public function props(): object; // Items in the order. public function collection(): array; }
  30. Katy Ereira - #PHPUK25 - @[email protected] // E-commerce checkout process.

    interface Handler { // Submit an order for processing public function process( Record $arg, ); } interface Record { // Customer details. public function props(): object; // Items in the order. public function collection(): array; } // E-commerce checkout process. interface Checkout { // Submit an order for processing public function submit( Order $order, ); } interface Order { // Customer details. public function getCustomer(): object; // Items in the order. public function getItems(): array; } Donʼt do that Do this // E-commerce checkout process. interface Handler { // Submit an order for processing public function process( Record $arg, ); } interface Record { // Customer details. public function props(): object; // Items in the order. public function collection(): array; }
  31. Katy Ereira - #PHPUK25 - @[email protected] // E-commerce checkout process.

    interface Checkout { // Submit an order for processing public function submit( Order $order, ); } interface Order { // Customer details. public function getCustomer(): object; // Items in the order. public function getItems(): array; } interface Checkout { public function submit( Order $order, ); } interface Order { public function getCustomer(): object; public function getItems(): array; } Donʼt do that Do this // E-commerce checkout process. interface Handler { // Submit an order for processing public function process( Record $arg, ); } interface Record { // Customer details. public function props(): object; // Items in the order. public function collection(): array; }
  32. Katy Ereira - #PHPUK25 - @[email protected] 🧈 Fat • Donʼt

    define unnecessary get and set methods. • Consider whether attributes are optional or required. • Beware of invalid state. • If something is mutable; think about why. keep it lean 󰱜 Samin says, “fat plays three distinct roles in the kitchen: as a main ingredient, as a cooking medium, and … as seasoning.ˮ
  33. Katy Ereira - #PHPUK25 - @[email protected] Donʼt do that Do

    this class User { public function setId( UUID $id ) {...} public function getId(): UUID {...} public function setName( string $name ) {...} public function getName(): string {...} }
  34. Katy Ereira - #PHPUK25 - @[email protected] class User { public

    function setId( UUID $id ) {...} public function getId(): UUID {...} public function setName( string $name ) {...} public function getName(): string {...} } class User { public function setId( UUID $id ) {...} public function getId(): UUID {...} public function setName( string $name ) {...} public function getName(): string {...} } class User { public function __construct( private readonly UUID $id, ) {}; public function getId(): UUID {...} public function setName( string $name ) {...} public function getName(): string {...} } Donʼt do that Do this class User { public function setId( UUID $id ) {...} public function getId(): UUID {...} public function setName( string $name ) {...} public function getName(): string {...} }
  35. Katy Ereira - #PHPUK25 - @[email protected] class User { public

    function __construct( private readonly UUID $id, ) {}; public function getId(): UUID {...} public function setName( string $name ) {...} public function getName(): string {...} } class User { public function __construct( private readonly UUID $id, public string $name, ) {}; public function getId(): UUID {...} } Donʼt do that Do this class User { public function __construct( private readonly UUID $id, ) {}; public function getId(): UUID {...} public function setName( string $name ) {...} public function getName(): string {...} }
  36. Katy Ereira - #PHPUK25 - @[email protected] class User { public

    function __construct( private readonly UUID $id, ) {}; public function getId(): UUID {...} public function setName( string $name ) {...} public function getName(): string {...} } Donʼt do that Do this
  37. Katy Ereira - #PHPUK25 - @[email protected] Donʼt do that Do

    this interface ContactDetails { public function getAddress(): string {} public function setAddress(string $a) {} public function getTown(): string {} public function setTown(string $t) {} public function getZip(): string {} public function setZip(string $zip) {} public function getCountry(): string {} public function setCountry(string $c) {} public function getAreaCode(): string {} public function setAreaCode(string $c) {} public function getPhone(): string {} public function setPhone(string $num) {} public function getEmail(): string {} public function setEmail(string $e) {} }
  38. Katy Ereira - #PHPUK25 - @[email protected] interface ContactDetails { public

    function getAddress(): string {} public function setAddress(string $a) {} public function getTown(): string {} public function setTown(string $t) {} public function getZip(): string {} public function setZip(string $zip) {} public function getCountry(): string {} public function setCountry(string $c) {} public function getAreaCode(): string {} public function setAreaCode(string $c) {} public function getPhone(): string {} public function setPhone(string $num) {} public function getEmail(): string {} public function setEmail(string $e) {} } interface ContactDetails { public function getAddress(): string {} public function setAddress(string $a) {} public function getTown(): string {} public function setTown(string $t) {} public function getZip(): string {} public function setZip(string $zip) {} public function getCountry(): string {} public function setCountry(string $c) {} public function getAreaCode(): string {} public function setAreaCode(string $c) {} public function getPhone(): string {} public function setPhone(string $num) {} public function getEmail(): string {} public function setEmail(string $e) {} } interface ContactDetails { public function getAddress(): Address {} public function setAddress(Address $a) {} public function getAreaCode(): string {} public function setAreaCode(string $c) {} public function getPhone(): string {} public function setPhone(string $num) {} public function getEmail(): string {} public function setEmail(string $e) {} } interface Address {} Donʼt do that Do this interface ContactDetails { public function getAddress(): string {} public function setAddress(string $a) {} public function getTown(): string {} public function setTown(string $t) {} public function getZip(): string {} public function setZip(string $zip) {} public function getCountry(): string {} public function setCountry(string $c) {} public function getAreaCode(): string {} public function setAreaCode(string $c) {} public function getPhone(): string {} public function setPhone(string $num) {} public function getEmail(): string {} public function setEmail(string $e) {} }
  39. Katy Ereira - #PHPUK25 - @[email protected] interface ContactDetails { public

    function getAddress(): Address {} public function setAddress(Address $a) {} public function getAreaCode(): string {} public function setAreaCode(string $c) {} public function getPhone(): string {} public function setPhone(string $num) {} public function getEmail(): string {} public function setEmail(string $e) {} } interface Address {} interface ContactDetails { public function getAddress(): Address {} public function setAddress(Address $a) {} public function getPhone(): Phone {} public function setPhone(Phone $num) {} public function getEmail(): string {} public function setEmail(string $e) {} } interface Address {} interface Phone {} Donʼt do that Do this interface ContactDetails { public function getAddress(): string {} public function setAddress(string $a) {} public function getTown(): string {} public function setTown(string $t) {} public function getZip(): string {} public function setZip(string $zip) {} public function getCountry(): string {} public function setCountry(string $c) {} public function getAreaCode(): string {} public function setAreaCode(string $c) {} public function getPhone(): string {} public function setPhone(string $num) {} public function getEmail(): string {} public function setEmail(string $e) {} }
  40. Katy Ereira - #PHPUK25 - @[email protected] interface ContactDetails { public

    function getAddress(): Address {} public function setAddress(Address $a) {} public function getPhone(): Phone {} public function setPhone(Phone $num) {} public function getEmail(): string {} public function setEmail(string $e) {} } interface Address {} interface Phone {} interface ContactDetails { public function getAddress(): Address {} public function setAddress(Address $a) {} public function getPhone(): Phone {} public function setPhone(Phone $num) {} public function getEmail(): Email {} public function setEmail(Email $e) {} } interface Address {} interface Phone {} interface Email {} Donʼt do that Do this interface ContactDetails { public function getAddress(): string {} public function setAddress(string $a) {} public function getTown(): string {} public function setTown(string $t) {} public function getZip(): string {} public function setZip(string $zip) {} public function getCountry(): string {} public function setCountry(string $c) {} public function getAreaCode(): string {} public function setAreaCode(string $c) {} public function getPhone(): string {} public function setPhone(string $num) {} public function getEmail(): string {} public function setEmail(string $e) {} }
  41. Katy Ereira - #PHPUK25 - @[email protected] interface ContactDetails { public

    function getAddress(): Address {} public function setAddress(Address $a) {} public function getPhone(): Phone {} public function setPhone(Phone $num) {} public function getEmail(): string {} public function setEmail(string $e) {} } interface Address {} interface Phone {} Donʼt do that Do this
  42. Katy Ereira - #PHPUK25 - @[email protected] Donʼt do that Do

    this interface Person { public function getDob(): ?Date; public function setDob( Date $birthdate, ): void; public function getAge(): ?int; public function setAge( int $age ): void; }
  43. Katy Ereira - #PHPUK25 - @[email protected] Donʼt do that Do

    this interface Person { public function getDob(): ?Date; public function setDob( Date $birthdate, ): void; public function getAge(): ?int; public function setAge( int $age ): void; } interface Person { public function getDob(): ?Date; public function setDob( Date $birthdate, ): void; public function getAge(): ?int; public function setAge( int $age ): void; } interface Person { public function getDob(): ?Date; public function setDob( Date $birthdate, ): void; public function getAge(): ?int; }
  44. Katy Ereira - #PHPUK25 - @[email protected] Donʼt do that Do

    this interface Order { public function setStatus( Status $status ): void; public function getStatus( ): Status; }
  45. Katy Ereira - #PHPUK25 - @[email protected] interface Order { public

    function setStatus( Status $status ): void; public function getStatus( ): Status; } interface Order { public function process(); public function ship(); public function getStatus( ): Status; } Donʼt do that Do this interface Order { public function setStatus( Status $status ): void; public function getStatus( ): Status; }
  46. Katy Ereira - #PHPUK25 - @[email protected] interface Order { public

    function process(); public function ship(); public function getStatus( ): Status; } Donʼt do that Do this interface Order { public function setStatus( Status $status ): void; public function getStatus( ): Status; } interface Order { public function process(); public function ship(); public function isShipped( ): bool; }
  47. Katy Ereira - #PHPUK25 - @[email protected] 🍶 Acid • Check

    that use statements only reference inner layers. • Depend on abstractions, not concretions. • Avoid referencing specific infrastructure. • To break rules you must first understand them. manage complexity 󰱜 Samin says, “play to each elementʼs strengths: use Salt to enhance, Fat to carry, and Acid to balance flavor.ˮ
  48. Katy Ereira - #PHPUK25 - @[email protected] Donʼt do that Do

    this namespace ShopApp\Domain; use ShopApp\Implementation; interface Invoice { public function payUsing( Services\Cash $payment ): } namespace ShopApp\Implementation; class Cash {} class Card {}
  49. Katy Ereira - #PHPUK25 - @[email protected] namespace ShopApp\Domain; use ShopApp\Services;

    interface Invoice { public function payUsing( Services\Cash $payment ): } namespace ShopApp\Services; class Cash {} class Card {} namespace ShopApp\Domain; interface Invoice { public function payUsing( Payment $payment ): } Donʼt do that Do this namespace ShopApp\Domain; use ShopApp\Implementation; interface Invoice { public function payUsing( Services\Cash $payment ): } namespace ShopApp\Implementation; class Cash {} class Card {} namespace ShopApp\Implementation; use ShopApp\Domain; class Cash implements Domain\Payment {} class Card implements Domain\Payment {}
  50. Katy Ereira - #PHPUK25 - @[email protected] Donʼt do that Do

    this namespace ShopApp\Implementation; use ShopApp\Domain; use MySQL\Connection; class OrderStats { public Connection $mysql; public function count(): int { $orders = $this->mysql ->execute(‘SELECT * FROM orders’); return count($orders); } }
  51. Katy Ereira - #PHPUK25 - @[email protected] namespace ShopApp\Services; use ShopApp\Domain;

    use MySQL\Connection; class OrderStats { public Connection $mysql; public function count(): int { $orders = $this->mysql ->execute(‘SELECT * FROM orders’); return count($orders); } } namespace ShopApp\Implementation; use ShopApp\Domain; class OrderStats { private Domain\OrderRepo $orders; public function count(): int { $orders = $this->orders ->findAll(); return count($orders); } } Donʼt do that Do this namespace ShopApp\Implementation; use ShopApp\Domain; use MySQL\Connection; class OrderStats { public Connection $mysql; public function count(): int { $orders = $this->mysql ->execute(‘SELECT * FROM orders’); return count($orders); } }
  52. Katy Ereira - #PHPUK25 - @[email protected] Donʼt do that Do

    this namespace ShopApp\Infrastructure; use ShopApp\Domain; class MySQLOrders implements Domain\OrderRepo { public function __construct() { $this->sql = new MySQL\Connection(); } public function getAll(): array { return $this->sql->execute( ‘SELECT * FROM orders’, ); } }
  53. Katy Ereira - #PHPUK25 - @[email protected] namespace ShopApp\Infrastructure; use ShopApp\Domain;

    class MySQLOrders implements Domain\Orders { public function __construct() { $this->sql = new MySQL\Connection(); } public function getAll(): array { return $this->sql->execute( ‘SELECT * FROM orders’, ); } } namespace ShopApp\Infrastructure; use ShopApp\Domain; class MySQLOrders implements Domain\OrderRepo { public function __construct( private readonly MySQL\Connection $sql ) { } public function getAll(): array { return $this->sql->execute( ‘SELECT * FROM orders’, ); } } Donʼt do that Do this namespace ShopApp\Infrastructure; use ShopApp\Domain; class MySQLOrders implements Domain\OrderRepo { public function __construct() { $this->sql = new MySQL\Connection(); } public function getAll(): array { return $this->sql->execute( ‘SELECT * FROM orders’, ); } }
  54. Katy Ereira - #PHPUK25 - @[email protected] Protip: Deptrac A static

    analysis tool https://github.com/deptrac/deptrac
  55. Katy Ereira - #PHPUK25 - @[email protected] 🔥 Heat • Test

    implementations, not abstractions. • Avoid using mocks unless absolutely necessary. Use fake implementations. • Implementation details are subject to change; donʼt test private methods. perform under pressure 󰱜 Samin says, “apply the right type and quantity of heat for the proper amount of time ... and you will turn out vibrant and beautiful foodˮ
  56. Katy Ereira - #PHPUK25 - @[email protected] Testing Legacy Code -

    a talk. A shameless plug… https://youtu.be/U5BkMJpv_ZQ
  57. Katy Ereira - #PHPUK25 - @[email protected] Donʼt do that public

    function testGetValue(): void { $sut = Mockery::mock(Order::class); $cart = Mockery::mock(Cart::class); $cart->expects(‘total’)->andReturn(10); $sut->expects(‘getValue’) ->andReturn($cart->total()); $this->assertSame(10, $sut->getValue()); } System under test: class Order { public function getValue(): int { return $this->cart?->total() ?? 0; } } PASS
  58. Katy Ereira - #PHPUK25 - @[email protected] Donʼt do that public

    function testGetValue(): void { $sut = Mockery::mock(Order::class); $cart = Mockery::mock(Cart::class); $cart->expects(‘total’)->andReturn(10); $sut->expects(‘getValue’) ->andReturn($cart->total()); $this->assertSame(10, $sut->getValue()); } System under test: class Order { public function getValue(): int { return $this->cart?->total() ?? 0; } } PASS public function testGetValue(): void { $sut = Mockery::mock(Order::class); $cart = Mockery::mock(Cart::class); $cart->expects(‘total’)->andReturn(null); $sut->expects(‘getValue’) ->andReturn($cart->total()); $this->assertSame(0, $sut->getValue()); } FAIL
  59. Katy Ereira - #PHPUK25 - @[email protected] public function testGetValue(): void

    { $sut = Mockery::mock(Order::class); $cart = Mockery::mock(Cart::class); $cart->expects(‘total’)->andReturn(null); $sut->expects(‘getValue’) ->andReturn($cart->total()); $this->assertSame(0, $sut->getValue()); } Donʼt do that public function testGetValue(): void { $sut = Mockery::mock(Order::class); $cart = Mockery::mock(Cart::class); $cart->expects(‘total’)->andReturn(null); $sut->expects(‘getValue’) ->andReturn($cart->total()); $this->assertSame(0, $sut->getValue()); } System under test: class Order { public function getValue(): int { return $this->cart?->total() ?? 0; } } public function testGetValue(): void { $sut = new Order(); $cart = Mockery::mock(Cart::class); $cart->expects(‘total’)->andReturn(null); $sut->cart = $cart; $this->assertSame(0, $sut->getValue()); } FAIL PASS Do this
  60. Katy Ereira - #PHPUK25 - @[email protected] public function testGetValue(): void

    { $sut = new Order(); $cart = Mockery::mock(Cart::class); $cart->expects(‘total’)->andReturn(null); $sut->cart = $cart; $this->assertSame(0, $sut->getValue()); } Donʼt do that public function testGetValue(): void { $sut = Mockery::mock(Order::class); $cart = Mockery::mock(Cart::class); $cart->expects(‘total’)->andReturn(null); $sut->expects(‘getValue’) ->andReturn($cart->total()); $this->assertSame(0, $sut->getValue()); } System under test: class Order { public function getValue(): int { return $this->cart?->total() ?? 0; } } public function testGetValue(): void { $sut = new Order(); $sut->cart = new class implements Cart { public function total() { return null; } }; $this->assertSame(0, $sut->getValue()); } FAIL PASS Do this
  61. Katy Ereira - #PHPUK25 - @[email protected] Quick recap then that

    shallot! Onion Architecture Your application has many separate layers, accessed from the outside in. ✔ Identify the layers in your system - install Deptrac! DDD How your application grows depends a lot on the domain in which it is planted. ✔ Start from the ground for new functionality. Got Feedback? Katy Ereira joind.in/talk/ca21b @[email protected] Clean Coding Applications taste better when you master the elements of good coding. ✔ Delete redundant comments. Refactor to use DI.