آشنایی با Symfony با ساختن یه وبلاگ به صورت پروژه ای

eبین تعدا زیادی از تکنولوژی‌های وب که وجود دارن، پی‌اچ‌پی تو طول زمانی تغییرات سریعی رو پشت سر گذاشته و الان به یک زبان بالغ تبدیل شده.پی‌اچ‌پی امروز هم برای وب‌سایت‌ها و وب‌اپلیکیشن‌ها کوچیک و همینطور بزرگ استفاده میشه و شاید یکی از دلایل محبوبیتش، سادگیش باشه.با توجه به اینکه هرروز پیچیدگی وب‌اپلیکیشن‌ها بیشتر میشه، برنامه‌نویس‌ها بیشتر به استفاده از فریم‌ورک‌ها رو آوردن و حتی برای پروژه‌های کوچیکشون هم از اونا استفاده می‌کنن. شاید با خودتون بگید «فریم‌ورک چیه؟». فریم‌ورک‌ها اساسا چیزی بجز یکسری توابع و ساختاربندی‌های کلاسی نیستن که از قبل آماده شدن و قابل استفاده هستن، که مهمترین ویژگیشون اختراع نکردن چرخ از اول هست و این یعنی صرفه‌جویی زمان.تقریبا تمام زبان‌های برنامه‌نویسی برای خودشون فریم‌ورک‌هایی رو دارن که هرکدوم با هدفی ساخته شدن. یسری از فریم‌ورک‌های محبوب پی‌اچ‌پی، Zend Framework, Cake PHP, Smarty, Laravel و Symfony هستند، و من خودم بشخصه طرفدار سیمفونی هستم و دلایلش رو هم میگم. در نهایت سعی میکنم برای کسانی که دوست دارن با سیمفونی آشنا بشن، توضیحی از نحوه نصب و راه‌اندازی و کار مختصر باهاش رو توضیح بدم.انعطاف‌پذیریتو خیلی از پروژه‌هایی که کار کردم و دیدم، سیمفونی بهم ثابت کرد که خیلی زیاد انعطاف‌پذیره و یک انتخاب خوب برای کسب‌وکار. با قابلیت‌هایی مثل Dependency Injectorها یا Event Dispatcherها کاملا قابل تنظیم و شخصی‌سازی شده. سیمفونی یک ساختار OOP و SOA رو به صورت حرفه‌ای پیش برده که باعث شده برای کسایی که بخوان چه از ساختار Monolithic و چه از Micro Service بهره ببرن، گزینه‌ی مناسبی باشه، و این یعنی توسعه سریع، افزایش مقیاس پروژه و نگهداری راحت.برنامه‌نویس‌ها نیازی ندارن تا ویژگی‌های پایه و اساسی پروژه رو از اول بنویسن یا حتی بازنویسی کنن، مثل سرویس مدیرت فرم. سیمفونی برنامه‌نویس رو به چالش واقعی دعوت میکنه، محاسبات و مدیریت. بنظرم یکی از بهترین ویژگی‌های سیمفونی داشتن Web Debug Toolbar هست که حقیقتا یه دوست واقعی برای برنامه‌نویسه.ساختار تیکه تیکه! (باندل)بنظرم یکی از ویژگی‌های بسیار مثبت سیمفونی، ساختار تیکه تیکش هست. یعنی سیمفونی از اجزای کوچکتر ساخته شده که هرکدومشون جداگانه قابل استفاده هستن، یک نمونش HTTP Foundation که خیلی زیاد تو لاراول استفاده شده. در واقع خود سیمفونی، یک تیکه کوچیک از این تیکه‌هاست که در حقیقت وظیفش جمع کردن همه‌ی تیکه‌ها (اسم درستشون باندل هست) و در کنار هم قراردادنشونه. برای همین سیمفونی رو میشه به دو حالت استفاده کرد:یک فریم‌ورک کامل: کل ابزارهای سیمفونی و یک عالمه ابزاری که برنامه‌نویس‌های دیگه نوشتن رو کنار هم و یکجا داشته باشیدمیکرو فریم‌ورک‌ها: که درواقع شما توی پروژه‌ای که کار میکنید، بدون نصب سیمفونی، فقط از باندل‌های بدرد بخورش براش خودتون استفاده کنید، مثل FormBundleدر مورد TWIG!الان تو دنیایی هستیم که کمتر کسی علاقه‌ای به نوشتن ظاهر سایت و HTML بصورت دستی داره، بخصوص زمانی که تعداد فایلها قراره زیاد بشن و شما مجبور بشید به ازای هر صفحه یه فایل جدید بسازید. توییگ، یه موتور ساخت قالب هست که بهتون کمک خیلی زیادی توی طراحی میکنه. مثلا شما میتونید یک فایل html بسازید و داخلش از توییگ استفاده کنید و فقط متغیرها رو بهش پاس بدید. و اون برای شما به سادگی و سریع رندر کنه.سیمفونی به صورت پیشفرض از توییگ پشتیبانی میکنه و خیلی از زمان طراحی شما کم میکنه.داکترین (Doctrine)داکترین در واقع یک سری کتابخونه برای php هست که وظیفش ایجاد یک لایه‌ی اضافی برای ارتباط برنامه‌نویس با دیتابیسه، در حقیقت به جای اینکه برنامه‌نویس مجبور بشه کدهای sql رو دستی وارد کنه، با نصب داکترین، فقط به کدهای php بسنده میکنه و داکترین تمام کارهارو براش انجام میده.داکترین تبدیل به یک پروژه بالغ شده و بنظرم تمام امکانات مورد نیاز برنامه‌نویس رو در اختیارش قرار میده. خود داکترین شاخه‌های زیرمجموعه داره که مهمترین و پرمصرف‌ترینش Doctrin ORM هست که وظیفش بصورت ساده و خلاصه، تبدیل کلاس‌های php به مدل‌های دیتابیسی هست.سیمفونی بطور پیشفرض از Doctrine ORM پشتیبانی میکنه و نیازی نیست شما اون رو کانفیگ کنید.تست راحتبنظرم سیمفونی با ایده‌ی «تستینگ در اولویت» پیش رفته. Unit Testingها با استفاده از PHPUnit خیلی خوب از کدها جدا شدن مدیریتشون خیلی راحت شده. با یه کمک خیلی زیاد از PHPUnit این امکان برای برنامه‌نویس وجود داره تا به سادگی درخواست‌های http رو بازسازی، آزمایش و تست کنه بدون اینکه مجبور باشه کد زیادی رو با ابزار تست بزنه.در واقع این امکان سیمفونی حتی زمان زیادی رو هم توی تست حفظ میکنه و سرعت عمل رو بالا میبره. در مجموع اکوسیستم سیمفونی روی تستینگ و کیفیت بالای اون خیلی تمرکز داشته و موارد خوبی رو فراهم کرده.دیباگ مثل آب خوردنمحبوبترین ویژگی سیمفونی، Web Debug Tool هست که اطلاعات کاملی و دقیقی راجع به وضعیت فعلی درخواست‌ها، ترنسپایلر‌ها و غیره میده و حتی امکان تست سرعت رندر هم داره. Symfony Profiler همون ابزار دیباگینگ هست که در مورد تمام اتفاقات پروژه تو هر مرحله گزارش کامل و دقیق میده.نصب سیمفونیحتما براتون سوال شده که چطور میشه سیمفونی رو نصب و راه‌اندازی کرد. برای اینکار روش‌های مختلی وجود داره، اما بهترین روش که من هم ازش استفاده میکردم و راضی بودم، استفاده از نصاب خود سیمفونی هست که برای راه‌اندازیش میتونید به شکل زیر عمل کنید:دریافت فایل نصاببرای لینوکس و مکsudo curl -LsS https://symfony.com/installer -o /usr/bin/symfony sudo chmod a+x /usr/bin/symfonyبرای ویندوزC:\> php -r "readfile('https://symfony.com/installer');" > symfonyاستفاده از نصابsymfony new helloWorld ltsحتما مطمئن باشد که از PHP Phar Extension استفاده میکنید!نصب Composerکامپوزر، در حقیقت یک Package Manager هست که پکیج‌هایی که تو ریپازیتوری کامپوزر وجود دارن رو براتون پیدا و بهترین رو از لحاظ مطابقت با ورژن‌های شما نصب میکنه.کامپوزر رو میتونید به سادگی از ریپازیتوری‌های توزیع لینوکستون پیدا کنید. مثلاsudo packman -S composerیاsudo apt-get install composerراه اندازی سرور و ادامه کد زدندایرکتوریتون رو به فولدر helloWorld تغییر بدبدcd helloWorldو سرور داخلی سیمفونی رو اجرا کنید:app/console server:runگام به گام با هم پیش بریمبیاید باهم یه وب‌سایت ساده بنویسیم. فکر میکنم یه وبلاگ میتونه گزینه مناسبی برای شروع باشه. پس اگر هنوز نصاب سیمفونی و کامپوزر رو نصب نکردید، برگردید عقب و انجامشون بدید تا در ادامه مشکلی نداشته باشید.قدم اول، راه اندازی پروژهsymfony new MyBlog ltsپس از اجرا، باید همچین پاسخی رو دریافت کنید:Downloading Symfony... 5.4 MiB/5.4 MiB ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ 100% Preparing project... Symfony 2.8.24 was successfully installed. Now you can: * Change your current directory to ~/MyBlog * Configure your application in app/config/parameters.yml file. * Run your application: 1. Execute the php app/console server:start command. 2. Browse to the http://localhost:8000 URL. * Read the documentation at http://symfony.com/docو در نهایت دستور composer install رو داخل فولدر MyBlog اجرا کنید.حالا IDE یا ادیتورتون رو باز کنید و پروژه رو داخلش بیارید بالا.تنظیمات دیتابیستمام تنظیمات پروژه، داخل فولدر app/config قرار میگیرن. فولدر app موظف به نگهداری از تمام منابع اپلیکیشن هست، مثل قالب‌های توییگ، لاگ‌ها و تنظیمات و هسته اپلیکیشن.فایلهایی که داخل پوشه app/config هستن، معمولا با پسوند .yml یا .yaml قرار میگیرن که میتونم بگم از لحاظ کاربری مشابه .json هستن.حالا فایل app/config/parameters.yml رو باز کنید. با همچین ساختاری مواجه میشید:# This file is auto-generated during the composer install parameters: database_host: 127.0.0.1 database_port: null database_name: symfony database_user: root database_password: null mailer_transport: smtp mailer_host: 127.0.0.1 mailer_user: null mailer_password: null secret: 90830183902924603f797e4b794ff7e8353e43e9تیکه اول درواقع تنظیمات عمومی اپ برای ارتباط با دیتابیس هست. این تنظیمات رو به:database_name: MyBlog #یا هر اسم دیگه‌ای که برای دیتابیس دوست دارید database_user: root #یوزرنیم که باهاش به دیتابیس وصل میشید database_password: null #پسورد دیتابیستون...تغییر بدید. من در حالت عادی با MySql کار میکنم، اما سیمفونی گزینه‌های دیگه‌ای هم مثل MongoDb و دیتابیس‌های دیگه داره و از اونا پشتیبانی میکنه.ساخت دیتابیسیکی از گزینه‌های خیلی خوب سیمفونی که من دوست دارم، کنسولش هست. که خیلی از کارهارو برای شما انجام میده. میتونید اطلاعات بیشتری راجع بهش رو صرفا با تایپ app/console بدست بیارید.الان فقط ساخت دیتابیس برامون کافیه تا بریم مراحل بعدی:app/console doctrine:database:createو پاسخش:Created database `MyBlog` for connection named defaultحالا چک کنیم که تا اینجا همه‌چیز خوب پیشرفته:app/console server:run [OK] Server running on http://127.0.0.1:8000 // Quit the server with CONTROL-C.میتونید صفحه رو باز کنید و چک کنید. نوار سیاه پایین صفحه، همون Web Debug Tool هست که گفته بودم. بعد از اینکه مطمئن شدید اوضاع خوبه، پروسه رو قطع میکنیم و میریم گام بعدی.ساخت یک باندل برای مدیریت وبلاگیک نکته مهم، از اونجایی که سیمفونی ساختار تیکه تیکه داره (Bundle) شما میتونید هر قسمتی از سایت رو بر اساس ساختار خودتون به یک باندل تبدیل کنید. مثلا فرض میکنیم که وبلاگ سایت، برای خودش یه تیکه جداست، یعنی میتونیم خیلی راحت از پروژه حذفش کنیم و دیگه کاربرا بلاگی نبینن. برای اینکار، یه باندل جدید میسازیم:app/console generate:bundle Welcome to the Symfony bundle generator! Are you planning on sharing this bundle across multiple applications? [no]: Your application code must be written in bundles. This command helps you generate them easily. Give your bundle a descriptive name, like BlogBundle. Bundle name: MyBlogBundle Bundles are usually generated into the src/ directory. Unless you're doing something custom, hit enter to keep this default! Target Directory [src/]: What format do you want to use for your generated configuration? Configuration format (annotation, yml, xml, php) [annotation]: Bundle generation > Generating a sample bundle skeleton into app/../src/MyBlogBundle created ./app/../src/MyBlogBundle/ created ./app/../src/MyBlogBundle/MyBlogBundle.php created ./app/../src/MyBlogBundle/Controller/ created ./app/../src/MyBlogBundle/Controller/DefaultController.php created ./app/../tests/MyBlogBundle/Controller/ created ./app/../tests/MyBlogBundle/Controller/DefaultControllerTest.php created ./app/../src/MyBlogBundle/Resources/views/Default/ created ./app/../src/MyBlogBundle/Resources/views/Default/index.html.twig created ./app/../src/MyBlogBundle/Resources/config/ created ./app/../src/MyBlogBundle/Resources/config/services.yml > Checking that the bundle is autoloaded > Enabling the bundle inside app/AppKernel.php updated ./app/AppKernel.php > Importing the bundle's routes from the app/config/routing.yml file updated ./app/config/routing.yml > Importing the bundle's services.yml from the app/config/config.yml file updated ./app/config/config.yml Everything is OK! Now get to work :).سوال اول اینه:Are you planning on sharing this bundle across multiple applications? [no]که اگر جوابتون yes باشه، تمام فایلهای باندل، مثل قالب‌ها، تنظیمات و غیره رو توی خود فولدر باندل میریزه که شما بتونید راحت جداش کنید. در غیر این صورت، قالب‌ها داخل فولدر app/Resources/views ریخته میشن. (که عملا جدا کردنشون کار سختی میشه…)برای ادامه آموزشمون، میتونید این گزینه رو همینطور رها کنید (اینتر بزنید) و بریم سراغ سوال بعدی:Bundle name: MyBlogBundleاین اسم، ارتباطی با اسم پروژه نداره، یهو به ذهنم رسید، شما میتونید از BlogBundleیا HaminjuriBundle استفاده کنید… مهم اینه که حتما کلمه Bundle به آخرش، به صورت CamelCase اضافه بشه. سوال بعدی این خواهد بود:Target Directory [src/]:تمام باندل‌های سایت شما (مگر زمانی که خودتون کانفیگ کنید) داخل فولدر src ریخته میشن، درواقع شما ۹۹٪ زمانتون رو تو این فولدر صرف میکنید. ماهم این گزینه رو به حال خودش رها میکنیم.Configuration format (annotation, yml, xml, php) [annotation]نوع تنظیماتی که باندل قرار داشته باشه چطوره، میتونید از هر نوعی که گفته استفاده کنید ولی من annotation رو ترجیح میدم چون خیلی راحت میشه تغییرش داد و مدیریتش خیلی آسونتر هست. پس فقط اینتر رو میزنم و میرم سراغ گام بعدی!ساخت یک مدل ساده از پست وبلاگخب، تا اینجا خوب پیشرفتیم. فرض میکنیم کاربرای ما، نیازی به مدلسازی پیچیده‌ای ندارن و در حد ساده‌ی آموزش ما براشون کافیه. یک پست وبلاگ چه چیزا‌هایی احتیاج داره؟عنوان (title) از نوع رشته‌ای (string) که حتما باید وارد بشه (not nullable) و نباید تکراری باشه (unique)متن نوشته (content) از نوع متنی (text) که حتما باید وارد بشه (not nullable)تاریخ ساخت (createdAt) از نوع تاریخ (data) که حتما باید وارد بشه (not nullable)دوباره میریم سراغ کنسول:app/console doctrine:generate:entity Welcome to the Doctrine2 entity generator This command helps you generate Doctrine2 entities. First, you need to give the entity name you want to generate. You must use the shortcut notation like AcmeBlogBundle:Post. The Entity shortcut name: BlogBundle:Postاینجا بهش میگیم که این انتیتی (entity)، یا همون مدلمون، مربوط به کدوم باندل هست. اسم باندل که باهم درست کردیم دو نقطه اسم انتیتی که میشه پست…Configuration format (yml, xml, php, or annotation) [annotation]همچنان با انوتیشن پیش میریم. از اینجا به بعد ازتون میخواد که پراپرتی‌های مدل رو مشخص کنید. زمانی که کارتون تموم شد، میتونید اینتر رو بزنید و ادامه رو به کنسول بسپارید. پس پیش میریم:New field name (press <return> to stop adding fields): title Field type [string]: Field length [255]: Is nullable [false]: Unique [false]: true New field name (press <return> to stop adding fields): content Field type [string]: text Is nullable [false]: Unique [false]: New field name (press <return> to stop adding fields): createdAt Field type [string]: date Is nullable [false]: Unique [false]: New field name (press <return> to stop adding fields): Entity generation created ./src/BlogBundle/Entity/ created ./src/BlogBundle/Entity/Post.php > Generating entity class src/BlogBundle/Entity/Post.php: OK! > Generating repository class src/BlogBundle/Repository/PostRepository.php: OK! Everything is OK! Now get to work :).حالا اگر برید داخل src/BlogBundle/Entity متوجه میشید که فایل Post.phpبراتون ساخته شده. این فایل در واقع یه کلاس ساده PHP هست که صرفا پراپرتی‌های مدل رو نگه‌داری میکنه. در واقع این همون مدل پست ماست که میتونید بعدا خیلی راحت تغییرش بدید.ضمنا همزمان با ساخت این مدل، یک فایل هم داخل src/BlogBundle/Repositoryبه نام PostRepository.php ساخته شده، که در واقع وظیفه اصلیش اینه:یک جاهایی از پروژه شما نیاز پیدا میکنید تا مثلا یک رکورد خاص رو از دیتابیس، با محاسبات خاصی دریافت کنید. اینجا میتونید تمام دستورات لازم رو بنویسید تا همه‌جا بشه به سادگی ازشون استفاده کرد.حالا که مدل ساخته شد، اون رو به دیتابیس منتقل میکنیم تا ساختار دیتابیس با مدل‌های ما یکی بشه:app/console doctrine:schema:update --force Updating database schema... Database schema updated successfully! "1" queries were executedچرا از آرگومان --force استفاده کردیم؟ یک دلیلش اینه که مطمئن باشیم داریم درست پیش میریم. پیش میاد زمانی که احتمالا دستور رو اشتباه وارد کنیم و باعث بشیم تا دیتابیس بهم بریزه. مورد بعدی اینکه شما میتونید از آرگومان --dump-sql هم استفاده کنید تا ببینید دقیقا چه دستوری به SQL اجرا میشه.ساخت فرم برای مدیریت پست‌هاتا اینجا خوب پیشرفتیم. حالا باید این امکان رو برای کاربر فراهم کنیم، تا بتونه پست‌ها رو کراد (CRUD = Create, Read, Update and Delete) کنه. خوبی سیمفونی اینه که تقریبا تمام این کارها رو با کنسول میشه انجام داد. پس:app/console doctrine:generate:crud Welcome to the Doctrine2 CRUD generator This command helps you generate CRUD controllers and templates. First, give the name of the existing entity for which you want to generate a CRUD (use the shortcut notation like AcmeBlogBundle:Post) The Entity shortcut name: BlogBundle:Post By default, the generator creates two actions: list and show. You can also ask it to generate "write" actions: new, update, and delete. Do you want to generate the "write" actions [no]? yes Determine the format to use for the generated CRUD. Configuration format (yml, xml, php, or annotation) [annotation]: Determine the routes prefix (all the routes will be "mounted" under this prefix: /prefix/, /prefix/new, ...). Routes prefix [/post]: /blog Summary before generation You are going to generate a CRUD controller for "BlogBundle:Post" using the "annotation" format. Do you confirm generation [yes]? CRUD generation created ./src/BlogBundle/Controller//PostController.php created ./app/Resources/views/post/ created ./app/Resources/views/post/index.html.twig created ./app/Resources/views/post/show.html.twig created ./app/Resources/views/post/new.html.twig created ./app/Resources/views/post/edit.html.twig created ./src/BlogBundle/Tests/Controller/ created ./src/BlogBundle/Tests/Controller//PostControllerTest.php Generating the CRUD code: OK created ./src/BlogBundle/Form/ created ./src/BlogBundle/Form/PostType.php Generating the Form code: OK Updating the routing: OK Everything is OK! Now get to work :).بذارید مرحله به مرحله توضیح بدم. اول دستور app/console doctrine:generate:crud رو زدم. بعد ازم پرسید که برای چه انتیتی میخوام کراد بسازم و من هم BlogBundle:Post رو وارد کردم. بعد ازم پرسید که پیشوند مسیرم برای کاربر چیه، در واقع کاربر از چه مسیری توی سایت میتونه به این عملیات‌ها دسترسی داشته باشه. خودش بصورت پیشفرض، اسم انتیتی (Post) رو در نظر گرفته بود و من به /blogتغییرش دادم. و در نهایت پرسید که آیا مطمئن هستم؟ و منم مطمئنم…خب، حالا یه تست کنیم ببینیم تا اینجا چطور پیشرفتیم:app/console cache:clear app/console server:runاز این آدرس استفاده کنید تا بتونید بهتر ببینید:http://localhost:8000/app_dev.php/blog/app_dev.php در واقع محیط دیباگ رو فراهم میکنه. اگر واردش نکنید، سایت اصلی بدون گزینه‌های دیباگ و پولیش شده جلوتون قرار میگیره.تا اینجا عالی بود. پیشنهاد میکنم یکم با این صفحه کار کنید و چندتا پست نمونه بسازید، پاکشون کنید و تغییرشون بدید. چقدر راحت شد همه‌چیز.ولی ظاهرش اصلا قشنگ نیست… قدم بعدیمون، میریم سراغ یکم طراحی…یکم کار با قالب‌هاتمام قالب‌های مربوط به کراد، با توجه به اینکه قرار نیست ما این باندل رو جدا کنیم، تو فولدر app/Resources/views/post قرار گرفتن. از اسم فایل‌ها مشخصه که هر کدوم چه وظیفه‌ای به عهده دارن. پسوندشون هم .html.twig هست که درواقع یک فایل توییگه که باهاش به شکل فایل html برخورد میشه.فایل index.html.twigاگر به فایل src/BlogBundle/Controller/PostController.php نگاه کنید، که بعد از اجرای دستور doctrine:generate:crud ایجاد شده، متوجه میشید که تمام عملیات کراد اینجا انجام میشه.کنترلرها در واقع کلاس‌هایی هستن که وظیفشون مدیریت فعالیت‌های قسمت‌های مختلف باندل یا مسیرهای اوناست. اینو رو ببینید:/** * Lists all post entities. * * @Route("/", name="blog_index") * @Method("GET") */ public function indexAction() { $em = $this->getDoctrine()->getManager(); $posts = $em->getRepository('BlogBundle:Post')->findAll(); return $this->render('post/index.html.twig', array( 'posts' => $posts, )); }چون تعریف کردیم که تظیمات رو با انوتیشن انجام میدیم، تمام تعاریف اکشن‌ها (یا همون تیکه‌های کوچیک که وظیفه مدیریت قسمت‌های مختلف باندل رو دارن وکنترلر اونارو یجا جمع کرده) بالاشون، انوتیت میشن.مثلا اینجا به @Route دقت کنید، آرگومان اولش / هست، یعنی مسیرباندل/مسیر کنترلر/مسیر اکشن که اینجا برای ما میشه /blog/. دقت کنید که خود باندل بلاگ، مسیری رو نداره، و ما زمان ساخت فرم گفتیم که پیشوند مسیر رو از /post به /blog تغییر بده. اگر دوست داشتید که مسیر باندل رو هم عوض کنید، فایل app/config/routing.yml رو باز کنید و prefix باندل رو از / به هرچی که دوست دارید، مثلا hello تغییر بدید. در نتیجه این تغییر، کاربر شما برای دریافت لیست پست‌ها باید به آدرس hello/blog/ مراجعه کنه.خب، برمیگردیم به خود کد و اون رو مرور میکنیم. $em درواقع انتیتی منیجر داکترین تعریف شده. وقتی ما این کلاس رو از Symfony\Bundle\FrameworkBundle\Controller\Controller اکستند کردیم، امکاناتی مثل getDoctrine() به $this اضافه شده.بعد اومده و لیست تمام رکورد‌های موجود توی جدول پست رو گرفته، و در نهایت فایل app/Resources/views/post/index.html.twig رو رندر کرده و پست‌ها رو پاس داده بهش.حالا بریم سراغ فایل app/Resources/views/post/index.html.twig. تو خط اولش متوجه میشیم که {% extends 'base.html.twig'%} اجرا میشه، یعنی، این فایل ما یک بخشی از فایل اصلی base.html.twig هست که تو پوشه app/Resources/views/base.html.twig قرار گرفته.شما با تغییر دادن این فایل میتونید ظاهر سایت رو عوض کنید. مثلا من بالای base.html.twig میخوام اسم سایت رو اضافه کنم. فایلش رو باز میکنم و:حالا بالای صفحه یه هدر دارم و پایین هم یه فوتر. یه استایل ساده رو داخل فولدر web/style.css میذارم و اینا رو داخلش اضافه میکنم:body { font-family: sans; } .header { margin: 15px; background-color: #eee; } .footer { margin: 15px; border: 1px solid #ccc; }و در نهایت به base.html.twig میگم که این فایل رو بیاره و تو صفحه‌ها نمایش بده:دستورات توییگ با { { } } (بدون فاصله) اجرا میشن. و asset('...') میره و هرچیزی که داخل فولدر web بود رو برای شما با اون اسم میاره. دیگه نیازی نیست نگران آدرس فایل‌ها باشید!حالا میخوام جدولی که توی فایل app/Resources/views/post/index.html.twig بود رو یکم قشنگتر کنم، یه فایل table.css توی web میسازم و اینارو داخل مینویسم:table, td, th { border: 1px solid #ddd; text-align: left; } table { border-collapse: collapse; width: 100%; } th, td { padding: 15px; }و بعد، میخوام هرموقع که کاربر صفحه ایندکس پست‌ها (indexAction) رو باز میکنه، این استایل نمایش داده بشه. برای اینکار، فایل post/index.html.twig رو باز میکنم و:کارمون تموم شد. یه تست میکنیم:app/console cache:clear app/console server:runحالا صفحه پست‌ها رو باز کنید و ببینید چه اتفاقی افتاد…خب ما باهم تونستیم یه وب‌سایت خیلی ساده رو درست کنیم و تقریبا هیچ کدی نزنیم. سیمفونی هنوز مونده و خیلی خیلی جا برای یادگیری داره، اما بنظرم شروع خوبی داشتیم و قطعا خودتون میتونید از اینجا به بعد رو تنهایی پیش برید.پیشنهاد میکنم حتما سری به سایت سیمفونی، سایت آموزش داکترین و توییگ بزنید تا باهاشون بیشتر آشنا بشید.با آرزوی موفقیت روزافزون برای شما