آموزش شیگرایی در PHP
آموزش گامبهگام شیگرایی در PHP از کلاس و آبجکت تا ارثبری، انتزاع، چندریختی و Trait؛ با نمونههای عملی، جدولهای مقایسه و نقشهی یادگیری برای پروژه
اولین باری که کد شیگرا دیدم، احساس کردم وارد اتاقی شدهام که مبلمانش را جای دیگری چیدهاند. ده تابع پراکنده نبود که هر کدام یک کار کنند؛ یک ساختار منظم بود با کلاس ها، آبجکت ها، و روابطی که تا آن روز ندیده بودم. آن روز نفهمیدم. چند هفته بعد، در یک پروژه ی واقعی که باید یک سیستم مدیریت کاربران را با کد رویه ای می نوشتم، به دیوار خوردم: توابعی که ده پارامتر می گرفتند، متغیرهای عمومی که در همه ی فایل ها پراکنده بودند، و کدی که فقط خودم می توانستم بخوانم. همان روز فهمیدم چرا شی گرایی وجود دارد — نه به خاطر مد روز، بلکه به خاطر یک درد واقعی در پروژه های بزرگ. این مقاله، همان مسیری است که در سال های بعد طی کردم تا شی گرایی را بفهمم و به کار ببرم؛ مسیری که از تجربه ی پروژه های واقعی بیرون آمده، نه از کتاب های دانشگاهی.
اگر تازه با PHP آشنا شده اید، پیشنهاد می کنم پیش از این مقاله، آموزش PHP از صفر برای مبتدیان را بخوانید. این مقاله، مکمل آن است و فرض می کند با مبانی زبان — متغیرها، توابع، آرایه ها، و شرط ها — آشنایی دارید. اگر هم می خواهید بدانید شی گرایی در وردپرس چه جایگاهی دارد، توسعه وردپرس چیست و از کجا شروع کنیم تصویر کلی را نشان می دهد.
چرا شی گرایی، گام بعدی هر PHP کار است؟
در سال های کار روی پروژه های PHP، یک الگو را بارها دیده ام: توسعه دهنده ای که با کد رویه ای (procedural) شروع می کند و در پروژه های کوچک موفق است. اما وقتی پروژه بزرگ می شود — چند هزار خط کد، چند ده فایل، چند نفر در تیم — همان توسعه دهنده به دیوار می خورد. کدش پراکنده است، باگ های پنهان زیاد است، و هر تغییر کوچکی می تواند چند بخش دیگر را بشکند. اینجا است که شی گرایی، از «یک امکان خوب» به «یک ضرورت» تبدیل می شود.
سه دلیل اصلی که شی گرایی را برای PHP کار حرفه ای ضروری می کند:
- سازماندهی در مقیاس: کد شی گرا، به جای صدها تابع پراکنده، ساختاری از کلاس ها با مسئولیت های مشخص دارد. در پروژه های بزرگ، این تفاوت بین «کد قابل نگهداری» و «کد فاجعه» است.
- استفاده ی مجدد: با ارث بری و interface، می توان کد را در بخش های مختلف پروژه استفاده کرد، بدون کپی پیست.
- هم خوانی با اکوسیستم مدرن: اکثر فریم ورک های PHP (لاراول، سیمفونی) و اکثر کتابخانه های مدرن، شی گرا هستند. حتی وردپرس، که در ابتدا رویه ای بود، در نسخه های جدید به سمت شی گرایی حرکت کرده است — نمونه اش در هوک های وردپرس چیستند و چگونه کار می کنند دیده می شود.
یک نکته ی صادقانه: یادگیری شی گرایی، برای بعضی ها سخت است. نه به خاطر پیچیدگی مفاهیم، بلکه به خاطر تغییر ذهنیت. در کد رویه ای، شما به کد به عنوان «دنباله ای از دستورات» نگاه می کنید. در شی گرایی، به کد به عنوان «مجموعه ای از آبجکت ها که با هم کار می کنند» نگاه می کنید. این تغییر نگاه، معمولاً چند هفته طول می کشد. تجربه ی من این است که اگر در این چند هفته صبر کنید و پروژه های کوچک بسازید، یک روز یک دفعه آن را می فهمید — و از آن روز به بعد، به کد رویه ای برنمی گردید.
کد رویه ای، مثل فهرستی از دستورات است که پشت سر هم اجرا می شوند. کد شی گرا، مثل یک تیم از همکاران است که هر کدام کار خودشان را می دانند. در پروژه های کوچک، هر دو کار می کنند؛ در پروژه های بزرگ، فقط یکی شان پایدار می ماند.
از رویه ای به شی گرا: یک سفر ذهنی
برای اینکه شی گرایی را بهتر بفهمید، بیایید با یک مثال ساده شروع کنیم. فرض کنید می خواهید یک سیستم مدیریت محصولات را بسازید. در کد رویه ای، احتمالاً این کار را می کنید:
$products = [];
function add_product( &$products, $name, $price, $stock ) {
$products[] = [ 'name' => $name, 'price' => $price, 'stock' => $stock ];
}
function get_product_price( $product ) {
return $product['price'];
}
function calculate_total( $products ) {
$total = 0;
foreach ( $products as $product ) {
$total += $product['price'];
}
return $total;
}
این کد کار می کند، اما چند مشکل دارد. اول، «محصول» یک مفهوم ذهنی است، اما در کد فقط یک آرایه است؛ هیچ جا نمی توانید بگویید «یک محصول چه ویژگی هایی دارد و چه کارهایی می تواند انجام دهد». دوم، با رشد پروژه، توابع پراکنده می شوند — کدام تابع مربوط به محصول است؟ کدام مربوط به سبد خرید؟ هیچ ساختاری نیست. سوم، برای اضافه کردن ویژگی جدید (مثل تخفیف)، باید همه ی توابع را بازبینی کنید.
حالا همین مثال را در قالب شی گرا بنویسیم:
class Product {
public function __construct(
private string $name,
private float $price,
private int $stock = 0,
) {}
public function getName(): string {
return $this->name;
}
public function getPrice(): float {
return $this->price;
}
public function isInStock(): bool {
return $this->stock > 0;
}
}
در این کد، Product یک کلاس است که سه ویژگی (name، price، stock) و سه متد دارد. حالا مفهوم «محصول» در کد، شکلی پیدا کرده که با ذهن شما منطبق است. اگر فردا بخواهید متد جدیدی اضافه کنید — مثلاً applyDiscount() — فقط در همان کلاس اضافه می کنید، و همه ی جای کد که از کلاس استفاده می کنند، به طور خودکار آن را می بینند.
این تفاوت کوچک، در پروژه های بزرگ تفاوت عظیمی می سازد. یک پروژه با صد محصول مختلف و ده نوع تخفیف، در کد رویه ای به فاجعه تبدیل می شود؛ در کد شی گرا، هنوز قابل مدیریت است. تجربه ی من: این تغییر نگاه، اولین و مهم ترین گام در یادگیری شی گرایی است.
کلاس و آبجکت: اولین تعریف
در ساده ترین تعریف، کلاس نقشه یا قالب است، و آبجکت نسخه ای از آن قالب که در حافظه ساخته می شود. کلاس، تعریف می کند که «یک محصول چه ویژگی هایی دارد و چه کارهایی می تواند انجام دهد»، اما خودش محصول نیست. آبجکت، یک محصول مشخص است.
// کلاس (نقشه)
class User {
public string $name;
public string $email;
public function greet(): string {
return 'سلام، ' . $this->name;
}
}
// آبجکت (نسخه ی مشخص)
$ali = new User();
$ali->name = 'علی';
$ali->email = 'ali@example.com';
echo $ali->greet(); // سلام، علی
در این کد، User کلاس است و $ali آبجکت. می توانید چند آبجکت از یک کلاس بسازید:
$maryam = new User();
$maryam->name = 'مریم';
$reza = new User();
$reza->name = 'رضا';
هر آبجکت، ویژگی های مستقل خودش را دارد. تغییر name در $ali، روی $maryam اثری ندارد. این استقلال، یکی از پایه های شی گرایی است.
نکته ی ظریف: در PHP، هر آبجکت یک مرجع (reference) است، نه یک مقدار. یعنی وقتی یک آبجکت را به تابع می دهید، تابع می تواند ویژگی های آن را تغییر دهد. این رفتار، با آرایه ها فرق دارد. تجربه ی من: این تفاوت، در ابتدا گیج کننده است، اما بعد از چند بار کار با آبجکت ها، طبیعی به نظر می رسد.
سازنده و مخرب: چرخه ی حیات آبجکت
هر آبجکت، یک چرخه ی حیات دارد: متولد می شود، کار می کند، و از بین می رود. در PHP، دو متد خاص این چرخه را مدیریت می کنند:
- سازنده (
__construct): وقتی آبجکت ساخته می شود، به طور خودکار اجرا می شود. برای مقداردهی اولیه استفاده می شود. - مخرب (
__destruct): وقتی آبجکت از بین می رود، به طور خودکار اجرا می شود. برای پاک سازی منابع (مثل بستن اتصال دیتابیس) استفاده می شود.
نمونه ی سازنده، که در PHP ۸ می تواند بسیار کوتاه باشد:
class User {
public function __construct(
private string $name,
private string $email,
) {}
public function getEmail(): string {
return $this->email;
}
}
$ali = new User( 'علی', 'ali@example.com' );
echo $ali->getEmail();
این ویژگی — که در PHP 8.0 معرفی شد و Constructor Property Promotion نام دارد — یکی از دلایل اصلی است که شی گرایی در PHP ۸ بسیار خواناتر از نسخه های قبل است. اگر با ویژگی های مدرن PHP آشنا نیستید، تفاوت PHP ۷ و PHP ۸ مسیر دقیقی است.
مخرب، کمتر استفاده می شود؛ چون PHP به طور خودکار حافظه را آزاد می کند. اما در موارد خاص — مثل بستن اتصال دیتابیس یا بستن فایل — می تواند مفید باشد:
class DatabaseConnection {
private $handle;
public function __construct( string $dsn ) {
$this->handle = new PDO( $dsn );
}
public function __destruct() {
$this->handle = null;
}
}
تجربه ی من: در پروژه های واقعی، ۹۰ درصد کار با سازنده انجام می شود، و مخرب تقریباً هیچ وقت استفاده نمی شود. اما دانستن اینکه مخرب وجود دارد، در درک چرخه ی حیات آبجکت مهم است.
کپسوله سازی: پنهان کردن پیچیدگی
کپسوله سازی، یکی از مهم ترین مفاهیم شی گرایی است و در عین حال، یکی از کم فهمیده شده ترین. ایده ی اصلی این است: هر آبجکت باید پیچیدگی درونی خودش را پنهان کند و فقط یک رابط عمومی در اختیار بقیه بگذارد.
برای فهم این مفهوم، یک مثال ساده بزنیم. فرض کنید یک کلاس BankAccount دارید:
class BankAccount {
private float $balance = 0;
public function deposit( float $amount ): void {
if ( $amount <= 0 ) {
throw new InvalidArgumentException( 'مبلغ باید مثبت باشد' );
}
$this->balance += $amount;
}
public function withdraw( float $amount ): void {
if ( $amount > $this->balance ) {
throw new RuntimeException( 'موجودی کافی نیست' );
}
$this->balance -= $amount;
}
public function getBalance(): float {
return $this->balance;
}
}
در این کد، $balance یک ویژگی private است — یعنی هیچ کد بیرونی نمی تواند مستقیماً آن را تغییر دهد. همه ی دسترسی ها از طریق متدهای deposit و withdraw انجام می شود. مزیت این کار:
- اعتبارسنجی متمرکز: همه ی قوانین (مبلغ مثبت، موجودی کافی) در یک جا هستند.
- امکان تغییر ساختار داخلی: اگر بعداً بخواهید تعادل موجودی را از یک دیتابیس بخوانید، فقط کد کلاس را تغییر می دهید، بقیه ی کد سایت دست نمی خورد.
- جلوگیری از خطا: کد بیرونی نمی تواند مستقیماً موجودی را خراب کند.
در پروژه های واقعی، کپسوله سازی به این معنا نیست که همه ی ویژگی ها را private کنید — بلکه به این معناست که ویژگی ها و متدها را بر اساس «چیزی که بیرون نیاز دارد» تفکیک کنید. اگر امروز چیزی را public می کنید فقط چون کارتان راحت تر می شود، فردا با یک باگ پیچیده روبه رو می شوید.
کپسوله سازی، هنر جدا کردن «آن چیزی که هر کسی می تواند ببیند» از «آن چیزی که فقط خودم باید بدانم» است. هر جا این جدا کردن را انجام ندهید، یک نقطه ی ضعف امنیتی و یک منبع باگ در آینده ساخته اید.
سه سطح دسترسی: public، protected، private
PHP سه سطح دسترسی برای ویژگی ها و متدها دارد. تفاوت شان را در یک جدول ساده نشان می دهم:
| سطح | دسترسی از بیرون کلاس | دسترسی از کلاس فرزند | کاربرد |
|---|---|---|---|
public | بله | بله | رابط عمومی |
protected | خیر | بله | ویژگی های مشترک بین والد و فرزند |
private | خیر | خیر | پیچیدگی داخلی |
قاعده ی سرانگشتی من: پیش فرض همه چیز را private بگذارید، مگر اینکه دلیل مشخصی برای public یا protected داشته باشید. تجربه ی من: در پروژه هایی که از همان ابتدا این سخت گیری را اعمال کرده ام، باگ های کمتری دیده ام. برعکس، پروژه هایی که همه چیز public بود، بعد از چند ماه به یک وضعیت هرج و مرج رسیده اند.
یک نکته ی ظریف: در PHP ۸.۱ به بعد، ویژگی های readonly نیز وجود دارند که می توانند یک بار مقداردهی شوند و بعد دیگر تغییر نکنند. این ویژگی، در DTO و Value Object ها بسیار مفید است — موضوعی که در تفاوت PHP ۷ و PHP ۸ به آن اشاره کرده ام.
ارث بری: توسعه بدون بازنویسی
ارث بری، امکان ساختن یک کلاس جدید بر پایه ی یک کلاس موجود را می دهد. کلاس جدید (فرزند)، همه ی ویژگی ها و متدهای کلاس والد را به ارث می برد و می تواند رفتارهای خودش را هم اضافه یا بازنویسی کند. مثال:
class Animal {
public function __construct(
protected string $name,
) {}
public function makeSound(): string {
return '...';
}
public function getName(): string {
return $this->name;
}
}
class Dog extends Animal {
public function makeSound(): string {
return 'واق واق';
}
}
class Cat extends Animal {
public function makeSound(): string {
return 'میو';
}
}
$dog = new Dog( 'پاپی' );
$cat = new Cat( 'ملوس' );
echo $dog->makeSound(); // واق واق
echo $cat->makeSound(); // میو
مزایای ارث بری:
- عدم تکرار کد: ویژگی مشترک (مثل
name) فقط در والد تعریف می شود. - توسعه ی تدریجی: کلاس فرزند می تواند متدهای والد را بازنویسی کند یا متدهای جدید اضافه کند.
- چندریختی: می توان با یک متد مشترک، رفتارهای متفاوتی از کلاس های مختلف فراخوانی کرد (در بخش بعدی به تفصیل).
اما ارث بری، دام هایی هم دارد. مهم ترین شان: ارث بری زنجیره ای. اگر کلاس A از B ارث ببرد، B از C، و C از D، نگهداری این زنجیره بسیار دشوار می شود. تجربه ی من: عمق ارث بری را به دو یا حداکثر سه سطح محدود کنید. اگر بیشتر شد، به احتمال زیاد باید از ترکیب (Composition) به جای ارث بری استفاده کنید — یعنی به جای «یک Dog یک Animal است»، «یک Dog یک Animal دارد».
انتزاع: abstract class و interface
گاهی می خواهید یک کلاس والد را تعریف کنید که خودش نمی تواند مستقیماً نمونه سازی شود، اما فرزندانش را مجبور به پیاده سازی متدهای مشخصی کند. در PHP، دو ابزار برای این کار وجود دارد:
کلاس انتزاعی (abstract class): کلاسی که نمی تواند مستقیماً نمونه سازی شود و ممکن است متدهای انتزاعی (بدون پیاده سازی) داشته باشد:
abstract class Shape {
public function __construct(
protected string $name,
) {}
abstract public function area(): float;
public function describe(): string {
return 'این یک ' . $this->name . ' است';
}
}
class Circle extends Shape {
public function __construct( private float $radius ) {
parent::__construct( 'دایره' );
}
public function area(): float {
return pi() * $this->radius ** 2;
}
}
interface: یک قرارداد که مشخص می کند کلاس پیاده ساز باید چه متدهایی داشته باشد، بدون اینکه چیزی از پیاده سازی را تعیین کند:
interface Payable {
public function pay( float $amount ): bool;
public function getTotal(): float;
}
class Invoice implements Payable {
private array $items = [];
public function pay( float $amount ): bool {
// پیاده سازی
return true;
}
public function getTotal(): float {
$total = 0;
foreach ( $this->items as $item ) {
$total += $item['price'];
}
return $total;
}
}
تفاوت کلیدی: یک کلاس می تواند از یک abstract class ارث ببرد، اما می تواند چند interface را پیاده سازی کند. برای همین، در پروژه های بزرگ، interface ها انعطاف بیشتری می دهند. تجربه ی من: در پروژه های مدرن، بیشتر از interface استفاده می شود و کمتر از abstract class. اما در مواردی که بین کلاس ها رابطه ی «is-a» واقعی وجود دارد، abstract class انتخاب بهتری است.
چندریختی در عمل
چندریختی (Polymorphism) یکی از مفاهیمی است که در ابتدا انتزاعی به نظر می رسد اما در عمل، بسیار ساده است. یعنی: «می توان با یک متد مشترک، رفتارهای متفاوتی از کلاس های مختلف فراخوانی کرد».
مثال:
interface Notification {
public function send( string $message ): void;
}
class EmailNotification implements Notification {
public function send( string $message ): void {
// ارسال ایمیل
}
}
class SmsNotification implements Notification {
public function send( string $message ): void {
// ارسال پیامک
}
}
function notify_all( array $notifications, string $message ): void {
foreach ( $notifications as $notification ) {
$notification->send( $message );
}
}
notify_all( [
new EmailNotification(),
new SmsNotification(),
], 'سفارش شما ارسال شد' );
در این کد، تابع notify_all نمی داند که با Email کار می کند یا SMS. فقط می داند که هر آبجکت، متد send دارد. این استقلال از جزئیات، جوهر شی گرایی است. تجربه ی من: وقتی اولین بار چندریختی را درک کردم، شی گرایی برایم معنای واقعی پیدا کرد.
اهمیت چندریختی، در افزونه پذیری است. اگر فردا بخواهید یک نوع نوتیفیکیشن جدید (مثل Telegram) اضافه کنید، فقط یک کلاس جدید اضافه می کنید و تابع notify_all را دست نمی زنید. این اصل، در طراحی سیستم های بزرگ حیاتی است — همان اصلی که در اصول کدنویسی تمیز هم به آن اشاره کرده ام.
Trait ها: جایگزین مدرن چندارث بری
در PHP، یک کلاس فقط می تواند از یک کلاس والد ارث ببرد. برای جبران این محدودیت، PHP از Trait ها استفاده می کند. Trait یک بلوک کد قابل استفاده مجدد است که می توان آن را در چند کلاس وارد کرد:
trait Timestamps {
private ?string $created_at = null;
private ?string $updated_at = null;
public function touch(): void {
$this->updated_at = date( 'Y-m-d H:i:s' );
}
public function getCreatedAt(): ?string {
return $this->created_at;
}
}
class User {
use Timestamps;
// ...
}
class Product {
use Timestamps;
// ...
}
در این کد، هر دو کلاس User و Product از ویژگی های Timestamps استفاده می کنند، بدون اینکه بین شان رابطه ی ارث بری باشد. این ابزار، در پروژه های واقعی بسیار مفید است — به خصوص برای ویژگی هایی مثل log، timestamp، و متدهای کمکی.
یک هشدار مهم: Trait ها ابزار قدرتمندی هستند اما به راحتی می توانند به هرج و مرج منجر شوند. تجربه ی من: Trait ها را برای ویژگی های واقعاً مشترک استفاده کنید، نه به عنوان یک راه فرار از طراحی. اگر یک Trait بیش از ۲۰۰ خط شد یا بیش از ۵ متد داشت، احتمالاً به یک کلاس مستقل نیاز دارید که بقیه از آن استفاده کنند.
نام فضاها: نظم در پروژه های بزرگ
وقتی پروژه بزرگ می شود، احتمال تعارض نام کلاس ها بالا می رود. مثلاً دو کتابخانه ی مختلف ممکن است هر دو یک کلاس User داشته باشند. برای حل این مشکل، PHP از نام فضاها (Namespaces) استفاده می کند:
namespace App\Models;
class User {
// ...
}
namespace App\Services;
use App\Models\User;
class UserService {
public function find( int $id ): User {
// ...
}
}
مزیت نام فضاها:
- جلوگیری از تعارض نام: دو کلاس با نام یکسان در نام فضاهای مختلف می توانند وجود داشته باشند.
- سازماندهی بهتر: نام فضاها می توانند با ساختار پوشه ها منطبق باشند.
- هم خوانی با Composer: ابزار مدیریت وابستگی در PHP، از نام فضاها استفاده می کند. اگر با Composer آشنا نیستید، آموزش Composer در PHP نقطه ی شروع دقیقی است.
قاعده ی تجربی من: از همان پروژه ی اول، از نام فضاها استفاده کنید. حتی اگر پروژه ی کوچکی است، این عادت، در پروژه های بعدی سرمایه گذاری ارزشمندی است. در پروژه های وردپرسی، نام فضاها در افزونه ها و قالب های مدرن معمول است — و در بحث کدنویسی وردپرس چیست و از کجا شروع کنیم به آن اشاره کرده ام.
شی گرایی در وردپرس: نمونه های واقعی
وردپرس، در ابتدا به صورت رویه ای نوشته شد. اما در نسخه های جدید، به سمت شی گرایی حرکت کرده است. سه نمونه ی مهم:
یک — کلاس WP_Query: به جای استفاده از توابع پراکنده برای گرفتن نوشته ها، وردپرس یک کلاس شی گرا ارائه می دهد که همه ی پارامترها و رفتارها را در یک جا مدیریت می کند. استفاده از این کلاس، بسیار تمیزتر از توابع قدیمی است.
دو — کلاس WP_User: برای کار با کاربران، وردپرس یک کلاس ارائه می دهد. ویژگی ها و متدهای این کلاس، تمام رفتارهای مرتبط با کاربر را در یک ساختار منطقی جا می دهد.
سه — کلاس WP_REST_Controller: برای ساخت endpoint های REST API، وردپرس یک کلاس پایه ارائه می دهد که توسعه دهنده می تواند از آن ارث ببرد و endpoint های خودش را بسازد. این الگو، همان چیزی است که در ساخت API اختصاصی برای وردپرس به تفصیل باز کرده ام.
یادگیری این کلاس ها، در عمل به شما کمک می کند که بفهمید شی گرایی در پروژه های واقعی چطور پیاده می شود. توصیه ی من: به جای اینکه همه ی این کلاس ها را از حفظ یاد بگیرید، وقتی به مشکل برخوردید، به مستندات رسمی وردپرس مراجعه کنید و ساختار کلاس را ببینید. این روش یادگیری، بسیار موثرتر از حفظ کردن است.
اشتباهات رایج در یادگیری شی گرایی
در سال ها کار با توسعه دهندگانی که شی گرایی را یاد می گرفته اند، الگوهای مشخصی از اشتباهات را دیده ام:
| اشتباه | پیامد | روش درست |
|---|---|---|
| ساختن کلاس برای هر چیز | کد پراکنده و پیچیده | کلاس فقط برای مفاهیم واقعی |
| عمق زیاد ارث بری | نگهداری دشوار | حداکثر دو یا سه سطح |
| همه چیز public | از دست دادن کپسوله سازی | پیش فرض private |
| استفاده از Trait برای همه چیز | هرج و مرج در ساختار | Trait فقط برای ویژگی های واقعاً مشترک |
| نداشتن نام فضا | تعارض نام در پروژه های بزرگ | نام فضا از روز اول |
| کپی پیست بین کلاس ها | تکرار کد | استفاده از ارث بری یا trait |
| نادیده گرفتن تست | باگ های پنهان | تست نویسی از روز اول |
بزرگ ترین اشتباه، در تجربه ی من، «شی گرایی زودرس» است — یعنی ساختن کلاس های پیچیده برای پروژه های کوچک که اصلاً نیازی به شی گرایی ندارند. یادگیری شی گرایی، به معنای استفاده از آن در همه ی پروژه ها نیست. در پروژه های کوچک (مثل یک اسکریپت ساده یا یک ابزار یک بار مصرف)، کد رویه ای کاملاً کافی است. شی گرایی، برای پروژه هایی است که به سازماندهی، استفاده ی مجدد، و نگهداری بلندمدت نیاز دارند.
عادت های حرفه ای در کد شی گرا
بعد از یادگیری مبانی شی گرایی، تفاوت بین یک توسعه دهنده ی متوسط و حرفه ای، در عادت هاست. پنج عادتی که در پروژه های خودم به آن ها پایبندم:
- نام گذاری دقیق کلاس ها: نام کلاس، اسم یک مفهوم است، نه یک عمل.
UserRepositoryبهتر ازManageUsersاست. - هر کلاس، یک مسئولیت: اگر نام کلاس شما «و» دارد (مثل
UserAndOrderManager)، احتمالاً کلاس شما دو مسئولیت دارد و باید شکسته شود. این اصل، به عنوان «اصل مسئولیت یگانه» شناخته می شود. - ترکیب به جای ارث بری: وقتی بین دو کلاس رابطه ی «دارد» وجود دارد، از ترکیب استفاده کنید؛ وقتی رابطه ی «است» وجود دارد، از ارث بری. تجربه ی من: در بیش از ۷۰ درصد موارد، ترکیب انتخاب بهتری است.
- برنامه نویسی به interface، نه به پیاده سازی: در امضاهای متد، از interface استفاده کنید، نه از کلاس مشخص. این کار، کد شما را انعطاف پذیر می کند.
- تست نویسی: از همان ابتدا، برای کلاس های خود تست بنویسید. ابزار PHPUnit در اکوسیستم PHP، تست نویسی را بسیار ساده می کند.
یک توصیه ی عملی: در پروژه های خودم، همیشه از یک ابزار تحلیل ایستا مثل PHPStan استفاده می کنم. این ابزار، خطاهای نوع، متدهای استفاده نشده، و کدهای مشکوک را قبل از اجرا کشف می کند. تجربه ی من: در پروژه های شی گرا، این ابزار، تفاوت بین کد سالم و کد آشفته را می سازد — چیزی که در نوشتن کد PHP امن برای وردپرس هم به عنوان یکی از پایه های کد باکیفیت مطرح کرده ام.
در شی گرایی، کد خوب کدی است که وقتی فردا آن را باز می کنید، ساختارش را بفهمید و بتوانید بدون ترس تغییری در آن بدهید. کد شی گرای ضعیف، در ظاهر منظم است اما در عمل، پیکرهایی از پیچیدگی های پنهان است.
نقشه ی ادامه ی مسیر
بعد از یادگیری مبانی شی گرایی، سه مسیر اصلی برای ادامه وجود دارد:
مسیر اول — الگوهای طراحی (Design Patterns): الگوهای طراحی، راه حل های آماده برای مسائل پرتکرار در طراحی شی گرا هستند. مهم ترین شان: Factory، Singleton، Observer، Strategy، Repository. یادگیری این الگوها، کد شما را حرفه ای تر می کند. منابع پیشنهادی را در بهترین منابع یادگیری PHP در سال ۲۰۲۶ آورده ام.
مسیر دوم — فریم ورک های شی گرا: لاراول و سیمفونی، هر دو فریم ورک هایی هستند که بر پایه ی شی گرایی ساخته شده اند. یادگیری یکی از این فریم ورک ها، شما را با بهترین شیوه های شی گرایی در عمل آشنا می کند.
مسیر سوم — تست نویسی و ابزارهای کیفیت: نوشتن تست برای کد شی گرا، یکی از مهم ترین مهارت های حرفه ای است. ابزارهایی مثل PHPUnit، PHPStan و Rector، در این مسیر کمک می کنند. مسیر تست نویسی را در تست و دیباگ پروژه های توسعه وردپرس به تفصیل باز کرده ام — اصول یکسان است.
در هر سه مسیر، یک توصیه ی مشترک دارم: پروژه ی واقعی بسازید. شی گرایی، بدون پروژه، همانند شنا کردن روی کاغذ است. یک پروژه ی کوچک انتخاب کنید — مثلا یک سیستم مدیریت کاربران، یک ماشین حساب پیشرفته، یا یک سیستم ثبت سفارش — و با شی گرایی بسازید. در جریان ساخت، خیلی از مفاهیم خودشان جا می افتند. برای پروژه ی وردپرسی، ساخت نوع نوشته سفارشی در وردپرس و ساخت طبقه بندی سفارشی دو نقطه ی شروع خوب هستند که در آن ها می توانید شی گرایی را در عمل به کار ببرید.
یک تمرین عملی برای همین امشب
حالا که این نقشه را در دست دارید، یک تمرین کوچک برایتان دارم که می تواند مسیر یادگیری شی گرایی را از همین امشب شروع کند: یک کلاس ساده به نام Wallet بسازید که سه ویژگی داشته باشد — owner (نام صاحب کیف)، balance (موجودی، پیش فرض صفر)، و transactions (فهرست تراکنش ها، پیش فرض آرایه خالی). سپس سه متد بنویسید: deposit(float $amount) که مبلغ را به موجودی اضافه کند، withdraw(float $amount) که مبلغ را کم کند (به شرط کافی بودن موجودی)، و getHistory() که فهرست تراکنش ها را برگرداند.
شاید بپرسید چرا این تمرین ساده؟ چون در همین یک کلاس، شش مفهوم شی گرایی را تمرین می کنید: کلاس و آبجکت، سازنده، کپسوله سازی، سطح دسترسی، متد و ویژگی، و اعتبارسنجی. سی دقیقه وقت می گیرد، اما چیزهایی را که در چند صفحه خواندید، در دست شما جا می اندازد. اگر این تمرین را انجام دادید و کدتان درست کار کرد، فردا شب یک قدم جلوتر بروید: یک کلاس SavingWallet بسازید که از Wallet ارث ببرد و یک متد addInterest(float $rate) داشته باشد. همین دو تمرین، شما را از یک خواننده ی شی گرایی به یک نویسنده ی شی گرایی تبدیل می کند.
و اگر این تمرین را انجام دادید و در مسیر، به یک سوال جالب یا یک رفتار غیرمنتظره رسیدید — مثلاً اینکه چرا در PHP، آبجکت ها مرجع هستند اما آرایه ها مقدار، یا اینکه چرا یک trait با یک کلاس هم نام می تواند تعارض ایجاد کند — همان سوال را در دیدگاه ها بنویسید. شی گرایی، از آن دسته موضوعاتی است که هر سوال، یک پنجره ی تازه باز می کند؛ و آن پنجره، وقتی با چند نفر دیگر بحث شود، بسیار روشن تر می شود. اگر هم در حین تمرین با خطای نوع یا خطای طراحی روبه رو شدید، دیباگ کردن کدهای سفارشی وردپرس ایستگاه بعدی شماست — همان اصولی که در آنجا آورده ام، در کد شی گرای خالص هم به کار می آید. 🎯