Created
May 16, 2025 03:30
-
-
Save audinue/accb6c1db53c5c4a963a0ff2fc0919f9 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// =================================================== | |
// 🧠 Domain (Entity) | |
// =================================================== | |
namespace Domain { | |
class Product | |
{ | |
function __construct( | |
public readonly int $id, | |
public readonly string $name, | |
public readonly float $price | |
) {} | |
} | |
} | |
// =================================================== | |
// 💼 Application Business Rules (Use Case) | |
// =================================================== | |
namespace Application { | |
use Domain\Product; | |
interface ProductRepository | |
{ | |
/** @return Product[] */ | |
function getAll(): array; | |
} | |
class GetAllProducts | |
{ | |
/** | |
* @param ProductRepository $products | |
*/ | |
function __construct(private ProductRepository $products) {} | |
/** | |
* @return Product[] | |
*/ | |
function execute(): array | |
{ | |
return $this->products->getAll(); | |
} | |
} | |
} | |
// =================================================== | |
// 🧝 Interface Adapter (Controller + Presenter) | |
// =================================================== | |
namespace InterfaceAdapter { | |
interface ProductPresenter | |
{ | |
/** | |
* @param \Domain\Product[] $products | |
*/ | |
function present(array $products): void; | |
} | |
class ProductController | |
{ | |
function __construct( | |
private \Application\GetAllProducts $useCase, | |
private ProductPresenter $presenter | |
) {} | |
function showProductList(): void | |
{ | |
$products = $this->useCase->execute(); | |
$this->presenter->present($products); | |
} | |
} | |
} | |
// =================================================== | |
// ⚙️ Infrastructure (I/O Implementation) | |
// =================================================== | |
namespace Infrastructure { | |
use Domain\Product; | |
class InMemoryProductRepository implements \Application\ProductRepository | |
{ | |
function getAll(): array | |
{ | |
return [ | |
new Product(1, 'Apple', 10), | |
new Product(2, 'Banana', 20), | |
new Product(3, 'Cherry', 30), | |
]; | |
} | |
} | |
class DumpProductPresenter implements \InterfaceAdapter\ProductPresenter | |
{ | |
function present(array $products): void | |
{ | |
foreach ($products as $p) { | |
echo "{$p->id}. {$p->name} - Rp{$p->price}\n"; | |
} | |
echo "Total: " . count($products) . " produk\n"; | |
} | |
} | |
} | |
// =================================================== | |
// 🚀 Bootstrap / Runtime Wiring | |
// =================================================== | |
namespace { | |
// manual autoload ala warung kopi | |
$repo = new \Infrastructure\InMemoryProductRepository(); | |
$presenter = new \Infrastructure\DumpProductPresenter(); | |
$useCase = new \Application\GetAllProducts($repo); | |
$controller = new \InterfaceAdapter\ProductController($useCase, $presenter); | |
// jalanin entrypoint | |
$controller->showProductList(); | |
} | |
// =================================================== | |
// 📃 Test / Proof of Concept | |
// =================================================== | |
namespace Test { | |
class FakeRepo implements \Application\ProductRepository { | |
function getAll(): array { | |
return [ | |
new \Domain\Product(999, 'TEST', 123.45) | |
]; | |
} | |
} | |
class CollectingPresenter implements \InterfaceAdapter\ProductPresenter { | |
public array $log = []; | |
function present(array $products): void { | |
$this->log = $products; | |
} | |
} | |
$repo = new FakeRepo(); | |
$presenter = new CollectingPresenter(); | |
$useCase = new \Application\GetAllProducts($repo); | |
$controller = new \InterfaceAdapter\ProductController($useCase, $presenter); | |
$controller->showProductList(); | |
assert(count($presenter->log) === 1); | |
assert($presenter->log[0]->name === 'TEST'); | |
echo "\n[TEST PASSED]\n"; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@startuml | |
title Clean Architecture | |
hide empty members | |
package Domain { | |
class Product { | |
id: int | |
name: string | |
price: float | |
} | |
} | |
package Application { | |
interface ProductRepository { | |
getAll(): Product[] | |
} | |
class GetAllProducts { | |
execute(): Product[] | |
} | |
Product <.. ProductRepository | |
ProductRepository <-- GetAllProducts | |
} | |
package InterfaceAdapter { | |
interface ProductPresenter { | |
present(Product[]): void | |
} | |
class ProductController { | |
showProductList(): void | |
} | |
Product <.. ProductPresenter | |
GetAllProducts <--- ProductController | |
ProductPresenter <-- ProductController | |
} | |
package Infrastructure { | |
class InMemoryProductRepository { | |
getAll(): Product[] | |
} | |
class DumpProductPresenter { | |
present(Product[]): void | |
} | |
ProductRepository <|... InMemoryProductRepository | |
ProductPresenter <|... DumpProductPresenter | |
} | |
package Bootstrap { | |
class Main {} | |
GetAllProducts <---- Main | |
ProductController <--- Main | |
DumpProductPresenter <-- Main | |
InMemoryProductRepository <-- Main | |
} | |
@enduml |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Entities | |
type Product = { | |
id: number; | |
name: string; | |
price: number; | |
}; | |
// Use cases | |
type LoadProducts = () => Promise<Product[]>; | |
const getProducts = (loadProducts: LoadProducts) => () => loadProducts(); | |
// Adapters | |
type GetProducts = () => Promise<Product[]>; | |
type PresentProducts = (products: Product[]) => void; | |
const showProducts = | |
(getProducts: GetProducts, presentProducts: PresentProducts) => async () => | |
presentProducts(await getProducts()); | |
// Infrastructures | |
const loadProductsFromMemory: LoadProducts = async () => [ | |
{ id: 1, name: "Apple", price: 1 }, | |
{ id: 2, name: "Banana", price: 2 }, | |
{ id: 3, name: "Cherry", price: 3 }, | |
]; | |
import React from "react"; | |
import { createContext, useContext, useState, useEffect } from "react"; | |
import { createRoot } from "react-dom/client"; | |
class DependencyContainer<T> { | |
constructor( | |
private factories: Record<string, (self: any) => any> = {}, | |
private objects: Record<string, any> = {} | |
) {} | |
set<K extends keyof T | (string & {}), V>( | |
key: K, | |
factory: (self: DependencyContainer<T>) => V | |
): DependencyContainer<T & { [P in K]: V }> { | |
return new DependencyContainer({ ...this.factories, [key]: factory }); | |
} | |
get<K extends keyof T>(key: K): T[K] { | |
let object = this.objects[key as any]; | |
if (!object) { | |
object = this.factories[key as any](this); | |
this.objects[key as any] = object; | |
} | |
return object; | |
} | |
} | |
const Container = createContext( | |
new DependencyContainer({}) | |
.set("loadProducts", () => loadProductsFromMemory) | |
.set("getProducts", (x) => getProducts(x.get("loadProducts"))) | |
.set( | |
"showProducts", | |
(x) => (presentProducts: PresentProducts) => | |
showProducts(x.get("getProducts"), presentProducts) | |
) | |
); | |
const App = () => { | |
const container = useContext(Container); | |
const [products, setProducts] = useState<Product[]>([]); | |
useEffect(() => { | |
container.get("showProducts")(setProducts)(); | |
}, []); | |
return ( | |
<ul> | |
{products.map((product) => ( | |
<li>{product.name}</li> | |
))} | |
</ul> | |
); | |
}; | |
createRoot(document.getElementById("root")).render(<App />); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment