PSR یا استانداردهای برنامه‌نویسی PHP

PSR Contracts

آشنایی با قرارداد های PSR

قبل از معرفی PSR، کدنویسی با زبان PHP هیچ قانونی نداشت و هر کسی به هر شکلی که می خواست کدنویسی می کرد. PSR مخفف PHP Standards Recommendation یا «توصیه های استاندارد PHP» است. در سال ۲۰۰۹ جمعی از توسعه دهندگان پروژه های معروف PHP گرد هم آمده و گروهی به نام Framework Interoperability Group را ایجاد کردند. هدف این گروه پیدا کردن مباحث مشترک بین پروژه هایشان بود تا بتوانند بر اساس این مباحث مشترک با هم کار کنند. قراردادهای PSR به زودی به یکی از معروف ترین قراردادهای توسعه PHP تبدیل شدند و به همین دلیل در این مقاله می خواهیم در مورد بعضی از آن ها صحبت کنیم.

تعداد زیادی PSR وجود دارد و هر کدام از آن ها به یک مبحث خاص در PHP می پردازد اما کمیته FIG تمام آن ها را تایید نکرده است بلکه هر PSR باید ابتدا توسط اعضای کمیته به رای گذاشته شود و پس از رای آوردن تصویب خواهد شد. ما در این مقاله فقط به بعضی از PSR های تصویب شده می پردازیم که برای ما مهم هستند اما باید بدانید که آن ها به ترتیب نیستند (به طور مثال PSR-1 تصویب شده اما PSR-2 از رده خارج شده است) بنابراین اگر بین PSR های توضیح داده شده ترتیبی نمی بینید، تصور نکنید که اشتباهی رخ داده است. همچنین هر PSR مجموعه ای از قوانین است و به یک قانون محدود نیست.

PSR-1 استانداردهای اولیه کدنویسی

PSR-1 مربوط به استانداردهای اولیه کدنویسی است، استانداردهایی که بسیار ساده هستند و در عین حال باید رعایت شوند. توجه داشته باشید که تمام این استانداردها قراردادی هستند و لزومی به پیروی از آن ها ندارید اما شدیدا پیشنهاد می شود که به آن ها پایبند باشید.

در رابطه با فایل های PHP

تگ های PHP استفاده شده یا باید از نوع کامل باشند و یا از نوع خلاصه شده برای echo و نباید از انواع تگ های دیگر استفاده شود:

<?php ?>  ----> شکل کامل

<?= ?>  ----> شکل خلاصه

encoding فایل های PHP باید دقیقا از نوع UTF-8 without BOM باشد. همچنین برخی از فرآیند های خاص در این مقاله با عنوان side effect یا «عارضه» شناخته می شوند. عارضه یعنی اجرای منطقی که به طور مستقیم به «تعریف کلاس ها، توابع، ثابت ها و غیره» مربوط نباشد بلکه به include کردن یک فایل دیگر در این فایل باشد. بر اساس قرارداد PSR-1 یک فایل یا باید declaration انجام بدهد (کلاس، تابع، ثابت و غیره تعریف کند) و هیچ عارضه ای نداشته باشد، یا اینکه منطق عارضه را اجرا کند اما نمی تواند هر دو کار را انجام بدهد. چند مثال ساده از عارضه ها:

  • تولید خروجی به هر شکل
  • استفاده صریح و مستقیم از require یا include
  • اتصال به سرویس های خارجی (خارج از سرور)
  • تغییر تنظیمات ini
  • تولید یا نمایش خطاها (errors و exceptions)
  • ویرایش متغیرهای سراسری یا استاتیک
  • خواندن اطلاعات از یک فایل یا نوشتن اطلاعات در آن

کد زیر یک مثال از فایلی است که هم declaration انجام می دهد (تعریف کلاس، تابع، ثابت و غیره) و هم منطق عارضه ها را اجرا می کند (باید از این کد دوری کرد):

<?php

// side effect: change ini settings

ini_set('error_reporting', E_ALL);




// side effect: loads a file

include "file.php";




// side effect: generates output

echo "<html>\n";




// declaration

function foo()

{

    // function body

}

اما کد زیر مثالی از یک کد سالم و استاندارد است که declaration انجام می دهد اما عارضه ندارد:

<?php

// declaration

function foo()

{

    // function body

}




// اجرای شرطی به شکل زیر عارضه حساب نمی شود

if (! function_exists('bar')) {

    function bar()

    {

        // function body

    }

}

همچنین namespace ها و class ها باید از PSR-4 مربوط به autoloading پیروی کنند (در ادامه قوانین PSR-4 را توضیح خواهیم داد). به عبارتی هر کلاس باید در فایل مشخص خودش باشد و namespace حداقل تک سطحی داشته باشد.

در رابطه با کلاس های PHP

نام کلاس ها باید بر اساس StudlyCaps نوشته شود که در PSR با PascalCase هم معنی است، یعنی حرف اول هر کلمه با حروف بزرگ انگلیسی نوشته شود. مثال:

  • StudlyCaps یا همان PascalCase مانند MyClassIsThis
  • CamelCase مانند myClassIsThis

اگر از PHP 5.3 به بعد استفاده می کنید باید از namespace های رسمی استفاده کنید:

<?php

// PHP 5.3 and later:

namespace Vendor\Model;




class Foo

{

}

اما کدهایی که از PHP 5.2 به قبل استفاده می کنند باید نام کلاس ها را بر اساس namespace بنویسند:

<?php

// PHP 5.2.x and earlier:

class Vendor_Model_Foo

{

}

زمانی که از class صحبت می کنیم منظورمان class و interface و trait است. به یاد داشته باشید که ثابت های کلاس ها باید تماما با حروف بزرگ و آندرلاین نوشته شوند:

<?php

namespace Vendor\Model;




class Foo

{

    const VERSION = '1.0';

    const DATE_APPROVED = '2012-06-01';

}

همچنین در مورد نام خصوصیت های هر کلاس (class properties) می توانید از StudlyCaps یا camelCase یا under_score استفاده کنید. البته هر کدام را که انتخاب کردید باید به صورت ثابت در تمام قسمت های کد پیاده سازی کنید و نمی توانید در یک فایل از under_score و در فایلی دیگر از StudlyCaps استفاده کنید. نام متدها نیز باید بر اساس camelCase باشد که دقیقا مانند  StudlyCaps است با این تفاوت که حرف اول بزرگ نمی شود.

PSR-4 قوانین autoloader

حتما می دانید که برای وارد کردن یک فایل PHP در فایلی دیگر باید از دستوراتی مثل include و require استفاده کنیم. از طرفی افرادی که بر اساس معماری MVC کدنویسی می کنند معمولا ترجیح می دهند هر فایل را به یک کلاس جداگانه اختصاص بدهند که یعنی باید تک تک فایل ها را به صورت دستی و با دستورات require و include وارد کنند. این مسئله شاید در برنامه های بسیار کوچک مشکل ساز نباشد اما در برنامه های متوسط و بزرگ با ده ها یا صدها خط include و require روبرو هسیتم که هم حجم اسکریپت را افزایش می دهند و هم تصحیح آن ها در آینده را بسیار سخت می کنند.

Autoloading در PHP

خوشبختانه PHP یک تابع به نام spl_autoload_register  دارد که کار وارد کردن فایل ها را به صورت خودکار انجام می دهد:

spl_autoload_register('myAutoloader');




function myAutoloader($className)

{

    $path = '/path/to/class/';




    include $path.$className.'.php';

}

در واقع ما یک تابع را تعریف کرده ایم که نام کلاس مورد نظر را می گیرد، سپس مسیر آن را ساخته و آن را include می کند. ما حتی می توانیم این کد را درون یک حلقه قرار بدهیم و چندین فایل را بدین شکل بارگذاری کنیم.

پیشنهادهای PSR-4

ما در این بخش می خواهیم قراردادهایی در رابطه با autoloading را مورد بررسی قرار بدهیم که به آن PSR-4 می گویند. اولا در این قسمت منظور از class تمام موارد مشابه مانند class و trait و interface و غیره است. همچنین برای تعریف namespace ها قوانین دقیقی وجود دارد. ما به ساختار namespace زیر یک namespace کامل یا «نام کامل کلاس» می گوییم:

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

  • آدرس کامل کلاس باید حداقل یک سطح داشته باشد که به آن vendor namespace می گوییم. این مقدار در ساختار بالا همان NamespaceName است.
  • آدرس کامل کلاس می تواند سطوح بیشتری داشته باشد که در ساختار بالا به SubNamespaceNames اشاره دارد.
  • آدرس کامل کلاس باید حتما نام خود کلاس را در انتها ذکر کند که در ساختار بالا به صورت ClassName ذکر شده است.
  • باید با هر آدرس کامل کلاس به صورت case-sensitive (حساس به حروف بزرگ و کوچک) برخورد شود.
  • هر نوع autoloader ای که برای اسکریپت خود نوشته اید نباید هیچ خطایی (error) در هیچ سطحی به وجود بیاورد و همچنین نباید مقداری را برگرداند.

بهترین راه ایجاد autoloader بر اساس استانداردهای PSR-4 استفاده از composer است که package manager برای زبان PHP می باشد. برای این کار فایلcomposer.json را باز کرده و فیلد autoload را به آن اضافه کنید:

{

    "autoload": {

        "psr-4": {"App\\": "src/"}

    }

}

حالا دستور composer dump-autoload را اجرا می کنید. با این کار composer یک autoloader بر اساس استاندارد PSR-4 را namespace مورد نظر (در مثال بالا App) ایجاد می کند. این autoloader در مسیر vendor/autoload.php قرار خواهد گرفت. یادتان باشد که تمام کلاس هایتان باید در همین namespace (در مثال بالا App) ساخته شده باشد و هر فایل فقط یک کلاس داشته باشد. مثال:

<?php /* src/Controller/Home.php */




namespace App\Controller;




class Home { /* implementation */ }

تنها کاری که باقی مانده وارد کردن فایل autoloader در نقطه شروع برنامه است (مثلا فایل index.php یا هر فایلی که برنامه شما از آن شروع می شود):

<?php




require '../vendor/autoload.php';

با این کار می توانید کلاس ها را از هر جایی که دوست داشتید،‌ بدون require یا include کردن دستی، instantiate کنید (یک نمونه از کلاس بسازید):

use App\Controller\Home;




$homeController = new Home();

PSR-12 استانداردهای پیشرفته تر کدنویسی

این مجموعه از قراردادها مربوط به قراردادهای کدنویسی هستند که جزئی تر از PSR-1 می باشند و مسائل بیشتری را مشخص می کنند. این قرارد ها به منظور حذف سردرگمی بین توسعه دهندگان ایجاد شده اند و هدف اصلی آن ها هموار کردن همکاری بین گروه های مختلف توسعه است. اولین قانون PSR-12 این است که از قوانین PSR-1 تبعیت کنید.

قوانین عمومی

تمام فایل های PHP باید از Unix LF به عنوان line ending خود استفاده کنند. کاراکتر LF (مخفف Line Feed) کاراکتر هایی برای کنترل پایان خطوط در یک فایل می باشند. در واقع کامپیوتر ها برای اینکه بدانند یک خط کجا تمام شده و باید به خط بعدی بروند از این کاراکتر ها استفاده می کنند اما شما نمی توانید آن ها را ببینید. ما برای اعلام پایان خط دو نوع کاراکتر CR و LF داریم که به ترتیب کدهای 0x0D و 0x0A هستند. ویندوز برای اعلام انتهای خط از هر دو کاراکتر CR LF استفاده می کند، سیستم های Unix مانند لینوکس از LF و سیستم های مک (کمپانی اپل) از CR استفاده می کنند. احتمالا شما این مقادیر را به شکل r\ برای CR و n\ برای LF دیده باشید (ویندوز از r\n\ استفاده می کند). PSR-12 می گوید که فایل های PHP شما باید از LF استفاده کنند.

تگ پایانی PHP که به شکل <? می باشد، باید در فایل هایی که فقط کد PHP دارند حذف شود.  همچنین در انتهای خطوط نباید فضای خالی (whitespace) باشد. هر خط نباید از ۸۰ کاراکتر بیشتر باشد و اگر اینطور شد باید آن خط را به دو یا چند خط تقسیم کرد. همچنین هر خط فقط باید یک statement داشته باشد. برای indent کردن (تورفتگی خطوط) باید از چهار اسپیس استفاده شود و استفاده از tab ممنوع است.

تمام کلمات کلیدی و رزرو شده در PHP باید با حروف کوچک نوشته شوند. همچنین استفاده از کلمات مشخص کننده نوع داده باید با حروف کوچک باشد؛ به طور مثال به جای boolean از bool و به جای integer از int استفاده کنید.

قوانین declaration ها، namespace ها و import ها

زمانی که تگ های آغازین PHP را می نویسید باید آن ها را در خط خودشان بنویسید و هیچ اطلاعات دیگری در آن خط نباشد مگر آنکه کدهای PHP شما با کدهای دیگری مثل HTML ترکیب شده باشند. در واقع ابتدای فایل PHP یا header آن باید به ترتیب لیست زیر باشد. البته اگر اسکریپت شما به یکی از این موارد نیازی نداشت، آن را نادیده بگیرید اما ترتیب باید حفظ شود:

  • تگ آغازین PHP که به شکل php?>
  • docblock
  • یک یا چند دستور declare
  • تعریف namespace برای فایل (namespace declaration)
  • یک یا چند دستور use برای استفاده از کلاس ها
  • یک یا چند دستور use برای استفاده از توابع
  • یک یا چند دستور use برای استفاده از ثابت ها
  • بقیه کدهای فایل

دستورات import (وارد کردن منابع در فایل) نباید هیچ گاه با علامت backslash شروع شوند بلکه همیشه آدرس باید به صورت کامل ذکر شود. کد زیر یک مثال کامل از تمام این دستورات است:

<?php




/**

 * This file contains an example of coding styles.

 */




declare(strict_types=1);




namespace Vendor\Package;




use Vendor\Package\{ClassA as A, ClassB, ClassC as C};

use Vendor\Package\SomeNamespace\ClassD as D;

use Vendor\Package\AnotherNamespace\ClassE as E;




use function Vendor\Package\{functionA, functionB, functionC};

use function Another\Vendor\functionD;




use const Vendor\Package\{CONSTANT_A, CONSTANT_B, CONSTANT_C};

use const Another\Vendor\CONSTANT_D;




/**

 * FooBar is an example class.

 */

class FooBar

{

    // ... additional PHP code ...

}

namespace های ترکیبی (compound namespace) که بیشتر از دو سطح داشته باشند مجاز نیستند. به طور مثال کد زیر یک نمونه غلط و غیر مجاز از وارد کردن namespace ها است:

<?php




use Vendor\Package\SomeNamespace\{

    SubnamespaceOne\AnotherNamespace\ClassA,

    SubnamespaceOne\ClassB,

    ClassZ,

};

کد زیر نمونه یک کد صحیح و مجاز از وارد کردن namespace ها است:

<?php




use Vendor\Package\SomeNamespace\{

    SubnamespaceOne\ClassA,

    SubnamespaceOne\ClassB,

    SubnamespaceTwo\ClassY,

    ClassZ,

};

توجه داشته باشید که قانون مخصوص namespace های ترکیبی (compound namespace) است و ربطی به namespace های ساده ندارد.

در صورتی که کدهای PHP شما با کدهای دیگری مثل HTML ترکیب شده اند و می خواهید strict_type را در آن فعال کنید باید آن را در خط اول و در کنار تگ آغازین PHP قرار بدهید. همچنین حتما باید تگ PHP را ببندید. مثال:

<?php declare(strict_types=1) ?>

<html>

<body>

    <?php

        // ... additional PHP code ...

    ?>

</body>

</html>

دستورات declare نباید دارای فضای خالی (whitespace) باشند. همچنین اگر می خواهید از دستورات block declare استفاده کنید حتما باید فرمت آن به شکل زیر باشد (به مکان کروشه ها و فضای خالی دقت کنید):

declare(ticks=1) {

    // some code

}

قوانین کلاس ها، متدها و خصوصیات آن ها

مثل همیشه کلمه class به معنی class و trait و interface و موارد مشابه است. قانون اول این است که پس از کروشه پایانی (علامت })‌ نباید هیچ کامنت یا کدی (در همان خط) قرار بگیرد. برای ساخت یک نمونه/شیء از یک کلاس همیشه باید پرانتزها را ذکر کنید حتی اگر نیازی به هیچ آرگومانی نداشته باشیم:

new Foo();

دستورات extends و implements باید حتما در همان خطی باشند که نام کلاس در آن است و اجازه ندارند در خطوط بعدی نوشته شوند. کروشه آغازین برای باز کردن کلاس (علامت {)‌ باید حتما در خط خودش قرار بگیرد. همچنین قبل یا بعد از این کروشه ها نباید خطی خالی وجود داشته باشد. کروشه پایانی نیز باید بعد از بدنه کلاس و در خط جداگانه ای قرار بگیرد و قبل از آن خطی خالی وجود نداشته باشد. کد زیر یک نمونه ساده از این قوانین است:

<?php




namespace Vendor\Package;




use FooClass;

use BarClass as Bar;

use OtherVendor\OtherPackage\BazClass;




class ClassName extends ParentClass implements \ArrayAccess, \Countable

{

    // constants, properties, methods

}

لیست های دستورات implements و دستورات extends (در صورتی که برای interface ها باشد) می توانند به چند خط تقسیم شوند، البته به شرطی که indent (تورفتگی) شوند. در چنین حالتی اولین مورد باید از یک خط جدید شروع شود و  هر interface نیز فقط در یک خط قرار خواهد گرفت:

<?php




namespace Vendor\Package;




use FooClass;

use BarClass as Bar;

use OtherVendor\OtherPackage\BazClass;




class ClassName extends ParentClass implements

    \ArrayAccess,

    \Countable,

    \Serializable

{

    // constants, properties, methods

}

اگر از دستور use برای استفاده از trait ها درون یک کلاس استفاده می کنید، باید آن را در خطی جداگانه و دقیقا پس از کروشه آغازین بنویسید:

<?php




namespace Vendor\Package;




use Vendor\Package\FirstTrait;




class ClassName

{

    use FirstTrait;

}

در کد بالا توجه داشته باشید که حتی اگر یک کلاس فقط یک دستور use داشته باشد باید کروشه پایانی در خطی جداگانه قرار بگیرد. همچنین اگر چندین دستور use داشته باشیم، نمی توانیم آن ها را در یک خط بیاوریم بلکه باید هر کدام را در خط جداگانه ای قرار بدهید:

<?php




namespace Vendor\Package;




use Vendor\Package\FirstTrait;

use Vendor\Package\SecondTrait;

use Vendor\Package\ThirdTrait;




class ClassName

{

    use FirstTrait;

    use SecondTrait;

    use ThirdTrait;

}

در صورتی که کلاس شما علاوه بر use کدهای دیگری داشته باشد، باید یک خط بین این کدها و آخرین use فاصله بگذارید:

<?php




namespace Vendor\Package;




use Vendor\Package\FirstTrait;




class ClassName

{

    use FirstTrait;




    private $property;

}

همچنین هنگام استفاده از اپراتورهای insteadof و as باید دقیقا به شکل زیر عمل کنید (به فضای خالی، رفتن به خط جدید و تو رفتگی ها توجه کنید):

<?php




class Talker

{

    use A;

    use B {

        A::smallTalk insteadof B;

    }

    use C {

        B::bigTalk insteadof C;

        C::mediumTalk as FooBar;

    }

}

خصوصیات و ثابت ها

visibility باید برای تمام خصوصیات مشخص شود و نباید به مقادیر پیش فرض اتکا کرد. همچنین اگر از PHP 7.1 به بعد استفاده می کنید، یعنی قابلیت مشخص کردن visibility برای ثابت ها را نیز دارید بنابراین باید این کار را انجام بدهید. استفاده از کلیدواژه var برای ساخت خصوصیات مجاز نیست. در هر statement فقط باید یک خصوصیت را تعریف کنید و نباید همه را در یک دستور بسازید. در برخی از زبان ها مانند جاوا اسکریپت مبحث visibility تعریف نشده است بنابراین از قراردادهایی مانند شروع نام خصوصیات با آندرلاین (علامت _) برای مشخص کردن خصوصیات private استفاده می شود. توسعه دهندگان اجازه ندارند از این قراردادها در زبان PHP استفاده کنند و اگر نام خصوصیتی با آندرلاین شروع شود نباید معنی خاصی داشته باشد. همیشه باید بین تعیین تایپ (نوع داده) و خصوصیت یک اسپیس فاصله باشد. تعیین تایپ یک خصوصیت به شکل زیر انجام می شود:

<?php




namespace Vendor\Package;




class ClassName

{

    public $foo = null;

    public static int $bar = 0;

}

همانطور که می بینید int به ما می گوید که این خصوصیت یک عدد می باشد.

متدها و توابع

Visibility باید برای تمام متدها تعریف شود. قراردادهایی مانند شروع نام متد با آندرلاین برای مشخص کردن private بودنشان پذیرفته نیست (درست مانند خصوصیات) و توسعه دهندگان نباید از چنین قراردادهایی استفاده کنند. البته نام متدهای شما می تواند با _ شروع اما نباید به قصد مشخص کردن private یا protected باشد.

نام متدها و توابع نباید بعد از خودشان اسپیس داشته باشند، بلکه کروشه آغازین باید به خط بعدی برود و کروشه پایانی نیز باید پس از بدنه تابع/متد و در خط خودش قرار بگیرد. همچنین در نظر داشته باشید که نباید هیچ اسپیسی بعد از پرانتز آغازین و قبل از پرانتز پایانی وجود داشته باشد. با این حساب اگر بخواهیم یک مثال ساده از تعریف متدها بزنیم، کد زیر را نشان خواهیم داد (به محل پرانتزها، کاما ها (ویرگول انگلیسی)، اسپیس ها و کروشه ها دقت کنید):

<?php




namespace Vendor\Package;




class ClassName

{

    public function fooBarBaz($arg1, &$arg2, $arg3 = [])

    {

        // method body

    }

}

این قوانین برای توابع نیز صادق هستند:

<?php




function fooBarBaz($arg1, &$arg2, $arg3 = [])

{

    // function body

}

آرگومان های متدها و توابع

در لیست آرگومان ها نباید قبل از ویرگول ها اسپیس داشته باشیم بلکه یک اسپیس بعد از هر ویرگول قرار می گیرد. همچنین آرگومان هایی که دارای مقدار پیش فرض هستند همیشه باید در انتهای لیست قرار بگیرند:

<?php




namespace Vendor\Package;




class ClassName

{

    public function foo(int $arg1, &$arg2, $arg3 = [])

    {

        // method body

    }

}

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

<?php




namespace Vendor\Package;




class ClassName

{

    public function aVeryLongMethodName(

        ClassTypeHint $arg1,

        &$arg2,

        array $arg3 = []

    ) {

        // method body

    }

}

اگر متد/تابع شما دارای return type باشد (مشخص کرده باشید که چه نوع داده ای را برمی گرداند) باید بعد از علامت دو نقطه یک اسپیس قرار بدهید و سپس return type را مشخص کنید. یادتان باشد که return type و علامت دو نقطه باید در همان خطی باشند که پرانتز پایانی لیست آرگومان ها در آن قرار دارد به طوری که بین علامت دونقطه و پرانتز پایانی هیچ اسپیسی نباشد:

<?php




declare(strict_types=1);




namespace Vendor\Package;




class ReturnTypeVariations

{

    public function functionName(int $arg1, $arg2): string

    {

        return 'foo';

    }




    public function anotherFunction(

        string $foo,

        string $bar,

        int $baz

    ): string {

        return 'foo';

    }

}

در صورتی که می خواهید nullable type declaration انجام بدهید (اجازه بدهید یک آرگومان null یا دلخواه باشد) نباید بین علامت سوال و نام آرگومان هیچ فاصله ای وجود داشته باشد:

<?php




declare(strict_types=1);




namespace Vendor\Package;




class ReturnTypeVariations

{

    public function functionName(?string $arg1, ?int &$arg2): ?string

    {

        return 'foo';

    }

}

همچنین زمانی که از اپراتور reference (علامت &) برای یک آرگومان استفاده می کنید، نباید بعد از آن هیچ فاصله ای باشد. این مسئله در کد بالا به خوبی نمایان است. آیا با variadic three dot operator یا به زبان ساده تر همان اپراتور splat در PHP آشنا هستید؟ این اپراتور اجازه می دهد که تعداد نامشخصی داده را به یک آرگومان ارسال کنید. به طور مثال:

unction concatenate($transform, ...$strings) {

    $string = '';

    foreach($strings as $piece) {

        $string .= $piece;

    }

    return($transform($string));

}




echo concatenate("strtoupper", "I'd ", "like ", 4 + 2, " apples");

// This would print:

// I'D LIKE 6 APPLES

به عبارتی اولین آرگومان پاس داده شده به تابع concatenate رشته strtoupper است اما از آرگومان اول به بعد (دوم و سوم و چهارم الی بی نهایت - مقادیر I'd و like و 4+2 و apples) همه در رشته string$ قرار می گیرند یا به عبارتی جزئی از آرگومان دوم محسوب خواهند شد. زمانی که از این اپراتور استفاده می کنید نباید بین آن و نام آرگومان هیچ فاصله ای قرار بدهید:

public function process(string $algorithm, ...$parts)

{

    // processing

}

همچنین زمانی که می خواهید از اپراتور reference و splat با هم استفاده کنید، نباید هیچ فاصله ای بین آن دو باشد:

public function process(string $algorithm, &...$parts)

{

    // processing

}

کلیدواژه های abstract و final و static

اگر قصد استفاده از abstract یا final را دارید باید آن ها را قبل از دستورات visibility ذکر کنید اما static همیشه بعد از visibility می آید. کد زیر این مسئله را به خوبی نشان می دهد:

<?php




namespace Vendor\Package;




abstract class ClassName

{

    protected static $foo;




    abstract protected function zim();




    final public static function bar()

    {

        // method body

    }

}

فراخوانی توابع و متدها

زمانی که متد یا تابعی را فراخوانی می کنید نباید بین نام تابع/متد و پرانتز آغازین فاصله وجود داشته باشد. همچنین نباید هیچ فاصله ای پس از پرانتز آغازین و قبل از پرانتز پایانی وجود داشته باشد. در لیست آرگومان ها نیز نباید قبل از ویرگول ها فاصله ای باشد بلکه این فاصله پس از ویرگول ها می آید. مثال:

<?php




bar();

$foo->bar($arg1);

Foo::bar($arg2, $arg3);

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

<?php




$foo->bar(

    $longArgument,

    $longerArgument,

    $muchLongerArgument

);

در صورتی که می خواهید فقط یک آرگومان را بشکنید (مثلا آرگومان شما یک anonymous function یا آرایه است) مجاز به انجام این کار هستید اما این شکستن به معنی شکستن خود لیست آرگومان ها نیست بلکه می توانید فقط همان تک آرگومان را بشکنید:

<?php




somefunction($foo, $bar, [

  // ...

], $baz);




$app->get('/hello/{name}', function ($name) use ($app) {

    return 'Hello ' . $app->escape($name);

});

ساختارهای کنترل کننده

دستورات کلی برای استفاده از ساختارهای کنترل کننده (هر ساختاری که اجرای کد را کنترل می کند - مثلا شرط if) به شرح زیر هستند:

  • باید پس از کلیدواژه ساختار کنترلی حتما یک اسپیس وجود داشته باشد.
  • نباید پس از پرانتز آغازین هیچ فاصله ای وجود داشته باشد.
  • نباید قبل از پرانتز پایانی هیچ فاصله ای وجود داشته باشد.
  • باید بین پرانتز پایانی و کروشه آغازین یک اسپیس وجود داشته باشد.
  • بدنه ساختار کنترلی باید یک واحد indent شود (تو رفتگی داشته باشد).
  • بدنه ساختار کنترلی حتما باید بعد از کروشه آغازین و در خطی جدید قرار بگیرد.
  • کروشه پایانی حتما باید پس از بدنه و در خطی جداگانه قرار بگیرد.

توسعه دهندگان اجازه ندارند که از ساختارهای کنترلی بدون کروشه استفاده کنند. استفاده از کروشه ها باعث می شود که با اضافه شدن خط های جدید، ساختار خوانا و قابل خواندن باشد.

ساختارهای کنترل کننده (شرطی)

ساختار یک شرط if به شکل زیر است. در این ساختار به محل پرانتزها و دستورات else و elseif توجه داشته باشید:

<?php




if ($expr1) {

    // if body

} elseif ($expr2) {

    // elseif body

} else {

    // else body;

}

توسعه دهندگان باید از elseif به جای else if استفاده کنند. با انجام این کار تمام ساختارهای کنترلی تک واژه ای می شوند و یکپارچگی کدها استفاده می شوند. همچنین دستورات درون پرانتزهای ساختارهای کنترلی می توانند شکسته شوند. برای شکستن این دستورات باید از قوانین ذکر شده در قسمت قبلی (قرار گرفتن هر دستور در یک خط و indent شدن یک واحدی) پیروی کنید. در چنین حالتی اپراتورهای boolean همیشه باید یا ابتدای خط و یا در انتهای خط باشند و هر کدام از این دو مورد را که انتخاب کردید باید در کل کدها رعایت کنید:

<?php




if (

    $expr1

    && $expr2

) {

    // if body

} elseif (

    $expr3

    && $expr4

) {

    // elseif body

}

ساختار switch

یک دستور switch دقیقا به همان شکلی است که در کد زیر مشاهده می کنید. دستور case همیشه باید یک واحد نسبت به switch تورفتگی داشته باشد اما دستور break (یا هر دستور دیگری که switch را متوقف می کند) باید نسبت به case فرورفتگی نداشته باشد. در صورتی که یک case داشته باشیم که خالی نباشد اما رد شدن از آن به صورت عمدی نوشته شده باشد (دستوری نداشته باشد که ما را از switch خارج کند)، باید یک کامنت به شکل no break برایش بنویسید تا توسعه دهندگان دیگر مطمئن شوند که شما دچار اشتباه نشده اید. شما می توانید تمام این موارد را در کد نمونه زیر مشاهده کنید:

<?php




switch ($expr) {

    case 0:

        echo 'First case, with a break';

        break;

    case 1:

        echo 'Second case, which falls through';

        // no break

    case 2:

    case 3:

    case 4:

        echo 'Third case, return instead of break';

        return;

    default:

        echo 'Default case';

        break;

}

آرگومان های دستور switch قابلیت شکسته شدن دارند. اگر بخواهید چنین کاری را انجام بدهید باید از تمام قوانین ذکر شده برای این کار در ساختارهای شرطی نیز تبعیت کنید؛ یعنی هر آرگومان/دستور در یک خط جداگانه قرار گرفته و یک واحد indent می شود. پرانتز پایانی و کروشه آغازین باید در یک خط باشند و بینشان یک اسپیس باشد. همینطور اپراتورهای boolean نیز باید همیشه یا در ابتدای خط و یا در انتهای آن باشند، شما باید یکی از این دو حالت را انتخاب کرده و تمام کدهای خود را دقیقا به همین شکل بنویسید:

<?php




switch (

    $expr1

    && $expr2

) {

    // structure body

}

ساختارهای while و do while

یک دستور while دقیقا به شکل زیر نوشته می شود. به محل قرار گرفتن پرانتزها و اسپیس ها دقت کنید:

<?php




while ($expr) {

    // structure body

}

در مورد شکستن آرگومان ها نیز دقیقا همان قوانینی را پیروی می کنید که در قسمت switch توضیح دادم و برای تکراری نشدن موضوع دوباره آن ها را ذکر نمی کنم. کد زیر یک نمونه کد از نوشتار صحیح while می باشد:

<?php




do {

    // structure body;

} while ($expr);

همچنین یک ساختار ساده do while به شکل زیر است:

<?php




do {

    // structure body;

} while ($expr);

و اگر بخواهید آرگومان ها را بشکنید باید از تمام قوانینی که ذکر شد تبعیت نمایید:

<?php




do {

    // structure body;

} while (

    $expr1

    && $expr2

);

ساختارهای for و foreach

از آنجایی که قوانین این ساختارها بسیار شبیه به هم می باشد من از تکرار مکررات پرهیز کرده و فقط نمونه کدهای صحیح را برایتان قرار می دهم تا بر اساس قوانین ذکر شده در قسمت های قبل، آن ها را بررسی کنید:

<?php




for ($i = 0; $i < 10; $i++) {

    // for body

}

برای شکستن آرگومان های for نیز بدین شکل عمل خواهیم کرد:

<?php




for (

    $i = 0;

    $i < 10;

    $i++

) {

    // for body

}

همچنین استفاده از ساختار foreach به شکل زیر است:

<?php




foreach ($iterable as $key => $value) {

    // foreach body

}

در این ساختار شکستن آرگومان مجاز نیست.

ساختار try-catch-finally

ساختار try-catch-finally آخرین ساختار در این دسته است و شکل صحیح آن به صورت زیر می باشد:

<?php




try {

    // try body

} catch (FirstThrowableType $e) {

    // catch body

} catch (OtherThrowableType | AnotherThrowableType $e) {

    // catch body

} finally {

    // finally body

}

اپراتورها

قوانین استایلینگ در اپراتورها بر اساس مفهوم arity آن ها است؛‌ یعنی چه تعداد operand یا «عَمَلوَند» دریافت می کنند. عملوند همان عنصری است که اپراتور یا «عملگر» روی آن اجرا می شود. در این بخش زمانی که می گوییم استفاده از whitespace (اسپیس یا همان فضای خالی) در کنار یک اپراتور مجاز است، می توانید از چند اسپیس استفاده کنید تا خوانایی کد بهتر شود.

اپراتورهای unary

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

$i++;

++$j;

همچنین اپراتورهای Type casting نباید درون پرانتزهای خودشان هیچ اسپیسی داشته باشند:

$intValue = (int) $input;

اپراتورهای باینری

تمام اپراتورهای حسابی، مقایسه ای، انتسابی، bitwise، منطقی، رشته ای و type باید قبل و بعد از خود حداقل یک اسپیس داشته باشند. به مثال زیر توجه کنید:

if ($a === $b) {

    $foo = $bar ?? $a ?? $b;

} elseif ($a > $b) {

    $foo = $a + $b * $c;

}

اپراتورهای ternary

اپراتور شرطی ternary operator باید قبل و بعد از کاراکتر های ؟ و : حداقل یک اسپیس داشته باشد:

$variable = $foo ? 'foo' : 'bar';

زمانی که عملوند میانی این اپراتور حذف می شود باید از همان قوانین ذکر شده برای اپراتورهای مقایسه ای پیروی کنید:

$variable = $foo ?: 'bar';

Closure ها

همانطور که می دانید closure ها در زبان های برنامه نویسی یک هدف اصلی دارند: شلوغ نکردن global namespace یا global scope. در واقع closure ها راهی برای نوشتن یک تابع به صورت محلی هستند که فقط هدف خاص و کوچکی را دارد و نیازی به وجود آن در scope سراسری نیست. یک مثال ساده از آن ها را در کد زیر می بینید:

function replace_spaces ($text) {

    $replacement = function ($matches) {

        return str_replace ($matches[1], ' ', '&nbsp;').' ';

    };

    return preg_replace_callback ('/( +) /', $replacement, $text);

}

در PSR-12 نیز قوانینی مربوط به closure ها داریم که باید رعایت شوند. هر closure باید پس از کلیدواژه function یک اسپیس داشته باشد. همچنین باید قبل و بعد از کلیدواژه use نیز یک اسپیس داشته باشد. کروشه آغازین نباید به خط بعدی برود و در خط فعلی باقی می ماند اما کروشه پایانی باید از بدنه تابع و در خط جداگانه خودش قرار داشته باشد. نباید بعد از پرانتز آغازین آرگومان ها یا متغیرها، اسپیسی وجود داشته باشد. این مسئله برای پرانتز پایانی نیز صادق است. در لیست آرگومان ها نباید قبل از ویرگول ها اسپیس داشته باشیم بلکه باید پس از هر ویرگول یک اسپیس قرار بدهید. آرگومان های closure با مقادیر پیش فرض باید به انتهای لیست آرگومان ها منتقل شوند. اگر return type را مشخص کرده اید باید به قوانین آن پایبند باشید: در صورت وجود کلیدواژه use، علامت دو نقطه باید پس از بسته شدن پرانتز use و بدون هیچ فاصله ای بیاید. در صورتی که تمام قوانین ذکر شده را در کنار هم بگذارید چنین نتیجه ای می گیریم:

<?php




$closureWithArgs = function ($arg1, $arg2) {

    // body

};




$closureWithArgsAndVars = function ($arg1, $arg2) use ($var1, $var2) {

    // body

};




$closureWithArgsVarsAndReturn = function ($arg1, $arg2) use ($var1, $var2): bool {

    // body

};

همچنین شکستن لیست متغیرها/ آرگومان ها تابع قوانینی است که قبلا توضیح داده بودیم:

<?php




$longArgs_noVars = function (

    $longArgument,

    $longerArgument,

    $muchLongerArgument

) {

   // body

};




$noArgs_longVars = function () use (

    $longVar1,

    $longerVar2,

    $muchLongerVar3

) {

   // body

};




$longArgs_longVars = function (

    $longArgument,

    $longerArgument,

    $muchLongerArgument

) use (

    $longVar1,

    $longerVar2,

    $muchLongerVar3

) {

   // body

};




$longArgs_shortVars = function (

    $longArgument,

    $longerArgument,

    $muchLongerArgument

) use ($var1) {

   // body

};




$shortArgs_longVars = function ($arg) use (

    $longVar1,

    $longerVar2,

    $muchLongerVar3

) {

   // body

};

توجه داشته باشید که قوانین ذکر شده در تمام حالات برقرار هستند، حتی در زمانی که closure مستقیما در یک متد یا در فراخوانی تابع استفاده شود:

<?php




$foo->bar(

    $arg1,

    function ($arg2) use ($var1) {

        // body

    },

    $arg3

);

کلاس های ناشناس (anonymous class)

کلاس های ناشناس از قوانین ذکر شده در قسمت closure ها پیروی می کنند:

<?php




$instance = new class {};

کروشه آغازین می تواند روی همان خطی باشد که کلیدواژه class در آن قرار داده البته به شرطی که لیست interface آنقدر طولانی نباشد که از حد مجاز طول خط رد شده و به خط بعدی برویم. اگر به خط بعدی برویم باید کروشه آغازین را در خط جدیدی قرار دهید:

<?php




// Brace on the same line

$instance = new class extends \Foo implements \HandleableInterface {

    // Class content

};




// Brace on the next line

$instance = new class extends \Foo implements

    \ArrayAccess,

    \Countable,

    \Serializable

{

    // Class content

};

PSR های متعدد دیگری وجود دارند اما به غیر از PSR-12 و PSR-1 که در این مقاله بررسی کردیم، دیگر PSR ها به شدت تئوری و آکادمیک هستند و به درد برنامه نویسان عادی نمی خورند بنابراین از بررسی آن ها صرف نظر می کنیم.


منبع: وب سایت php-fig

نویسنده شوید
دیدگاه‌های شما (1 دیدگاه)

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

حجت
27 آبان 1402
خیلی عالی بود ممنون

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.