تالار گفتمان nCIS.ir

نسخه‌ی کامل: آموزش قدم به قدم Fat Free Framework
شما در حال مشاهده نسخه آرشیو هستید. برای مشاهده نسخه کامل کلیک کنید.
توی این تاپیک میخوام قدم به قدم کار با فریمورک قدرتمند و سبک F3 رو آموزش بدم. این فریمورک با حجم کلی کمتر از 3 مگابایت، کلی امکانات در اختیارتون میگذاره و سرعتش هم فوق العاده بالاست. اگه سؤالی داشتین، تاپیک جداگانه بزنید و بپرسین. این تاپیک برای جلوگیری از بی نظمی، بسته است و فقط مطالب آموزشی داخلش قرار میگیره. این آموزش به نوعی نسخه فارسی مستندات خود فریمورک هست ولی سعی میکنم هرجا ابهامی دیدم، تجربه شخصی خودم رو هم بیان کنم. ازطرفی مطالب رو هم با بیان خودم میگم و از توضیحات اضافه خودداری میکنم و جاهایی هم که خوب توضیح داده نشده توی مستندات، توضیحات تکمیلی رو اضافه میکنم.
Hello World در کمتر از یک دقیقه

اول از همه این نکته رو بگم که F3 با PHP نسخه 5.3 به بالا کار میکنه. پس همون اول به مسیر قرار گرفتن فایل PHP رفته و این دستور رو اجرا کنید:
php -v

خوب من این جواب رو گرفتم:
PHP 5.5.9 (cli) (built: Feb  5 2014 13:02:39)
Copyright © 1997-2014 The PHP Group
Zend Engine v2.5.0, Copyright © 1998-2014 Zend Technologies

که داره میگه نسخه PHP من 5.5.9 هست و درنتیجه مشکلی برای اجرای F3 ندارم.

خوب حالا فریمورک رو از لینک گیت هاب دانلود کنید: https://github.com/bcosca/fatfree/archive/master.zip

وقتی فایل رو از حالت فشرده خارج کنید، چند فایل و پوشه مختلف دریافت میکنید که حاوی مثال و یکسری مستندات اولیه است. مهمترین پوشه اینجا پوشه lib هست که من تغییر نام دادمش به f3 و فشرده کردم و براتون ضمیمه همین پست کردم که لازم نباشه فایلهای اضافه رو دانلود کنید.

حالا یک پوشه توی روت لوکال هاستتون بسازین به اسم f3project و پوشه f3 رو داخلش کپی کنید. یه فایل index.php هم بسازین و این کد رو داخلش بنویسید:
<?php
$f3 = require 'f3/base.php';
$f3->route('GET /', function() {
   echo 'Hello world!';
});
$f3->run();

حالا اگه مسیر /localhost/f3project رو باز کنید، پیغام !Hello World رو مشاهده میکنید. اما واقعاً چه اتفاقی افتاد؟

ابتدا ما با ضمیمه کردن کلاس پایه F3 یک شئ از کلاس اصلی فریمورک ساختیم و بعد، بهش گفتیم اگه مسیر / توی پروژه جاری درخواست شد (یعنی همون مسیر اصلی یا روت پروژه که در اینجا میشه /localhost/f3project)، تابعی که گفتیم رو اجرا کن و این تابع هم که کارش مشخصه: چاپ کردن پیغام معروف «سلام دنیا!»

ما توی متد route میتونیم مسیرهای مختلفی رو با پروتکلهای مختلف مثل GET و POST و... مشخص کنیم و بگیم هرکدوم چه کاری انجام میدن. در نهایت، با صدا زدن متد run از شئ F3 فریمورک رو در وضعیت شنود برای درخواستهای ورودی کاربر قرار میدیم و کاربر هم وقتی آدرس رو وارد میکنه، این متد آدرس رو میگیره و طبق route هایی که بهش دادیم، کارهای مشخص شده رو انجام میده. درمورد route یا مسیریابی توی جلسه بعد بیشتر توضیح میدم.
موتور مسیریابی

خوب مثال جلسه اولمون خیلی هم هضمش سخت نبود درسته؟ اگه دوست دارین یکم چاشنی بیشتر به سوپ بدون چربی (Fat Free) خودتون اضافه کنین، یک route دیگه قبل از دستور run اضافه کنید:
$f3->route('GET /about', function() {
   echo 'Donations go to a local charity... us!';
});


حالا مسیر localhost/f3project/about رو اجرا کنید: خطای 404

چی شد؟ خوب پوشه about که نداریم. فایلی هم به این اسم نداریم. پس خطای 404 درسته ولی چیکار کنیم که همه درخواستها رو index.php جواب بده؟ یه فایل htaccess. توی مسیر پروژه خودتون با این محتوا بسازین:
Options +FollowSymlinks
RewriteEngine On
RewriteBase /f3project
RewriteCond %{REQUEST_FILENAME} !-l
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule . index.php [L,NC,QSA]

این کد داره چیکار میکنه؟ خط اول داره میگه اگه تو مسیر به Symlink رسیدی (مفهومش شبیه همون Shortcutهای ویندوز هست)، اونها رو هم دنبال کن و برو تو پوشه مقصد اون میانبر. خط دوم میگه موتور بازنویسی آدرس رو روشن کن. خط سوم میگه هرجا ما اسم فایلی آوردیم منظورمون توی مسیر f3project/ داخل روت وب سرور هست. مثلاً اگه گفتیم index.php یعنی f3project/index.php/ و الی آخر. خط چهارم داره میگه اگه اون آدرسی که توی مرورگر درخواست شده، آدرس فیزیکی یه لینک نبود Rule بعدی رو پردازش کن (دقت کنید که بعد از علامت تعجب و خط تیره، حرف L کوچک انگلیسی هست نه عدد 1). خط پنجم یه شرط دیگه هم اضافه میکنه: آدرس درخواستی، یه پوشه فیزیکی هم نباید باشه. خط ششم هم میگه آدرس درخواستی، مسیر فیزیکی یه فایل هم نباید باشه. در نهایت خط هفتم داره میگه هر درخواستی اومده رو بفرست برای index.php و L یا Last یعنی این Rule یا قانون آخرین قانونی هست که اجرا میشه و اگه بعدش قانونی نوشتیم دیگه نادیده بگیره. NC یا Not Case-sensitive یعنی به بزرگی و کوچکی حروف حساس نباشه و QSA یا Query String Also یعنی اگه ته آدرس، Query String داشتیم (مثلاً آدرس اینطوری بود: localhost/f3project/view?id=5) اون id=5 رو هم برای index.php ارسال کن.

حالا چرا شرطهای خطوط چهارم تا ششم رو مینویسیم و مستقیماً نمیگیم هرچی بود رو واسه index.php بفرست؟ چون ممکنه توی آدرس، مسیر یک پوشه رو درخواست کرده باشیم (مثلاً f3project/admin) یا یک فایل فیزیکی واقعی مثل f3project/css/style.css یا عکسها و...) و نمیخوایم این درخواستها برای index.php ارسال بشن و خود اون فایلها واقعاً باید در جواب برای مرورگر کاربری که درخواستشون کرده ارسال بشن. بطور خلاصه الان ما گفتیم اگه آدرسی که درخواست شده بطور فیزیکی بعنوان یک لینک، پوشه یا فایل وجود نداشت، درخواست رو بفرست برای index.php تا بهش جواب بده.

وای چقدر حرف زدم! حالا صفحه رو Refresh کنید. اگه ماژول mod_rewrite آپاچی شما فعال باشه، باید پیغام زیر رو توی مسیر localhost/f3project/about مشاهده کنید:
Donations go to a local charity... us!


خوب حالا یه مرحله بریم جلوتر. این کدها رو قبل از صدازدن متد run بنویسید:
class WebPage
{
   public function display()
   {
       echo 'I cannot object to an object';
   }
}
$f3->route('GET /display', 'WebPage->display');

و آدرس localhost/f3project/display رو اجرا کنید و از نتیجه لذت ببرین.

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

یک پوشه درست کنید به اسم Controller و داخلش یک فایل به اسم Auth.php و توش این کد رو بنویسید:
<?php
namespace Controller;

class Auth
{
   public static function login()
   {
       echo 'Login here!';
   }
}

این کد رو هم توی index.php بنویسید:
$f3->route('GET /login', 'Controller\Auth->login');

و الان مسیر localhost/f3project/login رو اجرا کنید تا از تعجب خشکتون بزنه!

نقش Token ها توی مسیریاب

F3 از یک سیستم قدرتمند Domain-Specific Language یا به اختصار DSL استفاده میکنه تا به شما اجازه بده یک مسیر ساده، چندین کار انجام بده. این کد رو قبل از متد run بنویسید:
$f3->route('GET /birds/@count', function($f3) {
   $count = $f3->get('PARAMS.count');
   echo $count . ' bird' . ($count == 1 ? ' is' : 's are') . ' on the wall.';
});

حالا این مسیرها رو اجرا کنید: localhost/f3project/birds/1 و localhost/f3project/birds/5
احتمالاً متوجه شدین که چطور کار میکنه ولی برای اینکه ابهامی باقی نمونه، توضیح میدم: ما توی آدرس route خودمون یک توکن به اسم count@ تعریف کردیم که میتونه بعنوان بخشی از آدرس عمل کنه. درنتیجه هر درخواستی که با /birds/ شروع بشه، با این route مطابقت پیدا میکنه و در ادامه هرچی نوشته بشه رو میتونیم توی کد با روش PARAMS.count یعنی کلمه PARAMS و بعد یک نقطه و بعد اسم توکن بدون @ بخونیم. البته با کمک متد get از شئ f3 که اگه دقت کرده باشین، برای اینکه بتونیم توی تابعی که تعریف کردیم، از شئ f3 که بیرون از اون تعریف شده استفاده کنیم، شئ f3 رو بعنوان پارامتر براش ارسال کردیم.

وقتی آدرسها اینطوری داینامیک میشن، F3 توکنهای داخل آدرس رو بطور خودکار توی یک آرایه سراسری به اسم PARAMS جمع میکنه و ما بعداً با متد get شئ f3 میتونیم اندیسهای اون رو با الگوی PARAMS.tokenName بخونیم (بجای tokenName اسم توکن رو میگذاریم). البته شما میتونید از همون روش کلاسیک ['PARAMS['count هم استفاده کنید ولی روش PARAMS.count توی f3 قطعاً کاربردی تر و خلاصه تره. البته روش کلاسیک بدلیل تداخل کوتیشنها و... هم زیاد توصیه نمیشه. بعلاوه فریمورک F3 به شما اجازه میده توی ویوها و تمپلیتها (که در آینده توضیح میدیم) از الگوی PARAMS.count@ برای دسترسی به توکنهای آدرس، استفاده کنید. دقت کنید که شما میتونید از * هم بعنوان یک توکن ویژه استفاده کنید، وقتی که بقیه آدرس براتون مهم نیست. مثلاً اگه بنویسید:
$f3->route('GET /fixed*', function() {
   echo 'Enough! We always end up here.';
});

حالا تمام آدرسهای localhost/f3project/fixed و /localhost/f3project/fixed و localhost/f3project/fixed/5 و... به شما نتیجه یکسانی میدن چون گفتین دیگه بقیه آدرس براتون مهم نیست.

مسیرهای اسم گذاری شده

وقتی یه مسیر تعریف میکنید، میتونید به راحتی براش اسم تعریف کنید. از این اسم میتونید بعداً توی کدتون برای دسترسی به مسیر موردنظر استفاده کنید. برای این کار، بعد از اسم متد دستیابی (GET یا POST و...) باید یک @ بگذارین و بعد اسم دلخواه رو بنویسید و بعد : بگذارین و بعد از اون، مسیر رو مشخص کنید و بقیه ماجرا. این مثال رو ببینید:
$f3->route('GET @test: /test', function() {
   echo 'We are in test route.';
});
$f3->route('GET /redirect', function($f3) {
   $f3->reroute('@test');
});

حالا توی مرورگر به آدرس localhost/f3project/redirect مراجعه کنید. میبینید که به لطف متد reroute تونستیم کاربر رو به یک مسیر دیگه که همون localhost/f3project/test هست، با کمک اسمی که به این مسیر دادیم، هدایت کنیم. این قابلیت به شما کمک میکنه تا اگه بعداً به هر دلیلی خواستین آدرسی رو عوض کنید، فقط همونجایی که بهش اسم دادین رو اصلاح کنید و هرجایی بهش اشاره شده، خودبخود اصلاح بشه.

حالا اگه از توکن هم توی آدرس استفاده کرده باشین، میتونید موقع ریدایرکت کردن با کمک reroute مقدار اونها رو هم مشخص کنید. این کد رو برای درک بهتر ببینید:
$f3->route('GET @country: /country/@state/@city', function($f3) {
   echo $f3->get('PARAMS.city') . ' belongs to ' . $f3->get('PARAMS.state');
});
$f3->route('GET /city', function($f3) {
   $f3->reroute('@country(@state=Fars,@city=Shiraz)');
});

حالا این مسیر رو اجرا کنید: localhost/f3project/city و میبینید که به مسیر localhost/f3project/country/Fars/Shiraz هدایت میشین و پیغام زیر براتون به نمایش در میاد:
Shiraz belongs to Fars

مسیرهای نامگذاری شده توی قالبها

برای دسترسی به مسیرهای نامگذاری شده توی قالبها (Templates) و با کمک موتور قالب داخلی F3 (که توی جلسات آینده توضیحش میدم)، باید اونها رو با فیلتر alias مشخص کنید. مثال:
<a href="{{ 'country' | alias }}">Go to country</a>

یا اگه میخواین لینکی به یک مسیر که توکن هم داره تولید کنید، به این شکل عمل کنید:
<a href="{{ 'country', 'state=Fars,city=Shriaz' | alias }}">Go to Shiraz, Fars</a>

توی جلسات آینده بیشتر درمورد موتور قالب F3 صحبت میکنیم.

اصلاح مسیر

شما میتونین یک صفحه که قبلاً وجود داشته رو منسوخ کنید و بازدیدکننده ها رو به مسیر جدید بفرستین. برای این کار، تنها کاری که باید انجام بدین، نوشتن این دستوره:
$f3->redirect('GET|HEAD /obsoletepage', '/newpage');

که معادل این کد هست:
$f3->route('GET|HEAD /obsoletepage', function($f3) {
   $f3->reroute('/newpage');
});

حالا اگه یکی سعی کنه آدرس localhost/f3project/obsoletepage رو با هرکدوم از درخواستهای HTTP GET یا HTTP HEAD باز کنه، فریمورک اون رو به صفحه localhost/f3project/newpage منتقل میکنه. شما حتی میتونید کاربر رو به یه سایت دیگه بفرستین. مثال:
$f3->reroute('http://www.ncis.ir');

اصلاح مسیر میتونه بویژه زمانی سودمند باشه که میخواین روی بخشی از سایتتون کار کنید. میتونید با این شکل، یک مسیر براش تعریف کنید و کاربران سایت رو به یک صفحه با پیغام Under Construction هدایتشون کنید تا متوجه بشن اون بخش از سایت موقتاً از دسترس خارج شده. هروقت هم مشکل برطرف شد، route رو بردارین تا دوباره اون بخش فعال بشه. فقط حواستون باشه که route موقت رو باید قبل از route اصلی بنویسید.

هدایت کردن (Redirect) میتونه در عین حال که سودمنده، هزینه هم داشته باشه. تا جایی که ممکنه سعی کنید از reroute برای فرستادن کاربر به یک صفحه دیگه توی همون سایت، استفاده نکنید. منظورم اینه که اگه میتونید کاری که توی صفحه مقصد ریدایرکت انجام شده رو توی همین بخش به سادگی انجام بدین، بجای ریدایرکت کردن، کار مربوطه رو همینجا انجام بدین. نهایتاً میتونید دستورات قبلی رو کامنت کنید. برای مثال این دو کد رو درنظر بگیرین:
$f3->route('GET /offline', 'Site->offline');
$f3->route('GET /page', function($f3) {
   $f3->reroute('/offline');
});

$f3->route('GET /page', 'Site->offline');

توی کدهای بالا، دومی خیلی بهتره. علتش اینه که موتورهای جستجو به احتمال زیاد این Redirect کردن رو توی دیتابیس خودشون ثبت میکنن و اگه شما دوباره اون بخش رو فعال کرده باشین، مدتی طول بکشه تا از این تغییر باخبر بشن و ضمناً این موتورها خیلی خوششون نمیاد که مدام دیتابیسشون رو دستکاری کنید! البته روش دوم، URL رو توی نوار آدرس مرورگر کاربر عوض نمیکنه. اگه این موضوع، اون چیزی نیست که شما میخواین و واقعاً نیاز به فرستادن کاربر به صفحه دیگه دارین (مثلاً ثبت نام موفقیت آمیز و...)، خوب چاره ای بجز استفاده از reroute نیست ولی خوشبختانه F3 اینقدر هوشمند هست که اگه یه پردازشی وجود داشته باشه و بعد شما reroute رو صدا بزنید، یک هدر HTTP 303 See Other تنظیم کنه. برای تمام درخواستهای دیگه که کاربر به یک صفحه یا سایت دیگه هدایت میشه (بدون هرگونه پردازشی داخل route)، هدر HTTP 301 Moved Permanently ارسال خواهد شد.

خطای 404

در زمان اجرا، F3 بطور خودکار خطای 404 رو وقتی که ببینه درخواستی اومده که با هیچکدوم از مسیرهای اعلام شده توی برنامه شما همخوانی نداره، تولید میکنه. البته بعضی جاها هم نیاز هست که خودتون دستی این خطا رو تولید کنید. مثلاً اگه کاربر به مسیر GET /country/@country مراجعه کنه ولی کشوری رو مشخص کنه که شما توی دیتابیس ندارین، شاید دلتون بخواد خطای 404 نشونش بدین. اینجور وقتها کافیه از این کد استفاده کنید:
$f3->error(404);

ReST : Representational State Transfer
F3 بر اساس این مفهوم درست شده که آدرسهای HTTP معادل منابع مختلفی توی وب هستن (محدود به HTML نمیشن) و هر منبعی میتونه از یه وضعیت به وضعیت دیگه بره. بخاطر همین، F3 هیچ محدودیتی درخصوص اینکه چطور ساختار برنامه خودتون رو میخواین درست کنین، ایجاد نمیکنه. اگه دوست دارین از MVC استفاده کنین، F3 میتونه به شما کمک کنه تا به این ساختار پایبند بشین. ازطرف دیگه، فریمورک F3 از الگوی RMR هم پشتیبانی میکنه که پیاده سازیش راحتتره. یک مثال از رابط ReST :
class Item {
   function get() {}
   function post() {}
   function put() {}
   function delete() {}
}

$f3=require('f3/base.php');
$f3->map('/cart/@item','Item');
$f3->run();

متد ()f3->map$ به شما یک رابط ReST میده. برای این کار، متدهای HTTP داخل مسیرها رو به متدهای معادل خودشون توی کلاسی که براش معرفی میکنید، وصل میکنه. اگه برای شما درخواست ورودی مثل GET /cart/123 دریافت کنه، F3 بطور خودکار درخواست رو به متد ()get از کلاس Item میفرسته و اگه درخواستی بصورت POST /cart/123 بیاد، متد ()post از کلاس Item صدا زده میشه.

نکته: مرورگرها متدهای PUT و DELETE رو توی فرمهای معمولی HTML پشتیبانی نمیکنن. به این متدها و بقیه متدهای ReST (شامل HEAD و CONNECT) میتونید با کمک درخواستهای AJAX به سرور و تنظیم متد درخواست، دسترسی پیدا کنید.

اگه فریمورک یک متد HTTP دریافت کنه که توی کلاس پیاده سازی نشده (متدش وجود نداره)، یک خطای HTTP 405 Method Not Allowed تولید میکنه. F3 بطور خودکار خودش جواب درخواستهای HTTP که هدر OPTIONS رو داشته باشن میده و اینجور درخواستها رو برای کلاس نمیفرسته.

نقل قول:یادآوری: اگه میخواین یک API کامل ReST بسازین، احتمالاً نیاز به پیاده سازی قابلیتهایی نظیر استخراج یک آیتم، مجموعه ای از عناصر، ذخیره و بروزرسانی رکوردها و... خواهید داشت که معمولاً توی یک کلاس مجزا یا متدهای HTTP که گفته شد، قابل انجام نیست. بهتره همیشه قبل از اینکه شروع به کدنویسی کنید، برای API خودتون طرح ریزی مناسبی داشته باشین. مطالعه کتاب رایگان Web API Design رو در این زمینه توصیه میکنم.

بارگذاری خودکار F3
F3 راههای مختلفی برای بارگذاری کلاسها، فقط در زمانی که بهشون نیاز دارین، ارائه میکنه تا اینطوری، کلاسهای شما تا وقتی که بهشون نیاز پیدا نکردین، حافظه ای مصرف نکنن. ضمناً دیگه نیاز نیست فهرست طولانی از دستورات include یا require برای بارگذاری کلاسهایی که توی فایلها و پوشه های مختلف ذخیره شدن، بنویسید. فریمورک میتونه این کارها رو بطور خودکار برای شما انجام بده. فقط کافیه کلاسهاتون رو (هر کلاس توی یک فایل جداگانه) توی یک پوشه ذخیره کنید (برای مثال myclassfiles) و متغیر AUTOLOAD رو برای اشاره به اون پوشه تنظیم کنید:
$f3->set('AUTOLOAD', 'myclasses/');


نقل قول:مهم: اسم کلاس و اسم فایل باید دقیقاً مثل هم باشه (حتی ازنظر بزرگی و کوچکی حروف). البته اگه اسم فایل رو تماماً با حروف کوچک بنویسید، باز هم F3 میتونه پیداش کنه ولی انتظار نداشته باشین اگه اسم کلاستون Home بود، فایلهای hoME.php و HOME.php و... رو هم براتون پیدا کنه. در این حالت، فقط اسامی Home.php و home.php مجاز هستن.



وقتی کلاس یا متدتون رو با $obj = new User; صدا میزنید، F3 دنبال فایل user.php یا User.php توی مسیری که AUTOLOAD رو تنظیم کردین میگرده و وقتی پیداش کرد، اون رو با require ضمیمه میکنه.

مسیر AUTOLOAD همیشه از محل فایل index.php شما شروع میشه. البته میتونید بهش مسیر مطلق هم بدین. مثلاً var/www/mywebsite.com/myclassfiles/ یک مسیر مطلق و /myclassfiles/.. یک مسیر نسبی (نسبت به index.php) خواهد بود و اگه index.php توی مسیر /var/www/mywebsite.com/ قرار داشته باشه، هر دو مسیر (مطلق و نسبی) فوق به یک پوشه اشاره میکنن.

میتونید چندتا مسیر رو هم برای AUTOLOAD مشخص کنید. اگه کلاسهاتون توی پوشه های مختلفی قرار دارن، میتونید به فریمورک بگین که دنبال کلاس مناسب توی چند تا مسیر بگرده. برای این کار، باید AUTOLOAD رو اینطوری مقداردهی کنید:
$f3->set('AUTOLOAD', 'admin/autoload/; user/autoload; default/');

کار با فضاهای نام
F3 به قوانین PSR احترام میگذاره. بنابراین، اگه ساختار سلسله مراتبی کلاسها که با namespaceها درست کردین، منطبق با نامگذاری پوشه های حاوی اونها باشه، F3 به راحتی کلاس رو پیدا میکنه. برای مثال، کد زیر رو درنظر بگیرین:
$f3->set('AUTOLOAD', 'autoload/');
$obj = new GadgetsiPad'

خوب حالا میتونید یک سری پوشه داخل فولدر autoload درست کنید که با ساختار namespaceها همخوانی دارن. برای مثال، فولدری به اسم gadgets داخل فولدر autoload هست که فایل ipad.php که حاوی کد کلاس iPad هست، داخلشه. محتوای فایل ipad.php در کمترین حالت میتونه این باشه:
namespace Gadgets;
class iPad { }

دقت کنید که اسم تمام پوشه ها توی F3 باید به / ختم بشه.

نقل قول:یادآوری: فضاهای نام برای کمک به شما توی تعیین ساختار فایلها و پوشه های حاوی کلاسهای شما بوجود اومدن. اگه از اونها به درستی استفاده کنید، میتونید درکنار Autoloader فریمورک F3 برای مرتب نگه داشتن پوشه های مختلف حاوی کلاسهاتون استفاده کنید و دیگه مجبور نباشین تمام کلاسها رو توی یک پوشه بگذارین یا بحث تداخل اسامی و... پیش بیاد (نه تنها نمیتونید همزمان یک کلاس رو بیش از یکبار تعریف کنید، بلکه توی یک پوشه هم نمیتونید همزمان دو فایل همنام داشته باشین). اگه از این قابلیت درکنار Autoloader به خوبی استفاده نمیکنید، آزاد هستین هرجوری دلتون میخواد فایلهاتون رو ذخیره کنید ولی یادتون باشه که باید خودتون دستی اونها رو در مواقع لزوم ضمیمه کنید.

مسیریابی به سمت یک کلاس دارای فضای نام
F3 یک فریمورک مطلع از فضاهای نام هست و به شما اجازه میده از یک متد توی یک کلاسی که دارای فضای نام هست، بعنوان یک کنترل کننده مسیر استفاده کنید و برای این کار راههای مختلفی در اختیارتون قرار میده. مثلاً برای صدا زدن یک متد استاتیک:
$f3->set('AUTOLOAD', 'classes/');
$f3->route('GET|POST /', 'MainHome::show');

کد بالا، متد استاتیک ()show رو از کلاس Home که داخل فضای نام Main تعریف شده، در زمانی که درخواست GET یا POST برای صفحه اصلی سایت ارسال بشه، صدا میزنه. کلاس Home باید در مسیر classes/main/home.php ذخیره شده باشه تا Autoloader بتونه پیداش کنه.

حالا اگه متد شما استاتیک نبود، میتونید این شکلی ازش استفاده کنید:
$f3->route('GET|POST /', 'MainHome->show');

و F3 بطور خودکار یک شئ از کلاس Home میسازه و متد ()show رو روی اون صدا میزنه.

حساسیت به بزرگی و کوچکی حروف
توی سیستمهایی که به بزرگی و کوچکی حروف حساس هستن (تقریباً همه بجز ویندوز)، کلاسها فقط درصورتی پیدا میشن که فایل و مسیرش با فضای نام ازنظر بزرگی و کوچکی حروف همخوانی داشته باشن. البته F3 برای راحتی بیشتر، دنبال فایلها و پوشه هایی که با نسخه حروف کوچک namespace و نام کلاس یکسان باشن هم میگرده.

پس اگه کلاسی که باید بطور خودکار بارگذاری بشه، MainHome باشه، فایلهای ممکن و مجاز Main/Home.php یا mian/home.php هستن.

فایلهایی مثل main/Home.php یا Main/hoME.php یا MAIN/HOME.php توی مثال فوق، بارگذاری نمیشن.

اگه میخواین خودتون بحث حساسیت به بزرگی و کوچکی حروف رو مدیریت کنید، میتونید متغیر AUTOLOAD رو با یک آرایه مقداردهی کنید که خونه اولش، مسیری هست که باید دنبال فایلها بگرده و خونه دومش، تابعی هست که میخواین بعنوان Autoload معرفی کنید. مثلاً اگه میخواین تمام فایلها و پوشه ها با حروف بزرگ نامگذاری بشن و F3 فقط دنبال نسخه حروف بزرگ فایلها و پوشه ها بگرده، میتونید AUTOLOAD رو اینطوری مقداردهی کنید:
$f3->set('AUTOLOAD', array('classes/', function($class) {
    return strtoupper($class);
}));

مدیریت رویدادها
F3 چند شنونده برای رویدادهای مسیریابی داره که میتونن به شما برای ارتقاء جریان و ساختار کلاسهای کنترلر کمک کنن. فرض کنید چنین مسیری رو تعریف کردین:
$f3->route('GET /', 'Main->home');

اگه برنامه یک درخواست HTTP مطابق با مسیر بالا دریافت کنه، F3 اول یه شئ از کلاس Main میسازه، اما قبل از اونکه متد ()home رو صدا بزنه، دنبال متدی توی این کلاس میگرده به اسم ()beforeRoute و اگه پیداش کرد، اون رو اجرا میکنه و بعد میره سراغ متد ()home و وقتی کارش با متد ()home تموم شد، دنبال متدی به اسم ()afterRoute میگرده و اگه پیداش کرد، اون رو هم اجرا میکنه. متدهای ()beforeRoute و ()afterRoute توی یک کلاس بین تمام متدهایی که توی مسیرها فراخوانی میشن مشترک هستن (هر کلاسی که اونها رو داشته باشه، قبل و بعد از هر متدی که توی مسیر مشخص شده، این متدها براش اجرا میشن). یعنی اگه فرضاً مسیرهای زیر رو تعریف کرده باشین:
'GET /login', 'User->login'
'GET /logout', 'User->logout

برای هر دو مسیر متدهای ()beforeRoute و ()afterRoute اجرا میشن.

اگه متدهای فوق رو توی کلاس والد کنترلرهاتون بنویسید، برای تمامی درخواستهای همه کنترلرها اجرا میشن. البته میتونید درصورت نیاز توی یک کنترلر دلخواه، اونها رو بازنویسی (Override) کنید. کافیه توی همون کلاس، دوباره متد موردنظر رو تعریف کنید و ساختار دلخواهتون رو براش پیاده سازی کنید. درصورت نیاز به اجرای متد موجود توی کلاس والد، توی متد بازنویسی شده هم میتونید از کدهای زیر استفاده کنید:
parent::beforeRoute();
parent::afterRoute();

مسیریابهای پویا
این هم یکی دیگه از قابلیتهای F3 :
$f3->route('GET /products/@action', 'Products->@action');

اگه برنامه شما یک درخواست بابت products/itemize/ دریافت کنه، F3 بخش 'itemize' رو ازش استخراج میکنه و اون رو به توکن action@ میده. بعد F3 دنبال کلاسی به اسم Products میگرده و متد ()itemize اون رو صدا میزنه. مسیریابهای پویا میتونن شکلهای مختلفی داشته باشن:
$f3->route('GET public/@genre', 'Main::@genre');
$f3->route('GET /public /@controller/@action', '@controller->@action');

F3 اگه نتونه کنترل رو به کلاس یا متدی که توی مسیر درخواستی مشخص شده ارجاع بده (مثلاً کلاس یا متد رو پیدا نکنه)، یه خطای HTTP 404 Not Found تولید میکنه.

درخواستهای AJAX و همزمان
الگوهای مسیریابی متونن شامل اصلاحگرهایی باشن که به فریمورک میگن تصمیمش رو درمورد مسیریابی با توجه به نوع درخواست HTTP بگیره:
$f3->route('GET /example [ajax]', 'Page->getFragment');
$f3->route('GET /example [sync]', 'Page->getFull');

اولین دستور درخواست HTTP رو به ()Page->getFragment میفرسته، فقط درصورتی که هدر X-Requested-With: XMLHttpRequest (شئ AJAX) توسط سرور دریافت بشه. اگه یه درخواست عادی بیاد (Synchronous) یعنی درخواستهای معمولی که AJAX نیستن، F3 مسیر قبلی رو بیخیال میشه و میره سراغ مسیر بعدی که متد ()Page->getFull رو صدا میزنه.

اگه هیچ اصلاحگری مشخص نشده باشه، هر دو درخواست AJAX و همزمان به اون مسیر اختصاص داده میشن. اصلاحگرها توی ()f3->map$ هم تشخیص داده میشن.

مسیریابی توی خط فرمان
اگه میخواین یک مسیر خاص رو از خط فرمان، اسکریپت شل، Cron Jobs و... صدا بزنید، میتونید درخواست HTTP GET رو به شکل زیر شبیه سازی کنید:
cd /path/to/test/suite
php index.php /my-awesome-update-route

بعلاوه میتونید از cURL یا wget هم استفاده کنید. مثال:
wget -O /dev/null http://localhost/f3project/updaet-route
متغیرهای فریمورک F3

کاربرد اولیه

متغیرهایی که توی F3 تعریف میشن، سراسری هستن یعنی میشه توی هر جزء (Component) ازشون استفاده کرد. متغیرهای سراسری فریمورک با متغیرهای سراسری PHP یکی نیستن. یه متغیر F3 به اسم content به معنای متغیر content$ توی PHP نیست. F3 یه فریمورک با زبان وابسته به محدوده (Domain Specific Language یا DSL) هست و برای خودش یه جدول نمادهای جداگانه برای متغیرهای برنامه و سیستم داره. این فریمورک، مثل هر برنامه دیگه که با اصول شئ گرایی خوبی نوشته شده باشه، فضای نام سراسری PHP رو با ثابتها، متغیرها، توابع یا کلاسهایی که ممکنه با هر برنامه ای تداخل پیدا کنن، شلوغ نمیکنه. برخلاف بقیه فریمورکها، F3 از دستور define زبان PHP استفاده نمیکنه. تمام ثابتهای فریمورک توی کلاسهای مربوطه تعریف شدن.

برای ایجاد یک متغیر توی F3 این دستور رو مینویسیم:
$f3->set('var', value);

و بجای value مقدار موردنظر رو مینویسیم. دقت کنید که F3 تمام انواع داده PHP رو قبول میکنه (شامل اشیاء و توابع بدون نام و...)

برای تعریف چند متغیر همزمان از این دستور استفاده کنید:
$f3->mset(array(
    'foo' => 'bar',
    'baz' => 123,
));

برای بدست آوردن مقدار متغیر var از این دستور استفاده میکنیم:
$f3->get('var');

برای حذف یک متغیر F3 از حافظه وقتی که بهش دیگه احتیاج نداریم)، از این روش استفاده میکنیم:
$f3->clear('var');

برای اینکه ببینیم یه متغیری قبلاً تعریف شده یا نه هم از این روش استفاده میکنیم:
$f3->exists('var');

متغیرهای سراسری

F3 همونطور که گفتیم از جدول نمادهای خودش برای متغیرهاش استفاده میکنه که از جدول PHP جداست. بعضی از متغیرها به متغیرهای فوق سراسری PHP نسبت داده شدن. برای مثال، متغیر SESSION توی F3 معادل SESSION_$ توی PHP و متغیر REQUEST توی F3 معادل REQUEST_$ توی PHP هست. استفاده از متغیرهای فریمورک رو بجای متغیرهای PHP بیشتر توصیه میکنیم چون به شما کمک میکنه که بتونید داده ها رو بین توابع و بخشهای مختلف برنامه جابجا کنید. مثلاً توی یک تابع، متغیری رو تعریف کنید و یه تابع دیگه رو صدا بزنید و توی اون تابع متغیر رو بخونید یا اصلاحش کنید و... این روش امتیازات زیر رو هم داره:
  • میتونید از متغیرهای فریمورک مستقیماً توی قالبهاتون استفاده کنید (درمورد Template بعداً بیشتر توضیح میدیم).
  • توی هر تابع یا متد نیاز نیست از کلمه global استفاده کنین تا به PHP بگین که منظورتون یه متغیر سراسریه. تمام متغیرهای F3 توی کل برنامه سراسری هستن.
  • تنظیم متغیر F3 معادل یه متغیر سراسری PHP (مثل SESSION که معادل SESSION_$ هست)، بطور خودکار متغیر سراسری PHP متناظر باهاش رو هم تغییر میده. البته عکس این قضیه هم صادقه و تغییر متغیرهای سراسری PHP نسخه معادل اونها توی فریمورک رو هم اصلاح میکنه.

F3 فقط یه جدول عقب مونده از متغیرها و مقادیرشون رو نگه نمیداره. میتونه مدیریت سشن و چیزهای دیگه رو هم برای شما خودکار کنه. نسبت دادن چیزی به متغیر SESSION توی F3 بطور خودکار دستور شروع به کار سشن رو صدا میزنه. اگه از معادل PHP یعنی SESSION_$ استفاده کنید، برنامه شما خودش وظیفه داره قبلش سشن رو استارت کرده باشه.

بعنوان یک قانون، متغیرهای فریمورک بین درخواستهای مختلف HTTP موندگار نیستن. قفط SESSION و COOKIE و عناصر داخلی اونها که معادل SESSION_$ و COOKIE_$ خود PHP هستن، از قاعده بدون وضعیت بودن (Stateless) پروتکل HTTP مستثنی میشن.

یک سری متغیر سراسری هستن که از قبل تعریف شدن و بطور داخلی توی F3‌ استفاده میشن و میتونید از اونها توی برنامه خودتون استفاده کنید. مطمئن بشین که میدونید چیکار میکنید چون تغییر دادن بعضی از متغیرهای سراسری F3 ممکنه نتایج غیرقابل پیش بینی به بار بیاره.

فریمورک یک سری متغیر داره که به شما کمک میکنه ساختار فایلها و پوشه های پروژه تون رو مرتب نگه دارین. قبلاً با یکیش آشنا شدین: AUTOLOAD. یه متغیر دیگه هم هست به اسم UI که شامل مسیری هست که فایلهای HTML مربوط به ظاهر سایتتون رو داخلش میگذارین (Viewها و Templateها). DEBUG یکی دیگه از این متغیرهاست که اغلب توی فاز توسعه ازش استفاده خواهید کرد و کاربردش هم تنظیم کردین سطح گزارش گیری خطاهاست.

به مرجع سریع فریمورک برای دیدن فهرست کاملی از متغیرهای داخلی F3 مراجعه کنید.

قواعد نامگذاری

یک متغیر فریمورک میتونه شامل هر تعداد حروف الفبای انگلیسی، اعداد لاتین و کارکتر خط زیر _ باشه. باید با یک حرف الفبا شروع بشه و استفاده از فاصله توی اسمش هم مجاز نیست. اسم متغیر به بزرگی و کوچکی حروف حساسه.

F3 برای متغیرهای سراسری داخلی خودش از اسامی با حروف بزرگ استفاده میکنه. هیچ چیزی جلوی استفاده شما از این مدل نامگذاری رو برای متغیرهای خودتون نمیگیره ولی بعنوان یک قاعده کلی، عادت کنید از حروف کوچک (یا ساختار camelCase) برای متغیرهای خودتون استفاده کنید تا از تداخل احتمالی با متغیرهای فعلی فریمورک یا نسخه های آینده اون، جلوگیری کنید.

ضمناً شما نباید از کلمات کلیدی PHP مثل if و for و class و default و... بعنوان اسم متغیر استفاده کنید. این مسئله ممکنه نتایج غیرقابل پیش بینی تولید کنه.

کار با متغیرهای رشته ای و آرایه ها

F3 چند ابزار برای کمک به شما توی کار با متغیرهای خودش ارائه میکنه:
$f3->set('a', 'fire');
$f3->concat('a', 'cracker');
echo $f3->get('a'); // returns the string 'firecracker'

$f3->copy('a', 'b');
echo $f3->get('b'); // returns the same string: 'firecracker'

همچنین یکسری متدهای اولیه برای کار با آرایه ها توسط F3 در اختیار شماست:
$f3->set('colors', array('red', 'blue', 'yellow'));
$f3->push('colors', 'green'); // works like PHP's array_push()
echo $f3->pop('colors'); // returns 'green'

$f3->unshift('colors', 'purple'); // similar to array_unshift()
echo $f3->shift('colors'); // returns 'purple'

$f3->set('grays', array('light', 'dark'));
$result = $f3->merge('colors', 'grays'); // merges the two arrays

ساختار پوشه ها بعهده خود شما

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

[attachment=72]
کلمات داخل کادرها که با حروف بزرگ نوشته شدن، معادل متغیرهای F3 هستن که باید تغییرشون بدین.

کاملاً آزاد هستین که هرجوری دلتون میخواد فایلها و پوشه هاتون رو تنظیم کنید. فقط متغیرهای سراسری F3 رو تنظیم کنید تا اونها رو پیدا کنن. اگه میخواین یک سایت واقعاً امن داشته باشین، F3 حتی به شما اجازه میده که فایلهاتون رو توی پوشه ای که مستقیماً توسط وب قابل دسترسی نیست بگذارین (مثلاً بالاتر از public_html). تنها چیزی که لازم دارین توی پوشه public_html باشه، فایلهای index.php و htaccess. و فایلهای عمومی مثل CSS و JS و تصاویر و... هستن.

درباره مدیریت خطای F3
F3 خطاهای HTML داخلی خودش رو همراه با نمایش فراخوانی های مختلف توابع و... که به Stack Trace معروفه، برای کمک به شما در اشکال زدایی تولید میکنه. مثال:

[attachment=73]

اگه احساس میکنید خیلی ساده است یا میخواین یه کار دیگه وقتی خطا رخ میده انجام بدین، میتونید مدیریت خطای دلخواه خودتون رو بسازین:
$f3->set('ONERROR', function($f3) {
    // custom error handler code goes here
    // use this if you want to display errors in a
    // format consistent with your site's theme
    echo $f3->get('ERROR.text');
});

F3 یک متغیر سراسری به اسم ERROR داره که آخرین خطا رو بصورت یک آرایه داخلش نگهداری میکنه. میتونید از ساختار داخلی اون به صورت زیر استفاده کنید:
'ERROR.code'   - the HTTP status error code (404, 500, etc.)
'ERROR.status' - a brief description of the HTTP status code. e.g. 'Not Found'
'ERROR.text'   - error context
'ERROR.trace'  - stack trace stored in an array

اگه پروژه شما از قالب استفاده میکنه، ممکنه بخواین خطاها رو خارج از قالب نمایش بدین و یه صفحه کاملاً تمیز برای خطاها داشته باشین. برای اینکار باید بافر خروجی PHP که F3 از اون برای موتور قالبش استفاده میکنه رو خالی کنید. به این مثال دقت کنین:
$f3->set('ONERROR', function($f3) {
    // clear all output buffer levels
    while(ob_get_level()) {
        ob_end_clean();
    }
    // now display your fresh error page here
    echo $f3->get('ERROR.text');
});

وقتی میخواین برنامه خودتون رو توسعه بدین، بهتره سطح اشکال زدایی رو روی بالاترین مقدار تنظیم کنید تا تمام خطاها رو بتونید تا زمان رسیدن به ریشه اونها پیگیری کنید:
$f3->set('DEBUG', 3);

کافیه فقط این دستور رو توی سیکل راه اندازی برنامه خودتون (برای مثال ابتدای index.php) قرار بدین.

یادتون نره که به محض اینکه برنامه شما کامل شد و خواستین در وضعیت انتشار قرار بدین، پیغامهای اشکال زدایی رو مخفی کنید:
$f3->set('DEBUG', 0);

این کار باعث میشه Stack Trace توی صفحات خطایی که خود سیستم تولید میکنه ظاهر نشه (وظیفه نمایش ندادنش توی صفحات خطای سفارشی سازی شده خودتون، با شماست).

DEBUG از عدد 0 که باعث میشه Stack Trace ظاهر نشه، تا سطح 3 که همه کلاسها و توابع و جزئیات اشیاء و... رو نشون میده میتونه تنظیم بشه.

نقل قول:فراموش نکنید! Stack Trace میتونه شامل مسیرها، اسامی فایلها، دستورات پایگاه داده ها، اسامی کاربران و رمزهای عبور و... باشه. اگه توی محیط اجرا و بعد از تکمیل توسعه، DEBUG رو روی 0 تنظیم نکنید، ممکنه سایتتون رو در معرض ریسکهای امنیتی غیر ضروری قرار بدین.


فایلهای پیکربندی

اگه برنامه شما نیاز به قابلیت تنظیم توسط کاربر داره، F3 یک متد سودمند برای خوندن فایلهای تنظیمات در اختیارتون میگذاره. اینطوری، شما و کاربرانتون میتونید تنظیمات برنامه رو بدون دست زدن به کدهای PHP تغییر بدین.

چهار تا بخش از قبل تعریف شده وجود داره:
  • [globals] برای تعریف متغیرهای سراسری
  • [routes] برای تعریف مسیرها
  • [maps] برای تعریف نگاشت ها (توی بحث ReST مثالش رو معرفی کردیم)
  • [redirects] برای مسیرهای ریدایرکت شده به آدرس دیگه

نکته: اگه هیچ بخشی اعلام نشده باشه، [globals] بعنوان بخش پیشفرض درنظر گرفته میشه. میتونید تمام بخشها رو توی یک فایل بگذارین، هرچند جداکردن بخشها توی فایلهای جداگانه، بیشتر توصیه میشه چون انعطاف پذیری بیشتری به شما میده و فایلها رو هم ساده تر، کوتاه تر و خواناتر میکنه. اینطوری به کاربران اجازه میدین که هرکدوم از بخشهای مختلف تنظیمات رو جداگانه ویرایش کنن. مثلاً بدون نگرانی از اینکه مسیریابی ها خراب بشه، تنظیمات اصلی برنامه رو اصلاح کنن و...

خوب حالا ببینیم چطور از این قابلیت استفاده کنیم. بجای اینکه یه چنین کدی بنویسین:
$f3->set('num', 123);
$f3->set('str', 'abc');
$f3->set('hash', array('x' => 1, 'y' => 2, 'z' => 3));
$f3->set('items', array(7, 8, 9));
$f3->set('mix', array('this', 123.45, FALSE));

میتونید یک فایل با این محتوا بسازین و با نام دلخواه ذخیره کنید (مثلاً globals.cfg) :
[globals]
num=123
; this is a regular string
str=abc
; another way of assigning strings
str="abc"
; this is an array
hash[x]=1
hash[y]=2
hash[z]=3
; dot-notation is recognized too
hash.x=1
hash.y=2
hash.z=3
; this is also an array
items=7,8,9
; array with mixed elements
mix="this",123.45,FALSE

خوب حالا بجای کلی دستور $f3->set() یه فایل خوانا داریم که هر کسی میتونه بدون دانش PHP هم تغییرش بده. به نحوه گذاشتن توضیحات هم دقت کنید. حالا این تنظیمات رو چطوری بخونیم؟ خیلی ساده است:
$f3->config('globals.cfg');

مقادیررشته ای رو توی کوتیشن نمیگذاریم، مگه اینکه بخواین فاصله های انتهایی اونها هم جزو رشته باشن (محدوده رشته رو مشخص کنید). اگه کاما جزئی از رشته است، رشته رو توی کوتیشن جفت بگذارین وگرنه بعنوان جداکننده عناصر آرایه درنظر گرفته میشه. رشته رو میشه توی چند سطر هم نوشت. کافیه آخر هر سطری که رشته تمام نشده و بقیه اون رو میخواین توی سطر بعد بنویسین، بک اسلش بگذارین:
[globals]
str="this is a 
very long 
string"

F3 به شما امکان تعریف مسیرها توی فایل تنظیمات رو هم میده:
[routes]
GET /=home
GET /404=App->page404
GET /page/@num=Page->controller
; Cache the route for 10 minutes
GET /contact=App->contact, 600
; named route
GET @about: /about=Page->about

و نحوه خوندن مسیرها:
$f3->config('routes.cfg');

نگاشتها رو هم میشه به شکل مشابه توی یک فایل ذخیره کرد:
[maps]
/blog=BlogLogin
/blog/@controller=Blog@controller

و خوندنش:
$f3->config('maps.cfg');

و برای ریدایرکتها:
[redirects]
GET|HEAD /obsoletepage=/newpage
GET|HEAD /dash=@dashboard
GET|HEAD /search=https://www.google.com

و برای خوندن تنظیمات ریدایرکت:
$f3->config('redirects.cfg');

یادآوری: پسوند cfg. اختیاریه و هر اسم فایل دلخواه خودتون رو میتونید انتخاب کنید.

قسمت های سفارشی

هر قسمت دیگری بجز 3 قسمت بالا جزو [globals] محسوب میشه و یک متغیر به اسم اون بخش داخل متغیرهای سراسری تعریف میشه و مثل متغیرهای عادی میشه باهاش کار کنید. مثال:
[foo]
a=1
b=2

[foo.hash]
x=1
y=2
z=3

تنظیمات بالا رو اگه با متد ()f3->config بخونیم، معادل کد زیر خواهد شد:
$f3->set('foo', array(
    'a' => 1,
    'b' => 2,
    'hash' => array(
        'x' => 1,
        'y' => 2,
        'z' => 3,
    ),
));
فایلهای نمایش (View) و قالب ها (Template)

جداسازی مفاهیم
یک رابط کاربری مثل صفحه HTML بهتره مستقل از کد PHP مربوط به مسیریابی و منطق کاری پشتش باشه. این یک اصل توی الگوی طراحی MVC محسوب میشه. یه تغییر جزئی مثل تبدیل تگ h3 به p نباید نیاز به تغییر توی کد برنامه شما داشته باشه. به همین شکل، تبدیل یک مسیر ساده مثل GET /about به GET/about-us نباید هیچتأثیری توی رابط کاربری و منطق برنامه ایجاد کنه (View و Model توی MVC یا Representation و Method توی RMR).

ادغام کردن ساختار برنامه نویسی و رابط کاربری توی یک فایل (کدنویسی اسپاگتی)، باید میشه نگهداری برنامه در آینده تبدیل به یه کابوس بشه.

PHP بعنوان یک موتور قالب
F3 از PHP بعنوان یه موتور قالب پشتیبانی میکنه. به این تکه کد HTML نگاه کنید که بعنوان template.htm ذخیره شده:
<p>Hello, <?php echo $name; ?>!</p>

اگه تگهای کوتاه رو فعال کرده باشین، میتونید اینطوری هم بنویسید:
<p>Hello, <?= $name ?></p>

برای نمایش این قالب، شما میتونید کد PHP شبیه این بنویسید (که توی یک فایل جدا از قالب ذخیره شده):
$f3 = require 'f3/base.php';
$f3->route('GET /', function($f3) {
    $f3->set('name', 'world');
    $view = new View;
    echo $view->render('template.htm');
    // Previous two lines can be shortened to:
    // echo View::instance()->render('template.htm');
});
$f3->run();

تنها مشکل با PHP بعنوان موتور قالب اینه که بخاطر قرارگرفتن کدهای PHP توی این فایلها، زحمت زیادی لازمه که به اصول جداسازی اجزای برنامه پایبند باشیم و دربرابر تمایل به ادغام منطق با UI مقاومت کنیم.

نگاهی سریع به زبان قالب F3
بعنوان یه جایگزین برای PHP، میتونید از موتور قالب خود F3 استفاده کنید. با این کار، کد template.htm این شکلی میشه:
<p>Hello, {{ @name }}!</p>

و کد لازم برای نمایش این قالب:
$f3 = require 'lib/base.php';
$f3->route('GET /', function($f3) {
    $f3->set('name', 'world');
    $template = new Template;
    echo $template->render('template.htm');
    // Above lines can be written as:
    // echo Template::instance()->render('template.htm');
});
$f3->run();

مشابه توکن های مسیرهای پویا برای دریافت متغیرها ازطریق آدرس (GET/country/@state یادتونه؟)، توکن های قالب F3 هم با @ شروع میشن و بعدش یک سری حروف و عدد میاد که داخل آکولادها قرار گرفتن. اولین کارکتر باید یه حرف الفبای لاتین باشه. توکن های قالب یه ارتباط یک به یک با متغیرهای فریمورک دارن. فریمورک بطور خودکا توکن رو با مقداری که توی متغیر همنام با اون ذخیره شده، جایگزین میکنه.

توی مثال ما، F3 توکن name@ رو با مقداری که به متغیر name اختصاص دادیم، جایگزین میکنه. در زمان اجرا، خروجی کد بالا این میشه:
<p>Hello, world!</p>

نگران کارآیی قالبهای F3 هستین؟ در زمان اجرا F3 یک قالب رو فقط در اولین باری که اون رو با متد render نشون میدین، کامپایل/تبدیل به کد PHP میکنه. بعد از اون، فریمورک با این نسخه تبدیل شده به کد PHP سروکار داره و درنتیجه، سرعتش معادل قالبهای PHP خواهد بود. البته ممکنه بخاطر بهینه سازیهایی که توی کد توسط کامپایلر قالب اتفاق میفته (در زمانی که از قالبهای پیچیده تر استفاده میکنیم)، بهتر از قالب PHP هم باشه.

چه از قالب PHP یا موتور قالب داخلی F3 استفاده کنید، نمایش قالبها میتونه خیلی سریعتر بشه اگه APC یا WinCache و XCache یا OPCache و امثال اینها روی سرورتون فعال باشه.

همونطور که جلوتر گفتیم، متغیرهای فریمورک میتونن هر نوع داده PHP رو داشته باشن. البته استفاده از داده های غیر عددی توی قالبهای F3 ممکنه نتایج عجیبی ایجاد کنه اگه به خوبی به روش کار مسلط نباشین. عبارتهایی که توی آکولادها قرار دارن همیشه ارزیابی میشن و به رشته تبدیل میشن. بهتره متغیرهای UI رو به انواع ساده string و integer و boolean و float محدود کنید.

اما خوب آرایه ها چی میشن پس؟ F3 آرایه ها رو تشخیص میده و میتونید از اونها توی کدتون استفاده کنید. مثال:
<p>{{ @buddy[0] }}, {{ @buddy[1] }}, and {{ @buddy[2] }}</p>

و توی کدتون، buddy@ رو اینطوری مقداردهی کنید:
$f3->set('buddy', array('Tom', 'John', 'Harry'));

خوب اگه شما از {{ buddy@ }} توی قالبتون استفاده کنید، PHP 5.3 اون رو با کلمه 'Array' جایگزین میکنه چون توکن رو به رشته تبدیل میکنه ولی PHP 5.4 یه یادآوری Array to string conversion در زمان اجرا تولید میکنه.

F3 به شما اجازه میده از عبارتها توی قالب استفاده کنید. این عبارتها میتونن شکلهای مختلفی داشته باشن. مثلاً محاسبات ریاضی، عبارات منطقی، ثابت های PHP و... :
{{ 2*(@page-1} }}
{{ (int)765.29+1.2e3 }}
<option value="F" {{ @active?'selected="selected"':'' }}>Female</option>
{{ var_dump(@xyz) }}
<p>That is {{ preg_match('/Yes/i', @response)?'correct':'wrong' }}!</p>
{{ @obj->property }}

توضیح: دقت کنید که foo.@bar@ یک ادغام رشته هاست (foo.$bar$) درحالی که foo.bar@ معادل ['foo['bar@ هست. اگه شما میخواین [foo[$bar$ رو نمایش بدین، از الگوی معمولی [foo[@bar@ استفاده کنید.

متغیرهای فریمورک میتونن توابع بدون نام هم باشن:
$f3->set('func', function($a, $b) {
    return $a . ', ' . $b;
});

موتور قالب F3 توکن رو همونطوری که انتظار دارین، پردازش خواهد کرد. کافیه این کد رو بنویسید:
{{ @func('Hello', 'world') }}

نقل قول:یادآوری: اگه خطای UNDEFUNED VARIABLE یا UNDEFINED INDEX موقع نمایش قالب F3 گرفتین، معناش اینه که متغیر یا اندیس آرایه قبل از استفاده توی تابعتون که داره متد render رو صدا میزنه، تعریف نشده. دو راه حل دارین:
  • پیشنهاد شده: تمام متغیرها رو تعریف کنید، حتی اگه NULL باشن:
    • (f3->set('myVar', NULL$
    • ('f3->set('myArray.myIndex', 'First Item$
  • سریع ولی کثیف: از یک @ اضافه جلوی توکن خودتون استفاده کنید تا خطاها رو خاموش کنید:
    • {{ myArray.MyIndex@@ }}
به گزارش مشکل شماره 201 توی مخزن GitHub فریمورک مراجعه کنید:
https://github.com/bcosca/fatfree/issues/201

قالب درون قالب
جایگزینی ساده متغیرها کاریه که هر موتور قالبی انجام میده. F3 چیزای بیشتری تو آستنیش داره:
<include href="header.htm" />

دستور بالا محتوای قالب header.htm رو توی مکانی که اون رو نوشتیم، قرار میده. میتونید حتی محتوای پویا بارگذاری کنید:
<include href="{{ @content }}" />

یکی از کاربردهای این قابلیت وقتیه که صفحات مختلفی دارین که قالب یکسانی دارن ولی محتوای بخشی از صفحه براساس آدرس درخواستی، تغییر میکنه. اینکه به فریمورک بگیم یک صفحه رو در بخشی از قالب اصلی بارگذاری کنه، کار راحتیه:
// switch content to your blog sub-template
$f3->set('content', 'blog.htm');
// in another route, switch content to the wiki sub-template
$f3->set('content', 'wiki.htm');

یه قالب فرعی (قالبی که مثل همین blog.htm یا wiki.htm توی یه قالب دیگه قرار میگیره) میتونه باز خودش هر تعداد قالب دیگه رو که میخواد، صدا بزنه. F3 از قالبهای تودرتوی نامحدود پشتیبانی میکنه.

میتونید اسامی فایلهاتون رو با پسوندی بجز htm. یا html. هم ذخیره کنید ولی موقعی که درحال طراحی اونها هستین، این پسوندها کمک میکنن تا به راحتی با دوبار کلیک روی فایل، اون رو توی مرورگر ببینید و اشکالاتش رو برطرف کنید. موتور قالب محدود به HTML نیست و میتونید از فایلهای دیگه هم استفاده کنید.

دستور include یه خاصیت اختیاری if هم داره که به شما اجازه میده یه شرط بگذارین که اگه برقرار شد، اونوقت قالب فرعی ضمیمه بشه:
<include if="{{ count(@items) >= 2}} href="items.htm" />

داده های فعلی برای قالب فرعی هم ارسال میشن. البته میتونید درصورت نیاز متغیرهای جدید هم برای قالب فرعی بفرستین یا حتی مقادیر قبلی رو وقتی میخواین برای قالب فرعی بفرستین، تغییر هم بدین. برای این کار از خاصیت with استفاده میکنیم:
<!-- pass $a=2 to sub.htm -->
<include href="sub.htm with="a=2" />

<!-- pass $v='something and $c='something quoted' to sub.htm -->
<include href="sub.htm" with="b=something,c='something quoted'" />

<!-- pass uppercased value of $d to sub.htm -->
<set d="abs" />
<include href="sub.htm" with="d={{strtouppwer($d)}}" /><!-- $d='ABC' -->
{{@d}} <!-- $d='abc' -->

جداسازی بخشهایی از قالب
در زمان نوشتن و اشکال زدایی برنامه های قدرت گرفته از F3 و طراحی قالب ها، یه وقت هایی ممکنه غیرفعال کردن نمایش بلاکی از HTML مفید باشه. برای این کار میشه از دستور <exclude> استفاده کنید:
<exclude>
    <p>A chunk of HTML we don't want displayed at the moment</p>
</exclude>

این کار مشابه تگ <-- comment --!> در HTML بنظر میرسه ولی دستور <exclude> باعث میشه که بلاک موردنظر کلاً وقتی که قالب رندر میشه، توی سورس کد وجود نداشته باشه.

روش دیگه جداکردن بخشهایی از قالب، اضافه کردن توضیحاته:
{* <p> A chunk of HTML we don't want displayed and the moment</p> *}

قسمت های شرطی
یکی دیگه از قابلیتهای کاربردی موتور قالب، دستور <check> هست. این دستور به شما اجازه میده بخشی از کد فقط درصورتی که شرط خاصی برقرار باشه نمایش داده بشه:
<check if="{{ @page=='Home' }}">
    <false><span>Inserted if condition is false</span></false>
</check>
<check if="{{ @gender=='M' }}">
    <true>
        <div>
            <p>Appears when condition is true</p>
        </div>
    </true>
    <false>
        <div>
            <p>Appears when condition is false</p>
        </div>
    </false>
</check>

میتونید هرچقدر دوست دارین، دستورات <check> تودرتو بنویسید. یه عبارت F3 داخل خاصیت if اگه NULL یا یک رشته خالی یا false یا یه آرایه خالی یا صفر باشه، بطور خودکار بلاک <false> رو صدا میزنه. اگه بلاک <false> نداشته باشین، نوشتن بلاک <true> اختیاریه:
<check if="{{ @loggedin }}">
    <p>HTML chunk to be included if condition is true</p>
</check>

تکرار قسمتها
F3 میتونه از تکرار قسمتهای HTML هم پشتیبانی کنه:
<repeat group="{{ @fruits }}" value="{{ @fruit }}">
    <p>{{ trim(@fruit) }}</p>
</repeat>

خاصیت group از یه آرایه به اسم fruits@ داره برای تکرار بخش داخل <repeat> استفاده میکنه و هربار یکی از عناصرش رو توی متغیر fruit@ میگذاره (یه چیزی شبیه foreach خود PHP). برای مثداردهی اون میتونید این شکلی کار کنید:
$f3->set('fruits', array('apple', 'orange ', ' banana'));

اگه fruit@ رو مقداردهی دستی کنید، F3 اون رو نادیده میگیره چون داره از این متغیر برای بدست آوردن مقدار جاری آرایه fruits$ استفاده میکنه. خروجی کدهای بالا این میشه:
<p>apple</p>
<p>orange</p>
<p>banana</p>

فریمورک از تعداد نامحدود بلاکهای <repeat> تودرتو هم پشتیبانی میکنه:
<repeat group="{{ @div }}" key="{{ @ikey }}" value="{{ @idiv }}">
    <div>
        <p><span><b>{{ @ikey }}</b></span></p>
        <p>
        <repeat group="{{ @idiv }}" value="{{ @ispan }}">
            <span>{{ @ispan }}</span>
        </repeat>
        </p>
    </div>
</repeat>

برای پرکردن متغیرهای بالا، میتونید کدی شبیه این بنویسید:
$f3->set('div', array(
    'coffee' => array('arabica', 'barako', 'liberica', 'kopiluwak'),
    'tea' => array('darjeeling', 'pekoe', 'samovar'),
);

نتیجه ای که بدست میارین، این خروجی HTML خواهد بود:
<div>
    <p><span><b>coffee</b></span></p>
    <p>
        <span>arabica</span>
        <span>barako</span>
        <span>liberica</span>
        <span>kopiluwak</span>
    </p>
</div>
<div>
    <p><span><b>tea</b></span></p>
    <p>
        <span>darjeeling</span>
        <span>pekoe</span>
        <span>samovar</span>
    </p>
</div>

شگفت آوره نه؟ و تنها کاری که باید انجام بدین، تعیین مقادیر متغیر div توی F3 هست تا جایگزین توکن div@ بشه. F3 باعث میشه هر دو مورد برنامه نویسی و طراحی قالب واقعاً راحت بشه.

خاصیت value توی دستور <repeat> مقدار عنصر جای رو توی هر دور حلقه برمیگردونه. اگه نیاز به اندیسهای آرایه داشتین، میتونید از خاصیت key استفاده کنید. خاصیت key اختیاریه.

<repeat> یه پارامتر اختیاری دیگه به اسم counter داره که میتونید مشابه کد زیر ازش استفاده کنید:
<repeat group="{{ @fruits }}" value="{{ @fruit }}" counter="{{ @ctr }}">
    <p class="{{ @ctr%2?'odd':'even' }}">{{ trim(@fruit) }}</p>
</repeat>

F3 بطور توکار تعداد پیمایشهای حلقه رو میشماره و اون رو توی متغیر/توکن ctr@ ذخیره میکنه که توی مثال بالا برای اینکه کلاس odd/even رو به پاراگراف بدیم، ازش استفاده کردیم.

شامل شدن Javascript و CSS
اگه توکنهای F3 رو توی بخشهای <script> و <style> قالبتون بگذارین، فریمورک اونها رو هم جایگزین میکنه:
<script type="text/javascript">
    function notify() {
        alert('You are logged in as: {{ @userID }}');
    }
</script>

استفاده از دستورات قالب توی تگهای <script> و <style> هیچ پیچیدگی و کار اضافه ای لازم نداره:
<script type="text/javascript">
	var discounts=[];
    <repeat group="{{ @rates }}" value="{{ @rate }}">
        // whatever you want to repeat in Javascript, e.g.
        discounts.push("{{ @rate }}");
    </repeat>
</script>

کدگذاری سند HTML
F3 بطور پیشفرض از کدگذاری UTF-8 استفاده میکنه ولی میتونید درصورت نیاز تغییرش بدین. کافیه متغیر سیستمی ENCODING رو به شکل زیر مقداردهی کنید:
$f3->set('ENCODING', 'ISO-8859-1');

یکبار که به فریمورک گفتین کدگذاری موردنظر شما چیه، تا وقتی که دوباره تغییرش ندادین برای تمام قالبهای HTML و XML از همون استفاده میکنه.

تمام انواع قالب
همونطور که قبلاً گفتیم، فریمورک محدود به قالبهای HTML نیست. میتونین قالبهای XML رو هم پردازش کنین. مکانیزم کار خیلی شبیهه. شما هنوز هم از الگوی {{ variable@ }} و {{ expression }} برای توکن ها استفاده میکنید و دستورات <repeat> و <check> و <include> و <exclude> رو در اختیار دارین. فقط باید به F3 بگین که میخواین یه قالب XML رو بجای HTML بفرستین:
echo Template::instance()->render('template.xml', 'application/xml');

پارامتر دوم، نوع MIME سندی هست که قراره رندر بشه.

عنصر View توی MVC هر چیزی رو که توی Model و Controller قرار نمیگیره شامل میشه، که معناش اینه که نمایش شما میتونه (و باید) شامل تمام انواع رابط های کاربری مثل RSS، ایمیل، RDF، فایلهای متنی، FOAF و... میشه. مثال زیر به شما نشون میده که چطور نمایش ایمیل رو از لایه منطقی برنامه خودتون جدا کنید:
MIME-Version: 1.0
Content-type: text/html; charset={{ @ENCODING }}
From: {{ @from }}
To: {{ @to }}
Subject: {{ @subject }}

<p>Welcome, and thanks for joining {{ @site }}!</p>

قالب ایمیل بالا رو به اسم welcome.txt (یا هر اسم دلخواه دیگه) ذخیره کنید. کد F3 مربوط به مقداردهی و نمایش قالب، این شکلی میشه:
$f3->set('from', '<no-reply@ncis.ir>');
$f3->set('to', '<admin@ncis.ir>');
$f3->set('subject', 'Welcome');
ini_set('sendmail_from', $f3->get('from'));
mail(
    $f3->get('to'),
    $f3->get('subject'),
    Template::instance()->render('welcome.txt', 'text/html')
);

ترفند: بجای تابع ()mail معمولی (SMTP) میتونید از ()imap_mail برای کار با سرورهای IMAP استفاده کنید.

خوب این الان چیز خاصی نبود؟ البته، اگه چند تا گیرنده ایمیل داشته باشین، میتونید از بانک اطلاعاتی برای پرکردن نام، نام خانوادگی و توکن های ایمیل استفاده کنید.

جایگزین دیگه، استفاده از افزونه SMTP فریمورک هست:
$mail = new SMTP('smtp.gmail.com', 465, 'SSL', 'account@gmail.com', 'secret');
$mail->set('from', '<no-reply@ncis.ir>');
$mail->set('to', '"Admin" <admin@ncis.ir>');
$mail->set('subject', 'Welcome');
$mail->send(Template::instance()->render('welcome.txt'));

پشتیبانی از چند زبان
F3 از قابلیت چند زبانی پشتیبانی میکنه. اول یه فایل لغتنامه درست کنید با ساختار زیر (یکی برای هر زبان) :
<?php
return array(
    'love' => 'I live F3',
    'today' => 'Today is {0,date}',
    'pi' => '{0,number}',
    'money' => 'Amount remaining: {0,number,currency}',
);

خوب این رو به اسم dict/en.php ذخیره کنید (اسم پوشه و اسم فایل اختیاریه). اجازه بدین یه فایل دیکشنری دیگه هم درست کنیم. اینبار فارسی:
<?php
return array(
    'love' => 'من عاشق F3 هستم',
    'today' => 'امروز {0,date} هست',
    'money' => 'مبلغ باقیمانده: {0,number,currency}',
);

فایلهای دیکشنری چیزی بجز زوجهای کلید-مقدار نیستن. F3 بطور خودکار متغیرهای فریمورک رو براساس کلیدهای موجود توی فایلهای زبان ایجاد میکنه. بخاطر همین، قراردادن این متغیرها بعنوان توکن توی قالبهاتون کار راحتیه.

نقل قول:یادآوری: زوجهای کلید-مقدار لغتنامه ها وقتی که مورد استفاده قرار بگیرن، تبدیل به متغیرهای F3 میشن. مطمئن بشین که کلیدها با هیچ متغیری که با دستور ()f3->set$ یا ()f3->mset$ یا ()f3->config$ ساختین، تداخل پیدا نمیکنن. بعلاوه، میتونید از متغیر PREFIX برای تعریف یک پیشوند برای تمام کلیدهای زبان استفاده کنید (با دستور ()f3->set$ مقدارش رو تعیین کنید تا به اول همه متغیرهای زبان اضافه بشه.

مثال هایی از چند زبان سازی با کمک موتور قالب F3 :
<h1>{{ @love }}</h1>
<p>
{{ @today,time() | format }}.<br />
{{ @money,365.25 | format }}<br />
{{ @pi, 3.1415 | format }}
</p>

و نسخه معادلش (البته طولانی تر) که داره از قالب خود PHP استفاده میکنه:
<?php $f3 = Base::instance(); ?>
<h1><?php echo $f3->get('love'); ?></h1>
<p>
    <?php echo $f3->get('today', time()); ?>.<br />
    <?php echo $f3->get('money, 365.25); ?><br />
    <?php echo $f3->get('pi', 3.1415); ?>
</p>

خوب حالا باید به F3 بگیم که کجا دنبال فایلهای زبان بگرده:
$f3->set('LOCALES', 'dict/');

اما فریمورک از کجا میفهمه که از چه زبانی باید استفاده کنه؟ F3 بطور خودکار این رو در مرحله اول با نگاه کردن به هدر درخواست HTTP تشخیص میده و دقیق ترش، هدر Accept-Language که ازطرف مرورگر کاربر ارسال شده رو بررسی میکنه.

برای تغییر این روش، میتونید به F3 بگین که از چه زبانی استفاده کنه:
$f3->set('LANGUAGE', 'fa');

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

اگه زبان اصلی سایت شما انگلیسی نیست و همه رشته ها رو هم توی تمام زبانها ترجمه نکردین، باید به F3 بگین که از زبان بومی شما بعنوان مرجع استخراج شناسه هایی که توی زبان موردنظر تعریف نشدن، استفاده کنه. برای این کار باید متغیر FALLBACK رو تعریف کنید:
$f3->set('FALLBACK', 'fa'); // Farsi is now the default fallback language

گونه های مختلف زبان
میتونید فایلهای لغتنامه جداگانه برای گونه های مختلف زبان از قبیل en-US و es-AR و fa-IR و... استفاده کنید. توی این حالت، F3 اول از فایل لغتنامه گونه زبان مشخص شده (مثلاً en-US) استفاده میکنه. اگه کلید رو داخلش پیدا نکرد، میره دنبال فایل لغتنامه ریشه اون زبان (مثل en) میگرده و اگه باز هم پیدا نکرد، میره توی زبان اصلی که مشخص کردین دنبالش میگرده.

متوجه الگوی عجیب {Today is {0,date توی مثال قبلیمون شدین؟ قابلیت چند زبان سازی F3 با سیستم قواعد قالب بندی رشته های پروژه ICU سازگاری داره. فریمورک از زیرمجموعه خاص خودش از پیاده سازی قالب بندی متن ICU استفاده میکنه. توی این حالت نیاز به نصب بودن افزونه intl روی PHP هم ندارین.

یه نکته دیگه: F3 میتونه از الگوی فایلهای ini. هم برای لغتنامه استفاده کنه:
love=I love F3
today=Today is {0,date}
pi={0,number}
money=Amount remaining: {0,number,currency}

فقط کافیه این فایل رو به اسم dict/en.ini (یعنی با پسوند ini. ذخیره کنید و بقیه کارها رو خود فریمورک انجام میده.

نقل قول:متغیر LANGUAGE رشته هایی مشابه هدر Accept-Language رو قبول میکنه که یک فهرست 2 کارکتری از کدها هست که ممکنه بعدش یک خط تیره و یک کد 2 حرفی کشور بیاد.

نکته: با وجود اینکه زبانهای POSIX از خط زیر برای جداکردن استفاده میکنن (مثل en_US.UTF-8) باز هم شما باید متغیر LANGUAGE رو با خط تیره تعریف کنید (en-US) و فایلهای لغتنامه هم از همین الگو استفاده میکنن (مثل es-AR.php یا fa-IR.ini).

برای اطلاعات بیشتر، مستندات متد Base->language رو نگاه کنید.

خنثی سازی داده ها
بطور پیشفرض، هر دو کنترل کننده ویو و موتور قالب تمامی متغیرها رو Escape میکنن و تبدیل به معادلهای بی خطر HTML میکنن تا از حملات احتمالی XSS و Code Injection در امان باشین. درمقابل ممکنه بخواین گاهی اوقات، یک محتوای HTML معتبر رو از برنامه خودتون به قالب بفرستین:
$f3->set('ESCAPE', false);

با این کار، کلاً خنثی سازی غیرفعال میشه که زیاد توصیه نمیشه. ممکنه نخواین تمام متغیرها بدون خنثی سازی ارسال بشن. F3 به شما اجازه میده جلوی خنثی سازی متغیرها رو بطور جداگانه بگیرین. توی قالبهای F3 میتونید از فیلتر raw استفاده کنید:
{{ @html_content | raw }}

توی قالبهای PHP باید مستقیماً از متد raw استفاده کنید:
<?php echo View::instance()->raw($html_content); ?>

علاوه بر خنثی سازی خودکار متغیرهای F3، فریمورک به شما اجازه خنثی سازی دستی ورودیهای کاربر رو هم که ازطریق فرمها و... ارسال شده میده:
$f3->scrub($_GET, 'p; br; span; div; a');

این دستور باعث میشه تمام تگها (بجز اونهایی که توی پارامتر دوم مشخص شدن) و کارکترهای مخرب و خطرناک از متغیری که توی پارامتر اول مشخص شده حذف بشن. اگه متغیر یه آرایه باشه، تمام عناصرش بصورت بازگشتی خنثی سازی میشن. اگه بعنوان پارامتر دوم، کارکتر * رو بفرستین، متد ()f3->scrub$ تمام تگها رو اجازه میده که ارسال بشن و بهشون دست نمیزنه و فقط کارکترهای نا امن رو حذف میکنه.

توسعه فیلترها و تگهای سفارشی
با موتور قالب F3 میتونید فیلترهای سفارشی خودتون رو ایجاد کنید:
{{ @desc, 100 | crop }}

میتونید حتی چند فیلتر رو با هم ترکیب کنید:
{{ @desc,100 | crop,raw }}

برای این کار شما باید کلاس Template رو گسترش بدین و متد موردنظرتون رو بهش اضافه کنید. برای کنترل کننده های شخصی تگها و عناصر HTML که میتونن هر چیزی که میخواین رو نمایش بدن، توضیحات متد template->extend رو بررسی کنید. برای توضیحات بیشتر درباره فیلترهای سفارشی و نحوه کار کلی سیستم قالب، قسمت قالب بندی توسعه یافته رو مطالعه کنید (در آینده توضیح میدیم).
پایگاه داده ها

اتصال به موتور پایگاه داده ها
F3 طوری طراحی شده که کار با پایگاههای داده SQL رو به ملایمت یه نسیم کنه. اگه دوست ندارین درگیر جزئیات SQL بشین و تمایل دارین از داده ها به شکل شئ گرا استفاده کنید، بخشهای اولیه این آموزش رو نادیده بگیرین. هرچند اگه نیاز به انجام کارهای پیچیده روی داده ها داشته باشین و بهینه بودن کارآیی دیتابیس براتون مهمه، SQL مسیری هست که باید طی کنید.

ارسال دستورات به موتورهای SQL مثل MySQL و SQLite و SQL Server و Sybase و Oracle با دستور آشنای ()f3->set$ انجام میشه. اما قبلش باید به دیتابیس وصل بشیم. برای این کار میتونیم از روش زیر استفاده کنیم:
$db = new DB\SQL('sqlite:/absolute/path/to/your/database.sqlite'));

یه مثال دیگه برای MySQL :
$db = new DB\SQL(
    'mysql:host=localhost;port=3306;dbname=blog',
    'root',
    'ncis'
);

ارسال کوئری به دیتابیس
خوب. وصل شدن که راحت بود، نه؟ خیلی شبیه کاری بود که توی PHP معمولی و خام انجام میدادیم. فقط کافیه ساختار DSN دیتابیسی که میخواین بهش وصل بشین رو بدونین. برای توضیحات بیشتر، مستندات PDO سایت PHP رو بخونید.

خوب اجازه بدین کدمون رو ادامه بدیم:
$f3->set('result', $db->exec('SELECT * from categories'));
echo Template::instance()->render('abc.htm');

چی شد؟ نباید چیزی رو مثل PDO و اشاره گرها و fetch کردن و... انجام بدیم؟ جواب ساده است: مجبور نیستین. F3 همه چیز رو با انجام این کارها در پشت پرده، ساده کرده.

ایندفعه میخوایم قالب HTML موردنظر (یعنی abc.htm) رو بسازیم که به همین کوتاهیه:
<repeat group="{{ @result }}" value="{{ @item }}">
    <div>{{ @item.name }}</div>
</repeat>

در اکثر مواقع دستور SQL که با SET به متغیر دادین، برای تولید یک آرایه آماده برای استفاده توی HTML کافیه و شما میتونید از متغیر آرایه result توی قالبتون مستقیماً استفاده کنید. اما F3 مانع از این نمیشه که خروجی رو توی کنترلر کننده های داخلی SQL که توی F3 وجود دارن قرار بدین. درحقیقت کلاس DB\SQL مستقیماً از کلاس PDO خود PHP مشتق شده و درنتیجه اگه بخواین کنترل کاملی داشته باشین، به تمام اجزا و عناصر داخلی PDO توی هر فرآیند دسترسی خواهید داشت.

تراکنش ها (Transaction)
خوب یه مثال دیگه. بجای اینکه یه دستور رو بعنوان پارامتر به ()db->exec$ بدیم، میتونین یه آرایه از دستورات SQL بفرستین:
$db->exec(array(
    "DELETE FROM diet WHERE food='cola'",
    "INSERT INTO diet (food) VALUES ('carrot')"
    'SELECT * FROM diet',
));

F3 اینقدر هوشمند هست که بفهمه وقتی یه آرایه از دستورات SQL میفرستین، به معنای یه ترنزکشن (تراکنش) دسته جمعی SQL هست. نیاز نیست شما نگران rollback و commit توی SQL باشین چون فریمورک خودش اگه مشکلی توی هرکدوم از دستورات پیش بیاد، وضعیت دیتابیس رو به قبل از اجرای اولین دستور برمیگردونه و اگه خطایی رخ نداد، F3 خودش تمام تغییرات رو توی دیتابیس ثبت میکنه.

البته یه راه دیگه هم برای شروع و خاتمه Transaction بصورت دستی وجود داره:
$db->begin();
$db->exec("DELETE FROM diet WHERE food='cola'");
$db->exec("INSERT INTO diet (food) VALUES ('carrot')");
$db->exec('SELECT * FROM diet');
$db->commit();

اگه خطایی توی دستورات رخ بده، rollback بطور خودکار صدا زده میشه و شما لازم نیست کاری انجام بدین.

برای بدست آوردن فهرست تمام دستوراتی که برای دیتابیس ارسال شدن، این کد رو بنویسید:
echo $db->log();

کوئری های پارامتری
ارسال پارامترهای متنی به دستورات SQL خطرناکه. این دستور رو درنظر بگیرین:
$db->exec("SELECT * FROM users WHERE username='" . $f3->get('POST.userID') . "'");

اگه متغیر userID که به روش POST ارسال شده، از هیچ فرآیند خنثی سازی کدهای مخرب رد نشه، یک کاربر بدخواه ممکنه این متن رو بفرسته و باعث بشه دیتابیس شما به شکل غیر قابل بازگشتی خراب بشه (اگه پشتیبان نداشته باشین) :
admin"; DELETE FROM users; SELECT "1

خوشبختانه کوئری های پارامتری به شما کمک میکنن این ریسکها رو از بین ببرین:
$db->exec('SELECT * FROM users WHERE userID=?', $f3->get('POST.userID'));

اگه F3 متوجه بشه که مقدار پارامتر/توکن کوئری یه رشته است، لایه اتصال داده پشت صحنه رشته رو خنثی سازی (Escape) میکنه و اگه لازم بود، کوتیشن هم بهش اضافه میکنه.

مثال قسمت قبلی ما اگه اینطوری نوشته بشه، درمقابل SQL Injection خیلی امن تر خواهد بود:
$db->exec(
    array(
        'DELETE FROM diet WHERE food=:name',
        'INSERT INTO diet (food) VALUES (?)',
        'SELECT * FROM diet',
    ),
    array(
        array(':name' => 'cola'),
        array(1 => 'carrot'),
        NULL,
    )
);

CRUD (با کلی حالت مختلف)
F3 یه نگاشت کننده ارتباط اشیاء (ORM) داره که بین برنامه شما و داده ها قرار میگیره و نوشتن برنامه هایی که عملیات رایج روی داده ها انجام میدن (مثل ساخت، استخراج، بروزرسانی و حذف - CRUD) رو روی دیتابیسهای SQL و NoSQL خیلی راحت میکنه. نگاشت کننده های داده ها بیشتر کارها رو با نگاشت کردن عملیات روی اشیاء PHP به کوئریهای متناظرشون انجام میدن.

فرض کنید یه دیتابیس MySQL دارین که شامل جدولی از کاربران برنامه شماست (SQLite و PostgreSQL و SQL Server و Sybase هم فرقی ندارن). ممکنه این جدول با کوئری مثل این درست شده باشه:
CREATE TABLE users (
    userID VARCHAR(255),
    password CHAR(32),
    visits INT,
    PRIMARY KEY(userID)
);

نکته: MongoDB و NoSQL ذاتاً بدون طرح و الگو (Schema) هستن. F3 خودش یه پیاده سازی داخلی سبک و سریع از NoSQL داره به اسم Jig که از فایلهای معمولی PHP که داده ها رو بصورت Serialize شده یا JSON ذخیره میکنن. این لایه ها نیاز به ساختار داده پیچیده ای ندارن. فیلدها میتونن توی رکوردهای مختلف با هم فرق کنن. حتی میشه هر زمان خواستین، فیلد جدید تعریف کنین یا فیلدی رو حذف کنید.

حالا به SQL برگردیم. اول باید به دیتابیس وصل بشیم:
$db = new DB\SQL(
    'mysql:host=localhost;port=3306;dbname=news',
    'root',
    'ncis'
);

برای استخراج یک رکورد از جدولمون:
$user = new DB\SQL\Mapper($db, 'users');
$user->load(array('userID=?', 'tarzan'));

خط اول یه شئ نگاشت کننده داده میسازه که با جدول users توی دیتابیس ارتباط داره. پشت صحنه، F3 ساختار جدول users رو استخراج میکنه و تشخیص میده که چه فیلد یا فیلدهایی بعنوان کلید اصلی جدول انتخاب شده. در این مرحله، شئ نگاشت کننده (Mapper) هنوز شامل هیچ داد های نیست (بهش میگن وضعیت خشک یا Dry State) و شئ user$ هیچی بجز یه شئ ساختاربندی شده نیست. البته متدهایی داره که برای انجام عملیات اولیه CRUD بعلاوه یکسری امتیازات جانبی که بعداً میبینید، بدرد میخورن. حالا، برای استخراج یک رکورد از جدول users که توی فیلد userID مقدار tarzan رو داره، ما از متد load استفاده میکنیم. به این کار، مرطوب سازی خودکار (Auto Hydrating) شئ نگاشت کننده میگن که اون رو از وضعیت خشک خارج میکنه.

ساده بود، نه؟ F3 میفهمه که یک جدول SQL ساختاری داره که توی موتور پایگاه داده ها ذخیره شده. برخلاف بقیه فریمورکها، F3 نیاز به هیچ تعریف کلاس اضافه ای نداره (مگه اینکه بخواین نگاشت کننده های داده ها رو توسعه بدین تا اشیاء پیچیده تری بسازین). هیچ نگاشتی از فیلدهای شئ به فیلدهای دیتابیس لازم نیست انجام بدین. نیاز به تولیدکننده های کد (Code Generator) نیست و درنتیجه اگه دیتابیس تغییر کرد، نیاز نیست دوباره کد رو تولید کنید. نیاز به فایلهای مضحک XML/YAML برای تنظیم کردن مدلتون ندارین. دستورات پیچیده لازم نیست تا یه رکورد رو استخراج کنید. با F3، تغییر یه فیلد ساده varchar توی جدول SQL شما نیاز به کوچکترین تغییری توی کد برنامه نداره. برای وفاداری به جنبه «جداسازی مفاهیم» توی MVC، مدیران پایگاه داده ها همونقدر روی تغییر ساختار دیتابیس کنترل دارن که یه طراح قالب روی قالبهای HTML/XML کنترل داره.

اگه میخواین با دیتابیسهای NoSQL کار کنین، شباهت دستورات، خیره کننده است. برای مثال به کد نگاشت کننده داده MongoDB دقت کنید:
$db = new DB\Mongo('mongodb://localhost:27017', 'testdb');
$user = new DB\Mongo\Mapper($db, 'users');
$user->load(array('userID' => 'tarzan'));

همینطور به کد Jig نگاه کنید که شبیه موتور قالب F3 هست:
$db = new DB\Jig('db/data/', DB\Jig::FORMAT_JSON);
$user = new DB\Jig\Mapper($db, 'users');
$user->load(array('@userID=?', 'tarzan'));

ORM هوشمند SQL
فریمورک بطور خودکار فیلدهای توی جدول رو به فیلدهای متناظر توی شئ نگاشت کننده نسبت میده. برای مثال، وقتی میگیم:
$user = new DB\SQL\Mapper($db, 'users');

فیلدهای user->userID$ و user->password$ به ترتیب به فیلدهای userID و password توی جدول ما در دیتابیس نگاشت میشن.

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

F3 ساختار نگاشت دهنده داده خودش رو از ساختار دیتابیس مشتق میکنه. هیچ کار حدسی وجود نداره. F3 تفاوتهای بین MySQL و SQLite و MSSQL و Sybase و PostgreSQL رو تشخیص میده.

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

خوب حالا ما میخواین تعداد بازدید کاربر رو افزایش بدیم و رکورد متناظر با اون رو در جدول users تغییر بدیم. برای این کار باید این شکلی کار کنیم:
$user->visits++;
$user->save();

اگه بخوایم یه رکورد جدید درج کنیم، این شکلی کار میکنیم:
$user = new DB\SQL\Mapper($db, 'users');
// or $user = new DB\Mongo\Mapper($db, 'users');
// or $user = new DB\Jig\Mapper($db, 'users');
$user->userID = 'jane';
$user->password = md5('secret');
$user->visits = 0;
$user->save();

همونطور که میبینید، ما از همون متد save استفاده کردیم. اما F3 از کجا میفهمه چه موقع باید یه رکورد جدید درج کنه و چه موقع باید رکورد رو بروزرسانی کنه؟ در زمانی که یک رکورد رو استخراج میکنید، فریمورک وضعیت کلیدهای اصلی رو بررسی میکنه (یا id_ اگه از MongoDB و Jig استفاده کنید) و درنتیجه میفهمه که چه رکوردی باید بروزرسانی یا حذف بشه (حتی اگه کلیدهای اصلی رو تغییر بدین). اما رکوردی که بطور دستی پر شده و از دیتابیس خونده نشده، هیچ مقداری برای کلید اصلی خودش نداره و درنتیجه وقتی ما شئ user$ رو ایجاد کردیم و فیلدهاش رو یکی یکی با مقادیر خودمون پر کردیم (بدون خوندن از دیتابیس)، F3 میفهمه که این یه رکورد جدیده و باید اون رو درج کنه نه بروزرسانی.

یک شئ نگاشت بعد از فراخوانی ()save خالی نمیشه. اگه میخواین یه رکورد جدید ثبت کنید، باید اول شئ نگاشت رو با فراخوانی ()reset تخلیه کنید:
$user->reset();
$user->userID = 'cheetah';
$user->password = md5('unknown');
$user->save();

صدا زدن ()save برای دفعه دوم بدون صدا زدن ()reset باعث میشه رکوردی که در حال حاضر توسط شئ نگاشت بهش اشاره شده، بروزرسانی بشه.

پیش بینی های احتیاطی برای جداول SQL
درسته که تعریف کلید اصلی توی همه جداول دیتابیس یک موضوع منطقیه، اما F3 جلوی استفاده شما از اشیاء نگاشت داده که با جداول فاقد کلید اصلی کار میکنن رو نمیگیره. تنها مشکلی که پیش میاد اینه که شما نمیتونید یک رکورد نگاشت شده رو حذف یا بروزرسانی کنین چون واقعاً برای F3 راهی وجود نداره که بفهمه کدوم رکورد رو دارین بهش اشاره میکنن و ضمناً موقعیتهای مکان یابی (اینکه الان روی چندمین رکورد هستین) قابل اطمینان نیستن. ردیف سطرها رو نمیشه بین موتورهای مختلف SQL انتقال داد و به راه انداز دیتابیس PHP هم ممکنه ارسال نشن.

برای حذف یه رکورد از جدولمون، از متد ()erase استفاده میکنیم. مثال:
$user = new DB\SQL\Mapper($db, 'users');
$user->load(array('userID=? AND password=?', 'cheetah', 'chimp'));
$user->erase();

دستور زبان کوئری Jig هم خیلی شبیهه:
$user = new BD\Jig\Mapper($db, 'users');
$user->load(array('@userID=? AND @password=?', 'cheetah', 'chimp'));
$user->erase();

و همینطور MongoDB :
$user = new DB\Mongo\Mapper($db, 'users');
$user->load(array('userID' => 'cheetah', 'password' => 'chimp'));
$$user->erase();

وضعیت داده های شئ نگاشت کننده
برای اینکه بفهمیم یه نگاشت کننده با داده های معتبری از یک رکورد جدول پر شده یا نه، از متد ()dry استفاده میکنیم:
if($user->dry()) {
    echo 'No record matching criteria';
}

فراتر از CRUD
درمورد CRUD صحبت کردیم. یکسری متدهای بیشتر توی نگاشت کننده وجود داره که میتونه مفید باشه:
$user = new DB\SQL\Mapper($db, 'users');
$user->copyFrom('POST');
$user->save();

متد ()copyFrom شئ نگاشت کننده رو با عناصر یه متغیر فریمورک از نوع آرایه پر میکنه. اندیسهای آرایه ای که همنام با فیلدهای شئ (و درنتیجه جدول دیتابیس) باشن، توی فیلدهای متناظرشون قرار میگیرن. درنتیجه وقتی فرم ارسال میشه، اگه عنصری داخلش باشه که اسمش userID باشه، درصورتی که متد با روش POST ارسال بشه، توی متغیر ['POST['userID_$ در PHP میشه که معادل متغیر POST.userID توی F3 هست و درنتیجه توی فیلد user->userID$ میشه و بعد از ذخیره شدن، توی فیلد userID رکورد جدید در دیتابیس ثبت میشه.

نقل قول:خطر: بطور پیشفرض copyFrom تمام آرایه ارائه شده رو تحت پوشش قرار میده. درنتیجه ممکنه درصورتی که کاربر یکسری فیلدهای اضافه که توی دیتابیس شما هست ولی شما توی فرم نگذاشته بودین رو با ابزارهایی مثل FireBug یا Developer Tools (یا حتی با ساخت یه فرم دیگه) ارسال کنه، یه حفره امنیتی توی برنامه شما پیش بیاد. از پارامتر دوم متد copyFrom برای ساخت یه فیلتر برای خلاص شدن از فیلدهای ناخواسته استفاده کنید. برای توضیحات بیشتر مستندات متد copyFrom رو ببینید.

ازطرف دیگه ممکنه یه جاهایی بخوایم فیلدهای دیتابیس رو توی یه متغیر F3 از نوع آرایه قرار بدیم تا بعداً (فرضاً توی قالب) بخوایم ازش استفاده کنیم:
$user = new DB\SQL\Mapper($db, 'users');
$user->load(array('userID=?', 'jane'));
$user->copyTo('user');

حالا میتونیم توی قالبمون از توکن {{user.userID@ }} استفاده کنیم. مثال:
<input type="text" name="userID" value="{{ @user.userID }}" />

متدهای save و update و copyFrom و نسخه های پارامتری load و erase درمقابل SQL Injection امن هستن.

پیمایش و صفحه بندی
بطور پیشفرض متد load فقط اولین رکوردی رو که با شرایط موردنظر مطابقت داشته باشه میخونه. اگه بیشتر از یک رکورد لازم دارین، میتونین از متد skip استفاده کنید تا توی رکوردهایی که با شرایط تعیین شده مطابقت دارن، حرکت کنین:
$user = new DB\SQL\Mapper($db, 'users');
$user->load(array('visits > ?', 3));

// For MongoDB users:
// $user = new DB\Mongo\Mapper($db, 'users');
// $user->load(array('visits' => array('$gt' => 3)));

// If you prefer Jig:
// $user = new DB\Jig\Mapper($db, 'users');
// $user->load('@visits>?', 3);

// Display the userID of the first record that matches the criteria
echo $user->userID;
// Go to the next record that matches the same criteria
$user->skip(); // Same as $user->skip(1);
// Back to the previous record
$user->skip(-1);
// Move three records forward
$user->skip(3);

ممکنه بخواین از ()user->next$ بعنوان جایگزین ()user->skip$ و از متد ()user->prev$ بجای (1-)user->skip$ استفاده کنین.

میتونید از متد dry برای بررسی اینکه از محدوده خارج نشدین استفاده کنین. مثال برای نمایش تمام userID ها:
$user->load(array('visits>?', 3));
while(!$user->dry()) {
    echo '<p>' . $user->userID . '</p>' . PHP_EOL;
    $user->next();
}

متد load یه پارامتر دوم هم داره که اختیاریه: آرایه از انتخابها که بصورت زوج کلید/مقدار هست:
$user->load(
    array('visits>?', 3),
    array(
        'order' => 'userID DESC',
        'offset' => 5,
        'limit' => 3,
    )
);

این دستور، کوئری زیر رو اجرا میکنه:
SELECT * FROM users
WHERE visits>3
ORDER BY userID DESC
LIMIT 3 OFFSET 5;

این یکی از راههای نمایش اطلاعات در قسمتهای کوچک محسوب میشه. یکی دیگه از راهها صفحه بندی نتایجه:
$page = $user->paginate(2, 5, array('visits>?', 3));

توی سناریوی بالا، F3 رکوردهایی رو که با شرط visits>3 تطبیق دارن پیدا میکنه. بعد نتایج رو به 5 رکورد (در صفحه) تقسیم میکنه و رکوردهای مربوط به صفحه 2 (با شروع صفحات از 0) رو برمیگردونه. خروجی F3 که توی page$ قرار میگیره، یه آرایه شامل عناصر زیره:
[subset] آرایه ای از اشیاء نگاشت کننده که با شرایط مطابقت داشتن
[total] تعداد کل رکوردهای تمام صفحات
[limit] اندازه صفحه که در اینجا 5 هست
[count] تعداد صفحات
[pos] موقعیت واقعی زیرمجموعه

موقعیت واقعی زیرمجموعه اگه پارامتر اول paginate منفی باشه یا از تعداد کل صفحات بیشتر باشه، NULL خواهد بود.

فیلدهای مجازی
گاهی وقتا میخواین نتیجه محاسبه شده یه فیلد یا یه مقدار استفاده شده از یه جدول دیگه رو برگردونین. اینجور وقتها فیلدهای مجازی به کار میان. ORM کوچک SQL به شما این اجازه رو میده که روی داده های دریافتی از فیلدهای موجود کار کنید. فرض کنید جدول زیر رو داریم:
CREATE TABLE products (
    productID VARCHAR(255),
    description TEXT,
    supplierID VARCHAR(255),
    unitprice DECIMAL(10, 2),
    quantity INT,
    PRIMARY KEY (productID)
);

دقت کنید که فیلد totalPrice وجود نداره ولی ما میتونیم به فریمورک بگیم تا از موتور دیتابیس بخواد اون رو با کمک فیلدهای موجود حساب کنه و به ما تحویل بده:
$item = new DB\SQL\Mapper($db, 'products');
$item->totalPrice = 'unitprice*quantity';
$item->load(array('productID=:pid', ':pid' => 'apple'));
echo $item->totalPrice;

کد بالا یه فیلد مجازی به اسم totalPrice تعریف میکنه که با ضرب unitprice در quantity محاسبه میشه. نگاشت کننده SQL فرمول/قانون رو ذخیره میکنه و درنتیجه وقتی میخوایم اطلاعات رو از دیتابیس بگیریم، میتونیم از فیلدمجازی خودمون مثل یه فیلد نگاشت شده معمولی استفاده کنیم.

میتونید فیلدهای مجازی پیچیده تری هم داشته باشین:
$item->mostNumber = 'MAX(quantity)';
$item->load(();
echo $item->mostNumber;

ایندفعه، F3 محصولی که بیشترین تعداد رو داره میخونه. دقت کنید که متد ()load هیچ شرطی تعریف نکرده، درنتیجه تمام رکوردهای دیتابیس پردازش میشن. البته فیلد مجازی mostNumber در کنار شرایطی که برای load مشخص میکنید، به شما اجازه میده که بیشترین تعداد محصولاتی که شرایط خاصی دارن و توی گروه مشخصی قرار میگیرن رو هم پیدا کنید.

حتی میتونید یک مقدار رو از جدول دیگه بخونید:
$item->supplierName = 'SELECT name FROM suppliers WHERE products.supplierID = suppliers.supplierID';
$item->load();
echo $item->supplierName;

هربار که یک رکورد رو از جدول proucts بارگذاری کنید، ORM فیلد supplierID رو از این جدول با فیلد supplierID از جدول suppliers مطابقت میده و فیلد name اون رو استخراج میکنه و توی فیلد مجازی supplierName میگذاره.

برای حذف یه فیلد مجازی از دستور unset خود PHP استفاده کنید:
unset($item->totalPrice);

دستور isset رو هم میتونید برای چک کردن اینکه یه فیلد مجازی تعریف شده یا نه، استفاده کنید:
if(isset($item->totalPrice)) {
    echo $item->totalPrice;
}

دقت کنید که فیلدهای مجازی باید قبل از استخراج اطلاعات تعریف بشن. ORM محاسبه اصلی و استخراج از جداول دیگه رو انجام نمیده و این کار، وظیفه موتور پایگاه داده است که تمام این کارهای سخت رو انجام بده.

جوینده، یابنده است
اگه نیاز به حرکت رکورد به رکورد بین نتایج ندارین، میتونید کل رکوردها رو با یه شلیک بدست بیارین:
$frequentUsers = $user->find(array('visits>?', 3), array('order' => 'userID'));

معادل Jig کد بالا:
$frequentUsers = $user->find(array('@visits>?', 3), array('order' => 'userID'));

و نسخه MongoDB :
$frequentUsers = $user->find(array('visits' => array('$gt' => 3)), array('userID' => 1));

متد ()find جدول users رو جستجو میکنه تا رکوردهایی که با شرایط مطابقت دارن رو پیدا کنه. نتایج رو براساس فیلد userID مرتب میکنه و نتیجه رو بصورت یه آایه از اشیاء نگاشت کننده برمیگردونه. پس دقت کنید که ('find('visits>3 با دستور ('load('visits>3 فرق میکنه. اولی یه آرایه از تمام رکوردهای پیدا شده رو برمیگردونه و دومی، یک ارجاع به اولین رکوردی هست که با شرایط تطبیق داره. متد find تأثیری روی skip نداره.

نقل قول:مهم: تعریف یک شرط خالی، NULL یا رشته با طول صفر بعنوان پارامتر اول متدهای find و load باعث استخراج تمام رکوردها میشه. مطمئن باشین که میدونین چیکار میکنین وگرنه ممکنه از محدودیت حافظه (memory_limit) تعیین شده توسط PHP توی جداول بزرگ تجاوز کنین.

دستور زبان متد find این شکلیه:
find(
    $criteria,
    array(
        'group' => 'foo',
        'order' => 'foo,bar',
        'limit' => 5,
        'offset' => 0,
    )
);

متد find یه آرایه از اشیاء برمیگردونه. هر شئ، یه نگاشت کننده به رکوردی هست که با شرط موردنظر مطابقت داشته:
$place = new DB\SQL\Mapper($db, 'places');
$list = $place->find("state='New York'");
foreach($list as $obj) {
    echo $obj->city . ', ' . $obj->country;
}

اگه میخواین شئ نگاشت کننده رو به یه آرایه معمولی تبدیل کنین، از متد cast استفاده کنید:
$array = $place->cast();
echo $array['city'] . ', ' . $array['country'];

برای اینکه بدونید چند رکورد توی جدول با شرایط شما مطابقت داره، از متد count استفاده کنید:
if(!$user->count(array('visits>?', 10)) {
    echo 'We need a better ad plan!';
}

یه متد دیگه هم هست به اسم select که شبیه find عمل میکنه (ازنظر خروجی) ولی کنترل بیشتری روی فیلدهای بازگشتی به شما میده و ساختارش شبیه SQL هست:
select(
    'foo, bar, MIN(baz) AS lowest',
    array('foo > ?', 3),
    array(
        'group' => 'foo, bar',
        'order' => 'baz',
        'limit' => 5,
        'offset' => 3,
    )
);

متدهای find و select محتوای شئ نگاشت رو تغییر نمیدن. پس صدا زدن متد dry برای اینکه بفهمیم چیزی توسط این متدها پیدا شده یا نه، معنا نداره. اگه رکوردی پیدا نشه، این متدها یه آرایه خالی برمیگردونن.

نقل قول:به خاطر بسپارین:
متد load شئ نگاشت کننده جاری رو پر میکنه درحالی که find آرایه ای از اشیاء نگاشت کننده پر شده، برمیگردونه.

گزارش گیری
اگه خواستین بفهمین چه دستورات SQL خاصی مستقیماً از برنامه شما (یا غیر مستقیم ازطریق اشیاء نگاشت کننده) اجرا شده تا فرضاً گلوگاههای برنامه و جاهایی که کارآیی کم میشه رو پیدا کنین، میتونید از دستور ساده زیر استفاده کنید:
echo $db->log();

F3 جریان تمام دستوراتی که به پایگاه داده SQL ارسال شده رو همراه با زمانی که اجرای هرکدوم طول کشیده رو نگهداری میکنه. این دقیقاً همون اطلاعاتیه که شما برای دستکاری کارآیی برنامه بهش نیاز دارین.

یه وقتهایی اینها کافی نیست
در اکثر مواقع، میتونید با امکانات راحتی که متدهای نگاشت کننده داده ها در اختیارتون گذاشته، زندگی راحتی داشته باشین. اگه خواستین فریمورک یکسری کارهای سنگینتر انجام بده، میتونید کلاس نگاشت کننده رو با مشتق کردن کلاسهای خودتون از اون و اضافه کردن متدهای بیشتر، گسترش بدین. البته این کار مستلزم اینه که دستهاتون رو به یکسری کدهای SQL سطح پایین آلوده کنید:
class Vendor extends DB\SQL\Mapper
{
    // Instantiate mapper
    function __cunstruct(DB\SQL $db)
    {
        // This is where the mapper and DB structure synchronization occurs
        parent::__construct($db, 'vendors');
    }

    // Specialized query
    function listByCity()
    {
        return $this->select('vendorID,name,city', null, array('order' => 'city DESC'));
        /*
        We could have done thesame thing with plain SQL:
        return $this->db->exec('SELECT vendorID,name,city FROM vendors ORDER BY city DESC;');
        */
    }
}

$vendor = new Vendor;
$vendor->listByCity();

توسعه نگاشت کننده های داده به این شکل، راه ساده ای برای ساخت مدلهای دیتابیس اختصاصی برنامه خودتونه.

مزایا و معایب
اگه با SQL آشنایی دارین، شاید بگین هر چیزی توی ORM رو میشه با کوئریهای سنتی SQL انجام داد. قطعاً همینطوره. میتونیم بدون شنونده های رویداد (Event Listeners) اضافه و با کمک ماشه های دیتابیس (DB Triggers) و روال های ذخیره شده (Stored Procedures) کارهامون رو انجام بدیم. ORM فقط یه سربار غیر ضروریه. اما نکته اینجاست که نگاشت کننده های داده به شما قابلیتهای اضافه تری بخاطر استفاده از اشیاء برای معادلسازی موجودیتهای دیستابیس میدن. بعنوان یه برنامه نویس، میتونید کدتون رو سریعتر بنویسید و خلاقانه کار کنید. برنامه نهایی اگه کوتاهتر نباشه، قطعاً تمیزتر خواهد بود. اما باید یه تعادل بین مزایای ORM و سرعت برقرار کنید. بخصوص وقتی که پای مدیریت پایگاه داده های پیچیده و بزرگ میاد وسط. به خاطر بسپارین که همه ORMها - فرقی نمیکنه که چقدر ساده یا پیچیده باشن - چیزی بیشتر از یه لایه دسترسی دیگه نیستن. اونها کماکان باید کار رو به لایه زیرین موتور SQL پاس بدن.

توی طراحی ORM فریمورک F3 متدهایی برای اتصال مستقیم اشیاء به هم (یعنی Join های SQL) ارائه نشده، چون این کار مثل بازکردن قوطی کرمهاست! این کار باعث میشه برنامه شما پیچیده تر از اون چیزی بشه که باید باشه و مواردی مثل بارگذاری اشیاء به روش کند (Lazy - بارگذاری اطلاعات جدول وابسته در لحظه نیاز) یا حریصانه (Eager - بارگذاری اطلاعات جدول وابسته در لحظه استخراج اطلاعات جدول اصلی) هم وجود داره که گاهی اوقات به بن بست منجر میشه و یا حتی ممکنه هماهنگی و تطابق اطلاعات توی اشیاء نگاشت کننده با اطلاعات اصلی دیتابیس بهم بریزه. راههای غیر مستقیم برای رسیدن به این موارد ازطریق نگاشت کننده های SQL وجود داره (استفاده از فیلدهای مجازی)، ولی شما باید خودتون با قبول مسئولیت ریسکش، براش برنامه نویسی کنید.

اگه میخواین مفاهیم «خالص» شئ گرایی رو توی برنامه خودتون پیاده سازی کنین تا همه داده ها رو در بر بگیرن (چون هر چیزی یه شئ محسوب میشه)، فراموش نکنید که داده ها تقریباً همیشه بیشتر از خود برنامه عمر میکنن. برنامه شما ممکنه خیلی قبل از اینکه اطلاعات شما از حافظه پاک بشه، تمام و از حافظه خارج شده باشه. بی دلیل یه لایه دیگه از پیچیدگی به برنامه خودتون با کمک اشیاء در هم پیچیده شده و کلاسهایی که خیلی از Schema و ساختار فیزیکی دیتابیس فاصله گردن، اضافه نکنید.

قبل از اینکه اشیاء مختلف رو در هم ادغام کنید تا جداول پشت صحنه دیتابیس رو دستکاری کنید، درمورد این موضوع فکر کنید: ساخت صفحات نمایش برای نشون دادن ارتباطها و رویدادها برای تعریف رفتار اشیاء توی موتور پایگاه داده ها کارآمدتره. دیتابیسهای رابطه ای برای مدیریت کردن جداول موقت و نمایشی، جداول ادغام شده و رویدادها طراحی شدن. اونها مخازن داده کودن و ابلهی نیستن. جداولی که توی یک جدول موقت ادغام میشن، ازنظر برنامه مثل یک جدول مجزا دیده میشن و F3 میتونه بطور خودکار یک جدول موقت رو مثل یک جدول معمولی نگاشت کنه. معادلسازی Joinها بعنوان اشیاء رابطه ای توی PHP کندتر از کد ماشین، جبر رابطه ای و منطق بهینه سازی موتور پایگاه داده هاست. بعلاوه، ادغام جداول بصورت تکراری توی برنامه شما (نه توی دیتابیس) میتونه علامت این باشه که طراحی پایگاه داده های شما نیازمند بازبینی و اصلاحه و جداول موقت جزء اساسی استخراج داده ها محسوب میشن. اگه یه جدول اطلاعاتی رو از یه جدول دیگه مرتب بخونه، بهتره با کمک یه جدول موقت ساختار دیتابیس رو نرمالسازی کنید. بعد یه شئ نگاشت کننده بسازین که از روی اون جدول موقت درست شده. این کار هم سریعتره و هم زحمت کمتری لازم داره.

این دستور رو ببینید که یه جدول موقت توی موتور دیتابیس شما میسازه:
CREATE VIEW combined AS
    SELECT
        projects.project_id AS project,
        users.name AS name
    FROM projects
    LEFT OUTER JOIN USERS ON
        projects.project_id=users.project_id AND
        projects.user_id=users.user_id;

کد برنامه شما خیلی ساده میشه چون نیاز به مدیریت دو شئ نگاشت (یکی برای پروژه ها و یکی دیگه برای کاربران) اونهم فقط برای بدست آوردن اطلاعات از دو جدول ادغام شده، نخواهد داشت:
$combined = new DB\SQL\Mapper($db, 'combined');
$combined->load(array('project=?', 123));
echo $combined->name;

ترفند: از ابزارها به همون شکلی که براش طراحی شدن استفاده کنید. F3 همین الان هم یه کلاس کمکی SQL داره. اگه نیاز به یه چکش بزرگتر داشتین، از اون استفاده کنید Ysmile

سعی کنید یه تعادل بین راحتی و کارآیی پیدا کنید. SQL همیشه نقطه بازگشت شما در مواقعی هست که روی ساختارهای پیچیده و قدیمی داده ها کار میکنید.
افزونه ها (Plug-Ins)
افزونه ها، چیزی بیشتر از کلاسهایی که بطور خودکار بارگذاری میشن نیستن که از امکانات داخلی فریمورک برای گسترش امکانات و کاربردهای F3 استفاده میکنن. اگه شما هم علاقمند به همکاری هستین، یه یادداشت توی ناحیه بحث Fat-Free توی گروههای گوگل بگذارین یا به ما توی کانال FreeNode توی IRC تحت عنوان fatfree# اطلاع بدین. ممکنه یکی دیگه داره روی پروژه مشابه شما کار میکنه. جامعه کاربری فریمورک خیلی از شما تشکر میکنه اگه تلاشهامون رو یکپارچه کنیم (موازی کاری انجام ندیم).

تصویر

تصاویر CAPTCHA
مواردی هست که شما میخواین فرمهاتون درمقابل روباتهای Spam و اسکریپتهای خودکار مخرب، امنیت بیشتری داشته باشه. F3 یه متد به اسم ()captcha داره که تصاویری رو با متن تصادفی درست میکنه و به شکلی نمایش میده که فقط توسط انسانها قابل تشخیص باشه.
$img = new Image();
$img->captcha('fonts/CoolFont.ttf', 16, 5, 'SESSION.captcha_code');
$img->render();

این مثال یه تصویر تصادفی با فونت مشخص شده توسط شما تولید میکنه. پوشه /fonts باید داخل مسیر UI برنامه ایجاد شده باشه. پارامتر دوم اندازه قلم رو مشخص میکنه. بزرگنمایی 2 برابر هم اعمال میشه. یعنی اگه سایز فونت رو 16 بگذارین، تصویری با ارتفاع 32 پیکسل ایجاد میشه (بخاطر بهتر دیده شدن متن). پارامتر سوم تعداد کارکترهای متن Captcha هست که توش از کارکترهای هگزادسیمال (0-9 و A-F) استفاده میشه. مقادیر معتبر برای پارامتر سوم، بین 4 تا 13 هست.

آخرین پارامتر نمایانگر یه متغیر F3 هست. از اون برای نگهداری متن معادل تصویر Captcha استفاده کنید. برای اینکه متغیر رو توی تمام صفحات و همچنین Refresh شدن صفحه امن کنیم، ما توی مثال بالا از یه متغیر سشن استفاده کردیم. SESSION.captcha_code معادل ['SESSION['captcha_code_$ هست و شما میتونید بعداً برای چک کردن اینکه متن واردشده توسط کاربر درسته یا نه، ازش استفاده کنید.

پردازش تصاویر
پلاگین Image امکانات خیلی بیشتری برای پردازش تصاویر در اختیارتون میگذاره مثل تغییر اندازه، برش زدن (Crop)، قراردادن یه تصویر روی یکی دیگه (Watermark)، سیاه و سفید کردن، فیلترهایی مثل مات کردن و تبدیل به عکس قدیمی (Sepia)، تنظیم روشنایی و وضوح (Brightness و Contrast) و خیلی چیزهای دیگه. لطفاً یه نگاهی به مرجع API کلاس Image بندازین تا با سایر امکاناتش آشنا بشین.

گزارش گیری (Log)
ببینید چقدر تولید یه گزارش ساز سفارشی برای ذخیره کردن رویدادهای مختلف برنامه شما راحته:
$logger = new Log('app-events.log');
$logger->write('User John logged in.');

نشانه گذاری Markdown
شما میتونید علائم Markdown موجود در متن رو به HTML تبدیل کنید:
$filePath = 'content/readme.md';
$fileContent = $f3->read($filePath); // read file contents
echo Markdown::instance()->convert($fileContent);

وب

گرفتن اطلاعات از سایر سایتها
ما تقریباً هر چیزی که توی فریمورک برای ساخت یه سایت مستقل موجود بود رو گفتیم (البته خیلی خلاصه و بعداً جزئیات هرکدوم رو بطور کامل پوشش میدیم). برای اغلب برنامه ها، این امکانات به خوبی جوابگوی نیاز شما خواهد بود. اما اگه برنامه شما نیاز به خوندن اطلاعات از یه وب سرور دیگه توی شبکه داشته باشه چیکار میکنین؟ F3 پلاگین وب رو برای کمک به شما در این شرایط ارائه کرده:
$web = new Web;
$request = $web->request('http://www.ncis.ir/');
// another way to do it:
$request = Web::instance()->request('http://www.ncis.ir/');

این مثال ساده، یه درخواست HTTP به صفحه ای که توی آدرس www.ncis.ir وجود داره میفرسته و نتیجه رو توی متغیر request$ ذخیره میکنه. خروجی متد ()request یه آرایه است که شامل پاسخ HTTP و مواردی مثل ['request['headers$ و ['request['body$ و... است. ما میتونستیم خروجی رو با متد ()set فریمورک هم ذخیره کنیم یا مستقیماً با echo نمایش بدیم. گرفتن خروجی HTML یه سایت دیگه ممکنه خیلی کاربردی بنظر نیاد ولی قطعاً توی برنامه های ReSTful مثل کوئری فرستادن برای یه سرور CouchDB خیلی سودمنده:
$host = 'localhost:5984';
$web->request($host, '/_all_dbs');
$web->request($host, '/testdb/', array('method' => 'PUT'));

ممکنه متوجه شده باشین که میتونیم پارامترهای اضافه هم برای متد ()request مشخص کنیم:
$web->request(
    'https://www.example.com:443?' .
    http_build_query(array(
        'key1' => 'value1',
        'key2' => 'value2',
    )),
    array(
        'headers' => array(
            'Accept: text/html,application/xhtml+xml,applciation/xml',
            'Accept-Language: en-us',
        ),
        'follow_location' => false,
        'max_redirects' => 30,
        'ignore_errors' => true,
    )
);

اگه متغیر CACHE توی فریمورک فعال شده باشه و اگه سرور خارجی به برنامه شما دستور بده که پاسخ درخواست HTTP رو کش کنه، F3 خودش رو با درخواست سازگار میکنه و نسخه کش شده پاسخ رو هردفعه فریمورک درخواست مشابهی از برنامه شما دریافت کنه، استخراج میکنه (درست شبیه یه مرورگر واقعی).

F3 از هر مفهومی که توی سرور وب شما برای اجرای متد ()request قابل استفاده است، استفاده میکنه: پوششهای جریان (Stream wrappers) توی PHP (یعنی allow_url_fopen) و ماژول cURL یا سوکتهای سطح پایین.

مدیریت دانلود فایلها
F3 یه ابزار برای ارسال فایلها به کلاینت (یعنی مجبور کردن به دانلود فایل) داره. میتونید از اون برای پنهان کردن مسیر واقعی فایلهای دانلودی خودتون استفاده کنید. این کار به شما یه لایه اضافی امنیت میده، چون کاربران نمیتونن فایل رو مستقیماً دانلود کنن چون اسم واقعی فایل و مسیر ذخیره سازی اون رو نمیدونن. مثالی از روش استفاده:
$f3->route('GET /downloads/@filename', function($f3, $args) {
    // send() method returns false if file doesn't exist
    if(!Web::instance()->send('/real/path/' . $args['filename'])) {
        // Generate an HTTP 404 error
        $f3->error(404);
    }
});

برنامه های راه دور و توزیع شده
متد ()request رو میشه توی برنامه های پیچیده SOAP یا XML-RPC هم استفاده کرد، اگه نیاز به یه وب سرور دیگه برای پردازش داده ها روی کامپیوتر خودتون دارین تا قدرت محاسبات توزیع شده رو در اختیار بگیرین. سایت w3schools.com آموزشهای خوبی درمورد SOAP داره. ازطرف دیگه سایت TutorialsPoint.com دیدگاه خیلی خوبی درمورد XML-RPC به شما میده.

افزونه های بیشتر
افزونه های خیلی خیلی بیشتری توی F3 موجوده. میتونید به مرجع API اونها یه نگاهی بندازین تا اطلاعات بیشتری درباره هرکدوم پیدا کنید:
هنوز چیزی که میخواین رو پیدا نکردین؟ شاید اون رو توی افزونه هایی که کاربران نوشتن پیدا کنید: لینک
بهینه سازی

موتور کَش (Cache)
کش کردن صفحات وب ایستا - که باعث میشه بتونیم کد داخل مسیر (Route) رو نادیده بگیریم و نیاز نباشه دوباره قالبها رو پردازش کنیم - یکی از راههای کاهش بار کاری سرور وب شماست که بهش اجازه میده روی کارهای دیگه متمرکز بشه. میتونین موتور کش F3 رو با فرستادن پارامتر سوم برای متد ()route فعال کنین. فقط تعداد ثانیه هایی که لازمه تا نسخه کش شده اون مسیر منقضی (Expire) بشه رو مشخص کنید:
$f3->route('GET /my_page', 'App->method', 60);

روش کار اینطوری که وقتی F3 درخواست my_page/ رو برای اولین بار تشخیص بده، کنترل کننده مسیر رو که توی پارامتر دوم مشخص شده اجرا میکنه و تمام خروجی که برای مرورگر کلاینت میره رو توی کش داخلی فریمورک (سمت سرور) ذخیره میکنه. دستور مشابهی هم بطور خودکار برای مرورگر کاربر (سمت کلاینت) ارسال میشه تا بجای تولید دوباره درخواست مشابه توی دوره 60 ثانیه ای که مشخص کردیم، از نسخه دانلود شده و ذخیره شده صفحه روی سیستم کلاینت استفاده کنه. F3 از کش داخلی خودش (سمت سرور) برای هدف کاملاً متفاوتی استفاده میکنه: ارسال نسخه کش شده صفحه برای بقیه کاربرانی که توی این بازه 60 ثانیه ای، همون آدرس رو درخواست کردن. توی این بازه زمانی، کنترل کننده مسیر (پارامتر دوم) صدا زده نمیشه و نسخه کش شده تحویل کلاینت داده میشه. وقتی زمان 60 ثانیه ای تمام شد، با اولین درخواست به همون آدرس، F3 دوباره کش رو بعد از اجرا کنترل کننده مسیر، با اطلاعات جدید بازسازی میکنه.

صفحات وب با داده های ایستا بهترین گزینه ها برای کش کردن هستن. اگه پارامتر سوم متد ()route مشخص نشده باشه یا صفر باشه، F3 دیگه اون رو کش نمیکنه. F3 با قوانین HTTP سازگاره: فقط درخواستهای GET و HEAD رو میتونین کش کنین.

یه نکته مهم اینجا وجود داره که باید موقع طراحی برنامه خودتون درنظر بگیرین. صفحات وب رو کش نکنید، مگر اینکه از تأثیرات جانبی کش کردن در سمت کلاینت مطلع باشین. مطمئن بشین که کش رو فقط روی صفحاتی فعال میکنید که کاری با وضعیت کاربر ندارن (مثلاً اگه صفحه شما قراره نام کاربری رو یه جا نشون بده، نمیشه اون رو کش کنید).

برای مثال، شما میخواین سایتتون رو طوری طراحی کنین که تمام صفحات وب شما منوهای Home و About Us و Login رو وقتی که کاربر لاگین نکرده، نشون بدن. همچنین میخواین منوها به محض اینکه کاربر لاگین کرد، به Home و About Us و Logout تغییر کنن. اگه به F3 بگین که محتوای صفحه About Us رو کش کنه (که طبیعتاً منوها هم داخلش هستن)، اینکار انجام میشه و به مرورگر کلاینت هم میگه که اینکار رو انجام بده. از این به بعد، بدون توجه به وضعیت کاربر (لاگین هست یا نه)، مرورگر کاربر یه عکس (تصویر) از صفحه رو در همون وضعیتی که موقع کش کردن بود، نشون میده. درخواستهای بعدی کاربر به About Us قبل از انقضای کش، همون منوی قبلی رو نشون میده (حتی اگه کاربر وضعیتش تغییر کرده باشه و توی صفحات دیگه که کش نشدن، منوها عوض شده باشن). طبیعتاً این، چیزی که ما میخوایم نیست.

بعلاوه، وقتی از فایلهای ضمیمه شده توی فایلهای کش شده استفاده میکنید، مثال:
require_once '../inc/account_header.php';

مسیرهای نسبی دیگه کار نمیکنن، چون فایل کش شده توی مسیر فایل اصلی قرار نداره. شما یا باید مسیر کامل و مطلق بدین یا به F3 بگین که فایلهای کش شده رو توی مسیری بگذاره که PHP بتونه پیداشون کنه و یا اینکه مسیری که فایلهای کش شده قرار دارند رو به include_path مفسر PHP اضافه کنید. برای اینکار میشه از php.ini یا دستور set_include_path استفاده کنید.

چند نکته راهنما:
  • صفحات پویا رو کش نکنین. خوب این خیلی واضحه که شما نمیخواین داده هایی که مرتب تغییر میکنن رو کش کنین. البته میشه کش رو برای صفحاتی که شامل داده هایی هستن که هر یک ساعت یکبار یا روزانه یا حتی سالانه تغییر میکنن، فعال کرد. بخاطر مسائل امنیتی، F3 استفاده از موتور کش رو فقط به درخواستهای GET محدود میکنه و فرمهای ارسال شده توسط کاربر رو کش نمیکنه! کش رو روی هر صفحه ای که در نگاه اول ایستا بنظر میاد فعال نکنید. توی مثال ما، محتوای About Us ممکنه ایستا باشه ولی منوی این صفحه ایستا نیست.
  • کش رو روی صفحاتی که فقط توی یه وضعیت خاص قابل دسترسی هستن هم فعال نکنید. اگه میخواین صفحه About Us خودتون رو کش کنید، مطمئن بشین که وقتی کاربر لاگین نیست هم قابل دسترسیه.
  • اگه از یه RAMdisk یا هارد SSD استفاده میکنید، متغیر سراسری CACHE رو طوری تنظیم کنید که به اون درایو (که سرعتش بیشتره) اشاره کنه. اینطوری برنامه شما به سرعت یه ماشین مسابقه ای فرمول 1 حرکت میکنه.

نکته: مقدار timeout رو تا وقتی که برنامه خودتون رو برای استفاده نهایی توی محیط اجرا (Production) آماده نکردین، تنظیم نکنید. تغییراتی که توی اسکریپتها ایجاد میکنید، درصورتی که صفحه توی کش فریمورک وجود داشته باشه، تأثیری توی خروجی نهایی ندارن. اگه برنامه ای رو تغییر بدین که یه صفحه که تحت تأثیر کش هست رو تولید میکنه و میخواین که این تغییرات رو فوری ببینید، باید کش رو با حذف کردن فایلی که توی مسیر /cache (یا هر مسیر دیگه که توی متغیر CACHE تعیین کردین) ایجاد شده، پاک کنین. F3 دوباره بطور خودکار کش رو درصورت نیاز میسازه. سمت کلاینت، کار زیادی نمیتونین انجام بدین جز اینکه یا از کاربر بخواین کش مرورگرش رو پاک کنه و یا صبر کنه تا دوره کش تمام بشه.

PHP باید برای کارکرد صحیح موتور کش F3 تنظیم بشه. منطقه زمانی سیستم عامل شما باید با اون چیزی که توی php.ini مشخص شده یکسان باشه.

مشابه مسیرها، F3 به شما اجازه میده که کوئریهای دیتابیس رو هم کش کنید. تفاوت سرعت خیلی چشمگیر خواهد بود. بخصوص وقتی که دستورات پیچیده SQL رو بکار میبرین که شامل جستجو دنبال اطلاعات مشخصی توی حجم زیادی از داده ها میشه که به ندرت هم تغییر میکنن. با فعال کردن کش کوئریهای دیتایبس باعث میشه فریمورک نیاز نداشته باشه دوباره همون دستور SQL رو توی هر درخواست اجرا کنه. انجام این کار به سادگی اضافه کردن یه پارامتر سوم به متد ()exec هست:
$db->exec('SELECT * FROM `sizes`;', NULL, 86400);

اگه مطمئنیم نتایج این کوئری دیتابیس توی یه بازه 24 ساعته تغییری نمیکنه، عدد 86400 ثانیه رو بعنوان پارامتر سوم تعیین میکنیم تا F3 نیاز به اجرای این کوئری بیشتر از یکبار در روز نداشته باشه. درعوض، فریمورک نتایج رو توی کش ذخیره میکنه و هربار که کوئری مشابهی درخواست بشه (قبل از انضای کش)، نتایج رو از کش میخونه و بعد از انقضای کش هم دوباره کوئری رو اجرا میکنه و کش جدید میسازه.

نگاشت کننده داده SQL هم از موتور کش برای بهینه سازی همزمانی ساختار جداول با اشیاء معادلشون استفاده میکنه. مقدار پیشفرض زمان انقضای کش 60 ثانیه است. اگه هر تغییری توی ساختار جدول (نه مقادیر فیلدها) ایجاد کنید، باید صبر کنید تا زمان کش بگذره و بعد میتونین تغییرات رو توی برنامه خودتون مشاهده کنید. میتونید این رفتار رو با مشخص کردن پارامتر سوم برای سازنده نگاشت کننده داده ها تغییر بدین. اگه مطمئن شدین دیگه تغییری توی داده هاتون ایجاد نمیشه، میتونید عدد بزرگتری براش بگذارین تا F3 توی فواصل کمتری نیاز داشته باشه Schema جدول رو بخونه و ساختارش رو کشف کنه:
$user = new DB\SQL\Mapper($db, 'users', 86400);

بطور پیشفرض، موتور کش F3 خاموشه. میتونید اون رو روشن کنید و بهش اجازه بدین تا بطور خودکار APC یا WinCache یا xCache رو پیدا کنه. F3 اگه نتونه روش منابی برای ذخیره کش در پشت پرده پیدا کنه، از سیستم فایل استفاده میکنه (مثلاً پوشه /tmp/cache/) :
$f3->set('CACHE', true);

غیرفعال کردن کش هم خیلی راحته:
$f3->set('CACHE', false);

اگه میخواین قابلیت تشخیص خودکار رو تغییر بدین و نادیده بگیرین، میتونید اینکار رو انجام بدین. برای مثال، اگه میخواین از Memcached در پشت پرده استفاده کنید که اون رو هم F3 پشتیبانی میکنه، باید کدی شبیه این بنویسید:
$f3->set('CACHE', 'memcache=localhost:11211');

میتونید از موتور کش F3 برای نگهداری متغیرهای خودتون هم استفاده کنید. این متغیرها توی درخواستهای مختلف HTTP باقی میمونن و تا وقتی که دستوری برای حذف نیاد (دستی یا با منقضی شدن کش)، از بین نمیرن. برای ذخیره کردن یه متغیر توی کش اینطوری بنویسید:
$f3->set('var', 'I want this value saved', 90);

پارامتر سوم دستور ()f3->set$ به فریمورک میگه که متغیر رو توی کش برای 90 ثانیه نگه داره. اگه برنامه شما توی این بازه زمانی، دستور ('f3->get('var$ رو اجرا کنه، F3 بطور خودکار مقدار رو از کش دریافت میکنه. اگه از دستور ('f3->clear('var$ استفاده کنید، متغیر هم از کش و هم از RAM حذف میشه. اگه میخواین ببینین که یه متغیر توی کش وجود داره یا نه از دستور زیر استفاده کنید:
$f3->exists('var')

در این حالت، اگه متغیر وجود نداشته باشه، نتیجه false و اگه وجود داشته باشه، یه عدد که به شما میگه متغیر چه زمانی ذخیره شده (بصورت Timestamp و با دقت میلیونیوم ثانیه) رو برمیگردونه.

نگه داشتن Javascript و CSS توی رژیم سلامت
F3 یه فشرده ساز Javascript و CSS هم داره که توسط افزونه Web در دسترس شماست. این فشرده ساز میتونه تمام فایلهای CSS شما رو توی یه فایل قالب و همه فایلهای Javascript رو هم توی یه اسکریپت فشرده کنه تا تعداد درخواستهای HTTP موردنیاز صفحه وب به حداقل برسه. با این کار، صفحه سریعتر بارگذاری میشه و UX بهتری هم خواهید داشت.

برای اینکه از این قابلیت بتونین استفاده کنین، باید قالب HTML خودتون رو برای استفاده ازش آماده کنید.

برای CSS میتونید اینطوری بنویسید:
<link href="/minift/css?files=main.css,page.css" rel="stylesheet" type="text/css" />

و به همین شکل برای Javascript :
<script src="/minify/js?files=main.js,dialog.js" type="text/javascript"></script>

البته واضحه که باید یه کنترل کننده برای مسیر minify/ بنویسیم:
$f3->route('GET /minify/@type', function($f3, $args) {
    $path = $f3->get('UI') . $args['type'] . '/';
    $files = str_replace('../', '', $_GET['files']); // close potential hacking attempts
    echo Web::instance()->minify($files, null, true, $path);
}, 3600*24);
// @type will make PARAMS.type variable base point to the correct path
// make sure you organize your files to be minified into sub-folders per type, i.e. /ui/css/ and /ui/js/
// minify will grab each file specified in the querystring var named files and combine into one output
// save the minified file in F3 cache for 24 hours. future requests for this route will use cached version

خوب همه چی تمومه! ()minify هر فایلی رو (main.css و page.css توی مثال CSS و main.js و dialog.js توی مثال Javascript) میخونه، همه فاصله های اضافه و کامنتها رو حذف میکنه، تمام عناصر مرتبط رو بعنوان یه موجودیت وب ادغام میکنه و یه تاریخ انقضا هم میگذاره تا مرورگر وب داده ها رو کش کنه و هربار از F3 نخواهد دوباره براش اونها رو فشرده کنه. نکته مهم اینه که PARAMS.type به مسیر درست نگهداری فایلهای CSS و Javascript اشاره کنه وگرنه مکانیسم بازنویسی URL داخل فشرده ساز نمیتونه فایلها رو پیدا کنه.

کش کردن سمت کلاینت
توی مثالهای ما، F3 یه تاریخ انقضا برای مرورگر کلاینت ارسال میکنه تا هر درخواستی برای فایل یکسانی از CSS یا Javascript نسخه کش شده رو از کامپیوتر خود کلاینت بخونه. وقتی یه درخواست فایل به وب سرور میاد، F3 مسیر رو چک میکنه (توی این مثال، فایلهای CSS و Javascript) تا ببینه کش شدن یا نه. توی مثال بالا، برای مسیر یه کش 24 ساعته تنظیم کردیم. بعلاوه اگه مرورگر وب، هدر If-Modified-Since رو بفرسته و فریمورک ببینه کش تغییری نکرده، فقط یه جواب HTTP 304 Not Modified میفرسته تا پهنای باند سایتتون هدر نره و صفحه سریعتر بارگذاری بشه. بدون هدر If-Modified-Since، فریمورک F3 فایل کش شده اگه وجود داشته باشه میفرسته. اما اگه این هدر ارسال شده باشه، فریمورک کد کنترل کننده مسیر (پارامتر دوم ()route) رو هربار اجرا میکنه تا ببینه محتوا تغییری کرده با نسخه موجود در کش یا نه. اگه تغییر نکرده بود که همون جواب Not Modified رو تحویل میده ولی اگه تغییر کرده بود، محتوای جدید ارسال میشه.

ترفند: اگه شما فایلهای Javascript/CSS خودتون رو مرتب تغییر نمیدین (که در اکثر مواقع و وقتی از کتابخانه هایی مثل jQuery و MooTools و Dojo و... استفاده میکنید، همینطوره)، توی ذهنتون داشته باشین که حتماً پارامتر سوم رو برای مسیر فشرده ساز تعیین کنین تا هر دفعه F3 مجبور نباشه فایلها رو دوباره فشرده و ادغام کنه.

شتاب دادن به کدهای PHP
میخواین سایتتون باز هم سریعتر کار کنه؟ F3 با APC و XCache و WinCache سازگاری کامل داره. این افزونه های PHP کارآیی برنامه شما رو با بهینه سازی اسکریپتهای PHP که نوشتین (شامل کدهای فریمورک)، به شدت بالا میبرن.

فشردن گلوی پهنای باند!
یه برنامه سریع که تمام درخواستهای HTTP رو پردازش میکنه و بهشون در کوتاهترین زمان ممکن جواب میده، همیشه یه ایده خوب نیست. بخصوص وقتی که پهنای باند شما محدوده یا ترافیک وب سایتتون سنگینه. جواب دادن به درخواستهای صفحات در اولین فرصت ممکن، حتی باعث میشه که برنامه شما درمقابل حملات «رد کردن خدمات» (Denial-of-Service یا DoS) آسیب پذیر بشه. F3 یه قابلیت خوب داره که به شما اجازه میده گلوی پهنای باند رو فشار بدین و راهش رو تنگ کنین تا بتونین کنترل کنین که صفحات وب شما چقدر سریع پردازش بشن. میتونید برای این کار از پارامتر چهارم متد ()route استفاده کنید:
$f3->route('GET /login', '\Backoffice\Login->handler', 0, 512); // limit sent data to 512 Kbps

توی مثال بالا، F3 درخواست GET به مسیر login/ رو حداکثر با سرعت تقریبی 64KB/s یا همون 512Kpbs جواب میده.

کاهش پهنای باند توی سطح برنامه میتونه بطور خاص و ویژه، توی صفحات لاگین مفید باشه. کند پاسخ دادن به درخواستهای حملاتی مثل Dictionary Attack یا Bruteforce یه راه مناسب برای کاهش این نوع از ریسکهای امنیتی محسوب میشه.
تست واحد (Unit Testing)

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

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

F3 اشکال زدایی برنامه رو برای شما خیلی راحت میکنه - بدون تغییر در مسیری فرایندهای معمولی و ذهنیتهای شما. F3 از شما نمیخواد که کلاسهای پیچیده شئ گرا، ساختارهای سنگین تست و روالهای مزاحم بنویسین.

یک واحد (یا رویداد تست) میتونه یه تابع/متد یا یه کلاس باشه. بگذارین یه مثال ساده بزنیم:
function hello() {
    return 'Hello, World';
}

خوب این کد رو داخل یه فایل به اسم hello.php ذخیره کنید. حالا از کجا بفهمیم داره درست کار میکنه؟ بگذارین روال تست خودمون رو اینطوری بسازیم:
$f3 = require 'f3/base.php';

// Set up
$test = new Test;
include 'hello.php';

// This is where the tests begin
$test->expect(
    is_callable('hello'),
    'hello() is a function'
);

// Another test
$hello = hello();
$test->expect(
    !empty($hello),
    'Something was return'
);

// This test should succeed
$test->expect(
    is_string($hello),
    'Return value is a string'
);

// This test will fail
$test->expect(
    strlen($hello) == 13,
    'String length is 13'
);

// Display the results
foreach($test->results() as $result) {
    echo $result['text'] . '<br />';
    if($result['status']) {
        echo 'Pass';
    }
    else {
        echo 'Fail (' . $result['source'] . ')';
    }
    echo '<hr />';
}
/* Output:
Hello, Worldhello() is a function
Pass
--------------------------------------------------
Something was return
Pass
--------------------------------------------------
Return value is a string
Pass
--------------------------------------------------
String length is 13
Fail (/var/www/f3project/test.php:31)
--------------------------------------------------
*/

این کد رو به اسم test.php ذخیره کنید و در کنار hello.php بگذارین و ازطریق مرورگر اجرا کنید (برای مثال، localhost/f3project/test.php)

خوب حالا ببینیم فرایند تست واحد ما چطوری کار میکنه.

برای هر تست که میخواین اجرا کنین، متد ()expect رو از کلاس Test اجرا میکنید. این متد 2 پارامتر داره:
  1. test تستی هست که باید اجرا بشه. این قسمت میتونه هر عبارتی باشه که نتیجه true یا false میده. مثلاً $i == 1
  2. text متنی که برای این تست باید وقتی نتایج تست رو نشون میدین، ظاهر بشه. مثلاً input equals numeric 1

وقتی تستها اجرا میشن، کلاس Test داخل F3 نتایج هرکدوم از فراخوانیهای متد ()expect رو نگهداری میکنه. خروجی هرکدوم از این متدها توی یه آرایه چندبعدی نگهداری میشه که اسمش result هست و هر خونه از اون، اندیسهای زیر رو داره:
  • text که همون پارامتر شماره 2 توی متد ()expect هست.
  • status که یه نتیجه boolean (با مقدار true یا false) هست و نتیجه تست رو مشخص میکنه.
  • source که نام فایل و خطی که تست اجرا شده رو برای کمک به اشکال زدایی، معرفی میکنه.

در پایان تست، میتونید این آرایه رو پیمایش کنید و متن و نتیجه هر تست رو نشون بدین.

F3 به شما اجازه میده نتایج رو به هر شکلی که دوست دارین نشون بدین. میتونید خروجی رو توی متن ساده نشون بدین یا حتی از یه قالب HTML زیبا با رندرکردن یه فایل قالب که آرایه $test->results() رو پیمایش میکنه، استفاده کنید.

برای اجرای تست هم همونطور که گفتیم، کافیه فایل test.php رو ازطریق مرورگر اجرا کنید.

تقلید درخواستهای HTTP
F3 به شما امکان شبیه سازی درخواستهای HTTP رو ازطریق برنامه PHP خودتون میده تا بتونین رفتار یه مسیر مشخص رو بررسی کنید و فرضاً بفهمین اگه یه بازدیدکننده سایت، اون صفحه رو ازطریق مرورگر درخواست کنه، چه اتفاقی میفته.

یه مثال ساده از تقلید درخواست رو ببینین:
$f3->set('QUIET', true); // do not show output of the active route
$f3->mock('GET /test'); set the route that f3 will run
// run tests using expect() as shown above
$f3->set('QUIET', false); // allow test results to be shown later
$f3->clear('ERROR'); // clear any errors

ترفند: اگه مسیری دارین که پارامتر داره (مثل /test/@name) میتونید اون مسیر رو با تنظیم مقدار توکن توی دستور mock تست کنین و به نتایج در داخل تست ازطریق آرایه انجمنی PARAMS دسترسی پیدا کنید:
$f3->mock('GET /test/steve');
$name = $f3->get('PARAMS.name');
$test->expect(
    $name == 'steve',
    'URI param "name" equals "steve"'
);

برای تقلید یه درخواست POST و ارسال یه فرم شبیه سازی شده HTML اینطوری کار کنید:
$f3->mock('POST /test', array('foo' => 'bar')); // pass in form values using associative array

یادآوری: وقتی از mock استفاده میکنین یا نتایج تست رو توی یه قالب رندرشده نشون میدین یا چیزی رو توی دیتابیس تست میکنین، نیاز به تنظیم کردن F3 برای اطلاع پیدا کردن از محل قالبها، پارامترهای دیتابیس و... دارین.

انتظار بدترین چیزی که ممکنه اتفاق بیفته
وقتی از تست کردن کوچکترین واحدهای برنامه فارغ شدین، میتونین سراغ اجزاء بزرگتر، ماژولها و زیرسیستمها بشین و چک کنید بخشهای مختلف برنامه (که توی تستهای اولیه، بطور جداگانه درست کار میکردن)، حالا درکنار هم درست کار میکنن یا نه؟

ترفند: اگه فایلهای تست جداگانه درست میکنین و تستهاتون نیاز به دسترسی به تنظیمات دیتابیس و... دارن، از دستور include_once یا require_once برای ضمیمه کردن فایلی که تنظیمات رو در اختیار F3 میگذاره استفاده کنید و این فایل رو با دستوراتی که گفته شد، توی هرکدوم از فایلهای تست ضمیمه کنید. اگه از هر تست بطور جداگانه استفاده میکنید، include یا require هم مشکلی ایجاد نمیکنن ولی اگه قرار باشه چندتا فایل تست رو بصورت دسته ای و ترتیبی ترکیب و اجرا کنید، نسخه once_ به شما اطمینان میده که فایل تنظیمات بیشتر از یکبار خونده نمیشه.

تست کردن تکه های قابل مدیریت از کد منجر به برنامه های قابل اطمینانی میشه که به شکلی که شما میخواین کار میکنن و تستها رو به پارچه چرخه توسعه شما میبافه (جزء لاینفکی از چرخه توسعه میکنه).

سؤالی که باید از خودتون بپرسین اینه که: «آیا تمام سناریوها رو تست کردم؟» در اغلب موارد همون وضعیتهایی که توی تست درنظر نگرفتین، منجر به بروز باگهای احتمالی خواهد شد.

تست واحد کمک زیادی در به حداقل رسوندن اینجور خطاها میکنه. حتی چند تست جزئی توی هر بخش از برنامه میتونه سردردهای بعدی شما رو کاهش بده. درمقابل، نوشتن برنامه بدون تست واحد، دردسرها و مشکلات رو به برنامه دعوت میکنه.

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

متغیرهای سیستمی
برای استفاده از این متغیرها، خیی ساده از شئ f3 میخوایم که اونها رو به ما بده:
$current_url = $f3->get('PATH');

حالا ببینیم این متغیرهای سیستمی چی هستن؟

AGENT

نوع: رشته، فقط خواندنی
یه رشته شامل عامل کاربری HTTP یا همون User Agent مثل:
Mozilla/5.0 (Linux; Android 4.2.2; Nexus 7) AppleWebKit/537.31

AJAX

نوع: منطقی، فقط خواندنی
اگه درخواست XML HTTP تشخیص داده بشه، مقدار true و درغیر اینصورت مقدار false داره. مقدارش ازطریق محاسبه نتیجه دستور زیر بدست میاد:
$headers['X-Requested-With'] == 'XMLHttpRequest'

ALIAS

نوع: رشته
شامل اسم مستعار مسیر جاری هست. اگه مسیر جاری اسم نداشته باشه، مقدارش یه رشته خالیه.

ALIASES

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

AUTOLOAD

نوع: رشته | آرایه مقدار پیشفرض: ./
مسیر(های) جستجو بدنبال کلاسهای تعریف شده توسط کاربر که فریمورک سعی میکنه بطور خودکار در زمان اجرا بارگذاری کنه رو مشخص میکنه. وقتی چند مسیر رو مشخص میکنید، میتونید از | یا , یا ; بعنوان جداکننده استفاده کنید.

نقل قول:بخاطر بسپارین: مسیرها باید با یه / تموم بشن. مثال:
$f3->set('AUTOLOAD', 'app/;inc/;./');

اینجا رو برای جزئیات بیشتر ببینید.

BASE

نوع: رشته، فقط خواندنی مقدار پیشفرض: تشخیص خودکار
مسیر کنترل کننده اصلی index.php

BODY

نوع: رشته، فقط خواندنی
بدنه درخواست HTTP توی پردازش ReSTful که شامل جریان php://input هست که توی درخواستهای PUT ازش استفاده میشه. اگه RAW رو فعال کرده باشین، مقدار BODY برابر با false هست.

CACHE

نوع: منطقی | رشته مقدار پیشفرض: false
پشت صحنه کَش. F3 میتونه از ماژولهای Memcache و APC و WinCache و XCache و کش مبتنی بر سیستم فایل پشتیبانی کنه.

برای مثال: اگه میخواین از ماژول memcache استفاده کنین، یه رشته تنظیم لازمه. برای مثال:
$f3->set('CACHE', 'memcache=localhost')

که از پورت 11211 پیشفرض استفاده میکنه یا:
$f3->set('CACHE', 'memcache=192.168.72.72:11212')

وقتی مقدار true بهش بدین یا وقتی اتصال با سرور memcached مشخص شده در بالا برقرار نشه، F3 خودش تشخیص میده که APC یا WinCache یاXCache وجود دارن یا نه و اگه بودن، اولین موردی که در دسترس بود بجای memcached استفاده میشه. اگه هیچکدوم از این موتورهای حافظه اشتراکی در دسترس نبود یا تشخیص داده نشد، از سیستم فایل بعنوان آخرین راه حل جایگزین استفاده میشه. مسیر پیشفرض کش سیستم فایل هم tmp/cache هست که میتونید با دستور زیر تغییرش بدین:
$f3->set('CACHE', 'folder=/var/tmp/f3filescache/');

اگه مقدار false به متغیر CACHE بدین، فریمورک از هیچ موتور کش خاصی استفاده نمیکنه.

CASELESS

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

COOKIE GET POST REQUEST SESSION FILES SERVER ENV

نوع: آرایه
این متغیرها، معادل آرایه های سراسری PHP توی F3 هستن. برای راحتی بیشتر شما، F3 بطور خودکار این متغیرها رو با متغیرهای سراسری PHP همزمان سازی میکنه. این متغیرها رو میتونید توی کل برنامه استفاده کنید. هرچند استفاده مستقیم از اینها توی قالبها بنا به دلایل امنیتی توصیه نمیشه.

CORS

این پارامتر مخصوص تنظیمات Cross-Origin Resource Sharing هست و شامل انتخابهای زیر میشه:
  • heaaders رشته یا آرایه. مقدار پیشفرضش یه رشته خالیه و مشخص کننده هدرهای مجاز توی درخواست های HTTP هست
  • origin رشته یا false. مقدار پیشفرضش false هست و هاست ریشه مجاز رو مشخص میکنه. برای مثال: mydomain.com.*
  • credentials منطقی. مقدار پیشفرض false هست و کوکیها رو مجاز میکنه.
  • expose رشته یا آرایه. مقدار پیشفرضش false هست و کنترل میکنه که چه هدرهایی از پاسخ باید برای مرورگر کلاینت ارسال بشن.
  • ttl عدد صحیح. مقدار پیشفرضش 0 هست و زمان کش شدن درخواست OPTIONS رو تعیین میکنه.

برای فعال کردن پشتیبانی اولیه از CORS فقط کافیه CORS.origin رو با مقدار * تنظیم کنید:
$f3->set('CORS.origin', '*');

DEBUG

نوع: عدد صحیح مقدار پیشفرض: 0
سطح جزئیات خطاها رو توی پیگیری پشته فراخوانی دستورات (Stack Trace) مشخص میکنه. مقادیر مجاز بین 0 تا 3 هستن:
  • 0 : گزارشات پشته فراخوانی بطور کامل پنهان میشن.
  • 1 : فقط فایلها و خطوط فراخوانی گزارش میشن.
  • 2 : اسم کلاسها و متدها هم گزارش میشن.
  • 3 : اطلاعات کامل و با جزئیات درمورد اشیاء هم گزارش میشن.

یادآوری: فقط مقدار پیشفرض 0 باید توی سرورهای نهایی و محیط Production استفاده بشه.

DIACRITICS

نوع: آرایه مقدار پیشفرض: آرایه خالی
یه آرایه انجمنی با ترکیب key => value برای ترجمه کارکترهای خارجی به معادل ASCII اونها

DNSBL

نوع: رشته مقدار پیشفرض: رشته خالی
فهرستی از لیست سیاه سرورهای DNS که با , از هم جدا شدن. F3 اگه IPv4 کاربر توی سرورهای مشخص شده توی این فهرست وجود داشته باشه، خطای 403 تولید میکنه.

EMOJI

نوع: آرایه مقدار پیشفرض: آرایه خالی
یه آرایه انجمنی با ترکیب key => value از توکنهای emoji (یا شکلکها) برای اضافه شدن به مجموعه اولیه موجود در فریمورک برای تبدیل یه رشته به نشانه های موجود توی فونت انتخاب شده. مستندات \UTF->emojify() رو نگاه کنید.

ENCODING

نوع: رشته مقدار پیشفرض: UTF-8
مجموعه کارکترهای مورد استفاده برای کدگذاری سند

ERROR

نوع: آرایه، فقط خواندنی
شامل اطلاعاتی درباره آخرین خطای HTTP هست که رخ داده:
  • ERROR.code شامل کد وضعیت HTTP هست (مثال: 307)
  • ERROR.status یه شرح خلاصه از کد وضعیت HTTP هست (مثال: Temporary Redirect)
  • ERROR.text شامل یه شرح خلاصه از خطای بوجود آمده است
  • ERROR.trace توی خطاهای HTTP 500 برای استخراج پشته فراخوانی (Stack Trace) بکار میره و یه آرایه است.

ESCAPE

نوع: منطقی مقدار پیشفرض: true
برای فعال/غیرفعال کردن خنثی سازی خودکار کارکترهای مخرب داخل token@ ها توی قالبها بکار میره.

EXEMPT

نوع: رشته مقدار پیشفرض: null
یه فهرست از آدرسهای IPv4 معاف شده از جستجوی DNSBL که با , از هم جدا میشن

EXCEPTION

نوع: شئ مقدار پیشفرض: null
شامل شئ استثنای تولیدشده درزمان بروز خطاهایی هست که مدیریت نشدن.

FALLBACK

نوع: رشته مقدار پیشفرض: en
زبان (و لغتنامه) پیشفرض وقتی که فایل ترجمه زبان انتخاب شده در دسترس نیست.

FRAGMENT

نوع: رشته مقدار پیشفرض: null
بخشی از URI که بعد از علامت اختیاری هش # میاد. برای مثال توی آدرس زیر:
http://forum.ncis.ir/Thread-آموزش+قدم+به+قدم+Fat+Free+Framework?pid=1511#pid1511

مقدار FRAGMENT برابر با pic1511 هست.

HALT

نوع: منطقی مقدار پیشفرض: true
اگه true باشه، فریمورک بعد از اینکه پشته فراخوانی رو گزارش کرد، اجرای برنامه رو متوقف میکنه (die بدون هیچ پیغامی). البته اگه خطای تولیدشده ازنوع مرگبار (Fatal) باشه، بهرحال برنامه توسط خود PHP متوقف میشه.

HEADERS

نوع: آرایه، فقط خواندنی
هدرهای HTTP که توسط سرور دریافت شدن. مثال (کوتاه شده) :
array(
    'Host' => 'fatfreeframework.com',
    'Accept-Encoding' => 'gzip,deflate,sdch',
    'Accept-Language' => 'en-US,en;q=0.8;ja;q=0.6'
)

HIGHLIGHT

نوع: منطقی مقدار پیشفرض: true
فعال/غیرفعال کردن رنگ آمیزی کردن دستورزبان (Syntax Highlighting) برای پشته فراخوانی و بلاکهای کد Markdown. وقتی فعال باشه، به فایل قالب code.css نیاز داره.

HOST

نوع: رشته، فقط خواندنی
اسم هاست سرور

IP

نوع: رشته، فقط خواندنی
آدرس IP کاربر. F3 اگه کلاینت HTTP پشت یه سرور پراکسی باشه، آدرس رو از هدرها استخراج میکنه. مقدار پیشفرض: اولین موردی که از گزینه های زیر توی هدرها پیدا بشه:
  1. Client-IP
  2. X-Forwarded-For
  3. $_SERVER['REMOTE_ADDR']

اگه هیچکدوم پیدا نشد، مقدارش یه رشته خالی خواهد بود.

JAR

نوع: آرایه
پارامترهای پیشفرض کوکی که شامل انتخاب های زیر هست:
  • expire مُهر زمانی UNIX که مشخص میکنه کوکی چه زمانی باید از بین بره. پیشفرض: 0
  • path مسیری در سرور که کوکی قابل دسترسی هست. پیشفرض: /
  • domain دامینی که کوکی توی اون قابل دسترسیه. پیشفرض: $_SERVER['SERVER_NAME'] اگه موجود باشه، وگرنه یه رشته خالی
  • secure تنظیم کردن کوکی وقتی که اتصال HTTPS موجوده. پیشفرض: نتیجه بررسی شرط زیر:
    $_SERVER['HTTPS'] == 'on'
  • httponly کوکی رو فقط ازطریق پروتکل HTTP قابل دسترسی میکنه. پیشفرض: true

میتونید به مستندات ()session_set_cookie_params توی راهنمای PHP برای اطلاعات بیشتر مراجعه کنید.

LANGUAGE

نوع: رشته مقدار پیشفرض: تشخیص خودکار
زبان(های) فعال فعلی رو مشخص میکنه. مقدارش برای بارگذاری فایل(های) زبان(های) مناسب از داخل پوشه ای که با LOCALES تعیین شده، استفاده میشه. پیشفرض: بطور خودکار ازطریق هدر Accept-Language توی درخواست HTTP تشخیص داده میشه. مثال: eu-US,en,es

یادآوری: زبان و تنظیمات بومی سازی سیستم براساس این متغیر بارگذاری میشه:
$f3->set('ENCODING', 'UTF-8');
$f3->set('LANGUAGE', 'fa-IR'); // the locale fa_IR.UTF-8 will be automatically loaded using setlocale

برای جزئیات بیشتر و مثال، قسمت Localisation رو توی مستندات کلاس Base نگاه کنید.

LOCALES

نوع: رشته مقدار پیشفرض: ./
مکان فایلهای لغتنامه زبان(ها) رو مشخص میکنه.

LOGS

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

ONERROR

نوع: مختلف مقدار پیشفرض: null
یه تابع قابل فراخوانی (Callback) - بصورت یه رشته یا تابع بی نام - برای استفاده بعنوان کنترل کننده سفارشی خطا، یا null

یادآوری: اگه هیچ تابع قابل فراخوانی مشخص نشه، صفحه خطای پیشفرض تولید میشه (HTML5 برای درخواستهای عادی و رشته JSON برای درخواستهای AJAX).

ONREROUTE

نوع: مختلف مقدار پیشفرض: null
تابع قابل فراخوانی که باید قبل از ارسال هدرهای Redirect اجرا بشه. اگه تابع معرفی شده خروجی false تولید نکنه، رفتار پیشفرض (ارجاع 301/302) نادیده گرفته میشه.

PACKAGE
نوع: رشته مقدار پیشفرض: Fat-Free Framework
یه رشته شامل اسم فریمورک

PARAMS

نوع: آرایه مقدار پیشفرض: آرایه خالی
مقادیر توکنهای تعریف شده توی الگوی ()route رو نگهداری میکنه. اندیس 0 این آرایه شامل URL دریافت شده نسبت به ریشه وب سایت هست.

PATH

نوع: رشته، فقط خواندنی
URL نسبت به BASE. مقدار پیشفرض: parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH)

PATTERN

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

PLUGINS

نوع: رشته مقدار پیشفرض: __DIR__ . '/'
مکان افزونه های F3 رو مشخص میکنه. مقدار پیشفرض، پوشه ای هست که کد فریمورک داخلش قرار داره (یعنی مسیر base.php)

PORT

نوع: عدد صحیح، فقط خواندنی
پورت TCP/IP که وب سرور برای شنود درخواستها ازش استفاده کرده. مقدار پیشفرض: $_SERVER['SERVER_PORT'] یا null اگه قابل دسترسی نباشه.

PREFIX

نوع: رشته مقدار پیشفرض: null
پیشوندی که باید برای LANGUAGE و LOCALES استفاده بشه.

PREMAP

نوع: رشته مقدار پیشفرض: رشته خالی
این متغیر اجازه استفاده از پیشوندها رو برای کنترل کننده های مسیرها (متدهای توابع) میده که بعد از افعال HTTP میان. برای مثال، اگه این متغیر رو با _action تنظیم کنید، مسیری مثل کد زیر:
$f3->route('POST /submit', 'Page->submit');

متد ()Page->action_submit رو صدا میزنه.

QUERY

نوع: رشته، فقط خواندنی
شامل رشته پرس و جو (Query String) موجود در URI میشه (هر چیزی که بعد از ? میاد).

QUIET

نوع: منطقی مقدار پیشفرض: false
سوئیچ نمایش یا پنهان سازی خروجی استاندارد و پیغامهای خطا رو روشن/خاموش میکنه. بطور خاص توی تست واحد مفیده.

RAW

نوع: منطقی مقدار پیشفرض: false
وقتی حجم زیاد اطلاعاتی رو که توی حافظه جا نمیشن رو ازطریق جریان php://input پردازش میکنید، باید RAW رو با مقدار true تنظیم کنید. متغیر BODY رو ببینید.

REALM

نوع: رشته، فقط خواندنی
URL کامل و استاندارد. مقدار پیشفرضش نتیجه عبارت زیر هست:
(isset($_SERVER['HTTPS']) && $_SERVER['HTTPS']=='on' || isset($headers['X-Forwarded-Proto']) && $headers['X-Forwarded-Proto'] == 'https' ? 'https' : 'http') . '://' . $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI']

RESPONSE

نوع: رشته، فقط خواندنی
بدنه آخرین پاسخ HTTP تولیدشده. F3 این متغیر رو بدون توجه به فعال/غیرفعال بودن QUIET پر میکنه.

ROOT

نوع: رشته، فقط خواندنی
مسیر مطلق پوشه ریشه وب سرور

ROUTES

نوع: آرایه مقدار پیشفرض: آرایه خالی
شامل مسیرهای تعریف شده توی برنامه است.
یادآوری: یه مسیر، چیزی بیشتر از یه URL ساده است و شامل فعل (یا افعال) HTTP و URL میشه. مثال: GET /about

SCHEME

نوع: رشته، فقط خواندنی
پروتکل سرور. مقدارش http یا https هست.

SERIALIZER

نوع: رشته مقدار پیشفرض: تشخیص خودکار
سریال کننده پیشفرضی که توی متد ()Base->serialize\ استفاده میشه رو مشخص میکنه. مقدار پیشفرضش igbinary هست اگه موجود باشه. درغیر اینصورت روی php تنظیم میشه که از تابع serialize خود PHP استفاده میکنه.

TEMP

نوع: رشته مقدار پیشفرض: tmp/
پوشه موقت رو برای کَش، قفلهای سیستم فایل، قالبهای کامپایل شده F3 و... تعیین میکنه. مقدار پیشفرضش پوشه /tmp داخل ریشه وب هست. اون رو به نحوی تنظیم کنید که با سیاستهای امنیتی سایتتون مطابقت داشته باشه.

TIME

نوع: عدد اعشاری مقدار پیشفرض: تشخیص خودکار
زمان شروع به کار فریمورک. مقدار پیشفرض: مهر زمانی Unix با دقت میلیونیوم ثانیه مثل تابع ()microtime با پارامتر true توی PHP

TZ

نوع: رشته مقدار پیشفرض: تشخیص خودکار
منطقه زمانی مورد استفاده. تغییر این مقدار بطور خودکار تابع ()date_default_timezone_set رو از PHP صدا میزنه. فهرست مناطق زمانی پشتیبانی شده رو اینجا میتونید مشاهده کنید.

UI

نوع: رشته مقدار پیشفرض: ./
مسیر جستجو برای فایلهای رابط کاربری که توی کلاسهای View و Template و توسط متد ()render مورد استفاده قرار میگیره رو مشخص میکنه. اگه میخواین چند مسیر رو تعیین کنین، باید از | یا , یا ; برای جداکردن مسیرها استفاده کنیدن.

UNLOAD

نوع: تابع قابل فراخوانی مقدار پیشفرض: null
کنترل کننده خروج از برنامه رو تعیین میکنه که در زمان اتمام کار برنامه اجرا میشه.

UPLOADS

نوع: رشته مقدار پیشفرض: ./
مسیری که فایلهای آپلود شده باید ذخیره بشن.

URI

نوع: رشته مقدار پیشفرض: تشخیص خودکار
شامل URI جاری درخواست HTTP

VERB

نوع: رشته مقدار پیشفرض: تشخیص خودکار
شامل روش درخواست HTTP جاری (GET یا POST یا PUT و...)

VERSION

نوع: رشته مقدار پیشفرض: 3.2.1-Release
یه رشته شامل نسخه جاری فریمورک F3 که درحال استفاده ازش هستین

دستورات قالب

Token

  • token@
    این شناسه با متغیر معادل خودش توی F3 جایگزین میشه.
  • {{ mixed expr }}
    عبارت مشخص شده توی expr ارزیابی میشه. عبارت میتونه شامل توکنهای قالب، ثابتها، عملگرها (یکانی، دوتایی یا شرطی سه گانه)، پرانتزها، تبدیل نوع داده ها و توابع باشه. اگه معادل هیچدوم از سایر دستورات قالب نباشه، مستقیماً همون متن نمایش داده میشه.
  • {{ string expr | esc }}
    متن expr مشخص شده رو بصورت Escape شده نمایش میده. این موضوع، رفتار پیشفرض خود فریمورکه و فیلتر esc فقط وقتی ضروریه که متغیر ESCAPE رو false کرده باشین.
  • {{ string expr | RAW }}
    متن expr مشخص شده رو بصورت Escape نشده نشون میده. از اونجا که F3 در حالت پیشفرض متنهای توکن رو خنثی سازی میکنه، میتونید از این فیلتر برای جلوگیری از خنثی سازی یه توکن خاص (درصورت نیاز) استفاده کنید.
  • {{ string expr, arg1, ..., argN | format }}
    متن expr رو با قالب ICU نمایش میده و آرگومانهایی که با , جدا کردین رو براش میفرسته که هرکدوم میتونن یکی از مقادیر زیر باشن:
    • time
    • number, integer
    • number, currency
    • number, percent
    • number, decimal
    به متد format برای جزئیات بیشتر و مثالهایی از کاربردش نگاه کنید. همچنین توضیحات قالب بندی ICU رو برای اعداد، واحدهای پولی، تاریخ و زمان مشاهده کنید.
  • {{ string name, args | alias }}
    یه URL از مسیر نامگذاری شده میسازه. برای مثال:
    {{ @name, 'a=5,b='.@id | alias }}
  • {~ string expr ~}
    expr رو مشابه {{ expr }} ارزیابی میکنه، ولی نتیجه رو نمایش نمیده.
  • {<em> text-block </em>}
    بخشی از قالب رو از پردازش و نمایش معاف میکنه. معادل <exlude> هست.

Include

<include [ if="{{ bool condition }}" ] href="{{ string subtemplate }}" [ with="{{ string additional_variables }}" ] />

محتوای subtemplate رو میگیره و توی موقعیت جاری توی قالب درج میکنه. اگه قسمت اختیاری if (بدون کروشه ها) رو بنویسید، این کار فقط وقتی انجام میشه که condition برابر با true باشه.

داده هایی که در اختیار قالب فعلی هست، بطور خودکار برای قالب فرعی هم ارسال میشه ولی درصورت نیاز میتونید یکسری متغیر اضافه تر هم بطور اختصاصی توسط قسمت with (باز هم بدون کروشه ها) و با کمک additional_variables برای قالب فرعی ارسال کنید. اینجا رو برای دیدن مثالها، کلیک کنید.

Exclude

<exclude>text-block</exclude>

text-block رو در زمان اجرا، از قالب جدا میکنه و پردازش نمیکنه. برای مواقعی که میخواین موقتاً یک بخش از قالب رو از دسترس خارج کنید (که میدونید درست کار میکنه و فعلاً برای افزایش سرعت کار، نمیخواین ظاهر بشه)، مفیده. این دستور، یه حالت جایگزین برای اینه:
{* text-block *}

Ignore

<ignore>text-block</ignore>

text-block رو به همون شکلی که هست نمایش میده و هیچ تفسیر/تغییری روش توسط موتور قالب اعمال نمیشه.

Check

<check if="{{ bool condition }}">
    <true>true-block</true>
    <false>false-block</false>
</check>

condition رو بررسی میکنه. اگه درست بود، true-block وگرنه false-block رندر میشه.

حالت کوتاه: اگه نیاز ندارین و نمیخواین قسمت false رو مشخص کنید، برای راحتی شما F3 تگهای باز و بسته قسمت true رو اختیاری گذاشته:
<check if="{{ @debugmode && @showtrace }}"><code>{{ @shortrace }}</code></check>

Loop

<loop from="{{ statement }}" to="{{ bool expr }}" [ step="{{ statement }}" ]>text-block</loop>

قسمت from یکبار اجرا میشه. اگه عبارت to معادل true بود، text-block رندر میشه و بخش اختیاری step (اگه وجود داشت - بدون کروشه ها) اجرا میشه و دوباره قسمت to ارزیابی میشه. حلقه تا وقتی ادامه پیدا میکنه که قسمت to نتیجه false تولید کنه.

مثال:
<loop from="{{ @i=0 }} to="{{ @i < count(@bar) }}" step="{{ @i++ }}">
    <p>{{ @i }}</p>
</loop>

Repeat

<repeat group="{{ array @group|expr }}" [ key="{{ scalar @key }}" ] value="{{ mixed @value }} [ counter="{{ scalar @counter }}" ]>
    text-block
</repeat>

text-block رو به تعداد عناصر موجود توی متغیر آرایه group@ یا عبارت expr تکرار میکنه. key@ و value@ کاری مشابه زوجهای key => value توی حلقه ()foreach توی خود PHP عمل میکنن. متغیر counter@ هربار یک واحد در طی تکرار حلقه افزایش پیدا میکنه. قسمتهای key و counter (بدون کروشه ها) اختیاری هستن.

Switch

<switch expr="{{ scalar expr }}">
    <case value="{{ scalar @value|expr }}" break="{{ bool true|false }}">
        text-block
    </case>
    .
    .
    .
    <default>
        message
    </default>
</switch>

معادل ساختار switch-case خود PHP هست. اگه برای یک case قسمت break رو false بگذارین، درصورت برقرار بودنش، caseهای بعدی هم تا زمان رسیدن به انتهای اولین case که break اون، true هست (یا تا پایان switch درصورت پیدا نکردن چنین case خاصی) اجرا میشن.

Set

<!-- set some variables -->
<set foo="{{ 1 + 2 }}" bar="{{ @foo + 3 }}" baz="xyz" />
<!-- set an array -->
<set myarray="{{ array('a', 'b', 'c') }}" />

برای مقداردهی یکسری متغیر بصورت پویا داخل خود قالب بکار میره.

مستندات رابط برنامه نویسی کاربردی (API)
مستندات API فریمورک F3 توی مسیر lib/api/index.html در داخل بسته توزیع شده ای که دانلود کردین، موجوده. F3 از Doxygen برای تولید مستندات در قالب HTML استفاده میکنه.