Traits and Namespaces in PHP
Traits and namespaces are powerful PHP features that solve different organizational challenges. Traits enable code reuse across unrelated class hierarchies without inheritance, while namespaces provide a way to organize code and avoid naming conflicts in large applications.
Together, they form the foundation of modern PHP application architecture, enabling clean, maintainable, and well-organized codebases that follow industry standards like PSR-4.
Traits are a mechanism for code reuse in single inheritance languages like PHP. They allow you to share methods across multiple classes without using inheritance.
Basic Trait Usage:
1<?php
2declare(strict_types=1);
3
4trait Timestampable {
5 protected DateTime $createdAt;
6 protected DateTime $updatedAt;
7
8 public function initTimestamps(): void {
9 $this->createdAt = new DateTime();
10 $this->updatedAt = new DateTime();
11 }
12
13 public function touch(): void {
14 $this->updatedAt = new DateTime();
15 }
16
17 public function getCreatedAt(): DateTime {
18 return $this->createdAt;
19 }
20
21 public function getUpdatedAt(): DateTime {
22 return $this->updatedAt;
23 }
24}
25
26class Article {
27 use Timestampable;
28
29 public function __construct(private string $title) {
30 $this->initTimestamps();
31 }
32
33 public function update(string $title): void {
34 $this->title = $title;
35 $this->touch();
36 }
37}
38
39class Comment {
40 use Timestampable;
41
42 public function __construct(private string $content) {
43 $this->initTimestamps();
44 }
45}
46
47$article = new Article('My Article');
48sleep(1);
49$article->update('Updated Article');
50
51echo "Created: " . $article->getCreatedAt()->format('H:i:s') . "\n";
52echo "Updated: " . $article->getUpdatedAt()->format('H:i:s') . "\n";
53?>Multiple Traits:
1<?php
2trait Loggable {
3 protected array $logs = [];
4
5 public function log(string $message): void {
6 $this->logs[] = [
7 'message' => $message,
8 'time' => new DateTime()
9 ];
10 }
11
12 public function getLogs(): array {
13 return $this->logs;
14 }
15}
16
17trait Cacheable {
18 protected ?array $cache = null;
19 protected int $cacheTTL = 3600;
20
21 public function getCached(string $key): mixed {
22 if ($this->cache === null || !isset($this->cache[$key])) {
23 return null;
24 }
25 return $this->cache[$key];
26 }
27
28 public function setCache(string $key, mixed $value): void {
29 $this->cache[$key] = $value;
30 }
31
32 public function clearCache(): void {
33 $this->cache = null;
34 }
35}
36
37class DataService {
38 use Loggable, Cacheable;
39
40 public function fetchData(string $id): array {
41 // Check cache first
42 $cached = $this->getCached($id);
43 if ($cached !== null) {
44 $this->log("Cache hit for: $id");
45 return $cached;
46 }
47
48 // Fetch from database
49 $this->log("Fetching from DB: $id");
50 $data = ['id' => $id, 'value' => 'data'];
51
52 // Store in cache
53 $this->setCache($id, $data);
54
55 return $data;
56 }
57}
58?>Trait Properties and Methods:
1<?php
2trait Validatable {
3 protected array $errors = [];
4
5 protected function addError(string $field, string $message): void {
6 $this->errors[$field][] = $message;
7 }
8
9 public function getErrors(): array {
10 return $this->errors;
11 }
12
13 public function hasErrors(): bool {
14 return !empty($this->errors);
15 }
16
17 public function clearErrors(): void {
18 $this->errors = [];
19 }
20}
21
22class UserForm {
23 use Validatable;
24
25 public function validate(array $data): bool {
26 $this->clearErrors();
27
28 if (empty($data['email'])) {
29 $this->addError('email', 'Email is required');
30 } elseif (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
31 $this->addError('email', 'Invalid email format');
32 }
33
34 if (empty($data['password'])) {
35 $this->addError('password', 'Password is required');
36 } elseif (strlen($data['password']) < 8) {
37 $this->addError('password', 'Password must be at least 8 characters');
38 }
39
40 return !$this->hasErrors();
41 }
42}
43?>When using multiple traits, PHP provides mechanisms to handle method name conflicts and control trait composition.
Conflict Resolution with insteadof:
1<?php
2declare(strict_types=1);
3
4trait Logger {
5 public function log(string $message): void {
6 echo "[LOG] $message\n";
7 }
8}
9
10trait Debugger {
11 public function log(string $message): void {
12 echo "[DEBUG] " . date('Y-m-d H:i:s') . " $message\n";
13 }
14}
15
16class Application {
17 use Logger, Debugger {
18 // Use Debugger's log instead of Logger's
19 Debugger::log insteadof Logger;
20 }
21}
22
23$app = new Application();
24$app->log('Application started');
25// Output: [DEBUG] 2024-01-15 10:30:00 Application started
26?>Method Aliasing with as:
1<?php
2trait EmailSender {
3 public function send(string $to, string $message): void {
4 echo "Sending email to $to: $message\n";
5 }
6}
7
8trait SmsSender {
9 public function send(string $to, string $message): void {
10 echo "Sending SMS to $to: $message\n";
11 }
12}
13
14class NotificationService {
15 use EmailSender, SmsSender {
16 // Keep both methods with different names
17 EmailSender::send as sendEmail;
18 SmsSender::send as sendSms;
19 // Choose one as the default 'send'
20 EmailSender::send insteadof SmsSender;
21 }
22}
23
24$service = new NotificationService();
25$service->send('[email protected]', 'Hello'); // Uses email
26$service->sendEmail('[email protected]', 'Hello');
27$service->sendSms('+1234567890', 'Hello');
28?>Changing Method Visibility:
1<?php
2trait DatabaseOperations {
3 protected function connect(): void {
4 echo "Connecting to database...\n";
5 }
6
7 protected function disconnect(): void {
8 echo "Disconnecting from database...\n";
9 }
10}
11
12class Repository {
13 use DatabaseOperations {
14 // Make connect public
15 connect as public;
16 // Keep disconnect protected
17 }
18
19 public function save(): void {
20 $this->connect();
21 echo "Saving data...\n";
22 $this->disconnect();
23 }
24}
25
26$repo = new Repository();
27$repo->connect(); // Now accessible
28$repo->save();
29?>Traits Requiring Methods:
1<?php
2trait Sortable {
3 public function sort(): array {
4 $items = $this->getItems();
5 usort($items, fn($a, $b) => $a <=> $b);
6 return $items;
7 }
8
9 // Trait requires this method from the using class
10 abstract protected function getItems(): array;
11}
12
13class Collection {
14 use Sortable;
15
16 private array $items = [];
17
18 public function add(int $item): void {
19 $this->items[] = $item;
20 }
21
22 // Must implement this for the trait
23 protected function getItems(): array {
24 return $this->items;
25 }
26}
27
28$collection = new Collection();
29$collection->add(3);
30$collection->add(1);
31$collection->add(2);
32
33print_r($collection->sort()); // [1, 2, 3]
34?>Traits Using Other Traits:
1<?php
2trait HasTimestamps {
3 protected DateTime $createdAt;
4 protected DateTime $updatedAt;
5
6 protected function initializeTimestamps(): void {
7 $this->createdAt = new DateTime();
8 $this->updatedAt = new DateTime();
9 }
10}
11
12trait SoftDeletes {
13 protected ?DateTime $deletedAt = null;
14
15 public function delete(): void {
16 $this->deletedAt = new DateTime();
17 }
18
19 public function restore(): void {
20 $this->deletedAt = null;
21 }
22
23 public function isDeleted(): bool {
24 return $this->deletedAt !== null;
25 }
26}
27
28trait Model {
29 use HasTimestamps, SoftDeletes;
30
31 public function __construct() {
32 $this->initializeTimestamps();
33 }
34}
35
36class Post {
37 use Model;
38
39 public function __construct(private string $title) {
40 parent::__construct(); // Call trait constructor logic
41 }
42}
43?>Namespaces provide a way to group related classes, interfaces, functions, and constants, preventing naming conflicts and improving code organization.
Basic Namespace Declaration:
1<?php
2declare(strict_types=1);
3
4namespace App\Models;
5
6class User {
7 public function __construct(
8 private string $name,
9 private string $email
10 ) {}
11
12 public function getName(): string {
13 return $this->name;
14 }
15}
16
17// In another file:
18namespace App\Controllers;
19
20use App\Models\User;
21
22class UserController {
23 public function create(string $name, string $email): User {
24 return new User($name, $email);
25 }
26}
27?>Namespace Hierarchies:
1<?php
2// File: src/Database/Connections/MySQLConnection.php
3namespace App\Database\Connections;
4
5class MySQLConnection {
6 public function connect(): void {
7 echo "Connecting to MySQL...\n";
8 }
9}
10
11// File: src/Database/Connections/PostgreSQLConnection.php
12namespace App\Database\Connections;
13
14class PostgreSQLConnection {
15 public function connect(): void {
16 echo "Connecting to PostgreSQL...\n";
17 }
18}
19
20// File: src/Database/ConnectionManager.php
21namespace App\Database;
22
23use App\Database\Connections\MySQLConnection;
24use App\Database\Connections\PostgreSQLConnection;
25
26class ConnectionManager {
27 public function getMySql(): MySQLConnection {
28 return new MySQLConnection();
29 }
30
31 public function getPostgreSQL(): PostgreSQLConnection {
32 return new PostgreSQLConnection();
33 }
34}
35?>The use Keyword:
1<?php
2namespace App\Services;
3
4// Import specific class
5use App\Models\User;
6
7// Import with alias
8use App\Database\ConnectionManager as DB;
9
10// Import multiple classes from same namespace
11use App\Repositories\{UserRepository, PostRepository, CommentRepository};
12
13// Import function
14use function App\Helpers\formatDate;
15
16// Import constant
17use const App\Config\MAX_UPLOAD_SIZE;
18
19class UserService {
20 public function __construct(
21 private UserRepository $repository,
22 private DB $db
23 ) {}
24
25 public function createUser(string $name): User {
26 return new User($name);
27 }
28}
29?>Global Namespace and Fully Qualified Names:
1<?php
2namespace App\Utils;
3
4class DateTime {
5 // Custom DateTime class
6}
7
8class DateFormatter {
9 public function format(): void {
10 // Use custom DateTime from current namespace
11 $custom = new DateTime();
12
13 // Use global DateTime class
14 $global = new \DateTime();
15
16 // Fully qualified class name
17 $user = new \App\Models\User('John', '[email protected]');
18 }
19}
20?>Namespace Constants and Functions:
1<?php
2namespace App\Config;
3
4const APP_NAME = 'My Application';
5const APP_VERSION = '1.0.0';
6const DEBUG_MODE = true;
7
8function getConfig(string $key): mixed {
9 return match($key) {
10 'name' => APP_NAME,
11 'version' => APP_VERSION,
12 'debug' => DEBUG_MODE,
13 default => null
14 };
15}
16
17// In another file:
18namespace App;
19
20use const App\Config\APP_NAME;
21use function App\Config\getConfig;
22
23echo APP_NAME . "\n";
24echo getConfig('version') . "\n";
25?>PSR-4 is a standard for autoloading classes from file paths, enabling automatic class loading without manual require statements.
PSR-4 Directory Structure:
1src/
2āāā Controllers/
3ā āāā UserController.php
4ā āāā PostController.php
5āāā Models/
6ā āāā User.php
7ā āāā Post.php
8āāā Services/
9ā āāā UserService.php
10ā āāā EmailService.php
11āāā Repositories/
12 āāā UserRepository.php
13 āāā PostRepository.phpComposer autoload Configuration:
1{
2 "autoload": {
3 "psr-4": {
4 "App\\": "src/"
5 }
6 }
7}Class File Mapping:
1<?php
2// File: src/Controllers/UserController.php
3namespace App\Controllers;
4
5use App\Services\UserService;
6use App\Models\User;
7
8class UserController {
9 public function __construct(
10 private UserService $service
11 ) {}
12
13 public function index(): array {
14 return $this->service->getAllUsers();
15 }
16}
17
18// File: src/Services/UserService.php
19namespace App\Services;
20
21use App\Repositories\UserRepository;
22use App\Models\User;
23
24class UserService {
25 public function __construct(
26 private UserRepository $repository
27 ) {}
28
29 public function getAllUsers(): array {
30 return $this->repository->findAll();
31 }
32
33 public function createUser(array $data): User {
34 return $this->repository->create($data);
35 }
36}
37
38// File: src/Models/User.php
39namespace App\Models;
40
41class User {
42 public function __construct(
43 private int $id,
44 private string $name,
45 private string $email
46 ) {}
47
48 public function getId(): int {
49 return $this->id;
50 }
51
52 public function getName(): string {
53 return $this->name;
54 }
55}
56
57// File: src/Repositories/UserRepository.php
58namespace App\Repositories;
59
60use App\Models\User;
61
62class UserRepository {
63 public function findAll(): array {
64 // Fetch from database
65 return [
66 new User(1, 'John', '[email protected]'),
67 new User(2, 'Jane', '[email protected]')
68 ];
69 }
70
71 public function create(array $data): User {
72 // Insert into database
73 return new User(
74 random_int(1, 1000),
75 $data['name'],
76 $data['email']
77 );
78 }
79}
80?>Using Autoloaded Classes:
1<?php
2// File: public/index.php
3require __DIR__ . '/../vendor/autoload.php';
4
5use App\Controllers\UserController;
6use App\Services\UserService;
7use App\Repositories\UserRepository;
8
9// Classes are automatically loaded
10$repository = new UserRepository();
11$service = new UserService($repository);
12$controller = new UserController($service);
13
14$users = $controller->index();
15
16foreach ($users as $user) {
17 echo $user->getName() . " - " . $user->getEmail() . "\n";
18}
19?>Combine traits and namespaces to create sophisticated, reusable code patterns.
Namespaced Traits:
1<?php
2// File: src/Traits/Timestampable.php
3namespace App\Traits;
4
5use DateTime;
6
7trait Timestampable {
8 protected DateTime $createdAt;
9 protected DateTime $updatedAt;
10
11 public function initTimestamps(): void {
12 $this->createdAt = new DateTime();
13 $this->updatedAt = new DateTime();
14 }
15
16 public function touch(): void {
17 $this->updatedAt = new DateTime();
18 }
19}
20
21// File: src/Traits/SoftDeletes.php
22namespace App\Traits;
23
24use DateTime;
25
26trait SoftDeletes {
27 protected ?DateTime $deletedAt = null;
28
29 public function delete(): void {
30 $this->deletedAt = new DateTime();
31 }
32
33 public function restore(): void {
34 $this->deletedAt = null;
35 }
36
37 public function isDeleted(): bool {
38 return $this->deletedAt !== null;
39 }
40}
41
42// File: src/Models/Post.php
43namespace App\Models;
44
45use App\Traits\Timestampable;
46use App\Traits\SoftDeletes;
47
48class Post {
49 use Timestampable, SoftDeletes;
50
51 public function __construct(private string $title) {
52 $this->initTimestamps();
53 }
54}
55?>Repository Pattern with Traits:
1<?php
2namespace App\Repositories;
3
4trait HasPagination {
5 protected int $perPage = 15;
6
7 public function paginate(int $page): array {
8 $offset = ($page - 1) * $this->perPage;
9 return $this->query()
10 ->limit($this->perPage)
11 ->offset($offset)
12 ->get();
13 }
14
15 public function setPerPage(int $perPage): void {
16 $this->perPage = $perPage;
17 }
18
19 abstract protected function query();
20}
21
22trait HasFilters {
23 protected array $filters = [];
24
25 public function where(string $column, mixed $value): self {
26 $this->filters[$column] = $value;
27 return $this;
28 }
29
30 public function getFilters(): array {
31 return $this->filters;
32 }
33}
34
35abstract class BaseRepository {
36 use HasPagination, HasFilters;
37
38 abstract public function find(int $id);
39 abstract public function findAll(): array;
40}
41
42class UserRepository extends BaseRepository {
43 public function find(int $id) {
44 // Implementation
45 }
46
47 public function findAll(): array {
48 // Implementation
49 }
50
51 protected function query() {
52 // Return query builder
53 }
54}
55?>Service Layer Pattern:
1<?php
2namespace App\Services;
3
4use App\Traits\Loggable;
5use App\Repositories\UserRepository;
6use App\Events\UserCreated;
7
8class UserService {
9 use Loggable;
10
11 public function __construct(
12 private UserRepository $repository
13 ) {}
14
15 public function createUser(array $data): User {
16 $this->log('Creating new user: ' . $data['email']);
17
18 try {
19 $user = $this->repository->create($data);
20 $this->log('User created successfully: ' . $user->getId());
21
22 // Dispatch event
23 event(new UserCreated($user));
24
25 return $user;
26 } catch (\Exception $e) {
27 $this->log('User creation failed: ' . $e->getMessage());
28 throw $e;
29 }
30 }
31}
32?>Dependency Injection Container:
1<?php
2namespace App\Container;
3
4class Container {
5 private array $bindings = [];
6 private array $instances = [];
7
8 public function bind(string $abstract, callable $concrete): void {
9 $this->bindings[$abstract] = $concrete;
10 }
11
12 public function singleton(string $abstract, callable $concrete): void {
13 $this->bindings[$abstract] = function() use ($abstract, $concrete) {
14 if (!isset($this->instances[$abstract])) {
15 $this->instances[$abstract] = $concrete($this);
16 }
17 return $this->instances[$abstract];
18 };
19 }
20
21 public function make(string $abstract) {
22 if (isset($this->bindings[$abstract])) {
23 return $this->bindings[$abstract]($this);
24 }
25
26 throw new \Exception("No binding found for: $abstract");
27 }
28}
29
30// Usage:
31use App\Services\UserService;
32use App\Repositories\UserRepository;
33
34$container = new Container();
35
36$container->singleton(UserRepository::class, function() {
37 return new UserRepository();
38});
39
40$container->bind(UserService::class, function($container) {
41 return new UserService(
42 $container->make(UserRepository::class)
43 );
44});
45
46$service = $container->make(UserService::class);
47?>Building a Complete Blog System with Traits and Namespaces
1<?php
2declare(strict_types=1);
3
4/**
5 * Complete Blog System using Traits and Namespaces
6 *
7 * Demonstrates:
8 * - Namespace organization (PSR-4 style)
9 * - Multiple trait composition
10 * - Trait conflict resolution
11 * - Repository pattern
12 * - Service layer
13 * - Real-world application architecture
14 */
15
16// ==================== TRAITS ====================
17
18namespace App\Traits;
19
20use DateTime;
21
22trait Timestampable {
23 protected DateTime $createdAt;
24 protected DateTime $updatedAt;
25
26 protected function initTimestamps(): void {
27 $this->createdAt = new DateTime();
28 $this->updatedAt = new DateTime();
29 }
30
31 public function touch(): void {
32 $this->updatedAt = new DateTime();
33 }
34
35 public function getCreatedAt(): string {
36 return $this->createdAt->format('Y-m-d H:i:s');
37 }
38
39 public function getUpdatedAt(): string {
40 return $this->updatedAt->format('Y-m-d H:i:s');
41 }
42}
43
44trait SoftDeletes {
45 protected ?DateTime $deletedAt = null;
46
47 public function delete(): void {
48 $this->deletedAt = new DateTime();
49 }
50
51 public function restore(): void {
52 $this->deletedAt = null;
53 }
54
55 public function isDeleted(): bool {
56 return $this->deletedAt !== null;
57 }
58}
59
60trait Sluggable {
61 protected string $slug;
62
63 protected function generateSlug(string $text): string {
64 $text = strtolower($text);
65 $text = preg_replace('/[^a-z0-9]+/', '-', $text);
66 return trim($text, '-');
67 }
68
69 public function getSlug(): string {
70 return $this->slug;
71 }
72}
73
74trait Publishable {
75 protected ?DateTime $publishedAt = null;
76
77 public function publish(): void {
78 $this->publishedAt = new DateTime();
79 }
80
81 public function unpublish(): void {
82 $this->publishedAt = null;
83 }
84
85 public function isPublished(): bool {
86 return $this->publishedAt !== null;
87 }
88}
89
90// ==================== MODELS ====================
91
92namespace App\Models;
93
94use App\Traits\{Timestampable, SoftDeletes, Sluggable, Publishable};
95
96class Post {
97 use Timestampable, SoftDeletes, Sluggable, Publishable;
98
99 public function __construct(
100 private int $id,
101 private string $title,
102 private string $content,
103 private int $authorId
104 ) {
105 $this->initTimestamps();
106 $this->slug = $this->generateSlug($title);
107 }
108
109 public function getId(): int {
110 return $this->id;
111 }
112
113 public function getTitle(): string {
114 return $this->title;
115 }
116
117 public function getContent(): string {
118 return $this->content;
119 }
120
121 public function getAuthorId(): int {
122 return $this->authorId;
123 }
124
125 public function update(string $title, string $content): void {
126 $this->title = $title;
127 $this->content = $content;
128 $this->slug = $this->generateSlug($title);
129 $this->touch();
130 }
131}
132
133class Comment {
134 use Timestampable, SoftDeletes;
135
136 public function __construct(
137 private int $id,
138 private int $postId,
139 private string $author,
140 private string $content
141 ) {
142 $this->initTimestamps();
143 }
144
145 public function getId(): int {
146 return $this->id;
147 }
148
149 public function getPostId(): int {
150 return $this->postId;
151 }
152
153 public function getAuthor(): string {
154 return $this->author;
155 }
156
157 public function getContent(): string {
158 return $this->content;
159 }
160}
161
162class User {
163 use Timestampable;
164
165 public function __construct(
166 private int $id,
167 private string $name,
168 private string $email,
169 private string $role = 'user'
170 ) {
171 $this->initTimestamps();
172 }
173
174 public function getId(): int {
175 return $this->id;
176 }
177
178 public function getName(): string {
179 return $this->name;
180 }
181
182 public function getEmail(): string {
183 return $this->email;
184 }
185
186 public function isAdmin(): bool {
187 return $this->role === 'admin';
188 }
189}
190
191// ==================== REPOSITORIES ====================
192
193namespace App\Repositories;
194
195use App\Models\{Post, Comment, User};
196
197class PostRepository {
198 private array $posts = [];
199 private int $nextId = 1;
200
201 public function create(string $title, string $content, int $authorId): Post {
202 $post = new Post($this->nextId++, $title, $content, $authorId);
203 $this->posts[$post->getId()] = $post;
204 return $post;
205 }
206
207 public function find(int $id): ?Post {
208 return $this->posts[$id] ?? null;
209 }
210
211 public function findBySlug(string $slug): ?Post {
212 foreach ($this->posts as $post) {
213 if ($post->getSlug() === $slug) {
214 return $post;
215 }
216 }
217 return null;
218 }
219
220 public function findAll(): array {
221 return array_filter(
222 $this->posts,
223 fn($post) => !$post->isDeleted()
224 );
225 }
226
227 public function findPublished(): array {
228 return array_filter(
229 $this->findAll(),
230 fn($post) => $post->isPublished()
231 );
232 }
233}
234
235class CommentRepository {
236 private array $comments = [];
237 private int $nextId = 1;
238
239 public function create(int $postId, string $author, string $content): Comment {
240 $comment = new Comment($this->nextId++, $postId, $author, $content);
241 $this->comments[$comment->getId()] = $comment;
242 return $comment;
243 }
244
245 public function findByPost(int $postId): array {
246 return array_filter(
247 $this->comments,
248 fn($c) => $c->getPostId() === $postId && !$c->isDeleted()
249 );
250 }
251}
252
253class UserRepository {
254 private array $users = [];
255 private int $nextId = 1;
256
257 public function create(string $name, string $email, string $role = 'user'): User {
258 $user = new User($this->nextId++, $name, $email, $role);
259 $this->users[$user->getId()] = $user;
260 return $user;
261 }
262
263 public function find(int $id): ?User {
264 return $this->users[$id] ?? null;
265 }
266}
267
268// ==================== SERVICES ====================
269
270namespace App\Services;
271
272use App\Repositories\{PostRepository, CommentRepository};
273use App\Models\{Post, Comment};
274
275class BlogService {
276 public function __construct(
277 private PostRepository $postRepository,
278 private CommentRepository $commentRepository
279 ) {}
280
281 public function createPost(string $title, string $content, int $authorId): Post {
282 $post = $this->postRepository->create($title, $content, $authorId);
283 echo "š Post created: {$post->getTitle()} (ID: {$post->getId()})\n";
284 return $post;
285 }
286
287 public function publishPost(int $postId): void {
288 $post = $this->postRepository->find($postId);
289 if ($post === null) {
290 throw new \Exception("Post not found");
291 }
292
293 $post->publish();
294 echo "š¢ Post published: {$post->getTitle()}\n";
295 }
296
297 public function addComment(int $postId, string $author, string $content): Comment {
298 $post = $this->postRepository->find($postId);
299 if ($post === null) {
300 throw new \Exception("Post not found");
301 }
302
303 if (!$post->isPublished()) {
304 throw new \Exception("Cannot comment on unpublished post");
305 }
306
307 $comment = $this->commentRepository->create($postId, $author, $content);
308 echo "š¬ Comment added by {$author}\n";
309 return $comment;
310 }
311
312 public function getPostWithComments(int $postId): array {
313 $post = $this->postRepository->find($postId);
314 if ($post === null) {
315 throw new \Exception("Post not found");
316 }
317
318 $comments = $this->commentRepository->findByPost($postId);
319
320 return [
321 'post' => $post,
322 'comments' => $comments
323 ];
324 }
325
326 public function getPublishedPosts(): array {
327 return $this->postRepository->findPublished();
328 }
329}
330
331// ==================== DEMO ====================
332
333namespace App;
334
335use App\Repositories\{PostRepository, CommentRepository, UserRepository};
336use App\Services\BlogService;
337
338echo str_repeat("=", 60) . "\n";
339echo " š° BLOG SYSTEM DEMO\n";
340echo str_repeat("=", 60) . "\n\n";
341
342// Setup
343$postRepo = new PostRepository();
344$commentRepo = new CommentRepository();
345$userRepo = new UserRepository();
346$blogService = new BlogService($postRepo, $commentRepo);
347
348// Create users
349echo "š„ Creating users...\n";
350$admin = $userRepo->create('John Admin', '[email protected]', 'admin');
351$user1 = $userRepo->create('Alice', '[email protected]');
352$user2 = $userRepo->create('Bob', '[email protected]');
353
354echo " Created: {$admin->getName()} (Admin: " .
355 ($admin->isAdmin() ? 'Yes' : 'No') . ")\n";
356echo " Created: {$user1->getName()}\n";
357echo " Created: {$user2->getName()}\n";
358
359// Create posts
360echo "\nš Creating blog posts...\n";
361$post1 = $blogService->createPost(
362 'Getting Started with PHP',
363 'This is a comprehensive guide to PHP programming...',
364 $admin->getId()
365);
366
367$post2 = $blogService->createPost(
368 'Advanced OOP Patterns',
369 'Learn about design patterns in PHP...',
370 $admin->getId()
371);
372
373// Publish posts
374echo "\nš¢ Publishing posts...\n";
375$blogService->publishPost($post1->getId());
376$blogService->publishPost($post2->getId());
377
378// Add comments
379echo "\nš¬ Adding comments...\n";
380$blogService->addComment(
381 $post1->getId(),
382 $user1->getName(),
383 'Great article! Very helpful.'
384);
385
386$blogService->addComment(
387 $post1->getId(),
388 $user2->getName(),
389 'Thanks for sharing this.'
390);
391
392$blogService->addComment(
393 $post2->getId(),
394 $user1->getName(),
395 'Looking forward to more advanced topics!'
396);
397
398// Display published posts
399echo "\nš Published Posts:\n";
400echo str_repeat("-", 60) . "\n";
401
402foreach ($blogService->getPublishedPosts() as $post) {
403 echo "\nš {$post->getTitle()}\n";
404 echo " Slug: {$post->getSlug()}\n";
405 echo " Published: {$post->getUpdatedAt()}\n";
406 echo " Status: " . ($post->isPublished() ? 'Published' : 'Draft') . "\n";
407
408 $data = $blogService->getPostWithComments($post->getId());
409 $commentCount = count($data['comments']);
410
411 echo " Comments: $commentCount\n";
412
413 if ($commentCount > 0) {
414 echo " Latest comments:\n";
415 foreach (array_slice($data['comments'], 0, 2) as $comment) {
416 echo " ⢠{$comment->getAuthor()}: " .
417 substr($comment->getContent(), 0, 40) . "...\n";
418 }
419 }
420}
421
422echo "\n" . str_repeat("=", 60) . "\n";
423echo "ā
BLOG SYSTEM DEMO COMPLETE!\n";
424echo str_repeat("=", 60) . "\n";
425echo "\nšÆ Key Features Demonstrated:\n";
426echo " ⢠Namespaced code organization (PSR-4 style)\n";
427echo " ⢠Multiple trait composition (Timestampable, SoftDeletes, etc.)\n";
428echo " ⢠Repository pattern for data access\n";
429echo " ⢠Service layer for business logic\n";
430echo " ⢠Trait-based feature addition\n";
431echo " ⢠Clean separation of concerns\n";
432?>This blog system demonstrates professional PHP architecture:
1. Trait Organization:
Timestampable - automatic timestamp managementSoftDeletes - soft deletion functionalitySluggable - URL-friendly slug generationPublishable - publication state management2. Namespace Structure:
App\Traits - reusable traitsApp\Models - domain modelsApp\Repositories - data access layerApp\Services - business logic layer3. PSR-4 Autoloading:
4. Design Patterns:
5. Real-World Features:
This architecture scales well for large applications!
Your progress is saved in this browser, on this device only.