SEPEHR.SYS

Starting SEPEHR.SYS…

Loading

سپهر محسنی

مهندس نرم‌افزار فول‌استک و هوش مصنوعی

NOTES.TXT ×

توسعهٔ مدرن PHP: بهترین شیوه‌ها برای امروز

6′ · ۱۴ دی ۱۴۰۴

PHP در سال‌های اخیر تحول چشمگیری داشته است. PHP 8.x قابلیت‌های قدرتمندی به همراه دارد که نوشتن کدی تمیزتر و کارآمدتر را ممکن می‌کند. این راهنما شیوه‌های مدرن کار با PHP را پوشش می‌دهد؛ شیوه‌هایی که هر توسعه‌دهنده‌ای باید با آن‌ها آشنا باشد.

قابلیت‌های PHP 8.3، 8.4 و 8.5 که باید از آن‌ها استفاده کنید

Property Hooks (PHP 8.4)

<?php
class User
{
    public string $name {
        // Custom getter logic
        get => trim($this->name ?? '');
        // Custom setter logic
        set {
            if (strlen(value) < 3) {
                throw new InvalidArgumentException('Name too short');
            }
            $this->name = value;
        }
    }
}

$user = new User();
$user->name = '  John  '; // Setter trims and validates
echo $user->name; // 'John'

Asymmetric Visibility (PHP 8.4)

<?php
class Product
{
    public readonly string $id; // Public read, private set

    public function __construct(string $id)
    {
        $this->id = $id; // Allowed in class scope
    }
}

$product = new Product('abc123');
echo $product->id; // 'abc123'
// $product->id = 'xyz'; // Error: Cannot modify readonly property

اپراتور Pipe (PHP 8.5)

<?php
$result = $input
    |> trim(?)
    |> strtolower(?)
    |> ucwords(?)
    |> str_replace(' ', '-', ?);

// Equivalent to:
// $result = str_replace(' ', '-', ucwords(strtolower(trim($input))));

Constructor Property Promotion

<?php
// Before PHP 8.0
class Product
{
    private string $name;
    private float $price;
    private int $stock;
    public function __construct(string $name, float $price, int $stock)
    {
        $this->name = $name;
        $this->price = $price;
        $this->stock = $stock;
    }
}
// PHP 8.0+ - Much cleaner!
class Product
{
    public function __construct(
        private string $name,
        private float $price,
        private int $stock = 0,
    ) {}
}

<Callout type="info"> قابلیت Constructor Property Promotion حجم کد تکراری (boilerplate) را به‌شدت کاهش می‌دهد. برای ساخت Value Objectهای تغییرناپذیر (immutable)، آن را با readonly ترکیب کنید. </Callout>

آرگومان‌های نام‌دار (Named Arguments)

<?php
function createUser(
    string $name,
    string $email,
    ?string $phone = null,
    bool $newsletter = false,
    string $role = 'user',
): User {
    // ...
}
// Clear and self-documenting
$user = createUser(
    name: 'John Doe',
    email: 'john@example.com',
    newsletter: true,
    // Skip phone, use default role
);

عبارت match

<?php
// Before: switch statement
function getStatusLabel(string $status): string
{
    switch ($status) {
        case 'pending':
            return 'Awaiting Review';
        case 'approved':
            return 'Approved';
        case 'rejected':
            return 'Rejected';
        default:
            return 'Unknown';
    }
}
// PHP 8.0+: match expression
function getStatusLabel(string $status): string
{
    return match ($status) {
        'pending' => 'Awaiting Review',
        'approved' => 'Approved',
        'rejected' => 'Rejected',
        default => 'Unknown',
    };
}
// Match with multiple conditions
function getDiscount(string $customerType): float
{
    return match ($customerType) {
        'vip', 'premium' => 0.20,
        'regular' => 0.10,
        'new' => 0.05,
        default => 0.0,
    };
}

Enumها

<?php
// Basic enum
enum Status: string
{
    case Pending = 'pending';
    case Approved = 'approved';
    case Rejected = 'rejected';
    public function label(): string
    {
        return match ($this) {
            self::Pending => 'Awaiting Review',
            self::Approved => 'Approved',
            self::Rejected => 'Rejected',
        };
    }
    public function color(): string
    {
        return match ($this) {
            self::Pending => 'yellow',
            self::Approved => 'green',
            self::Rejected => 'red',
        };
    }
}
// Usage
$status = Status::Pending;
echo $status->value; // 'pending'
echo $status->label(); // 'Awaiting Review'
// Type-safe function parameters
function updateOrderStatus(Order $order, Status $status): void
{
    $order->status = $status;
    $order->save();
}

<Note title="نکتهٔ Enum"> وقتی نیاز دارید مقدارها را در دیتابیس ذخیره کنید یا به JSON سریالایز کنید، از enumهای backed (با مقدارهای string یا int) استفاده کنید. </Note>

الگوهای طراحی در PHP مدرن

الگوی Repository

<?php
namespace App\Repositories;
use App\Models\User;
use Illuminate\Support\Collection;
interface UserRepositoryInterface
{
    public function find(int $id): ?User;
    public function findByEmail(string $email): ?User;
    public function all(): Collection;
    public function create(array $data): User;
    public function update(User $user, array $data): User;
    public function delete(User $user): bool;
}
class EloquentUserRepository implements UserRepositoryInterface
{
    public function __construct(
        private readonly User $model,
    ) {}
    public function find(int $id): ?User
    {
        return $this->model->find($id);
    }
    public function findByEmail(string $email): ?User
    {
        return $this->model->where('email', $email)->first();
    }
    public function all(): Collection
    {
        return $this->model->all();
    }
    public function create(array $data): User
    {
        return $this->model->create($data);
    }
    public function update(User $user, array $data): User
    {
        $user->update($data);
        return $user->fresh();
    }
    public function delete(User $user): bool
    {
        return $user->delete();
    }
}

الگوی Service Layer

<?php
namespace App\Services;
use App\DTOs\CreateOrderDTO;
use App\Events\OrderCreated;
use App\Models\Order;
use App\Repositories\OrderRepositoryInterface;
use App\Repositories\ProductRepositoryInterface;
use Illuminate\Support\Facades\DB;
class OrderService
{
    public function __construct(
        private readonly OrderRepositoryInterface $orderRepository,
        private readonly ProductRepositoryInterface $productRepository,
        private readonly PaymentService $paymentService,
    ) {}
    public function createOrder(CreateOrderDTO $dto): Order
    {
        return DB::transaction(function () use ($dto) {
            // Validate stock availability
            foreach ($dto->items as $item) {
                $product = $this->productRepository->find($item->productId);
               
                if ($product->stock < $item->quantity) {
                    throw new InsufficientStockException($product);
                }
            }
            // Create order
            $order = $this->orderRepository->create([
                'user_id' => $dto->userId,
                'status' => OrderStatus::Pending,
                'total' => $this->calculateTotal($dto->items),
            ]);
            // Add items and update stock
            foreach ($dto->items as $item) {
                $order->items()->create([
                    'product_id' => $item->productId,
                    'quantity' => $item->quantity,
                    'price' => $item->price,
                ]);
                $this->productRepository->decrementStock(
                    $item->productId,
                    $item->quantity
                );
            }
            event(new OrderCreated($order));
            return $order;
        });
    }
}

اشیای انتقال داده (DTO)

<?php
namespace App\DTOs;
use Illuminate\Http\Request;
readonly class CreateUserDTO
{
    public function __construct(
        public string $name,
        public string $email,
        public string $password,
        public ?string $phone = null,
        public bool $newsletter = false,
    ) {}
    public static function fromRequest(Request $request): self
    {
        return new self(
            name: $request->validated('name'),
            email: $request->validated('email'),
            password: $request->validated('password'),
            phone: $request->validated('phone'),
            newsletter: $request->boolean('newsletter'),
        );
    }
    public static function fromArray(array $data): self
    {
        return new self(
            name: $data['name'],
            email: $data['email'],
            password: $data['password'],
            phone: $data['phone'] ?? null,
            newsletter: $data['newsletter'] ?? false,
        );
    }
}

بهترین شیوه‌های تست‌نویسی

تست واحد با PHPUnit

<?php
namespace Tests\Unit\Services;
use App\DTOs\CreateOrderDTO;
use App\Models\Order;
use App\Models\Product;
use App\Repositories\OrderRepositoryInterface;
use App\Repositories\ProductRepositoryInterface;
use App\Services\OrderService;
use App\Services\PaymentService;
use PHPUnit\Framework\TestCase;
use Mockery;
class OrderServiceTest extends TestCase
{
    private OrderService $service;
    private $orderRepository;
    private $productRepository;
    private $paymentService;
    protected function setUp(): void
    {
        parent::setUp();
        $this->orderRepository = Mockery::mock(OrderRepositoryInterface::class);
        $this->productRepository = Mockery::mock(ProductRepositoryInterface::class);
        $this->paymentService = Mockery::mock(PaymentService::class);
        $this->service = new OrderService(
            $this->orderRepository,
            $this->productRepository,
            $this->paymentService,
        );
    }
    public function test_creates_order_successfully(): void
    {
        // Arrange
        $product = new Product(['id' => 1, 'stock' => 10, 'price' => 99.99]);
        $order = new Order(['id' => 1, 'status' => 'pending']);
        $this->productRepository
            ->shouldReceive('find')
            ->with(1)
            ->andReturn($product);
        $this->orderRepository
            ->shouldReceive('create')
            ->once()
            ->andReturn($order);
        $dto = new CreateOrderDTO(
            userId: 1,
            items: [new OrderItemDTO(productId: 1, quantity: 2, price: 99.99)],
        );
        // Act
        $result = $this->service->createOrder($dto);
        // Assert
        $this->assertInstanceOf(Order::class, $result);
        $this->assertEquals('pending', $result->status);
    }
    public function test_throws_exception_when_insufficient_stock(): void
    {
        // Arrange
        $product = new Product(['id' => 1, 'stock' => 1]);
        $this->productRepository
            ->shouldReceive('find')
            ->andReturn($product);
        $dto = new CreateOrderDTO(
            userId: 1,
            items: [new OrderItemDTO(productId: 1, quantity: 5, price: 99.99)],
        );
        // Assert & Act
        $this->expectException(InsufficientStockException::class);
        $this->service->createOrder($dto);
    }
    protected function tearDown(): void
    {
        Mockery::close();
        parent::tearDown();
    }
}

تست Feature

<?php
namespace Tests\Feature\Api;
use App\Models\User;
use App\Models\Product;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class OrderApiTest extends TestCase
{
    use RefreshDatabase;
    public function test_authenticated_user_can_create_order(): void
    {
        // Arrange
        $user = User::factory()->create();
        $product = Product::factory()->create(['stock' => 10, 'price' => 49.99]);
        // Act
        $response = $this->actingAs($user)
            ->postJson('/api/orders', [
                'items' => [
                    ['product_id' => $product->id, 'quantity' => 2],
                ],
            ]);
        // Assert
        $response->assertCreated()
            ->assertJsonStructure([
                'data' => ['id', 'status', 'total', 'items'],
            ]);
        $this->assertDatabaseHas('orders', [
            'user_id' => $user->id,
            'status' => 'pending',
        ]);
        $this->assertDatabaseHas('products', [
            'id' => $product->id,
            'stock' => 8, // 10 - 2
        ]);
    }
    public function test_unauthenticated_user_cannot_create_order(): void
    {
        $response = $this->postJson('/api/orders', [
            'items' => [],
        ]);
        $response->assertUnauthorized();
    }
}

بهینه‌سازی عملکرد

پیکربندی OPcache

; php.ini production settings
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0
opcache.revalidate_freq=0
opcache.save_comments=1
opcache.jit_buffer_size=256M
opcache.jit=1255

پیش‌بارگذاری (Preloading)

<?php
// preload.php
require __DIR__ . '/vendor/autoload.php';
// Preload frequently used classes
$classesToPreload = [
    \App\Models\User::class,
    \App\Models\Product::class,
    \App\Models\Order::class,
    \App\Services\OrderService::class,
    \App\Repositories\EloquentUserRepository::class,
];
foreach ($classesToPreload as $class) {
    class_exists($class);
}
; php.ini
opcache.preload=/var/www/app/preload.php
opcache.preload_user=www-data

جمع‌بندی

PHP مدرن، وقتی درست به کار گرفته شود، زبانی قدرتمند و خوش‌ساخت است. با بهره‌گیری از قابلیت‌های PHP 8.x، پیروی از الگوهای طراحی، نوشتن تست‌های جامع و بهینه‌سازی عملکرد، می‌توانید اپلیکیشن‌هایی بسازید که قابل‌نگهداری، مقیاس‌پذیر و سریع باشند.

نکات کلیدی:

  • از قابلیت‌های PHP 8.x مانند Property Hooks، Asymmetric Visibility، اپراتور Pipe، Enumها و عبارت‌های match استفاده کنید
  • برای رسیدن به معماری تمیز، الگوهای طراحی را پیاده‌سازی کنید
  • تست‌های جامع Unit و Feature بنویسید
  • برای عملکرد بهینه در محیط production، OPcache و JIT را پیکربندی کنید
  • برای انتقال دادهٔ type-safe از DTOها استفاده کنید

این یادداشت ترجمهٔ فارسی نوشتهٔ خودم است — نسخهٔ اصلی (انگلیسی) در dev.to