رتبه موضوع:
  • 0 رای - 0 میانگین
  • 1
  • 2
  • 3
  • 4
  • 5
آموزش فریم ورک Symfony
#1
در این تاپیک قصد داریم با ترجمه مستندات رسمی فریمورک فوق‌العاده قدرتمند و محبوب سیمفونی و افزودن تجربیات و توضیحات شخصی، شما را بصورت قدم به قدم با یکی‌از قدرتمندترین فریمورک‌های PHP آشنا سازیم.

نقل قول:برای جلوگیری از بی‌نظمی و شلوغی تاپیک، امکان درج پست توسط کاربران در این تاپیک وجود ندارد. درصورت وجود مشکل یا سؤالی درخصوص مطالب آموزش داده شده، تاپیک جداگانه ایجاد نمایید.

با ما همراه باشید. این تاپیک دنباله‌دار است...

[عکس: attachment.php?aid=391]


فایل‌های پیوست تصاویر بندانگشتی
   
تشکر شده توسط: Renegad , hamo , sm_pakdel , captain , gmail , meysam1366 , elephpant
#2
بخش 1 - اصول بنیادی سیمفونی و HTTP

تبریک می‌گوییم! با یادگیری سیمفونی شما در مسیر تبدیل‌شدن به یک برنامه‌نویس وب خلاق‌تر، انعطاف‌پذیرتر و محبوب‌تر قرار خواهید گرفت. Symfony برای بازگشت به پایه‌ها تولید شده است: برای توسعه ابزارهایی که به شما اجازه می‌دهند وب‌اپلیکیشن‌های خود را سریعتر و غنی‌تر تولید کنید، درحالی‌که از مسیر و چهارچوب کاری خود خارج نمی‌شوید. سیمفونی برپایه بهترین ایده‌های بسیاری از فناوری‌های مختلف بنا شده است: ابزارها و مفاهیمی که می‌خواهید بیاموزید، حاصل تلاش هزاران نفر در طی سال‌های متمادی است. به‌عبارت‌دیگر، شما نه‌تنها Symfony، بلکه اصول پایه وب، بهترین تمرین‌های توسعه و چگونگی استفاده از کتابخانه‌های شگفت‌انگیز زیادی را در PHP، داخل Symfony یا مستقل از آن خواهید آموخت. بنابراین، آماده شوید.

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

HTTP ساده است

HTTP (مخفف HyperText Transfer Protocol) یک زبان متنی است که به دو ماشین اجازه می‌دهند با یکدیگر ارتباط برقرار نمایند. همین! برای مثال، وقتی آخرین کتاب‌های کامیک xkcd را چک می‌کنید، (تقریباً) مکالمه زیر رخ می‌دهد:

[عکس: attachment.php?aid=392]

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

Symfony از پایه برمبنای همین واقعیت بنا شده است. چه این موضوع را بدانید یا نه، HTTP چیزی است که هرروز بکار می‌برید. با سیمفونی، یاد خواهید گرفت که چگونه در آن خبره شوید.

مرحله اول: کلاینت یک درخواست ارسال می‌کند

هر مکالمه در وب با یک درخواست (Request) آغاز می‌شود. درخواست، یک پیام متنی است که توسط یک سرویس‌گیرنده یا Client (یعنی یک مرورگر، یک نرم‌افزار برروی تلفن هوشمند و...) در یک قالب خاص که تحت‌عنوان HTTP شناخته می‌شود، تولید می‌گردد. کلاینت این درخواست را به یک سرویس‌دهنده یا Server ارسال می‌کند و سپس منتظر پاسخ (Response) می‌ماند.

نگاهی به بخش اول تعامل (درخواست) بین مرورگر و سرور وب xkcd بیندازید:

[عکس: attachment.php?aid=393]

در زبان HTTP این درخواست واقعاً چیزی شبیه این است:

GET / HTTP/1.1
Host: xkcd.com
Accept: text/html
User-Agent: Mozilla/5.0 (Macintosh)

این پیام ساده، تمام چیزهای لازم درباره اینکه کدام منبع توسط کلاینت درخواست شده‌است را در بر دارد. خط اول یک درخواست HTTP مهم‌ترین بخش آن و حاوی دو چیز است: URI و متد HTTP.

URI (مثلاً / یا /contact و...) آدرس یا موقعیت منحصربفردی است که منبع موردتقاضا توسط کلاینت را مشخص می‌کند. متد HTTP (برای مثال GET) مشخص‌کننده آن است که می‌خواهید با آن منبع چه‌کار کنید. متدهای HTTP در اصل فعل‌های (Verb) جمله درخواست هستند و چند روش رایج را تعریف می‌کنند که می‌توانید برروی منابع اعمال‌کنید:

GET : درخواست منبع از سرور
POST : ایجاد یک منبع برروی سرور
PUT : بروزرسانی یک منبع برروی سرور
DELETE : حذف یک منبع از سرور

با درنظر گرفتن این موضوع در ذهن، می‌توانید تصور کنید که وقتی درخواست HTTP برای حذف یک مطلب در وبلاگ ارسال می‌شود، چگونه بنظر خواهد رسید. برای مثال:

DELETE /blog/15 HTTP/1.1

نقل قول:نکته: درحقیقت 9 متد (یا فعل) HTTP وجود دارد که در استانداردهای HTTP مشخص شده‌اند، اما بیشتر آنها رواج زیادی ندارند یا پشتیبانی نمی‌شوند. در واقعیت، اغلب مرورگرهای مدرن فقط از POST و GET در فرم‌های HTML پشتیبانی می‌کنند. گونه‌های دیگر در قالب XMLHttpRequest پشتیبانی می‌شوند که توسط مسیریاب Symfony نیز پشتیبانی می‌گردد.

علاوه‌بر خط اول، یک درخواست HTTP شامل خطوط متغیر دیگری از اطلاعات است که هدرهای درخواست نامیده می‌شوند. هدرها می‌توانند محدوده وسیعی از اطلاعات را ازقبیل سرور میزبان درخواست‌شده، قالب‌های پاسخی که کلاینت می‌پذیرد (Accept) و برنامه‌ای که کلاینت برای تولید درخواست بکار برده (User-Agent) را در بر گیرد. هدرهای بسیارزیاد دیگری وجود دارد که می‌توانید در سایت Wikipedia در مقاله «فهرست فیلدهای هدر HTTP» بیابید.

مرحله دوم: سرور یک پاسخ می‌فرستد

وقتی سرور درخواست را دریافت‌کرد، دقیقاً متوجه می‌شود که چه منبعی موردنیاز کلاینت است (ازطریق URI) و قصد دارد با آن چه‌کاری انجام‌دهد (ازطریق متد). برای مثال در یک درخواست GET سرور منبع را آماده کرده و سپس آنرا در یک پاسخ HTTP ارسال می‌کند. پاسخ ارسال‌شده از وب‌سرور xkcd را درنظر بگیرید. این پاسخ وقتی به قالب HTTP تبدیل شود، چیزی شبیه این خواهد بود:

[عکس: attachment.php?aid=392]

در زبان HTTP پاسخ ارسال‌شده چیزی شبیه کد زیر است:

HTTP/1.1 200 OK
Date: Sat, 02 Apr 2011 21:05:05 GMT
Server: lighttpd/1.4.19
Content-Type: text/html

<html>
    <!-- ... HTML for the xkcd comic -->
</html>

پاسخ HTTP شامل منبع درخواست‌شده (محتوای HTML در این مثال)، به‌همراه سایر اطلاعات موردنیاز درباره پاسخ است. اولین خط اهمیت ویژه‌ای دارد و شامل کد وضعیت پاسخ HTTP (یعنی 200 در این مثال) است. کد وضعیت مشخص‌کننده نتیجه کلی درخواست کلاینت است. آیا درخواست با موفقیت مواجه شد؟ آیا خطایی رخ داد؟ کدهای وضعیت مختلفی وجود دارند که موفقیت، خطا، یا اینکه کلاینت نیاز به انجام کاری دارد (مثل مراجعه به صفحه دیگر) را مشخص می‌کنند. فهرست کاملی از این کدهای خطا را می‌توانید در مقاله «فهرست کدهای وضعیت HTTP» در سایت Wikipedia بیابید.

مشابه درخواست، یک پاسخ HTTP شامل بخش‌های مختلفی از اطلاعات جانبی تحت‌عنوان هدرهای HTTP است. برای مثال، یک هدر پاسخ مهم HTTP عبارت‌است‌از Content-Type. بدنه یک منبع خاص ممکن‌است در قالب‌های مختلفی نظیر HTML، XML، یا JSON بازگردانده‌شود و هدر Content-Type از انواع رسانه اینترنت نظیر text/html برای اعلام اینکه چه قالبی بازگردانده شده‌است، استفاده می‌کند. می‌توانید فهرست انواع رسانه رایج را در سایت IANA ببینید.

هدرهای زیاد دیگری وجود دارند، برخی از آنها بسیار قدرتمند هستند. برای مثال، هدرهای خاصی می‌توانند برای تولید یک سیستم Cache قدرتمند مورداستفاده قرارگیرند.

درخواست‌ها، پاسخ‌ها و توسعه وب

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

نقل قول:نکته: برای یادگیری بیشتر درمورد خصوصیات HTTP، مستندات اصلی HTTP 1.1 RFC یا HTTP Bis را مطالعه‌کنید، که یک تلاش فعال برای مشخص‌کردن خصوصیات اصلی آن است. یک ابزار عالی برای بررسی هدرهای درخواست و پاسخ درحال بازدید وب، افزونه Live HTTP Headers برای Firefox است.


درخواست‌ها و پاسخ‌ها در PHP

خوب با استفاده از PHP چگونه با «درخواست» تعامل داشته باشیم و یک «پاسخ» تولید کنیم؟ درحقیقت PHP کمی شما را از کل فرآیند جدا می‌کند:


$uri = $_SERVER['REQUEST_URI'];
$foo = $_GET['foo'];
header('Content-Type: text/html');
echo 'The URI requested is: ' . $uri;
echo 'The value of "foo" parameter is: ' . $foo;

با وجود اینکه عجیب بنظر می‌رسد، این برنامه کوچک درحقیقت اطلاعات لازم را از درخواست HTTP دریافت‌کرده و از آن برای تولید یک پاسخ HTTP استفاده می‌کند. بجای تفسیر پیام درخواست HTTP بصورت خام، PHP متغیرهای فوق سراسری نظیر $_SERVER و $_GET را آماده می‌کند که حاوی تمام اطلاعات درون درخواست هستند.

بطور مشابه، بجای ارسال پاسخ متنی درقالب HTTP، می‌توانید از تابع header برای تولید هدرهای پاسخ استفاده‌کنید و به‌سادگی محتوای واقعی را که بخش Content پیام پاسخ را می‌سازند، چاپ کنید. PHP یک پاسخ صحیح HTTP ساخته و آنرا به کلاینت پس می‌دهد.

HTTP/1.1 200 OK
Date: Sat, 03 Apr 2011 02:14:33 GMT
Server: Apache/2.2.17 (Unix)
Content-Type: text/html

The URI requested is: /testing?foo=symfony
The value of "foo" parameter is: symfony

درخواست‌ها و پاسخ‌ها در سیمفونی

سیمفونی یک روش جایگزین برای رویکرد خام PHP ازطریق دو کلاس مجزا ارائه کرده‌است که به شما اجازه می‌دهد با درخواست و پاسخ HTTP به‌شکل راحت‌تری تعامل داشته باشید. کلاس Request یک نمایش شئ‌گرای ساده از پیام درخواست HTTP است. با کمک آن، شما تمامی اطلاعاتی درخواست را در دست دارید:

use Symfony\Components\HttpFoundation\Request;

$request = Request::createFromGlobals();

// The URI being requested (e.g. /about) minus any query parameters
$request->getPathInfo();

// Retrieve GET and POST variables respectively
$request->query->get('foo');
$request->request->get('bar', 'default value if bar does not exist');

// Retrieve SERVER variables
$request->server->get('HTTP_HOST');

// Retrieve an instance of uploaded file identified by foo
$requeset->files->get('foo');

// Retrieve a cookie value
$request->cookies->get('PHPSESSID');

// Retrieve an HTTP request header, with normalized, lowercase keys
$request->headers->get('host');
$request->headers->get('content_type');

$request->getMethod(); // GET, POST, PUT, DELETE, HEAD
$request->getLanguages(); // An array of languages the client acceps

بعنوان یک هدیه جانبی، کلاس Request کارهای بسیار زیاد دیگری در پشت‌صحنه انجام می‌دهد که هرگز نیاز نیست نگران آنها باشید. برای مثال، متد isSecure() سه مقدار متفاوت را در PHP چک می‌کند تا بفهمد که از اتصال امن (یعنی HTTPS) استفاده می‌کنید یا خیر.

خصوصیات Request و ParameterBag ها

همان‌طور که در بالا مشاهده کردید، متغیرهای $_GET و $_POST به‌ترتیب ازطریق فیلدهای عمومی query و request قابل دسترسی هستند. هرکدام از این اشیاء یک شئ ParameterBag است، که متدهایی نظیر get() و has() و all() و بیشتر دارد. درحقیقت، هر خصوصیتی که در مثال قبلی بکار رفته، نمونه‌ای از یک ParameterBag است.

کلاس Request شامید یک خصوصیت عمومی بنام attributes نیز می‌باشد که داده‌های خاصی را مرتبط با چگونگی کارکرد داخلی برنامه، در خود نگه می‌دارد. در فریمورک Symfony، فیلد attributes حاوی مقادیری است که با مسیر درخواست‌شده مطابقت دارد، نظیر _controller و id (اگر یک شناسه {id} داشته باشید)، و حتی نام مسیر مطابقت داده‌شده (_route). خصوصیت attributes بطور کلی بعنوان مکانی که می‌توانید اطلاعات مربوط به درخواست را نگه‌داری کنید، در دسترس شما است.

سیمفونی همچنین شامل یک کلاس Response است: یک معادل ساده PHP برای پیام پاسخ HTTP. این موضوع به برنامه شما اجازه می‌دهد تا از یک رابط شئ‌گرا برای ساخت پاسخ موردنیاز جهت بازگشت به کلاینت استفاده‌کند:

use Symfony\Component\HttpFoundation\Response;

$response = new Response();

$response->setContent('<html><body><h1>Hello world!</h1></body></html>');
$response->setStatusCode(Response::HTTP_OK);
$response->headers->set('Content-Type', 'text/html');

// Prints the HTTP headers followed by the content
$response->send();

اگر سیمفونی هیچ ابزار دیگری هم ارائه نمی‌داد، با همین ابزارهای پایه نیز می‌توانستید به‌راحتی به اطلاعات درخواست دسترسی داشته باشید و از یک رابط شئ‌گرا برای ساخت پاسخ استفاده‌کنید. حتی در زمانی که قابلیت‌های بسیارزیاد و قدرتمند Symfony را می‌آموزید، فراموش نکنید که هدف برنامه شما همیشه تعامل با یک درخواست و تولید پاسخ مناسب برپایه منطق برنامه شماست.

کلاس‌های Request و Response بخشی از یک جزء مستقل همراه Symfony بنام HttpFoundation هستند. این عنصر (Component) را می‌توان بصورت کاملاً مستقل از فریمورک به‌کار برد و همچنین کلاس‌هایی برای مدیریت Session و آپلود فایل‌ها ارائه می‌دهد.

سفر از درخواست به پاسخ

مثل خود HTTP، اشیاء Request و Response بسیار ساده هستند. بخش سخت تولید یک برنامه، نوشتن کارهایی است که بین این دو بخش اتفاق می‌افتد. به‌عبارت‌دیگر، کار اصلی شما نوشتن کدی است که اطلاعات درخواست را پردازش‌کرده و خروجی را تولید می‌کند.

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

سیمفونی برای حل این مشکلات ساخته شده است تا شما درگیر این موضوعات نشوید.

کنترل‌کننده جلویی یا خط مقدم

بطور سنتی، برنامه‌ها به‌شکلی ساخته می‌شوند که هر «صفحه» از سایت، فایل فیزیکی خودش را دارد.

index.php
contact.php
blog.php

مشکلات متعددی در این روش وجود دارد که شامل عدم انعطاف‌پذیری آدرس‌های URL و این حقیقت می‌شود که هر فایل باید بصورت دستی مجموعه‌ای از فایل‌های هسته را ضمیمه‌کند تا امنیت، اتصال دیتابیس و «نمای ظاهری» سایت مستقل باقی بمانند. اگر بخواهید blog.php را به news.php تغییر دهید، بدون اینکه تمام لینک‌های شما خراب شوند، چه می‌کنید؟

روش بسیار بهتر، استفاده از یک کنترل‌کننده جلویی (Front Controller) است: یک فایل PHP که همه درخواست‌های ورودی برنامه شما را پردازش می‌کند. برای مثال:

/index.php             executes index.php
/index.php/contact     executes index.php
/index.php/blog        executes index.php

با استفاده از افزونه mod_rewrite آپاچی (یا معادل آن در سایر وب‌سرورها)، URLها را می‌توان به‌سادگی تمیز کرد تا به‌ترتیب به / و /contact و /blog تبدیل شوند.

اکنون، هر درخواستی دقیقاً بصورت یکسان مدیریت می‌شود. بجای اینکه URLهای مختلف، فایل‌های PHP مختلفی را اجرا کنند، کنترل‌کننده اصلی همیشه اجرا می‌شود و مسیریابی URLهای مختلف به بخش‌های مختلفی از برنامه شما بصورت داخلی انجام خواهد شد. این موضوع هر دو مشکل روند اولیه را حل می‌کند. تقریباً تمامی برنامه‌های وب مدرن امروزی (شامل برنامه‌هایی نظیر WordPress) از این روش استفاده می‌کنند.

مرتب باقی بمانید

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

// index.php
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

$request = Request::createFromGlobals();
$path = $request->getPathInfo(); // the URI path being requested

if(in_array($path, array('', '/'))) {
   $response = new Response('Welcome to the homepage.');
} elseif('/contact' === $path) {
   $response = new Response('Contact us');
} elseif('/blog' === $path) {
   $response = new Response('Blog');
} else {
   $response = new Response('Page not found.', Response::HTTP_NOT_FOUND);
}
$response->send();

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

جریان برنامه Symfony

وقتی به سیمفونی اجازه مدیریت درخواست‌ها را می‌دهید، زندگی بسیار آسان‌تر می‌شود. سیمفونی از روش یکسان و ساده‌ای برای هر درخواست استفاده می‌کند:

[عکس: attachment.php?aid=394]

درخواست‌های ورودی توسط سیستم مسیریابی تفسیرشده و به توابع کنترلر ارسال می‌شوند که اشیاء Response مناسب را بعنوان نتیجه بازمی‌گردانند.

هر «صفحه» از سایت شما در یک فایل تنظیمات مسیریابی مشخص می‌شود که URLهای مختلف را به توابع مختلف PHP هدایت می‌کند. وظیفه هر تابع PHP، که یک کنترل‌کننده نامیده می‌شود، استفاده از اطلاعات درخواست - درکنار بسیاری از ابزارهای دیگر که سیمفونی فراهم می‌کند - برای تولید و بازگرداندن یک شئ Response است. به‌عبارت دیگر، کنترلر جایی است که کد شما وارد می‌شود: مکانی که شما درخواست را پردازش می‌کنید و یک پاسخ می‌سازید.

به همین سادگی. برای مرور:
  • هر درخواست یک فایل کنترلر اصلی را اجرا می‌کند.
  • سیستم مسیریابی تشخیص می‌دهد که چه تابعی از PHP باید براساس اطلاعات دریافتی از درخواست و تنظیمات مسیریابی که ایجاد کرده‌اید، اجرا شود.
  • تابع مناسب PHP اجرا شده و در آنجا، کد شام شئ Response مناسب را تولیدکرده و بازمی‌گرداند.

یک درخواست سیمفونی در عمل

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

#app/config/routing.yml
contact:
    path: /contact
    defaults: { _controller: AppBundle:Main:contact }

<!-- app/config/routing.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/routing
        http://symfony.com/schema/routing/routing-1.0.xsd">
    <route id="contact" path="/contact">
        <default key="_controller">AppBundle:Main:contact</default>
    </route>
</routes>

// app/config/routing.php
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;

$collection = new RouteCollection();
$collection->add('contact', new Route('/contact', array('_controller' => 'AppBundle:Main:contact')));

return $collection;

وقتی کسی صفحه /contact را بازدید کند، این مسیر مورد مطابقت واقع می‌شود و کنترلر مشخص‌شده اجرا خواهد شد. همان‌طور که در بخش مسیریابی خواهید آموخت، رشته AppBundle:Main:contact یک دستورزبان کوتاه است که به یک متد خاص در PHP بنام contactAction درون یک کلاس بنام MainController اشاره می‌کند.

// src/AppBundle/Controller/MainController.php

namespace AppBundle\Controller;

use Symfony\Component\HttpFoundation\Response;

class MainController
{
    public function contactAction()
    {
        return new Response('<h1>Contact us!</h1>');
    }
}

در این مثال بسیار ساده، کنترلر به‌سادگی یک شئ Response با محتوای HTML معادل <h1>Contact us!</h1> ایجاد می‌کند. در بخش کنترلر، خواهید آموخت که چگونه یک کنترلر می‌تواند قالب‌ها را رندر کند و به شما اجازه‌دهد کد نمایش شما (یعنی هر چیزی که واقعاً خروجی HTML را می‌نویسد)، در یک فایل قالب جداگانه قرارگیرد. این کار کنترلر را آزاد می‌سازد تا تنها درباره بخش سخت کار نگران باشد: تعامل با پایگاه داده‌ها، کنترل داده‌های ارسال‌شده، یا ارسال پیام‌های ایمیل.

سیمفونی: برنامه خود را بسازید، نه ابزارهای خود را

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

خبر خوب این است که هیچ‌کدام از این مشکلات، منحصربفرد نیستند. سیمفونی یک فریمورک سرشار از ابزارهایی ارائه کرده‌است که به شما اجازه می‌دهد برنامه خودتان را بسازید، نه ابزارهای موردنیاز آنرا. با کمک سیمفونی، هیچ چیزی به شما تحمیل نمی‌شود: شما آزاد هستید که از فریمورک کامل Symfony استفاده‌کنید، یا فقط بخشی از کل سیمفونی را بکار گیرید.

ابزارهای مستقل: کامپوننت‌های سیمفونی

خوب سیمفونی چیست؟ ابتدا، سیمفونی مجموعه‌ای از بیش‌از بیست کتابخانه مستقل است که می‌توانند درون هر پروژه PHP بکار گرفته شوند. این کتابخانه‌ها که بنام اجزاء Symfony شناخته می‌شوند، شامل موارد کاربردی برای تقریباً هر وضعیتی و بدون اهمیت‌دادن به اینکه پروژه شما چگونه توسعه داده می‌شود، است. برای نام‌بردن چند مورد:

HttpFoundation

شامل کلاس‌های Request و Response است، همراه‌با کلاس‌های دیگری برای کنترل Session و آپلودهای فایل.

Routing

سیستم مسیریابی قدرتمند و سریعی که به شما اجازه می‌دهد یک URI خاص (نظیر /contact) را به اطلاعاتی درباره چگونگی مدیریت آن درخواست، مرتبط نمایید (برای مثال، اجراکردن متد contactAction())

Form

یک فریمورک کامل و انعطاف‌پذیر برای ساخت فرم‌ها و کنترل فرم‌های ارسال‌شده.

Validator

یک سیستم برای ساخت قواعدی درباره داده‌ها و سپس اعتبارسنجی این‌موضوع که آیا داده‌های ارسال‌شده توسط کاربران، آن قواعد را رعایت کرده‌است یا خیر.

Templating

یک جعبه‌ابزار برای رندرکردن قالب‌ها، کنترل ارث‌بری قالب‌ها از یکدیگر (یعنی یک قالب درون یک نمای کلی یا Layout قرارگیرد) و انجام سایر وظایف رایج قالب.

Security

یک کتابخانه قدرمند برای کنترل همه انواع امنیت درون یک برنامه.

Translation

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

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

راه‌حل کامل: فریمورک Symfony

خوب درنهایت، فریمورک سیمفونی چیست؟ فریمورک Symfony یک کتابخانه PHP است که دو وظیفه متمایز را انجام می‌دهد:

  1. مجموعه‌ای از کامپوننت‌ها (یعنی کامپوننت‌های سیمفونی) و کتابخانه‌های شخص ثالث (مثل SwiftMailer برای ارسال ایمیل) را ارائه می‌دهد.
  2. تنظیمات منطقی و یک کتابخانه «چسب» ارائه می‌کند که همه اجزاء دیگر را بهم گره می‌زند.

هدف فریمورک ادغام عناصر زیاد و مستقل از هم برای ارائه یک تجربه یکپارچه به توسعه‌دهنده است. حتی خود فریمورک نیز یک باندل (Bundle) سیمفونی (یعنی افزونه یا Plugin) است که می‌تواند تنظیم یا بطور کامل با چیز دیگری جایگزین‌گردد.

سیمفونی یک مجموعه قدرتمند از ابزارها را برای توسعه سریع وب‌اپلیکیشن‌ها بدون تحمیل چیزی برروی اپلیکیشن شما ارائه می‌دهد. کاربران عادی می‌توانند بسرعت شروع به توسعه با کمک یکی از توزیع‌های سیمفونی نمایند، که یک اسکلت اولیه پروژه با پیشفرض‌های معقول ارائه می‌دهد. برای کاربران پیشرفته‌تر، محدودیتی وجود ندارد.


فایل‌های پیوست تصاویر بندانگشتی
           
تشکر شده توسط: mnk_moh , mgbg , hamo , mahdirabbani , gmail , meysam1366 , elephpant
#3
بخش 2 - سیمفونی دربرابر PHP خام

چرا استفاده از سیمفونی بهتر از بازکردن یک فایل و نوشتن کد PHP خام است؟

اگر هرگز از یک فریمورک PHP استفاده نکرده باشید، با فلسفه MVC آشنا نباشد، یا فکر می‌کنید که همه حرف و حدیث‌ها درباره Symfony است، این بخش ویژه شماست. بجای اینکه به شما بگوییم سیمفونی اجازه می‌دهد نرم‌افزارها را سریع‌تر و بهتر از PHP خام توسعه دهید، خودتان آنرا خواهید دید.

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

در پایان، خواهید دید که چگونه سیمفونی می‌تواند شما را از وظایف پیش‌پاافتاده نجات دهد و بگذارد کنترل کدتان را دوباره بدست بگیرید.

یک وبلاگ ساده با PHP خام

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

<?php
// index.php
$link = new PDO('mysql:host=localhost;dbname=blog_db', 'myuser', 'mypassword');

$result = $link->query('SELECT id, title FROM post');
?>
<!DOCTYPE html>
<html>
   <head>
       <title>List of Posts</title>
   </head>
   <body>
       <h1>List of Posts</h1>
       <ul>
           <?php while ($row = $result->fetch(PDO::FETCH_ASSOC)) : ?>
           <li>
               <a href="/show.php?id=<?php echo $row['id'] ?>">
                   <?php echo $row['title'] ?>
               </a>
           </li>
           <?php endwhile ?>
       </ul>
   </body>
</html>

<?php
$link = null;
?>

نوشتن این فایل و اجرای آن سریع است و به‌مرور با بزرگ‌شدن برنامه شما، نگهداری آن غیرممکن خواهد بود. چند مشکل وجود دارد که شما باید آنها را برطرف نمایید:
  • کنترل‌نکردن خطا: اگر اتصال به دیتابیس با موفقیت انجام نشود، چه اتفاقی می‌افتد؟
  • سازماندهی ضعیف: اگر برنامه رشد کند، این فایل تنها به‌شدت غیرقابل نگهداری خواهد شد. کد مدیریت فرم‌های ارسال‌شده را کجا قرار می‌دهید؟ چگونه می‌توانید داده‌ها را اعتبارسنجی کنید؟ کد ارسال ایمیل کجا می‌رود؟
  • استفاده مجدد از کد سخت است: از آنجا که همه‌چیز در یک فایل قرار دارد، هیچ راهی برای استفاده مجدد از بخش‌های برنامه برای «صفحات» دیگر بلاگ وجود ندارد.

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

جداسازی نمایش

کد فوق می‌تواند مستقیماً از جداسازی «منطق» برنامه از کد «نمایش» خروجی HTML بهره‌مند شود:

// index.php
$link = new PDO('mysql:host=localhost;dbname=blog_db', 'myuser', 'mypassword');

$result = $link->query('SELECT id, title FROM post');

$posts = array();
while($row = $result->fetch(PDO::FETCH_ASSOC)) {
   $posts[] = $row;
}

$link = null;

// include the HTML presentation code
require 'templates/list.php';[/shcode=php]

اکنون کد HTML در یک فایل جداگانه قرار گرفته است (templates/list.php) که یک فایل HTML و حاوی محتوایی شبیه یک سیستم قالب PHP است:
[shcode=php]<!DOCTYPE html> <html>    <head>        <title>List of Posts</title>    </head>    <body>        <h1>List of Posts</h1>        <ul>            <?php foreach ($posts as $post): ?>            <li>                <a href="/read?id=<?php echo $post['id'] ?>">                    <?php echo $post['title'] ?>                </a>            </li>            <?php endforeach ?>        </ul>    </body> </html>

ازنظر مفهوم، فایلی که حاوی همه منطق برنامه است - index.php - بعنوان کنترل‌کننده (کنترلر) شناخته می‌شود. اصلاح کنترلر کلمه‌ای است که آنرا بسیار خواهید شنید، بدون توجه به اینکه از چه زبان یا فریمورکی استفاده می‌کنید. این کلمه به‌سادگی به محدوده‌ای از «کد شما» اشاره می‌کند که ورودی‌های کاربر را پردازش‌کرده و خروجی را آماده می‌کند.

در این مثال، کنترلر داده‌ها را از پایگاه داده‌ها آماده‌کرده و سپس یک قالب را برای نمایش آن اطلاعات ضمیمه می‌کند. با جداسازی کنترلر، می‌توانید به‌راحتی «فقط» فایل قالب را درصورت نیاز برای نمایش محتوای وبلاگ در ساختار دیگر تغییر دهید (برای مثال list.json.php برای قالب JSON).

جداسازی منطق (حوزه) برنامه

تا اینجا برنامه فقط یک صفحه داشت. اما اگر صفحه دوم نیاز به استفاده از اتصال مشابه به پایگاه داده‌ها یا حتی آرایه مشابعی از پست‌های وبلاگ داشت چه‌کنیم؟ کد را بازنویسی کنید تا رفتار هسته برنامه و توابع دسترسی به داده‌های برنامه در یک فایل جداگانه بنام model.php قرار گیرد:

// model.php
function open_database_connection()
{
   $link = new PDO('mysql:host=localhost;dbname=blog_db', 'myuser', 'mypassword');
   return $link;
}

function close_database_connection($link)
{
   $link = null;
}

function get_all_posts()
{
   $link = open_database_connection();

   $result = $link->query('SELECT id, title FROM post');

   $posts = array();
   while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
       $posts[] = $row;
   }

   close_database_connection($link);

   return $posts;
}

نقل قول:
نکته: نام فایل model.php به این دلیل بکار رفته‌است که منطق و دسترسی به داده‌های برنامه، بطور سنتی تحت عنوان لایه «مدل» شناخته می‌شود. در یک برنامه که به‌خوبی سازماندهی شده‌است، بخش عمده کد که معادل «منطق تجاری» شماست باید در مدل قرار گیرد (برخلاف باور عمومی که باید در کنترلر باشد). و برخلاف این مثال، فقط بخشی (یا هیچ) از مدل درحقیقت نگران دسترسی به دیتابیس است.

کنترلر (index.php) اکنون بسیار ساده است:

require_once 'model.php';

$posts = get_all_posts();

require 'templates/list.php';

اکنون تنها وظیفه کنترلر، دریافت داده‌ها از لایه مدل برنامه و فراخوانی یک قالب برای نمایش آن داده‌ها است. این یک مثال بسیار ساده از الگوی Model-View-Controller یا به‌اختصار MVC است.

جداسازی چیدمان صفحه

در این نقطه، برنامه به سه بخش کاملاً مجزا بازنویسی شده است که مزایای زیادی دارد و تقریباً امکان استفاده مجدد از هر بخش آن در سایر صفحات وجود دارد. تنها بخشی که نمی‌توان در سایر قسمت‌ها مجدداً مورداستفاده قرار داد، چیدمان صفحه یا لِی‌اوت (Layout) است. برای حل این مشکل، یک فایل layout.php ایجاد کنید:

<!-- templates/layout.php -->
<!DOCTYPE html>
<html>
   <head>
       <title><?php echo $title ?></title>
   </head>
   <body>
       <?php echo $content ?>
   </body>
</html>

قالب (templates/list.php) می‌تواند به‌سادگی برای «توسعه» چیدمان به‌کار رود:

<?php $title = 'List of Posts'; ?>

<?php ob_start(); ?>
   <h1>List of Posts</h1>
   <ul>
       <?php foreach ($posts as $post): ?>
       <li>
           <a href="/read?id=<?php echo $post['id'] ?>">
               <?php echo $post['title'] ?>
           </a>
       </li>
       <?php endforeach ?>
   </ul>
<?php $content = ob_get_clean() ?>

<?php include 'layout.php' ?>

اکنون دارای پیکربندی خاصی هستید که به شما اجازه می‌دهد از چیدمان کلی صفحات، مجدداً استفاده‌کنید. متأسفانه برای دستیابی به این موضوع، مجبور به استفاهد از چند تابع زشت PHP (ob_start() و ob_get_clean()) درون قالب هستید. سیمفونی از یک کامپوننت قالب‌بندی استفاده می‌کند که این‌کار را بصورت تمیز و ساده انجام می‌دهد و بزودی آنرا در عمل خواهید دید.

افزودن یک صفحه «نمایش» بلاگ

صفحه «فهرست» وبلاگ باید مجدداً بازنویسی شود تا کدها بهتر سازماندهی و قابل‌استفاده شوند. برای اثبات این موضوع، یک صفحه «نمایش» وبلاگ اضافه‌کنید که یک پست خاص را که با پارامتر id ارسال‌شده در آدرس صفحه، نمایش می‌دهد.

برای شروع، یک تابع جدید به model.php اضافه‌کنید که یک پست خاص را براساس id داده‌شده استخراج می‌کند:

// model.php
function get_post_by_id($id)
{
   $link = open_database_connection();
   $id = intval($id);
   $result = $link->query('SELECT created_at, title, body FROM post WHERE id = ' . $id);
   $row = $result->fetch(PDO::FETCH_ASSOC);

   close_database_connection($link);

   return $row;
}

حال یک فایل دیگر به اسم show.php بسازید - کنترلر برای این صفحه جدید:

require_once 'model.php';

$post = get_post_by_id($_GET['id']);

require 'templates/show.php';

درنهایت، یک فایل قالب جدید - templates/show.php - برای نمایش پست موردنظر:

<?php $title = $post['title'] ?>

<?php ob_start(); ?>
   <h1><?php echo $post['title'] ?></h1>

   <div class="date"><?php echo $post['created_at'] ?></div>
   <div class="body">
       <?php echo $post['body'] ?>
   </div>
<?php $content = ob_get_clean() ?>

<?php include 'layout.php' ?>

ساخت صفحه دوم اکنون بسیار ساده است و هیچ کدی تکرار نمی‌شود. هنوز هم این صفحه مشکلات زیادی را نشان می‌دهد که یک فریمورک می‌تواند برای شما حل‌کند. برای مثال، اگر پارامتر id در URL موجود نباشد، صفحه با خطا مواجه می‌شود. بهتر است چنین مواردی با یک خطای 404 نمایش داده شود، اما این موضوع فعلاً به‌سادگی قابل انجام نیست. بدتر از آن، اگر فراموش کنید که پارامتر id را با دستور intval() ایمن‌کنید، کل پایگاه داده‌های شما درمعرض خطر حمله SQL Injection قرار خواهد گرفت.

یک مشکل دیگر آن است که هر فایل کنترلر باید فایل model.php را جداگانه ضمیمه‌کند. حال اگر هر فایل کنترلر، ناگهان نیاز به ضمیمه‌کردن یک فایل اضافه یا انجام یک کار سراسری دیگر داشته باشد (برای مثال، اجرای امنیت)، چه باید بکنید؟ با وضع فعلی، آن کد باید به همه فایل‌های کنترلر اضافه شود. اگر فراموش‌کنید که چیزی را در یک فایل ضمیمه‌کنید، امیدواریم که مرتبط به امنیت نباشد...

یک «کنترلر جلویی» برای نجات

راه‌حل مشکل فوق، استفاده از کنترلر جلویی یا «خط‌مقدم» است: یک فایل PHP منفرد که «همه» درخواست‌ها در آن پردازش می‌شوند. با یک کنترلر جلویی، URIهای برنامه کمی تغییر می‌کنند، اما اجازه‌دهید کمی انعطاف‌پذیری را بیشتر کنیم:

Without a front controller
/index.php          => Blog post list page (index.php executed)
/show.php           => Blog post show page (show.php executed)

With index.php as the front controller
/index.php          => Blog post list page (index.php executed)
/index.php/show     => Blog post show page (index.php executed)

نقل قول:
بخش index.php از URI می‌تواند درصورت استفاده از قواعد بازنویسی آدرس Apache (یا معادل آن در سایر وب‌سرورها) حذف شود. در این حالت، URI صفحه نمایش پست به‌سادگی /show خواهد بود.

وقتی از کنترلر جلویی استفاده می‌کنید، یک فایل PHP مستقل (index.php در این مثال) همه درخواست‌ها را پردازش می‌کند. برای صفحه نمایش پست، /index.php/show درحقیقت فایل index.php را اجرا می‌کند، که اکنون مسئول آدرس‌دهی درخواست‌ها بصورت داخلی براساس URI کامل است. همان‌گونه که خواهید دید، یک کنترلر جلویی، ابزاری بسیار قدرتمند است.

ساخت کنترلر جلویی

شما می‌خواهید یک قدم بزرگ در برنامه بردارید. با کنترل‌کردن همه درخواست‌ها در یک فایل، می‌توانید مواردی مثل کنترل امنیت، بارگذاری تنظیمات، و مسیریابی را بصورت متمرکز مدیریت کنید. در این برنامه، index.php باید اکنون به‌اندازه کافی هوشمند باشد تا صفحه فهرست پست‌ها یا نمایش یک پست خاص را براساس URI درخواست‌شده نمایش دهد:

// index.php

// load and initialize any global libraries
require_once 'model.php';
require_once 'controllers.php';

// route the request internally
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
if ('/index.php' === $uri) {
   list_action();
} elseif ('/index.php/show' === $uri && isset($_GET['id'])) {
   show_action($_GET['id']);
} else {
   header('Status: 404 Not Found');
   echo '<html><body><h1>Page Not Found</h1></body></html>';
}

برای سازماندهی، هر دو کنترلر (قبلاً index.php و show.php) اکنون توابع PHP هستند و درون یک فایل جداگانه بنام controllers.php ذخیره شده‌اند:

function list_action()
{
   $posts = get_all_posts();
   require 'templates/list.php';
}

function show_action($id)
{
   $post = get_post_by_id($id);
   require 'templates/show.php';
}

بعنوان یک کنترلر جلویی، index.php یک نقش کاملاً جدید را پذیرفته است: وظیفه‌ای که شامل بارگذاری کتابخانه‌های هسته و مسیریابی برنامه است تا هرکدام از دو کنترلر (توابع list_action() و show_action()) فراخوانی شوند. در واقعیت، کنترلر جلویی نقطه شروع برای بررسی است و نقشی بسیار شبیه مکانیزم سیمفونی برای کنترل و مسیریابی درخواست‌ها ایفا می‌کند.

نقل قول:
یک مزیت دیگر کنترلر جلویی، URLهای انعطاف‌پذیر است. دقت‌کنید که URL صفحه نمایش پست وبلاگ می‌تواند از /show به /read با تغییر کد فقط در یک محل، تغییریایبد. قبلاً کل یک فایل باید تغییرنام پیدا می‌کرد. در سیمفونی، URLها می‌توانند حتی بیش از این انعطاف‌پذیر باشند.

تا اینجا، برنامه از یک فایل تک PHP به یک ساختار سازماندهی‌شده تبدیل شده و اجازه استفاده مجدد از کدها را می‌دهد. شما باید خوشحال‌تر باشید، اما هنوز از رضایت کامل دور هستیم. برای مثال، سیستم «مسیریابی» بی‌ثبات است و تشخیص نمی‌دهد که صفحه فهرست (/index) باید با / نیز قابل دسترسی باشد (اگر قوانین موردنیاز ماژول rewrite آپاچی اضافه شده باشند). همچنین، بجای توسعه وبلاگ، بخش عمده زمان صرف کار برروی «معماری» کد (مثل مسیریابی، فراخوانی کنترلرها، قالب‌ها و...) می‌شود. زمانی بیشتری باید صرف مدیریت فرم‌های ارسالی، اعتبارسنجی ورودی‌های کاربر، گزارش‌گیری و امنیت شود. چرا باید راهکارهای موردنیاز برای تمام این مشکلات رایج و روزمره را مجدداً اختراع کنید؟

اتصال به سیمفونی

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

در پوشه پروژه خود، یک فایل composer.json با محتوای زیر بسازید:

{
   "require": {
       "symfony/symfony": "3.0.*"
   },
   "autoload": {
       "files": ["model.php", "controllers.php"]
   }
}

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

composer install

درکنار دانلود وابستگی‌ها، کامپوزر یک فایل vendor/autoload.php نیز تولید می‌کند، که وظیفه بارگذاری خودکار تمام فایل‌های درون فریمورک سیمفونی را درکنار فایل‌هایی که در بخش autoload فایل composer.json مشخص کرده‌اید، انجام می‌دهد.

قلب فلسفه سیمفونی، این ایده است که وظیفه اصلی یک برنامه، تفسیر هر درخواست و بازگرداندن یک پاسخ است. بدین‌منظور، سیمفونی هر دو کلاس Request و Response را ارائه می‌کند. این کلاس‌ها نسخه‌های معادل شئ‌گرای درخواست HTTP خامِ تحت‌پردازش و خروجی HTTP تولیدشده هستند. از آنها برای بهبود وبلاگ استفاده‌کنید:

// index.php
require_once 'vendor/autoload.php';

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

$request = Request::createFromGlobals();

$uri = $request->getPathInfo();
if ('/' === $uri) {
   $response = list_action();
} elseif ('/show' === $uri && $request->query->has('id')) {
   $response = show_action($request->query->get('id'));
} else {
   $html = '<html><body><h1>Page Not Found</h1></body></html>';
   $response = new Response($html, Response::HTTP_NOT_FOUND);
}

// echo the headers and send the response
$response->send();


کنترلرها اکنون مسئول بازگرداندن یک شئ Response هستند. برای ساده‌سازی این‌کار می‌توانید یک تابع render_template() اضافه‌کنید که برحسب اتفاق، کمی شبیه موتور قالب Symfony عمل می‌کند:

// controllers.php
use Symfony\Component\HttpFoundation\Response;

function list_action()
{
   $posts = get_all_posts();
   $html = render_template('templates/list.php', array('posts' => $posts));

   return new Response($html);
}

function show_action($id)
{
   $post = get_post_by_id($id);
   $html = render_template('templates/show.php', array('post' => $post));

   return new Response($html);
}

// helper function to render templates
function render_template($path, array $args)
{
   extract($args);
   ob_start();
   require $path;
   $html = ob_get_clean();

   return $html;
}

با استفاده از بخش کوچکی از سیمفونی، برنامه انعطاف‌پذیرتر و قابل‌اطمینان‌تر است. Request یک روش قابل اعتماد برای دسترسی به اطلاعات درباره درخواست HTTP ارائه می‌کند. متد getPathInfo() یک URI تمیزشده باز می‌گرداند (همیشه /show بر می‌گرداند و هیچوقت /index.php/show خروجی نمی‌دهد). بنابراین، حتی اگر کاربر وارد مسیر /index.php/show شود، برنامه هوشمندی کافی برای مسیریابی درخواست ازطریق show_action را دارد.

برنامه مشابه در سیمفونی

وبلاگ راه زیادی را آمده‌است، اما هنوز کد زیادی برای چنین برنامه ساده‌ای دارد. درطول مسیر، شما یک سیستم مسیریابی ساده ایجاد کردید و متدی با کمک ob_start() و ob_get_clean() برای رندر قالب‌ها ایجاد نمودید. اگر به‌هردلیلی نیاز به ادامه ساخت این «فریمورک» از پایه داشتید، می‌توانید لااقل از کامپوننت‌های مستقل Routing و Templating در سیمفونی استفاده‌کنید، که این مشکلات را از قبل حل کرده‌اند.

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

// src/AppBundle/Controller/BlogController.php
namespace AppBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class BlogController extends Controller
{
    public function listAction()
    {
        $posts = $this->get('doctrine')
            ->getManager()
            ->createQuery('SELECT p FROM AppBundle:Post p')
            ->execute();

        return $this->render('Blog/list.html.php', array('posts' => $posts));
    }

    public function showAction($id)
    {
        $post = $this->get('doctrine')
            ->getManager()
            ->getRepository('AppBundle:Post')
            ->find($id);

        if (!$post) {
            // cause the 404 page not found to be displayed
            throw $this->createNotFoundException();
        }

        return $this->render('Blog/show.html.php', array('post' => $post));
    }
}

دو کنترلر هنوز هم سبک هستند. هرکدام از کتابخانه مدل رابطه‌ای شئ خاصی بنام Doctrine ORM استفاده می‌کنند تا اشیاء را از پایگاه داده‌ها استخراج‌کنند و از کامپوننت Templating برای رندرکردن یک قابل و بازگشت شئ Response بهره می‌برند. قالب فهرست اکنون کمی ساده‌تر است:

<!-- app/Resources/views/Blog/list.html.php -->
<?php $view->extend('layout.html.php') ?>

<?php $view['slots']->set('title', 'List of Posts') ?>

<h1>List of Posts</h1>
<ul>
   <?php foreach ($posts as $post): ?>
   <li>
       <a href="<?php echo $view['router']->path(
           'blog_show',
           array('id' => $post->getId())
       ) ?>">
           <?php echo $post->getTitle() ?>
       </a>
   </li>
   <?php endforeach ?>
</ul>

چیدمان صفحه تقریباً مثل گذشته است:

<!-- app/Resources/views/layout.html.php -->
<!DOCTYPE html>
<html>
   <head>
       <title><?php echo $view['slots']->output(
           'title',
           'Default title'
       ) ?></title>
   </head>
   <body>
       <?php echo $view['slots']->output('_content') ?>
   </body>
</html>

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

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

# app/config/routing.yml
blog_list:
    path:     /blog
    defaults: { _controller: AppBundle:Blog:list }

blog_show:
    path:     /blog/show/{id}
    defaults: { _controller: AppBundle:Blog:show }

حالا که سیمفونی همه وظایف پایه‌ای را مدیریت می‌کند، کنترلر جلویی به‌شدت ساده می‌شود و اینقدر کوچک شده‌است، شما هرگز نیاز به دستکاری آن بعد از ایجاد، ندارید (و اگر از یک توزیع سیمفونی استفاده کرده‌باشید، اصلاً حتی نیاز به ایجاد آن هم نخواهید داشت!) :

// web/app.php
require_once __DIR__ . '/../app/bootstrap.php';
require_once __DIR__ . '/../app/AppKernel.php';

use Symfony\Component\HttpFoundation\Request;

$kernel = new AppKernel('prod', false);
$kernel->handle(Request::createFromGlobals())->send();

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

برای نمایش بصری چگونگی مدیریت هر درخواست در Symfony، تصویر چرخه درخواست را مجدداً مشاهده کنید.

آنچه که سیمفونی ارائه می‌دهد

در بخش‌های آینده، درمورد اینکه چگونه هرکدام از اجزاء سیمفونی کار می‌کند و همچنین ساختار پیشنهادی پروژه، مطالب زیادی خواهید آموخت. فعلاً نگاهی به اینکه چگونه مهاجرت وبلاگ از PHP خام به سیمفونی، حیات پروژه را توسعه‌داد، بیاندازید:
  • برنامه شما یک کد واضح و با سازماندهی یکپارچه دارد (هرچند سیمفونی شما را مجبور به این کار نمی‌کند). این موضوع قابلیت استفاده مجدد از کد را گسترش‌داده و به برنامه‌نویسان جدید اجازه می‌دهد سریع‌تر در پروژه شما خلاقانه ظاهر شوند.
  • صددرصد کدی که نوشته‌اید، مخصوص برنامه شماست. نیاز به توسعه یا نگهداری ابزارهای سطح‌پایین مثل بارگذاری خودکار، مسیریابی، یا رندرکردن کنترلرها ندارید.
  • سیمفونی به شما دسترسی به ابزارهای بازمتنی نظیر کامپوننت‌های داکترین (Doctrine)، قالب‌بندی (Templating)، امنیت (Security)، فرم (Form)، اعتبارسنجی (Validation) و ترجمه (Translation) را می‌دهد (صرفاً چندمورد اندک از همه ابزارهای موجود نام برده شد).
  • برنامه اکنون به‌لطف کامپوننت مسیریابی، از URLهای کاملاً انعطاف‌پذیر لذت می‌برد.
  • معماری سیمفونی با محوریت HTTP به شما دسترسی به ابزارهای قدرتمندی نظیر کَش (Cache) در HTTP را با کمک کش داخلی HTTP سیمفونی یا ابزارهای قدرتمندتری نظیر Varnish می‌دهد. این موضوع در بخش‌های بعدی بطور کامل در مبحث مدیریت حافظه پنهان پوشش داده می‌شود.

و شاید بهترین مورد اینکه با استفاده از سیمفونی، شما اکنون دسترسی به تمام مجموعه ابزارهای بازمتنی که توسعه جامعه کاربری Symfony توسعه داده می‌شود، دارید! یک انتخاب خوب از ابزارهای جامعه کاربران سیمفونی را می‌توان در [/url]KnpBundles.com یافت.

قالب‌های بهتر

اگر تصمیم به استفاده از آن داشته‌باشید، سیمفونی با یک استاندارد موتور قالب موسوم به Twig عرضه می‌شود که نوشتن قالب‌ها را سریع‌تر و خواندن آنها را ساده‌تر می‌کند. معنای این حرف آن است که برنامه مشابه، حتی می‌تواند باز هم کد کمتری داشته باشد. برای مثال، قالب فهرست که در ساختار Twig نوشته شده‌است، بصورت زیر خواهد بود:

{# app/Resources/views/blog/list.html.twig #}
{% extends "layout.html.twig" %}

{% block title %}List of Posts{% endblock %}

{% block body %}
   <h1>List of Posts</h1>
   <ul>
       {% for post in posts %}
       <li>
           <a href="{{ path('blog_show', {'id': post.id}) }}">
               {{ post.title }}
           </a>
       </li>
       {% endfor %}
   </ul>
{% endblock %}

چیدمان layout.html.twig نیز ساده‌تر است:

{# app/Resources/views/layout.html.twig #}
<!DOCTYPE html>
<html>
   <head>
       <title>{% block title %}Default title{% endblock %}</title>
   </head>
   <body>
       {% block body %}{% endblock %}
   </body>
</html>

Twig به‌خوبی در سیمفونی پشتیبانی می‌شود و درحالی‌که همیشه امکان استفاده از قالب‌های PHP در سیمفونی وجود دارد، مزایای زیاد Twig همچنان مورد بحث خواهد بود. برای اطلاعات بیشتر، بخش قالب‌بندی را مطالعه‌کنید.

از کتاب آشپزی سیمفونی بیشتر بیاموزید
تشکر شده توسط: hamo , gmail , elephpant
#4
بخش 3 - نصب و تنظیم سیمفونی
به سیمفونی خوش‌آمدید! شروع یک پروژه جدید سیمفونی ساده است. درحقیقت شما اولین برنامه سیمفونی خود را در عرض چند دقیقه آماده اجرا خواهید دید.

نقل قول:
آیا شما آموزش‌های تصویری را ترجیح می‌دهید؟ فیلم‌های توسعه لذت‌بخش با سیمفونی را از KnpUniversity بررسی کنید.

برای ساده‌تر کردن ساخت برنامه‌های جدید، سیمفونی یک برنامه نصب عرضه کرده‌است. دانلودکردن آن، اولین قدم شماست.

نصب برنامه نصب‌کننده سیمفونی
استفاده از برنامه نصب سیمفونی تنها راه توصیه‌شده برای ساخت پروژه‌های جدید سیمفونی است. این برنامه، یک نصب‌کننده با کمک PHP است که باید فقط یک‌بار برروی سیستم شما نصب شود و با کمک آن می‌توانید هرتعداد که بخواهید، پروژه سیمفونی ایجاد کنید.

نقل قول:
برنامه نصب به PHP5.4 نیاز دارد. اگر هنوز از نسخه 5.3 قدیمی استفاده می‌کنید، نمی‌توانید با برنامه نصب سیمفونی کار کنید. بخش ایجاد برنامه‌های سیمفونی بدون برنامه نصب را برای یادگیری روش ادامه کار، مطالعه کنید.

برحسب نوع سیستم‌عامل شما، برنامه نصب می‌تواند به شکل‌های مختلفی نصب گردد:

سیستم‌های Linux و Mac OS X
کنسول خط فرمان را بازکرده و دستورات زیر را اجرا کنید:
$ sudo curl -LsS https://symfony.com/installer -o /usr/local/bin/symfony
$ sudo chmod a+x /usr/local/bin/symfony

این کار یک دستور symfony سراسری در سیستم شما ایجاد می‌کند.

سیستم‌های Windows
خط فرمان ویندوز را بازکرده و دستور زیر را اجرا کنید:
php -r "readfile('https://symfony.com/installer');" > symfony

اکنون باید فایل سیمفونی دریافت‌شده را به پوشه ریشه پروژه‌های خود منتقل کنید. برای مثال:
move symfony C:\xampp\htdocs

ایجاد پروژه سیمفونی
وقتی برنامه نصب سیمفونی دردسترس باشد، می‌توانید پروژه جدید را با دستور new ایجاد کنید:
 # Linux
$ cd /var/www/html
$ symfony new my_project_name

# Mac OS X
$ cd /Applications/MAMP/htdocs
$ symfony new my_project_name

# Windows
c:\> cd xampp\htdocs/
c:\xampp\htdocs\> php symfony new my_project_name

این دستور یک پوشه جدید بنام my_project_name ایجاد می‌کند که شامل یک پروژه جدید برمبنای آخرین نسخه پایدار سیمفونی است. بعلاوه برنامه نصب، سیستم شما را ازنظر وجود نیازمندی‌های فنی موردنیاز برای اجرای برنامه‌های سیمفونی بررسی می‌کند. اگر این نیازمندی‌ها وجود نداشته باشد، فهرستی از تغییرات موردنیاز را برای رسیدن به آن نیازمندی‌ها مشاهده خواهد کرد.

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

ایجاد پروژه براساس یک نسخه مشخص سیمفونی
در مواردی که نیاز به ایجاد یک پروژه برمبنای نسخه خاصی از سیمفونی دارید، از پارامتر اختیاری دوم دستور new استفاده کنید:
# use the most recent version in any Symfony branch
$ symfony new my_project_name 2.8
$ symfony new my_project_name 3.0

# use a specific Symfony version
$ symfony new my_project_name 2.7.3
$ symfony new my_project_name 2.8.1

# use a beta or RC version (useful for testing new Symfony versions)
$ symfony new my_project 3.0.0-BETA1
$ symfony new my_project 2.7.0-RC1

برنامه نصب شامل یک نسخه خاص بنام lts نیز می‌باشد که آخرین نسخه سیمفونی که پشتیبانی بلندمدت دارد را نصب خواهد کرد:
$ symfony new my_project_name lts

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

ایجاد برنامه‌های سیمفونی بدون برنامه نصب
اگر همچنان از PHP5.3 استفاده می‌کنید یا به هر دلیلی نمی‌توانید برنامه نصب را اجرا کنید، می‌توانید از روش جایگزین نصب با کمک کامپوزر برای ایجاد برنامه‌های سیمفونی استفاده کنید.

کامپوزر یک برنامه مدیریت وابستگی‌ها است که توسط برنامه‌های مدرن PHP مورد استفاده قرار می‌گیرد و می‌توان از آن برای ایجاد پروژه‌های جدید سیمفونی نیز استفاده‌کرد. اگر آنرا بصورت سراسری نصب نکرده‌اید، کار را با نصب کامپوزر بصورت سراسری پیش ببرید.

ایجاد یک برنامه سیمفونی با کمک کامپوزر
وقتی کامپوزر روی کامپیوتر شما نصب شد، از دستور create-project برای ایجاد یک برنامه جدید سیمفونی برمبنای آخرین نسخه پایدار آن استفاده‌کنید:
$ composer create-project symfony/framework-standard-edition my_project_name

اگر نیاز به نصب نسخه خاصی از سیمفونی دارید، باید شماره آنرا بعنوان پارامتر دوم دستور create-project اعلام‌کنید:
$ composer create-project symfony/framework-standard-edition my_project_name "3.0.*"

نقل قول:
اگر اتصال اینترنت شما کند باشد، ممکن‌است فکر کنید که کامپوزر هیچ کاری نمی‌کند. اگر چنین است، پارامتر -vvv را به دستور قبلی اضافه‌کنید تا خروجی کامل همراه‌با جزئیات هر کاری که کامپوزر انجام می‌دهد را مشاهده‌نمایید.

اجرای برنامه سیمفونی
سیمفونی از سرور داخلی عرضه‌شده توسط PHP استفاده می‌کند تا برنامه‌ها را در زمان توسعه، اجرا نماید. بنابراین، برای اجرای یک پروژه سیمفونی کافی است به پوشه پروژه رفته و دستور زیر را اجرا کنید:
php bin/console server:run

سپس در مرورگر خود آدرس http://localhost:8000 را اجرا نمایید تا صفحه خوش‌آمدگویی سیمفونی را مشاهده کنید:

[عکس: attachment.php?aid=398]

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

نقل قول:
سرور داخلی PHP از نسخه 5.4 به بعد وجود دارد. اگر از نسخه قدیمی 5.3 استفاده می‌کنید، باید در وب‌سرور خود یک میزبان مجازی (VirtualHost) ایجاد کنید.

دستور server:run فقط در زمان توسعه نرم‌افزار مناسب است. برای اجرای برنامه سیمفونی روی سرورهای نهایی باید وب‌سرور Apache یا Nginx خود را به‌روشی که در بخش پیکربندی یک وب‌سرور توضیح داده‌ایم، تنظیم‌کنید.

بررسی تنظیمات و پیکربندی برنامه سیمفونی
برنامه‌های سیمفونی همراه‌با یک ابزار تست ویژوال پیکربندی سرور عرضه می‌شوند که به شما می‌گوید آیا سیستم شما برای استفاده از سیمفونی آماده است یا خیر. برای بررسی تنظیمات خود، این آدرس را باز کنید:
http://localhost:8000/config.php

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

بروزرسانی این کتابخانه‌های جانبی بصورت مرتب، یک تمرین خوب برای جلوگیری از باگ‌ها و آسیب‌پذیری‌های امنیتی است. دستور update کامپوزر را برای بروزرسانی آنها بصورت یکجا استفاده کنید:
$ cd my_project_name/
$ composer update

برحسب میزان پیچیدگی پروژه شما، ممکن است این عملیات بروزرسانی چند دقیقه طول بکشد.

نقل قول:
سیمفونی یک دستور برای بررسی اینکه وابستگی‌های پروژه شما دارای آسیب‌پذیری خاصی هستند یا نه، ارائه می‌کند:
$ php bin/console security:check

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

نصب برنامه Demo سیمفونی
برنامه آزمایشی سیمفونی یک برنامه کاملاً کاربردی است که روش پیشنهادی توسعه برنامه‌های سیمفونی را نشان می‌دهد. برنامه بعنوان یک ابزار آموزشی برای تازه‌کارهای سیمفونی درنظر گرفته شده و سورس‌کد آن حاوی هزاران کامنت و یادداشت سودمند است.

برای دانلود پروژه دموی سیمفونی دستور demo را در خط فرمان در هر مسیر دلخواه از سیستم خود اجرا کنید:
 # Linux, Mac OS X
$ symfony demo

# Window
C:\projects\> php symfony demo

[عکس: attachment.php?aid=399]

بعد از تکمیل دانلود، به پوشه symfony_demo واردشده و دستور زیر را اجرا کنید:
php app/console server:run

و مطابق معمول، با نشانی http://localhost:8000 ازطریق مرورگر برنامه را اجرا کنید.

[عکس: attachment.php?aid=400]

[عکس: attachment.php?aid=401]

[عکس: attachment.php?aid=402]

نصب یک توزیع سیمفونی
پروژه سیمفونی شامل بسته‌هایی تحت‌عنوان «توزیع» است، که برنامه‌های کاملاً اجرایی شامل هسته سیمفونی، مجموعه‌ای از ابزارهای کاربردی، یک ساختار پوشه‌ای بامعنا و برخی تنظیمات پیشفرض هستند. درحقیقت وقتی که شما یک برنامه سیمفونی در بخش قبل ایجاد کردید، توزیع «پیشفرض» سیمفونی را دانلود کردید که به آن، Symfony Standard Edition می‌گویند.

توزیع Symfony Standard Edition با فاصله زیادی نسبت به سایر توزیع‌ها، محبوبترین توزیع است و همچنین، بهترین توزیع برای توسعه‌دهندگانی است که تازه کار با سیمفونی را شروع کرده‌اند. گرچه جامعه کاربری سیمفونی توزیع‌های محبوب دیگری را نیز عرضه کرده‌است که ممکن‌است بخواهید از آنها در برنامه خود استفاده کنید:

توزیع Symfony CMF Standard Edition بهترین توزیع برای شروع به کار برروی پروژه CMF سیمفونی است که به توسعه‌دهندگان کمک می‌کند راحت‌تر قابلیت‌های CMS را به برنامه‌های تولیدشده توسط سیمفونی اضافه‌کنند.

توزیع Symfony REST Edition چگونگی تولید برنامه‌هایی برای ارائه یک RESTful API را با استفاده از FOSRestBundle و چند ابزار مرتبط دیگر نشان می‌دهد.

استفاده از کنترل سورس
اگر از سیستم کنترل نسخه‌ای مثل Git استفاده می‌کنید، می‌توانید با خیال راحت تمامی کدهای پروژه خود را کامیت‌کنید. دلیل آن، وجود یک فایل .gitignore در داخل برنامه سیمفونی است که بطور خاص برای پروژه تنظیم شده است.

برای راهنمایی بیشتر درخصوص بهترین روش پیکربندی پروژه برای ذخیره در Git، راهنمای ایجاد و ذخیره یک پروژه سیمفونی در گیت را مطالعه کنید.

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

برای اینکه به برنامه قابلیت اجرا بدهید، بعد از دریافت برنامه از مخزن، دستور install کامپوزر را فراخوانی نمایید تا تمامی وابستگی‌های موردنیاز برنامه، دانلود و نصب شوند:
$ cd my_project_name/
$ composer install

کامپوزر از کجا می‌داند باید چه چیزی را دانلود و نصب کند؟ علت آن است که در زمان کامیت‌کردن یک برنامه سیمفونی، فایل‌های composer.json و composer.lock نیز کامیت می‌شوند. این فایل‌ها به کامپوزر می‌گویند که چه وابستگی‌هایی (و دقیقاً چه نسخه‌ای از آنها) باید برای برنامه نصب شود.

شروع به توسعه
حال که یک برنامه کاملاً کاربردی سیمفونی در اختیار دارید، می‌توانید توسعه پروژه را شروع کنید! توزیع شما ممکن‌است شامل برخی کدهای نمونه باشد. فایل README.md همراه توزیع را مشاهده کنید (آنرا بعنوان یک فایل متنی باز نمایید) تا متوجه شوید که چه کدهای نمونه‌ای همراه توزیع شما ارائه شده‌است.

اگر در سیمفونی تازه‌کار هستید، آموزش ساخت اولین صفحه شما در Symfony را مشاهده نمایید، که به شما می‌آموزد چگونه صفحات جدید ایجاد کنید، تنظیمات را تغییر دهید، و هر کار دیگری را که در پروژه جدیدتان نیاز دارید، انجام دهید.

مطمئن شوید که کتاب آشپزی سیمفونی را نیز مطالعه می‌کنید که شامل مقالات گسترده‌ای درباره حل مسائل مختلف و خاص با استفاده از سیمفونی است.


فایل‌های پیوست تصاویر بندانگشتی
                   
تشکر شده توسط: hamo , meysam1366 , YN97 , gmail , Thorin , elephpant




کاربران در حال بازدید این موضوع: 1 مهمان