آشنایی با مفاهیم Class و Object در متودولوژی OOP

پیش از این گفتیم که هر کلاس از دو ویژگی تحت عناوین Attribute و Behavior برخوردار است که به ترتیب نشانگر خصیصه‌های کلاس مذکور و کارهایی که آن کلاس از عهده‌ٔ آن‌ها برمی‌آید هستند. در عین حال، در متودولوژی شیئ‌گرایی این دو اصطلاح تحت عناوین دیگری نیز به کار می‌روند که عبارتند از Property و Method که در طول این دوره از این معادل‌ها استفاده خواهیم کرد. به منظور درک بهتر این موضوع، ابتدا داخل پوشهٔ oop که در آموزش گذشته ساختیم پوشهٔ جدیدی با نامی دلخواه همچون class-and-object ساخته سپس فایلی به نام index.php حاوی کدهای زیر داخل آن می‌سازیم:

<?php
class User
{
    public $name = "Behzad";
    public $lastName = "Moradi";
    public $dob = 1362;

    public function showFullName()
    {
        return $this->name . ' ' . $this->lastName;
    }
}

در خط اول، تگ آغازین php?> را نوشته سپس از کلیدواژه یا کیورد class استفاده کرده که وظیفهٔ ساخت کلاس را بر عهده دارد و نامی دلخواه همچون User برای کلاس خود انتخاب نموده‌ایم.

به خاطر داشته باشید در تمامی زبان‌های برنامه‌نویسی Keyword به یک سری دستورات گفته می‌شود که به صورت پیش‌فرض در هستهٔ زبان برنامه‌نویسی گنجانده شده و معنای خاصی دارند و از همین روی نمی‌توان از آن‌ها برای نام‌گذاری متغیرها، کلاس‌ها، متدها و ... استفاده نمود.

آنچه می‌خواهیم متعلق به کلاس مذکور باشد را می‌باید داخل علائم {} بنویسیم به طوری که علامت } به منزلهٔ نقطهٔ شروع این کلاس است و علامت { نیز پایان این کلاس را مشخص می‌سازد. اساساً هر متغیری که داخل بدنهٔ کلاس یا به عبارتی مابین علائم } و { تعریف شده تحت عنوان Property شناخته می‌شود و همان‌طور که پیش از این متذکر شدیم، پراپرتی‌ها به عنوان خصائص یا ویژگی‌های یک کلاس تلقی می‌شوند. به طور مثال، در کلاس فوق سه پراپرتی تعریف کرده‌ایم تحت عناوین name$ و lastName$ و همچنین dob$ که برای هر کدام از آن‌ها نیز یک مقدار اولیه در نظر گرفته‌ایم.

نکته با توجه به این که کلاس همچون نقشهٔ یک خانه است که در آن خصوصیات کلی خانه مشخص می‌گردد، لذا استفاده از مقادیر پیش‌فرض برای پراپرتی‌ها خیلی کار صحیحی نیست بلکه این کار می‌باید در حین ساخت آبجکت از طریق مفاهیمی همچون Setter و Getter صورت گیرد اما در کلاس فوق صرفاً جهت آموزش این موضوع از مقادیر پیش‌فرض استفاده شده است.

همان‌طور که ملاحظه می‌شود، پیش از نام این پراپرتی‌ها (متغیرها) از کیورد public استفاده کرده‌ایم که اصطلاحاً یک Access Modifier است. به عبارتی، این کیورد مشخص‌کنندهٔ سطح دسترسی این پراپرتی‌ها است و همان‌طور که از نامش مشخص است، از هر کجای سورس‌کد می‌توان به این پراپرتی‌ها دسترسی پیدا کرد زیرا اساساً به شکلِ عمومی تعریف شده‌اند (در آموزش‌های آتی به طور مفصل پیرامون انواع سطوح دسترسی صحبت خواهیم نمود.)

در ادامه، یک فانکشن/متد/تابع تحت عنوان ()showFullName ساخته‌ایم که این وظیفه را دارا است تا مقدار پراپرتی‌های name$ و lastName$ را بازگرداند. در زبان برنامه‌نویسی پی‌اچ‌پی، به منظور تعریف یک متد از کیورد function استفاده می‌کنیم و از آنجا که می‌خواهیم سطح دسترسی این متد عمومی‌ باشد، پیش از هر چیز از کیورد public استفاده نموده‌ایم.

در نام‌گذاری این متد، از روش اصطلاحاً camelCase استفاده نموده‌ایم بدین شکل که حرف اول کلمهٔ اول کوچک سپس حرف اول سایر کلمات به صورت بزرگ نوشته می‌شود. به عبارتی، می‌توان گفت که نام متد به صورت شماتیک همچون کوهان شتر خواهد بود (توجه داشته باشیم که در نام‌گذاری متدها نمی‌توان نام متد را با یک عدد آغاز کرد و یا از اِسپیس یا دَش برای جداسازی کلمات استفاده نمود.)

حال برسیم به محتوای داخل متد ()showFullName که با کیورد return آغاز می‌شود. در واقع، هر جایی از کد که از این کلیدواژه استفاده شود، دستوری که پس از آن قرار می‌گیرد بازگردانده شده و سایر دستوراتی که در خطوط بعدی قرار گرفته‌اند دیگر اجرا نخواهند شد. با این توضیحات، داخل این متد دستور داده‌ایم که در صورت فراخوانی این متد، یک اِسپیس مابین پراپرتی‌های name$ و lastName$ قرار گرفته و مجموعه کل این استرینگ ریترن گردد.

چیزی که در اینجا نیاز به توضیح بیشتر دارد، کیورد this$ است. در حقیقت،‌ این کیورد به خودِ کلاس User اشاره دارد که با در نظر داشتن این نکته می‌توان گفت که دستور this->name$ را بدین شکل می‌توان تفسیر نمود که گفته‌ایم «این کلاس» را مبنا قرار داده سپس به دنبال یک پراپرتی تحت عنوان name گشته و مقدار آن را به دست آورده‌ایم. نکتهٔ دیگری که در ارتباط با this$ می‌باید به خاطر داشته باشیم آن است که به منظور هدف قرار دادن یک پراپرتی، پس از کیورد this$ از علائم <- استفاده نموده، سپس علامت $ پراپرتی را حذف نموده و صرفاً نام آن را درج می‌کنیم.

تا این مرحله از کار،‌ توانسته‌ایم با موفقیت یک کلاس بسیار ساده که حاوی یک سری پراپرتی و یک متد است بسازیم. حال در ادامه قصد داریم ببینیم که به چه شکل می‌توان این کلاس را مورد استفاده قرار داد که برای این منظور، کدهای فوق را به صورت زیر تکمیل می‌کنیم:

<?php
class User
{
    public $name = "Behzad";
    public $lastName = "Moradi";
    public $dob = 1362;

    public function showFullName()
    {
        return $this->name . ' ' . $this->lastName;
    }
}

$objectMadeFromUserClass = new User();
echo $objectMadeFromUserClass->showFullName();

از روی کلاس User، آبجکتی با نامی دلخواه همچون objectMadeFromUserClass$ که نامی بامسمی است ساخته سپس در خط پانزدهم متد ()showFullName را روی این آبجکت فراخوانی کرده و با دستور echo خروجی آن را چاپ کرده‌ایم به طوری که خواهیم داشت:

Behzad Moradi

توجه داشته باشیم که به منظور ساخت یک آبجکت جدید از روی کلاسی خاص، می‌باید از کیورد new استفاده نماییم. همچنین آوردن علائم () پس از نام کلاس در چنین شرایطی اختیاری است؛ به عبارتی، هم User و هم ()User هر دو صحیح هستند اما این در حالی است که اگر کانستراکتور این کلاس پارامتر ورودی بگیرید، درج علائم () ضروری است (در ادامهٔ آموزش‌ها به طور مفصل با مفهوم کانستراکتور آشنا خواهیم شد.)

با توجه به این که سازوکار دستور return برای دولوپرهایی که تازه‌کار هستند ممکن است کمی گیج‌کننده باشد، در ادامه سعی می‌کنیم این موضوع را بیشتر توضیح دهیم. برای این منظور، کد فوق را به صورت زیر ریفکتور می‌کنیم:

<?php
class User
{
    public $name = "Behzad";
    public $lastName = "Moradi";
    public $dob = 1362;

    public function showFullName()
    {
        echo $this->name . ' ' . $this->lastName;
    }
}

$objectMadeFromUserClass = new User();
$objectMadeFromUserClass->showFullName();

همان‌طور که ملاحظه می‌شود، داخل متد ()showFullName دستور return را حذف کرده و از دستور echo به جای آن استفاده کرده‌ایم که این تغییر باعث می‌گردد به محض فراخوانی این متد، مقادیر پارامترهایی که پس از دستور echo آمده‌اند چاپ گردد. با توجه به این که خود این متد دیگر به جای ریترن کردن استرینگ مد نظر آن را چاپ می‌کند، لذا در خط پانزدهم دیگر لزوی به درج دستور echo نخواهیم داشت و در صورت اجرای این اسکریپت، خروجی در کنسول یا مرورگر چاپ خواهد شد. با مد نظر قرار دادن این توضیحات، می‌توان به این نتیجه رسید که دستور return آنچه در ادامه‌اش قرار می‌گیرد را به عنوان خروجی متد ارسال می‌کند و تصمیم با ما است که در حین استفاده از آن متد و با استفاده از دستور echo آن خروجی را چاپ کنیم یا نکنیم.

درآمدی بر تفاوت‌های مابین فانکشن و متد

پیش از این گفتیم که ()showFullName را هم می‌توان فانکشن قلمداد کرد و هم متد اما اگر بخواهیم کمی دقیق‌تر به موضوع نگاه کنیم، اساساً Function و Method دو مقوله متفاوت از یکدیگر هستند که در ادامه تفاوت‌های آن‌ها را بازگو خواهیم کرد. برای روشن‌تر شدن موضوع، تکه کد زیر را مد نظر قرار می‌دهیم:

function multiply($numOne, $numTwo)
{
    echo $numOne * $numTwo;
}

multiply(2, 5);

تابعی تعریف کرده‌ایم تحت عنوان ()multiply که دو پارامتر ورودی گرفته، آن‌ها را در یکدیگر ضرب کرده و خروجی را چاپ می‌کند و در ادامه هم می‌بینیم که چگونه آن را مورد استفاده قرار داده‌ایم. در یک اسکریپت معمولی که به روش Procedural نوشته شده باشد، این بلوک از کد فانکشن نامیده می‌شود. حال چنانچه این بلوک از کد داخل یک کلاس قرار گیرد، تحت عنوان متد شناخته خواهد شد به طوری که داریم:

class Calculator
{
    function multiply($numOne, $numTwo)
    {
        echo $numOne * $numTwo;
    }
}

$cal = new Calculator();
$cal->multiply(2, 5);

در تفسیر اسکریپت فوق باید بگوییم که کلاسی ساخته‌ایم تحت عنوان Calculator که صرفاً حاوی یک فانکشن (یا بهتر بگوییم یک متد) است که در ادامه آبجکتی از روی این کلاس ساخته و متد مذکور را فراخوانی کرده‌ایم.

در برنامه‌نویسی شیئ‌گرا، معمولاً کلاس‌ها در فایل‌های جداگانه ساخته شده سپس هر جایی که نیاز داشته باشیم، آن‌‌ها را ایمپورت کرده و مورد استفاده قرار می‌دهیم که در همین راستا، در ادامه قصد داریم ببینیم که به چه شکل می‌توان کلاس User را به شکلی مستقل ساخت.

برای این منظور، داخل پوشهٔ class-and-object پوشه‌ای می‌سازیم تحت عنوان classes و داخل آن فایلی به نام User.php حاوی کدهای زیر می‌سازیم:

<?php
class User
{
    public $name = "Behzad";
    public $lastName = "Moradi";
    public $dob = 1362;

    public function showFullName()
    {
        echo $this->name . ' ' . $this->lastName;
    }
}

از این پس، ساختار این پروژه به صورت زیر خواهد بود:

class-and-object
├── classes
│   └── User.php
└── index.php

توجه داشته باشیم که بهتر است نام فایل با نام کلاس یکسان باشد چرا که اگر بخواهیم در آینده از مفهومی تحت عنوان Autoloading استفاده نماییم، به مشکلی برنخواهیم خورد. حال در ادامه قصد داریم تا داخل فایل index.php از این کلاس استفاده نماییم که برای همین منظور، این فایل را به صورت زیر تکمیل می‌کنیم:

<?php
$objectMadeFromUserClass = new User();
$objectMadeFromUserClass->showFullName();

چنانچه این فایل را داخل مرورگر باز کنیم، قاعداً هیچ‌ چیزی در خروجی مشاهده نخواهیم کرد که برای رفع این مشکل،‌ این فایل را به صورت زیر تکمیل‌تر می‌نماییم:

<?php
ini_set('display_errors', '1');

$objectMadeFromUserClass = new User();
$objectMadeFromUserClass->showFullName();

 همان‌طور که می‌بینیم، ابتدا از فانکشن ()ini_set استفاده کرده و از طریق آن، مقدار display_errors را برابر با عدد 1 قرار داده‌ایم. در واقع، کاری که این دستور انجام می‌دهد این است که تنظیمات فایل php.ini را اُورراید نموده و این امکان را در اختیار توسعه‌دهنده قرار می‌دهد تا ارورها و اِکسپشن‌های احتمالی را مشاهده کند. حال اگر این اسکریپت را مجدد داخل مرورگر ران کنیم، به عنوان خروجی خواهیم داشت:

Fatal error: Uncaught Error: Class 'User' not found in /var/www/oop/class-and-object/index.php:4 

می‌بینیم که مفسر پی‌اچ‌پی این پیام را در معرض دیدمان قرار می‌دهد که کلاسی تحت عنوان User که در خط چهارم مورد استفاده قرار گرفته را نیافته است که برای رفع این مشکل، مجدد فایل فوق را به صورت زیر تکمیل‌تر می‌نماییم:

<?php
ini_set('display_errors', '1');
require_once "classes/User.php";

$objectMadeFromUserClass = new User();
$objectMadeFromUserClass->showFullName();

در حقیقت، با استفاده از دستور require_once به مسیر قرارگیری کلاس User رفته و آن را داخل این فایل ایمپورت کرده‌ایم و از این پس چنانچه این اسکریپت را اجرا نماییم، خروجی به درستی در معرض دیدمان قرار می‌گیرد.

اکنون فرض کنیم که چندین و چند کلاس مختلف داریم که قصد استفاده از تمامی آن‌ها را داخل فایل index.php داریم که در این صورت، این فایل به شکل زیر خواهد بود:

<?php
ini_set('display_errors', '1');
require_once "classes/User.php";
require_once "classes/User2.php";
require_once "classes/User3.php";

$objectMadeFromUserClass = new User();
$objectMadeFromUserClass->showFullName();

$objectMadeFromUser2Class = new User2();
$objectMadeFromUser2Class->showFullName();

$objectMadeFromUser3Class = new User3();
$objectMadeFromUser3Class->showFullName();

همان‌طور که ملاحظه می‌شود، بارها و بارها استفاده از دستور require_once کاری فرسایشی و البته باگ‌زا خواهد بود! به منظور ساده‌سازی بیشتر، می‌توان یک فایل اصلی با نامی بامسمی همچون bootstrap.php و یا init.php ساخته، کلیهٔ دستورات require_once را داخل نوشته و صرفاً آن فایل را داخل فایل‌های مختلفی که داریم ایمپورت نماییم. برای درک بهتر این موضوع، ابتدا کلاس‌های User2 و User3 را ساخته سپس فایلی تحت عنوان init.php ساخته و آن را تکمیل می‌کنیم. برای شروع، کلاس User2 به صورت زیر خواهد بود:

<?php
class User2
{
    public function showFullName()
    {
        echo "This is User2";
    }
}

کلاس User3 نیز به صورت زیر است:

<?php
class User3
{
    public function showFullName()
    {
        echo "This is User3";
    }
}

حال فایلی تحت عنوان init.php ساخته و آن را به صورت زیر تکمیل می‌کنیم:

<?php 
require_once "classes/User.php";
require_once "classes/User2.php";
require_once "classes/User3.php";

در حقیقت، هر سه کلاس موجود در این پروژه را داخل این فایل ایمپورت کرده‌ایم سپس داخل فایل index.php خودِ فایل init.php را به صورت زیر ایمپورت می‌نماییم:

<?php
ini_set('display_errors', '1');
require_once "init.php";

$objectMadeFromUserClass = new User();
$objectMadeFromUserClass->showFullName();

$objectMadeFromUser2Class = new User2();
$objectMadeFromUser2Class->showFullName();

$objectMadeFromUser3Class = new User3();
$objectMadeFromUser3Class->showFullName();

این اسکریپت به درستی کار خواهد کرد اما در عین حال اصولی نیست! در واقع، هر موقع که کلاس جدیدی به پروژه اضافه می‌شود، به صورت دستی می‌باید فایل init.php را تکمیل کنیم که همین مسئله ممکن است منجر به بروز خطاهای سهوی گردد و اینجا است که می‌باید با مفهومی تحت عنوان اُوتولودینگ در زبان برنامه‌نویسی پی‌اچ‌پی آشنا شویم که در ادامه این موضوع را به طور کامل تشریح خواهیم نمود.

آشنایی با مفهوم Autoloading

این اصطلاح حاکی از آن است که وقتی یک آبجکت جدید از روی کلاسی ساخته می‌شود، خود مُفسر پی‌اچ‌پی به صورت خودکار آن کلاس را داخل فایل مذکور ایمپورت خواهد کرد که این کار از طریق فانکشنی تحت عنوان ()spl_autoload_register عملی می‌گردد. به منظور درک بهتر این موضوع، فایل init.php را به صورت زیر ریفکتور می‌کنیم:

<?php
spl_autoload_register(function ($class) {
    require_once "classes/{$class}.php";
});

فانکشن ()spl_autoload_register یک پارامتر ورودی از جنس کلوژر می‌گیرد و می‌بینیم که برای این کلوژر یک پارامتر ورودی تحت عنوان class$ در نظر گرفته‌ایم و در ادامه نیز از دستور require_once استفاده نموده سپس مسیر کلاس‌های این پروژه را در نظر گرفته‌ایم (توجه داشته باشیم برای آن که مقدار متغیر class$ به درستی چاپ شود، می‌باید آن را داخل علائم {} قرار داد.) 

به خاطر داشته باشید
همچون مثال فوق،‌ هر فانکشنی که بی‌نام باشد، تحت عنوان Closure شناخته می‌شود.

حال اگر اسکریپت فوق را اجرا نماییم، می‌بینیم که به صورت خودکار هر سه کلاس استفاده شده داخل فایل index.php ایمپورت شده‌اند. در واقع، قابلیت اُوتولودینگ این اطمینان را به ما می‌دهد که می‌توانیم به صورت خودکار کلاس‌های مورد استفاده را در فایل‌های مد نظرمان ایمپورت نماییم. در عین حال، می‌توان یک گام فراتر رفته و این پروسه را به شکلی اصولی‌تر در پروژه‌های مبتنی بر معماری OOP پیاده‌سازی نماییم که در ادامه این موضوع را به تفصیل تشریح خواهیم نمود.

آشنایی با سازمان PHP-FIG

PHP-FIG که بر گرفته از کلمات PHP Framework Interop Group می‌باشد، سازمانی عام‌المنفعه است که هدف اصلی‌اش استانداردسازی پروژه‌های نوشته‌شده با زبان پی‌اچ‌پی از طریق ایجاد یک سری اصول و قوانین است. این گروه توسط جمعی در حدود ۵ نفر از توسعه‌دهندگان فریمورک‌های پی‌اچ‌پی در سال 2009 شکل گرفت و به مرور زمان افراد بیشتری به هستهٔ اولیهٔ گروه محلق گردید و این در حالی است که فضا برای مشارکت کلیهٔ علاقه‌مندان باز است.

PHP Standard Recommendation یا به اختصار PSR حاوی ده‌ها استاندارد برای توسعهٔ نرم‌افزار با زبان پی‌اچ‌پی است که استاندارد چهارم تحت عنوان PSR-4: Autoloader مرتبط با مقولهٔ Autoloading است که در ادامه قصد داریم آن را مورد بررسی قرار دهیم.

آشنایی با PSR-4: Autoloader

این استاندارد هر آنچه در مورد مقولهٔ اُوتولودینگ در پروژه‌های پی‌اچ‌پی نیاز است را شامل می‌گردد که از آن جمله می‌توان به نحوهٔ نام‌گذاری فایل‌ها و ... اشاره کرد. در ادامه، ابتدا دو مثال از این استاندارد خواهیم زد سپس به توضیح شیوهٔ نام‌گذاری آن‌ها می‌پردازیم:

FULLY QUALIFIED CLASS NAME NAMESPACE PREFIX BASE DIRECTORY RESULTING FILE PATH
Acme\Log\Writer\File_Writer\ Acme\Log\Writer /acme-log-writer/lib/. acme-log-writer/lib/File_Writer.php/.
Symfony\Core\Request\ Symfony\Core vendor/Symfony/Core/. vendor/Symfony/Core/Request.php/.

به طور کلی، قانونی که به منظور شیوهٔ نام‌گذاری کلاس‌ها مورد استفاده قرار می‌گیرد به صورت زیر است:

\<NamespaceName>(\<SubNamespaceNames>)*\<ClassName>

Vendor Namespace که در بالا در قالب NamespaceName نمایش داده شده است می‌باید به نوعی دربرگیرندهٔ سازنده، نام پروژه یا دولوپر کلاس مذکور باشد به طوری که مثلاً در Symfony\Core\Request\ نام Symfony به سازندهٔ کلاس مذکور اشاره دارد. در ارتباط با SubNamespaceNames باید گفت که به هر تعداد که بخواهیم می‌توانیم زیرشاخه داشته باشیم که در مثال فوق‌الذکر Core به عنوان زیرشاخهٔ Symfony محسوب می‌گردد و در نهایت به ClassName می‌رسیم که در این مثال Request است.

بر اساس این استاندارد، کلیهٔ کلاس‌ها می‌باید به پسوند php. ختم شوند مضاف بر این که به صورت PascalCase نوشته شوند؛ به عبارتی، حرف اول نام کلاس به صورت بزرگ نوشته شده سپس چنانچه نام کلاس از چند کلمه تشکیل شده بود، حرف اول سایر کلمات نیز به شکل بزرگ نوشته شوند (به طور مثال، می‌توان نام فرضی FileHandler را مد نظر قرار داد که در آن حرف اول کلمات File و Handler به صورت بزرگ نوشته شده‌اند.)

حال بر اساس این اصول، در ادامه خواهیم دید که به چه شکل می‌توان بر پایهٔ اصل PSR-4 اقدام به ایمپورت کردن فایل‌های پروژه نمود اما قبل از آن نیاز است تا با مفهومی تحت عنوان کامپوزر آشنا شویم.

Composer چیست؟

کامپوزر (Composer) چیست؟ مقاله‌ای است که در آن به طور مفصل پیرامون تاریخچه، ماهیت و همچنین کاربرد پَکِیج مَنِجِر کامپوزر توضیح داده شده است اما چنانچه بخواهیم به طور خلاصه به این پرسش پاسخ دهیم که «کامپوزر چیست؟» باید بگوییم کامپوزر ابزاری است که پروسهٔ Dependency Management (مدیریت وابستگی‌) پروژه را برای توسعه‌دهندگان زبان برنامه‌نویسی برنامه‌نویسی پی‌اچ‌پی تسهیل می‌سازد. به عبارت دیگر، کامپوزر چک می‌کند ببیند که یک پروژه چه وابستگی‌هایی به سایر پروژه‌ها دارا است، سپس آن‌ها را بسته به نسخهٔ مد نظر به سادگی روی سیستم نصب می‌سازد. با توجه به این که قصد داریم تا از استاندارد PSR-4: Autoloader برای فرآیند اُوتولودینگ استفاده نماییم، نیاز به نصب کامپوزر خواهیم داشت که در ادامه نحوهٔ انجام این کار را توضیح خواهیم داد.

راهنمای نصب کامپوزر روی گنو/لینوکس

به منظور نصب کامپوزر روی سیستم‌عامل اوبونتو، ابتدا به ساکن باید اطمینان حاصل کنیم که ابزار curl روی سیستم نصب است؛ سپس با استفاده از کامند زیر کَش ابزار مدیریت پکیج‌ لینوکس را به‌روزرسانی می‌کنیم:

$ sudo apt-get update

حال با استفاده از کامند زیر می‌توانیم کامپوزر را به صورت اصطلاحاً Globally یا «سراسری» روی سیستم نصب نماییم:

$ curl -sS https://getcomposer.org/installer | sudo php -- --install-dir=/usr/local/bin --filename=composer

پس از موفقیت‌آمیز بودن مراحل فوق، با استفاده از کامند زیر می‌توانیم از صحتِ نصب کامپوزر اطمینان حاصل نماییم:

$ composer
   ______
  / ____/___  ____ ___  ____  ____  ________  _____
 / /   / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/
/ /___/ /_/ / / / / / / /_/ / /_/ (__  )  __/ /
\____/\____/_/ /_/ /_/ .___/\____/____/\___/_/
                    /_/
Composer version 1.8.5 2019-04-09 17:46:47

Usage:
  command [options] [arguments]

Options:
  -h, --help                     Display this help message
  -q, --quiet                    Do not output any message
  -V, --version                  Display this application version
      --ansi                     Force ANSI output
      --no-ansi                  Disable ANSI output
  -n, --no-interaction           Do not ask any interactive question
      --profile                  Display timing and memory usage information
      --no-plugins               Whether to disable plugins.
  -d, --working-dir=WORKING-DIR  If specified, use the given directory as working directory.
  -v|vv|vvv, --verbose           Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug

Available commands:
  about                Shows the short information about Composer.
  archive              Creates an archive of this composer package.
  browse               Opens the package's repository URL or homepage in your browser.
  check-platform-reqs  Check that platform requirements are satisfied.
  clear-cache          Clears composer's internal package cache.
  clearcache           Clears composer's internal package cache.
  config               Sets config options.
  create-project       Creates new project from a package into given directory.
  depends              Shows which packages cause the given package to be installed.
  diagnose             Diagnoses the system to identify common errors.
  dump-autoload        Dumps the autoloader.
  dumpautoload         Dumps the autoloader.
  exec                 Executes a vendored binary/script.
  global               Allows running commands in the global composer dir ($COMPOSER_HOME).
  help                 Displays help for a command
  home                 Opens the package's repository URL or homepage in your browser.
  i                    Installs the project dependencies from the composer.lock file if present, or falls back on the composer.json.
  info                 Shows information about packages.
  init                 Creates a basic composer.json file in current directory.
  install              Installs the project dependencies from the composer.lock file if present, or falls back on the composer.json.
  licenses             Shows information about licenses of dependencies.
  list                 Lists commands
  outdated             Shows a list of installed packages that have updates available, including their latest version.
  prohibits            Shows which packages prevent the given package from being installed.
  remove               Removes a package from the require or require-dev.
  require              Adds required packages to your composer.json and installs them.
  run-script           Runs the scripts defined in composer.json.
  search               Searches for packages.
  self-update          Updates composer.phar to the latest version.
  selfupdate           Updates composer.phar to the latest version.
  show                 Shows information about packages.
  status               Shows a list of locally modified packages, for packages installed from source.
  suggests             Shows package suggestions.
  u                    Upgrades your dependencies to the latest version according to composer.json, and updates the composer.lock file.
  update               Upgrades your dependencies to the latest version according to composer.json, and updates the composer.lock file.
  upgrade              Upgrades your dependencies to the latest version according to composer.json, and updates the composer.lock file.
  validate             Validates a composer.json and composer.lock.
  why                  Shows which packages cause the given package to be installed.
  why-not              Shows which packages prevent the given package from being installed.

از این پس می‌توانیم به سادگی با استفاده از کامپوزر اقدام به پیاده‌سازی اُتولودینگ در پروژه‌ٔ خود نماییم. پروژه‌های پی‌اچ‌پی که در آن‌ها از پَکِیج مَنِجر کامپوزر استفاده می‌شود دارای فایلی تحت عنوان composer.json در روت پروژه هستند که این فایل حاوی دیتایی در قالب فرمت جیسون است که مشخص می‌سازد ابزار کامپوزر از چه تنظیماتی می‌باید تبعیت نماید. برای همین منظور، داخل پوشهٔ class-and-object فایلی با این نام حاوی دیتای زیر می‌سازیم:

{

}

همان‌طور که ملاحظه می‌شود، یک آرایهٔ خالی داخل این فایل ایجاد کرده‌ایم. حال در مسیر روت پروژه، کامند زیر را اجرا می‌کنیم:

/var/www/oop/class-and-object$ composer dump-autoload -o

در صورتی که اجرای کامند فوق بدون هیچ‌گونه مشکلی تکمیل گردد،‌ خواهیم دید که در روت پروژه فولدر جدیدی تحت عنوان vendor ساخته خواهد شد که حاوی محتوای زیر است:

vendor
├── autoload.php
└── composer
    ├── autoload_classmap.php
    ├── autoload_namespaces.php
    ├── autoload_psr4.php
    ├── autoload_real.php
    ├── autoload_static.php
    ├── ClassLoader.php
    └── LICENSE

کاری که کامند dump-autoload انجام می‌دهد آن است که بر اساس قوانینی که داخل فایل composer.json تعریف کرده‌ایم (که در حال حاضر هیچ قانونی داخل این فایل تعریف نشده و صرفاً یک آرایهٔ خالی قرار داده‌ایم.) اقدام به آپدیت فایل autoload_classmap.php می‌کند و از آنجا که این اولین باری است که این کامند را اجرا می‌کنیم، کامپوزر ابتدا فایل‌ها و فولدرهای پیش‌نیاز من جمله فولدر vendor را ساخته سپس تَسک‌های مد نظر را عملی می‌سازد (در دفعات بعدی که این کامند اجرا گردد دیگر صرفاً فایل autoload_classmap.php به‌روزرسانی خواهد شد. همچنین لازم به یادآوری است که آپشن o- بر گرفته از کلمهٔ Optimized است به منظور اجرای این کامند به شکلی بهینه است.) 

آنچه در این پوشه در حال حاضر برای‌مان حائز اهمیت می‌باشد فایلی به نام autoload.php است که این وظیفه را دارد تا به صورت خودکار کلیهٔ کلاس‌هایی که در جای‌جای این وب اپلیکیشن استفاده می‌نماییم را ایمپورت نماید. حال فایل composer.json را به صورت زیر تکمیل می‌کنیم:

{
    "autoload": {
        "classmap": ["classes"]
    }
}

در واقع، یک کلید اصلی داریم تحت عنوان autoload که داخل آن کلیدی درج شده به نام classmap که مقدارش یک آرایه است و همان‌طور که می‌بینیم، نام پوشهٔ classes را به عنوان یکی از اِلِمان‌های این آرایه در نظر گرفته‌ایم. اکنون مجدد دستور composer dump-autoload -o را اجرا می‌کنیم و چنانچه در این پروسه مشکلی رخ ندهد، محتویات فایل autoload_classmap.php که داخل پوشهٔ vendor/composer قرار دارد به صورت زیر آپدیت خواهد شد:

<?php

// autoload_classmap.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'User' => $baseDir . '/classes/User.php',
    'User2' => $baseDir . '/classes/User2.php',
    'User3' => $baseDir . '/classes/User3.php',
);

می‌بینیم که هر سه کلاس موجود داخل پوشهٔ classes داخل این فایل به رسمیت شناخته شده‌اند. اکنون فایل index.php را به صورت زیر تکمیل نماییم تا از این پس هر کلاسی را که ساختیم و سپس استفاده نمودیم، به صورت خودکار ایمپورت گردد:

<?php
ini_set('display_errors', '1');
require_once 'vendor/autoload.php';

$objectMadeFromUserClass = new User();
$objectMadeFromUserClass->showFullName();

$objectMadeFromUser2Class = new User2();
$objectMadeFromUser2Class->showFullName();

$objectMadeFromUser3Class = new User3();
$objectMadeFromUser3Class->showFullName();

همان‌طور که ملاحظه می‌شود، از این پس دیگر نیازی به فایل init.php نخواهیم داشت بلکه در عوض مسیر فایل autoload.php را درج می‌نماییم و اگر این اسکریپت را اجرا نماییم، بدون هیچ مشکلی هر سه متد اجرا خواهند شد. در متودولوژی شیئ‌گرایی، باز هم می‌توان یک گام فراتر رفته و پروژه‌های اصولی‌تر نوشت که این امر با استفاده از مفهومی تحت عنوان Namespace عملی می‌گردد که در ادامه بیشتر با این مفهوم آشنا خواهیم شد.

آشنایی با مفهوم Namespace

Namespace قابلیتی است که از نسخهٔ PHP 5.3 به بعد به این زبان اضافه شد که در پروژه‌های پیچیده‌ای که با زبان پی‌اچ‌پی نوشته‌ می‌شوند، بسیار مشکل‌گشا خواهد بود. پیش از این گفتیم که یکی از مزایایی که OOP برای دولوپرها به ارمغان می‌آورد، ماژولار کردن سورس‌کد است به طوری که می‌توان از ماژول‌ها یا کلاس‌هایی که پیش از این نوشته شده در سایر پروژه‌ها استفاده نمود اما در عین حال ممکن است که در این حوزه با مشکلاتی مواجه شویم.

فرض کنیم توسعه‌دهنده‌‌ای کلاسی به نام User نوشته که مستقل از سایر کلاس‌ها است و هر توسعه‌دهندهٔ دیگری می‌تواند به سادگی از آن در پروژه‌های خود استفاده کند. در عین حال، خودمان نیز پیش از این کلاسی تحت عنوان User ساخته‌ایم و این در حالی است که اگر بخواهیم از هر دو کلاس داخل یک فایل استفاده نماییم، مسلماً به کانفلیکت (تداخل) خواهیم خورد و این گره‌ای است که به دست نِیم‌اِسپیس باز می‌گردد.

در واقع، نِیم‌اِسپیس این امکان را در اختیار توسعه‌دهندگان زبان پی‌اچ‌پی قرار می‌دهد تا کلاس‌ها و دیگر فایل‌های مد نظر خود که مرتبط با یکدیگر هستند را داخل یک فضا با نامی منحصربه‌فرد قرار داده و این تضمین ایجاد خواهد شد که مثلاً در مثال فوق کلاس User که خودمان نوشتیم در یک نِیم‌اِسپیس است و دقیقاً چنین کلاسی که متعلق به دولوپر دیگری می‌باشد در نِیم‌اِسپیس متفاوتی است که همین مسئله مشکل تداخل اسمی هر دو کلاس را مرتفع می‌سازد.

برای درک بهتر این موضوع، پوشه‌ای می‌سازیم به نام namespacing که حاوی فولدرها و فایل‌های زیر خواهد بود:

namespacing
├── index.php
├── lib1
│   └── Database.php
└── lib2
    └── Database.php

برای شروع، داخل پوشهٔ lib1 فایلی تحت عنوان Database.php حاوی کدهای زیر می‌سازیم:

<?php
class Database
{
    public function connect()
    {
        echo "This is the connect() method from lib1 folder\n";
    }
}

به همین منوال،‌ پوشه‌ٔ دیگری به نام lib2 ساخته و فایل Database.php قرارگرفته داخل آن را به صورت زیر تکمیل می‌کنیم:

<?php
class Database
{
    public function connect()
    {
        echo "This is the connect() method from lib2 folder\n";
    }
}

حال فایلی به نام index.php ساخته و آن را به صورت زیر تکمیل می‌کنیم:

<?php
ini_set('display_errors', '1');
require_once "lib1/Database.php";
require_once "lib2/Database.php";

$obj1 = new Database();
echo $obj1->connect();
$obj2 = new Database();
echo $obj2->connect();

همان‌طور که می‌بینیم، ابتدا هر دو فایل را ایمپورت کرده سپس آبجکت‌هایی از روی هر دو کلاس Database ساخته سپس متدهای ()connect آن‌ها را فراخوانی نموده‌ایم به طوری که به عنوان خروجی خواهیم داشت:

PHP Fatal error:  Cannot declare class Database, because the name is already in use in /var/www/oop/namespacing/lib2/Database.php on line 4

می‌بینیم که متن خطا حاکی از آن است که مفسر پی‌اچ‌پی می‌خواهد در خط چهارم کلاسی تحت عنوان Database را ایمپورت کند اما این در حالی است که در خط سوم این کار یک بار صورت گرفته و امکان ایمپورت مجدد آن کلاس وجود ندارد. حال با استفاده از نِیم‌اِسپیس‌ها قصد داریم تا این مشکل را رفع کنیم که برای این منظور کلاس Database قرارگرفته داخل پوشهٔ lib1 را به صورت زیر ریفکتور می‌کنیم:

<?php
namespace lib1;

class Database
{
    public function connect()
    {
        echo "This is the connect() method from lib1 folder\n";
    }
}

همان‌طور که می‌بینیم، به منظور استفاده از نِیم‌اِسپیس‌ها می‌باید کلیدواژهٔ namespace را پیش از هر دستور دیگری نوشته سپس نامی دلخواه برای آن انتخاب کرد. به همین منوال، کلاس Database موجود در پوشهٔ lib2 را به صورت زیر آپدیت می‌کنیم:

<?php
namespace lib2;

class Database
{
    public function connect()
    {
        echo "This is the connect() method from lib2 folder\n";
    }
}

پس از انتخاب نِیم‌اِسپیسی منحصربه‌فرد همچون lib2 برای این فایل، حال نوبت به آپدیت فایل index.php به صورت زیر می‌رسد:

<?php
ini_set('display_errors', '1');
require_once "lib1/Database.php";
require_once "lib2/Database.php";

$obj1 = new lib1\Database();
$obj1->connect();
$obj2 = new lib2\Database();
$obj2->connect();

که با اجرای اسکریپت فوق خواهیم داشت:

/var/www/oop/namespacing$ php index.php
This is the connect() method from lib1 folder
This is the connect() method from lib2 folder

در واقع، در حین ساخت آبجکت از روی هر دو کلاس Database، نیم‌اسپیس آن کلاس را نوشته سپس یک علامت \ قرار داده و در نهایت نام کلاس را نوشته‌ایم.

یکی از قابلیت‌های کاربردی نِیم‌اِسپیس‌ها، مفهومی تحت عنوان Alias است بدین شکل که می‌توان از نامی مستعار برای کلاس‌های خود استفاده نمود. به طور مثال داریم:

<?php
ini_set('display_errors', '1');
require_once "lib1/Database.php";
require_once "lib2/Database.php";

use lib1\Database as BehzadDB;
use lib2\Database as SahandDB;

$obj1 = new BehzadDB();
$obj1->connect();
$obj2 = new SahandDB();
$obj2->connect();

همان‌طور که می‌بینیم، ابتدا کیورد use را نوشته سپس نام کامل کلاس + نِیم‌اِسپیس را درج کرده سپس کیورد as را نوشته و نامی دلخواه برای کلاس مذکور در نظر گرفته‌ایم و در حین ساخت آبجکت نیز دقیقاً‌ از همان نام که اصطلاحاً تحت عنوان Alias شناخته می‌شود استفاده کرده‌ایم.

در ادامه، به منظور درک بیشتر قابلیت‌های نِیم‌اِسپیس‌‌ها، پوشه‌ای تحت عنوان lib3 به صورت زیر به پروژه اضافه می‌کنیم:

namespacing
├── index.1.php
├── index.2.php
├── index.php
├── lib1
│   └── Database.php
├── lib2
│   └── Database.php
└── lib3
    └── sub1
        └── sub2
            └── sub3
                └── User.php

در واقع، داخل این پوشه سه لایه پایین‌تر رفته و پوشه‌های دیگری ساخته‌ایم و در نهایت هم داخل پوشهٔ sub3 فایلی تحت عنوان User.php حاوی کدهای زیر ساخته‌ایم:

<?php 
namespace lib3\sub1\sub2\sub3;

class User 
{
    public function showText()
    {
        echo "User class \n";
    }
}

حال قصد داریم تا این کلاس را داخل فایل index.php استفاده نماییم که برای این منظور خواهیم داشت:

<?php
ini_set('display_errors', '1');
require_once "lib1/Database.php";
require_once "lib2/Database.php";
require_once "lib3/sub1/sub2/sub3/User.php";

$obj1 = new lib1\Database();
$obj1->connect();
$obj2 = new lib2\Database();
$obj2->connect();
$obj3 = new lib3\sub1\sub2\sub3\User();
$obj3->showText();

به عنوان خروجی هم خواهیم داشت:

/var/www/oop/namespacing$ php index.php
This is the connect() method from lib1 folder
This is the connect() method from lib2 folder
User class 

در واقع، پس از ایمپورت کردن فایل User.php، اقدام به ساخت یک آبجکت جدید از روی کلاس User کرده‌ایم با مد نظر قرار دادن این نکته که می‌باید در حین ساخت آبجکت نیز مسیر کامل را بدهیم و چنانچه از کدی به صورت زیر استفاده نماییم:

$obj3 = new User();

با ارور زیر روبه‌رو خواهیم شد:

PHP Fatal error:  Uncaught Error: Class 'User' not found in /var/www/oop/namespacing/index.php:11

در عین حال اگر بخواهیم که در حین ساخت آبجکت فقط از نام کلاس استفاده نماییم، می‌توانیم ساختار زیر را مد نظر قرار دهیم:

<?php
ini_set('display_errors', '1');
require_once "lib1/Database.php";
require_once "lib2/Database.php";
require_once "lib3/sub1/sub2/sub3/User.php";

use lib3\sub1\sub2\sub3\User;

$obj1 = new lib1\Database();
$obj1->connect();
$obj2 = new lib2\Database();
$obj2->connect();
$obj3 = new User();
$obj3->showText();

همان‌طور که می‌بینیم، از کلیدواژهٔ use استفاده کرده سپس آدرس کامل کلاس مذکور را نوشته‌ایم و در حین ساخت آبجکت در خط سیزدهم، صرفاً نام کلاس را بدون ایجاد هیچ گونه مشکلی مورد استفاده قرار داده‌ایم.

حال که با سازوکار نِیم‌اِسپیس آشنا شدیم، مجدد به پروژهٔ class-and-object باز می‌گردیم تا ببینیم به چه شکل می‌توانیم نِیم‌اِسپیس را با اُتولودینگ ادغام نمود. برای این منظور، پیش از هر چیز فایل composer.json را به صورت زیر تغییر می‌دهیم:

{
    "autoload": {
        "psr-4": {
            "SokanAcademy\\": "classes"
        }
    }
}

این بار به جای کلید classmap، از کلیدی تحت عنوان psr-4 استفاده نموده‌ایم و داخل آن نیز نِیم‌اِسپیسی دلخواه همچون SokanAcademy در نظر گرفته‌ایم و مسیر منتسب به آن نیز پوشهٔ classes است. حال نیاز است تا به پوشهٔ classes مراجعه کرده و نِیم‌اِسپیس انتخابی خود را برای تمامی کلاس‌های موجود داخل این کلاس درج نماییم به طوری که مثلاً برای کلاس User.php داریم:

<?php
namespace SokanAcademy;

class User
{
    public $name = "Behzad";
    public $lastName = "Moradi";
    public $dob = 1362;

    public function showFullName()
    {
        echo $this->name . ' ' . $this->lastName;
    }
}

و کلاس User2 را به صورت زیر تغییر می‌دهیم:

<?php
namespace SokanAcademy;

class User2
{
    public function showFullName()
    {
        echo "This is User2";
    }
}

و همچنین کلاس User3 را نیز به صورت زیر آپدیت خواهیم کرد:

<?php
namespace SokanAcademy;

class User3
{
    public function showFullName()
    {
        echo "This is User3";
    }
}

حال از طریق کامندلاین، داخل پوشهٔ class-and-object کامند composer dump-autoload -o را اجرا می‌کنیم به طوری که اگر پس از اجرای موفقیت‌آمیز این کامند به فایل autoload_classmap.php مراجعه کنیم، خواهیم داشت:

<?php

// autoload_classmap.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'SokanAcademy\\User' => $baseDir . '/classes/User.php',
    'SokanAcademy\\User2' => $baseDir . '/classes/User2.php',
    'SokanAcademy\\User3' => $baseDir . '/classes/User3.php',
);

می‌بینیم که بر اساس نِیم‌اِسپیس انتخابی، کلاس‌ها به منظور اُتولودینگ به درستی شناسایی شده‌اند. حال وارد فایل index.php شده و تغییرات زیر را اِعمال می‌کنیم:

<?php
ini_set('display_errors', '1');
require_once 'vendor/autoload.php';

$objectMadeFromUserClass = new SokanAcademy\User();
$objectMadeFromUserClass->showFullName();

$objectMadeFromUser2Class = new SokanAcademy\User2();
$objectMadeFromUser2Class->showFullName();

$objectMadeFromUser3Class = new SokanAcademy\User3();
$objectMadeFromUser3Class->showFullName();

در واقع، تنها تغییری که اِعمال کرد‌ه‌ایم آن است که پیش از نام کلاس‌ها، از نِیم‌اِسپیس SokanAcademy استفاده نموده‌ایم که اجرای این اسکریپت بدون هیچ گونه مشکلی صورت خواهد گرفت (در این مرحله از کار می‌توانیم فایل init.php که پیش از ساخته بودیم را حذف نماییم چرا که اساساً بلااستفاده است.)

جمع‌بندی
در این آموزش با مفاهیم اصلی شیئ‌گرایی از جمله کلاس و آبجکت آشنا شده و دیدیم که به چه شکل می‌توان یک سری پراپرتی و متد برای کلاس‌ خود در نظر گرفته و پس از ساخت آبجکت آن‌ها را فراخوانی نمود. همچنین در ادامه به بررسی مقولهٔ اُتولودینگ پرداخته و ملاحظه شد که به چه شکل می‌توان به صورت خودکار کلاس‌های مورد استفاده را داخل فایل‌های مد نظر خود ایمپورت نمود و در نهایت هم به بررسی مقولهٔ نِیم‌اِسیپس‌ها پرداختیم که پس از PHP 5.3 به این زبان افزوده شده و امروزه استفاده از آن‌ها را در تمامی فریمورک‌های مطرح این زبان شاهد هستیم.

2-1
آشنایی با مفاهیم Class و Object در متودولوژی OOP
بهزاد مرادی
۱۳۹۸/۰۳/۰۶
2-2
آشنایی با مفاهیم Constructor و Destructor در OOP
بهزاد مرادی
۱۳۹۸/۰۳/۰۷
2-3
آشنایی با متدهای به اصطلاح Magic در زبان PHP
بهزاد مرادی
۱۳۹۸/۰۳/۰۷
2-4
آشنایی با مفهوم وراثت در زبان PHP
بهزاد مرادی
۱۳۹۸/۰۳/۰۷
2-5
آشنایی با مفهوم Access Modifier در زبان PHP
بهزاد مرادی
۱۳۹۸/۰۳/۰۷
2-6
آشنایی با مفهوم Interface در متودولوژی OOP
بهزاد مرادی
۱۳۹۸/۰۳/۰۸
2-7
آشنایی با کلاس‌های Abstract در زبان PHP
بهزاد مرادی
۱۳۹۸/۰۳/۰۸
2-8
آشنایی با مفاهیم Setter و Getter در متودولوژی OOP
بهزاد مرادی
۱۳۹۸/۰۳/۰۸
2-9
آشنایی با کاربردهای کلیدواژهٔ final در ساخت کلاس و متد در زبان PHP
بهزاد مرادی
۱۳۹۸/۰۳/۰۹
2-10
آشنایی با مفهوم Method Chaining در متودولوژی OOP
بهزاد مرادی
۱۳۹۸/۰۳/۰۹
2-11
آشنایی با مفهوم Dependency Injection در زبان PHP
بهزاد مرادی
۱۳۹۸/۰۳/۰۹
2-12
آشنایی با مفهوم Type Hinting در زبان PHP
بهزاد مرادی
۱۳۹۸/۰۳/۰۹
2-13
آشنایی با کاربردهای پراپرتی‌ها و متدهای Static در زبان PHP
بهزاد مرادی
۱۳۹۸/۰۳/۱۰
2-14
آشنایی با مفهوم Trait در زبان PHP
بهزاد مرادی
۱۳۹۸/۰۳/۱۰
2-15
آشنایی با نحوهٔ استفاده از کانستنت‌ها در متودولوژی OOP در زبان PHP
بهزاد مرادی
۱۳۹۸/۰۳/۱۰
2-16
آشنایی با نحوهٔ Clone کردن یک آبجکت در زبان PHP
بهزاد مرادی
۱۳۹۸/۰۳/۱۱
2-17
آشنایی با مفهوم Return Type Declaration در زبان PHP
بهزاد مرادی
۱۳۹۸/۰۳/۱۱
2-18
آشنایی با PHP Standard Library
بهزاد مرادی
۱۳۹۸/۰۳/۱۱
2-19
آشنایی با نحوهٔ مدیریت اِکسپشن‌ها در زبان PHP
بهزاد مرادی
۱۳۹۸/۰۳/۱۲
2-20
آشنایی با استاندارد کامنت‌گذاری PHPDoc
بهزاد مرادی
۱۳۹۸/۰۳/۱۲
rocket
نظرات
اگر login نکردی برامون ایمیلت رو بنویس: