กรอบงาน PHP: การเลือกระหว่าง Symfony และ Laravel

เผยแพร่แล้ว: 2022-03-11

วันนี้ เมื่อเริ่มต้นโครงการใหม่ หนึ่งในการตัดสินใจที่สำคัญคือการเลือกกรอบงานที่เหมาะสม เป็นเรื่องยากที่จะจินตนาการถึงการสร้างเว็บแอปพลิเคชันที่ซับซ้อนตั้งแต่เริ่มต้นโดยปราศจากสิ่งนี้

ภาษายอดนิยมมากมายสำหรับการพัฒนาเว็บมีเฟรมเวิร์ก "ค่าเริ่มต้น" เช่น Ruby on Rails for Ruby หรือ Django สำหรับ Python อย่างไรก็ตาม PHP ไม่มีค่าเริ่มต้นเดียวและมีตัวเลือกยอดนิยมมากมายให้เลือก

ตามเทรนด์ของ Google และ GitHub เฟรมเวิร์ก PHP ที่ได้รับความนิยมมากที่สุดคือ Symfony ที่มีดาว 13.7k และ Laravel ที่มีดาว 29k (ในขณะที่เขียนบทความนี้)

ในบทความนี้ ฉันจะเปรียบเทียบเฟรมเวิร์กทั้งสองนี้และแสดงให้คุณเห็นถึงวิธีการใช้ฟีเจอร์ที่เรียบง่ายและใช้ได้ทุกวันกับแต่ละเฟรมเวิร์ก ด้วยวิธีนี้ คุณสามารถเปรียบเทียบโค้ดของตัวอย่างในชีวิตจริงแบบเคียงข้างกัน

บทความนี้สันนิษฐานว่ามีทักษะ PHP ที่แข็งแกร่งและเข้าใจกระบวนทัศน์สถาปัตยกรรม MVC แต่ไม่จำเป็นต้องมีประสบการณ์กับ Symfony หรือ Laravel มาก่อน

ผู้เข้าแข่งขัน

Laravel

เมื่อพูดถึง Laravel เราหมายถึง Laravel เวอร์ชัน 4 ขึ้นไป Laravel 4 เปิดตัวในปี 2013 และแสดงถึงการเขียนกรอบงานใหม่ทั้งหมด การทำงานของเฟรมเวิร์กถูกแยกออกเป็นส่วนประกอบต่างๆ ซึ่งจัดการด้วย Composer แทนที่จะรวมทุกอย่างไว้ในที่เก็บโค้ดขนาดใหญ่เพียงแห่งเดียว

Laravel ประกาศตัวเองว่าเป็นเฟรมเวิร์กสำหรับการพัฒนาอย่างรวดเร็วด้วยไวยากรณ์ที่เรียบง่ายและสวยงาม ซึ่งง่ายต่อการเรียนรู้ อ่าน และบำรุงรักษา เป็นเฟรมเวิร์กที่ได้รับความนิยมมากที่สุดในปี 2559 ตามเทรนด์ของ Google เฟรมเวิร์กนั้นได้รับความนิยมมากกว่าเฟรมเวิร์กอื่นๆ ถึง 3 เท่า และใน GitHub ก็มีดาวมากกว่าคู่แข่งถึงสองเท่า

ซิมโฟนี

Symfony 2 เปิดตัวในปี 2011 แต่จะต้องไม่สับสนกับ Symfony 1 ซึ่งเป็นเฟรมเวิร์กที่แตกต่างกันโดยสิ้นเชิงโดยมีหลักการพื้นฐานต่างกัน Fabien Potencier ได้สร้าง Symfony 2 และเวอร์ชันปัจจุบันคือ 3.2 ซึ่งเป็นเวอร์ชันที่เพิ่มขึ้นของ Symfony 2 ดังนั้นจึงมักเรียกง่ายๆว่า Symfony2/3

เช่นเดียวกับ Laravel 4 Symfony 2 ได้รับการออกแบบให้เป็นชุดส่วนประกอบที่แยกส่วน มีประโยชน์สองประการที่นี่: เราสามารถแทนที่ส่วนประกอบใดๆ ในโปรเจ็กต์ Symfony และเราสามารถใช้และใช้ส่วนประกอบ Symfony ใดๆ ในโครงการที่ไม่ใช่ของ Symfony ส่วนประกอบ Symfony สามารถทำหน้าที่เป็นตัวอย่างโค้ดที่ยอดเยี่ยม และใช้ในโปรเจ็กต์โอเพ่นซอร์สจำนวนมาก เช่น Drupal, phpBB และ Codeception อันที่จริง Laravel เองใช้ส่วนประกอบ Symfony ไม่น้อยกว่า 14 ชิ้น การทำความเข้าใจ Symfony ทำให้คุณได้รับประโยชน์มากมายเมื่อทำงานกับโครงการอื่น

การติดตั้งเฟรมเวิร์ก

เฟรมเวิร์กทั้งสองมาพร้อมกับตัวติดตั้งและตัวตัดทอนที่พร้อมใช้งานผ่านเว็บเซิร์ฟเวอร์ในตัวของ PHP

การติดตั้ง Symfony

การติดตั้ง Symfony ทำได้ง่ายดังต่อไปนี้:

 # Downloading Symfony installer sudo curl -LsS https://symfony.com/installer -o /usr/local/bin/symfony # Granting permissions to execute installer sudo chmod a+x /usr/local/bin/symfony # Creating new Symfony project symfony new symfony_project # Launching built-in server cd symfony_project/ && php bin/console server:start

แค่นั้นแหละ! การติดตั้ง Symfony ของคุณมีอยู่ใน URL http://localhost:8000

การติดตั้ง Laravel

กระบวนการติดตั้ง Laravel เกือบจะเหมือนกันและง่ายเหมือนกับสำหรับ Symfony; ข้อแตกต่างเพียงอย่างเดียวคือคุณติดตั้งโปรแกรมติดตั้งของ Laravel ผ่าน Composer:

 # Downloading Laravel installer using Composer composer global require "laravel/installer" # Creating new Laravel project laravel new laravel_project # Launching built-in server cd laravel_project/ && php artisan serve

ตอนนี้คุณสามารถไปที่ http://localhost:8000 และตรวจสอบการติดตั้ง Laravel ของคุณ

หมายเหตุ: ทั้ง Laravel และ Symfony ใช้งานพอร์ต localhost เดียวกัน (8000) ตามค่าเริ่มต้น ดังนั้นคุณจึงไม่สามารถให้อินสแตนซ์เริ่มต้นเหล่านี้ทำงานพร้อมกันได้ อย่าลืมหยุดเซิร์ฟเวอร์ Symfony โดยเรียกใช้ php bin/console server:stop ก่อนเปิดเซิร์ฟเวอร์ Laravel

เกี่ยวกับการติดตั้งเฟรมเวิร์ก

นี่คือตัวอย่างการติดตั้งพื้นฐาน สำหรับตัวอย่างการใช้งานขั้นสูงเพิ่มเติม เช่น สามารถกำหนดค่าโปรเจ็กต์ด้วยโดเมนท้องถิ่นหรือเรียกใช้หลายโปรเจ็กต์พร้อมกัน กรอบงานทั้งสองมีกล่อง Vagrant:

  • ลาราเวลโฮมสเตด,
  • ซิมโฟนีโฮมสเตด

การกำหนดค่าเฟรมเวิร์กพื้นฐาน

การกำหนดค่าพื้นฐานของ Symfony

Symfony ใช้ YAML เป็นไวยากรณ์สำหรับระบุการกำหนดค่า การกำหนดค่าเริ่มต้นจะอยู่ในไฟล์ app/config/config.yml และดูเหมือนตัวอย่างต่อไปนี้:

 imports: - { resource: parameters.yml } - { resource: security.yml } - { resource: services.yml } framework: secret: '%secret%' router: { resource: '%kernel.root_dir%/config/routing.yml' } # ... # Twig Configuration twig: debug: '%kernel.debug%' strict_variables: '%kernel.debug%' # ...

ในการสร้างการกำหนดค่าเฉพาะสภาพแวดล้อม ให้สร้างไฟล์ app/config/config_ENV.yml ที่มีพารามิเตอร์การกำหนดค่าพื้นฐาน นี่คือตัวอย่างไฟล์ config_dev.yml สำหรับสภาพแวดล้อมการพัฒนา:

 imports: - { resource: config.yml } # ... web_profiler: toolbar: true # ...

ตัวอย่างนี้เปิดเครื่องมือ web_profiler Symfony สำหรับสภาพแวดล้อมการพัฒนาเท่านั้น เครื่องมือนี้ช่วยคุณในการดีบักและกำหนดโปรไฟล์แอปพลิเคชันของคุณในหน้าต่างเบราว์เซอร์

ในไฟล์การกำหนดค่า คุณยังสามารถสังเกตเห็นการสร้าง %secret% ซึ่งช่วยให้เราสามารถใส่ตัวแปรเฉพาะสภาพแวดล้อมในไฟล์ parameters.yml ที่แยกจากกัน ไฟล์นี้อาจไม่ซ้ำกันในทุกเครื่องและไม่ได้จัดเก็บไว้ภายใต้การควบคุมเวอร์ชัน สำหรับการควบคุมเวอร์ชัน เรามีไฟล์ parameters.yml.dist พิเศษที่เป็นเทมเพลตสำหรับไฟล์ parameters.yml

นี่คือตัวอย่างของไฟล์ parameters.yml :

 parameters: database_host: 127.0.0.1 database_port: null database_name: symfony database_user: root database_password: null secret: f6b16aea89dc8e4bec811dea7c22d9f0e55593af

การกำหนดค่าพื้นฐานของ Laravel

การกำหนดค่า Laravel ดูแตกต่างจาก Symfony มาก สิ่งเดียวที่พวกเขามีเหมือนกันคือพวกเขาทั้งคู่ใช้ไฟล์ที่ไม่ได้เก็บไว้ภายใต้การควบคุมเวอร์ชัน ( .env ในกรณี Laravel) และเทมเพลตสำหรับสร้างไฟล์นี้ ( .env.example ) ไฟล์นี้มีรายการคีย์และค่าต่างๆ เช่นตัวอย่างต่อไปนี้:

 APP_ENV=local APP_KEY=base64:Qm8mIaur5AygPDoOrU+IKecMLWgmcfOjKJItb7Im3Jk= APP_DEBUG=true APP_LOG_LEVEL=debug APP_URL=http://localhost

เช่นเดียวกับไฟล์ Symfony YAML ไฟล์นี้สำหรับ Laravel มนุษย์สามารถอ่านได้และดูสะอาดตา คุณยังสามารถสร้างไฟล์ .env.testing ที่จะใช้เมื่อรันการทดสอบ PHPUnit

การกำหนดค่าแอปพลิเคชันถูกเก็บไว้ใน config .php ในไดเร็กทอรีการกำหนดค่า การกำหนดค่าพื้นฐานจะถูกเก็บไว้ในไฟล์ app.php และการกำหนดค่าเฉพาะส่วนประกอบจะถูกเก็บไว้ในไฟล์ <component>.php (เช่น cache.php หรือ mail.php ) ต่อไปนี้คือตัวอย่างไฟล์ config/app.php :

 <?php return [ 'name' => 'Laravel', 'env' => env('APP_ENV', 'production'), 'debug' => env('APP_DEBUG', false), 'url' => env('APP_URL', 'http://localhost'), 'timezone' => 'UTC', 'locale' => 'en', // ... ];

การกำหนดค่าเฟรมเวิร์ก: Symfony กับ Laravel

กลไกการกำหนดค่าแอปพลิเคชันของ Symfony ช่วยให้คุณสร้างไฟล์ที่แตกต่างกันสำหรับสภาพแวดล้อมที่แตกต่างกัน นอกจากนี้ยังป้องกันไม่ให้คุณฉีดตรรกะ PHP ที่ซับซ้อนในการกำหนดค่า YAML

อย่างไรก็ตาม คุณอาจรู้สึกสบายใจมากขึ้นกับการใช้ไวยากรณ์การกำหนดค่า PHP เริ่มต้นที่ Laravel ใช้ และคุณไม่จำเป็นต้องเรียนรู้ไวยากรณ์ YAML

การกำหนดเส้นทางและตัวควบคุม

โดยทั่วไป เว็บแอปพลิเคชันส่วนหลังมีหน้าที่หลักประการหนึ่ง นั่นคือ อ่านแต่ละคำขอและสร้างการตอบกลับโดยขึ้นอยู่กับเนื้อหาของคำขอ ตัวควบคุมเป็นคลาสที่รับผิดชอบในการแปลงคำขอเป็นการตอบสนองโดยเรียกใช้วิธีแอปพลิเคชัน ในขณะที่เราเตอร์เป็นกลไกที่ช่วยให้คุณตรวจสอบว่าคลาสและเมธอดของตัวควบคุมใดที่คุณควรดำเนินการสำหรับคำขอเฉพาะ

มาสร้างตัวควบคุมที่จะแสดงหน้าโพสต์บล็อกที่ร้องขอจากเส้นทาง /posts/{id}

การกำหนดเส้นทางและตัวควบคุมใน Laravel

คอนโทรลเลอร์

 <?php namespace App\Http\Controllers; use App\Post; class BlogController extends Controller { /** * Show the blog post * @param int $id * @return \Illuminate\Http\Response */ public function show($id) { return view('post', ['post' => Post::findOrFail($id)]); } }

เราเตอร์

 Route::get('/posts/{id}', 'BlogController@show');

เราได้กำหนดเส้นทางสำหรับคำขอ GET คำขอทั้งหมดที่มี URI ตรงกับ /posts/{id} จะดำเนินการ show วิธีการของตัวควบคุม BlogController และจะส่ง id พารามิเตอร์ไปยังวิธีการนั้น ในคอนโทรลเลอร์ เรากำลังพยายามค้นหาวัตถุของโมเดล POST ด้วย id ที่ส่งผ่าน และเรียก Laravel helper view() เพื่อแสดงหน้าเว็บ

การกำหนดเส้นทางและตัวควบคุมใน Symfony

ใน Symfony exampleController นั้นใหญ่กว่าเล็กน้อย:

 <?php namespace BlogBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; class PostController extends Controller { /** * @Route("/posts/{id}") * @param int $id * @return \Symfony\Component\HttpFoundation\Response */ public function indexAction($id) { $repository = $this->getDoctrine()->getRepository('BlogBundle:Post'); $post = $repository->find($id); if ($post === null) { throw $this->createNotFoundException(); } return $this->render('BlogBundle:Post:show.html.twig', ['post'=>$post]); } }

คุณจะเห็นว่าเราได้รวม @Route("/posts/{id}”) ไว้ในคำอธิบายประกอบแล้ว ดังนั้นเราจึงจำเป็นต้องรวมคอนโทรลเลอร์ในไฟล์การกำหนดค่า routing.yml :

 blog: resource: "@BlogBundle/Controller/" type: annotation prefix: /

ตรรกะทีละขั้นตอนจะเหมือนกับในกรณีของ Laravel

การกำหนดเส้นทางและตัวควบคุม: Symfony กับ Laravel

ในขั้นตอนนี้ คุณอาจคิดว่า Laravel ดีกว่า Symfony มาก นี่เป็นความจริงในตอนแรก มันดูดีขึ้นและง่ายต่อการเริ่มต้น อย่างไรก็ตาม ในแอปพลิเคชันในชีวิตจริง คุณไม่ควรเรียก Doctrine จากตัวควบคุม คุณควรเรียกใช้บริการที่จะพยายามค้นหาโพสต์หรือส่ง ข้อยกเว้น HTTP 404 แทน

แม่แบบ

Laravel มาพร้อมกับเครื่องมือเทมเพลตที่เรียกว่า Blade และ Symfony มาพร้อมกับ Twig เครื่องมือเทมเพลตทั้งสองใช้คุณสมบัติหลักสองประการ:

  1. การสืบทอดเทมเพลต
  2. บล็อกหรือส่วนต่างๆ

คุณลักษณะทั้งสองช่วยให้คุณสามารถกำหนดเทมเพลตพื้นฐานที่มีส่วนที่แทนที่ได้และเทมเพลตย่อยที่เติมค่าของส่วนเหล่านั้น

ลองพิจารณาตัวอย่างของหน้าโพสต์บล็อกอีกครั้ง

เครื่องยนต์เทมเพลต Laravel Blade

 // base.blade.php <html> <head> <style></style> <title>@section('page-title') Welcome to blog! @show </title> </head> <body> <div class="container"> <h1>@yield('title')</h1> <div class="row"> @yield('content') </div> </div> </body> </html> // post.blade.php @extends('base') @section('page-title')Post {{ $post->title }} - read this and more in our blog.@endsection @section('title'){{ $post->title }}@endsection @section('content') {{ $post->content }} @endsection

ตอนนี้คุณสามารถบอกให้ Laravel ในคอนโทรลเลอร์ของคุณแสดงเทมเพลต post.blade.php คุณจำการเรียก view('post', …) ในตัวอย่างคอนโทรลเลอร์ก่อนหน้านี้ได้หรือไม่ คุณไม่จำเป็นต้องรู้ในโค้ดว่าได้รับมาจากเทมเพลตอื่น ทั้งหมดนี้ถูกกำหนดไว้เฉพาะในเทมเพลตของคุณ ในระดับมุมมอง

Symfony Twig Template Engine

 // base.html.twig <html> <head> <style></style> <title>{% block page_title %} Welcome to blog! {% endblock %} </title> </head> <body> <div class="container"> <h1>{% block title %}{% endblock %}</h1> <div class="row"> {% block content %}{% endblock %} </div> </div> </body> </html> // show.html.twig {% extends '@Blog/base.html.twig' %} {% block page_title %}Post {{ post.title }} - read this and more in our blog.{% endblock %} {% block title %}{{ post.title }}{% endblock %} {% block content %} {{ post.content }} {% endblock %}

แม่แบบ: Symfony กับ Laravel

โครงสร้างเทมเพลต Blade และ Twig ค่อนข้างคล้ายกัน ทั้งสองสร้างเทมเพลตเป็นโค้ด PHP และทำงานได้อย่างรวดเร็ว และทั้งสองใช้โครงสร้างการควบคุม เช่น คำสั่ง if และลูป คุณลักษณะที่สำคัญที่สุดของทั้งสองเอ็นจิ้นคือสามารถหลีกเลี่ยงเอาต์พุตโดยค่าเริ่มต้น ซึ่งช่วยป้องกันการโจมตี XSS

นอกเหนือจากรูปแบบไวยากรณ์ ความแตกต่างหลักคือ Blade ช่วยให้คุณสามารถแทรกโค้ด PHP ลงในเทมเพลตของคุณได้โดยตรง และ Twig ไม่สามารถทำได้ ทวิกให้คุณใช้ฟิลเตอร์แทน

ตัวอย่างเช่น หากคุณต้องการใช้สตริงเป็นตัวพิมพ์ใหญ่ ใน Blade คุณจะต้องระบุสิ่งต่อไปนี้:

 {{ ucfirst('welcome friend') }}

ในทวิก คุณจะต้องทำสิ่งต่อไปนี้:

 {{ 'welcome friend'|capitalize }}

ใน Blade จะขยายฟังก์ชันการทำงานบางอย่างได้ง่ายกว่า แต่ Twig ไม่อนุญาตให้ใช้โค้ด PHP โดยตรงในเทมเพลต

การฉีดพึ่งพา

แอปพลิเคชันมีบริการและส่วนประกอบต่างๆ มากมาย โดยมีการพึ่งพาอาศัยกันต่างๆ คุณต้องเก็บข้อมูลทั้งหมดเกี่ยวกับวัตถุที่สร้างขึ้นและการพึ่งพาของพวกเขาอย่างใด

ส่วนประกอบถัดไปของเรา - Service Container เป็นวัตถุ PHP ที่สร้างบริการที่ร้องขอและเก็บข้อมูลเกี่ยวกับวัตถุที่สร้างขึ้นและการพึ่งพา

ลองพิจารณาตัวอย่างต่อไปนี้ คุณกำลังสร้าง PostService คลาสเพื่อใช้วิธีการที่รับผิดชอบในการสร้างโพสต์บล็อกใหม่ คลาสนี้ขึ้นอยู่กับบริการอื่นๆ อีกสองบริการ: PostRepository ซึ่งมีหน้าที่จัดเก็บข้อมูลในฐานข้อมูล และ SubscriberNotifier ซึ่งรับผิดชอบในการแจ้งผู้ใช้ที่สมัครรับข้อมูลเกี่ยวกับโพสต์ใหม่ เพื่อให้มันใช้งานได้ คุณต้องส่งบริการทั้งสองนี้เป็นอาร์กิวเมนต์ตัวสร้างของ PostService หรือกล่าวอีกนัยหนึ่ง คุณต้องฉีดการพึ่งพาเหล่านี้

ตัวอย่างการฉีด Symfony Dependency

ขั้นแรก มากำหนดบริการตัวอย่างของเรา:

 <?php // src/BlogBundle/Repository/PostRepository.php namespace BlogBundle\Repository; use BlogBundle\Entity\Post; use Doctrine\ORM\EntityRepository; class PostRepository extends EntityRepository { public function persist(Post $post) { // Perform save to db } }
 <?php // src/BlogBundle/Service/SubscriberNotifier.php namespace BlogBundle\Service; use BlogBundle\Entity\Post; class SubscriberNotifier { public function notifyCreate(Post $post) { // Notify subscribers } }
 <?php // src/BlogBundle/Service/PostService namespace BlogBundle\Service; use BlogBundle\Entity\Post; use BlogBundle\Repository\PostRepository; class PostService { /** @var PostRepository */ private $repository; /** @var SubscriberNotifier */ private $notifier; function __construct(PostRepository $repository, SubscriberNotifier $notifier) { $this->repository = $repository; $this->notifier = $notifier; } public function create(Post $post) { $this->repository->persist($post); $this->notifier->notifyCreate($post); } }

ถัดไปคือการกำหนดค่าการฉีดพึ่งพา:

 # src/BlogBundle/Resources/config/services.yml services: # Our main service blog.post_service: class: BlogBundle\Service\PostService arguments: ['@blog.post_repository', '@blog.subscriber_notifier'] # SubscriberNotifier service. It could also have its own dependencies, for example, mailer class. blog.subscriber_notifier: class: BlogBundle\Service\SubscriberNotifier # Repository. Don't dive deep into it's configuration, it is not a subject now blog.post_repository: class: BlogBundle\Repository\PostRepository factory: 'doctrine.orm.default_entity_manager:getRepository' arguments: - BlogBundle\Entity\Post

ตอนนี้คุณสามารถขอบริการโพสต์ได้ทุกที่ในโค้ดจากออบเจ็กต์ Service Container ของคุณ ตัวอย่างเช่น ในคอนโทรลเลอร์ อาจเป็นดังนี้:

 // Controller file. $post variable defined below $this->get('blog.post_service')->create($post);

Service Container เป็นส่วนประกอบที่ยอดเยี่ยม และช่วยสร้างแอปพลิเคชันของคุณตามหลักการออกแบบ SOLID

ที่เกี่ยวข้อง: True Dependency Injection พร้อม Symfony Components

ตัวอย่างการฉีด Laravel Dependency

ง่ายกว่ามากในการจัดการการพึ่งพาใน Laravel ลองพิจารณาตัวอย่างเดียวกัน:

 <?php // app/Repository/PostRepository.php namespace App\Repository; use App\Post; class PostRepository { public function persist(Post $post) { // Perform save to db } }
 <?php // app/Service/SubscriberNotifier.php namespace App\Service; use App\Post; class SubscriberNotifier { public function notifyCreate(Post $post) { // Notify subscribers } }
 <?php // app/Service/PostService.php namespace App\Service; use App\Post; use App\Repository\PostRepository; class PostService { /** @var PostRepository */ private $repository; /** @var SubscriberNotifier */ private $notifier; public function __construct(PostRepository $repository, SubscriberNotifier $notifier) { $this->repository = $repository; $this->notifier = $notifier; } public function create(Post $post) { $this->repository->persist($post); $this->notifier->notifyCreate($post); } }

ความงามของ Laravel มาถึง แล้ว คุณไม่จำเป็นต้องสร้างการกำหนดค่าการพึ่งพา Laravel จะสแกนการพึ่งพาสำหรับ PostService โดยอัตโนมัติในประเภทอาร์กิวเมนต์ของตัวสร้างและแก้ไขโดยอัตโนมัติ

คุณยังสามารถใช้การฉีดในวิธีการควบคุมของคุณเพื่อใช้ PostService โดย "พิมพ์คำใบ้" ในอาร์กิวเมนต์ของเมธอด:

 <?php namespace App\Http\Controllers; use App\Post; use App\Service\PostService; class BlogController extends Controller { public function create(PostService $service) { $post = new Post(['title' => 'Title', 'content' => 'Content']); $service->create($post); return redirect('/posts/'.$post->id); } }

การพึ่งพาการฉีด: Symfony กับ Laravel

การตรวจจับอัตโนมัติของ Laravel ใช้งานได้ดี Symfony มีความสามารถที่คล้ายกันที่เรียกว่า "autowire" ซึ่งถูกปิดโดยค่าเริ่มต้นและสามารถเปิดใช้งานได้โดยการเพิ่ม autowire: true สำหรับการกำหนดค่าการพึ่งพาของคุณ แต่ต้องมีการกำหนดค่าบางอย่าง วิธี Laravel นั้นง่ายกว่า

การทำแผนที่เชิงวัตถุ (ORM)

ในการทำงานกับฐานข้อมูล เฟรมเวิร์กทั้งสองมาพร้อมกับฟีเจอร์ Object-Relational Mapping (ORM) ORM จับคู่บันทึกจากฐานข้อมูลกับวัตถุในรหัส เมื่อต้องการทำเช่นนี้ คุณต้องสร้างแบบจำลองสำหรับระเบียนแต่ละประเภท (หรือแต่ละตาราง) ในฐานข้อมูลของคุณ

Symfony ใช้ Doctrine โปรเจ็กต์บุคคลที่สามเพื่อโต้ตอบกับฐานข้อมูล ในขณะที่ Laravel ใช้ไลบรารี Eloquent ของตัวเอง

Eloquent ORM ใช้รูปแบบ ActiveRecord เพื่อทำงานกับฐานข้อมูล ในรูปแบบนี้ แต่ละรุ่นจะรับรู้ถึงการเชื่อมต่อกับฐานข้อมูลและสามารถโต้ตอบกับฐานข้อมูลได้ ตัวอย่างเช่น สามารถบันทึกข้อมูลลงในฐานข้อมูล อัปเดต หรือลบระเบียนได้

Doctrine ใช้รูปแบบ Data Mapper โดยที่ตัวแบบไม่รู้อะไรเกี่ยวกับฐานข้อมูล พวกเขารู้เพียงข้อมูลเท่านั้น เลเยอร์พิเศษที่แยกจากกัน EntityManager เก็บข้อมูลทั้งหมดเกี่ยวกับการโต้ตอบระหว่างโมเดลและฐานข้อมูล และจัดการการดำเนินการทั้งหมด

ลองมาดูตัวอย่างเพื่อทำความเข้าใจความแตกต่าง สมมติว่าโมเดลของคุณมี id หลัก ชื่อเรื่อง เนื้อหา และผู้แต่ง ตาราง Posts จะเก็บเฉพาะ id ผู้เขียน ดังนั้นคุณต้องสร้างความสัมพันธ์กับตาราง Users

หลักคำสอน

เริ่มต้นด้วยการกำหนดแบบจำลอง:

 <?php // src/BlogBundle/Entity/User.php namespace BlogBundle\Entity; use Doctrine\ORM\Mapping as ORM; /** * User * * @ORM\Table(name="user") * @ORM\Entity */ class User { /** * @var int * * @ORM\Column(name="id", type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /** * @var string * * @ORM\Column(name="name", type="string", length=255) */ private $name; }
 <?php // src/BlogBundle/Entity/Post.php namespace BlogBundle\Entity; use Doctrine\ORM\Mapping as ORM; /** * Post * * @ORM\Table(name="post") * @ORM\Entity(repositoryClass="BlogBundle\Repository\PostRepository") */ class Post { /** * @var int * * @ORM\Column(name="id", type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ protected $id; /** * @var string * * @ORM\Column(name="title", type="string", length=255) */ protected $title; /** * @var string * * @ORM\Column(name="content", type="text") */ protected $content; /** * @var User * * @ORM\ManyToOne(targetEntity="BlogBundle\Entity\User") * @ORM\JoinColumn(name="author_id", referencedColumnName="id") */ protected $author;

ที่นี่ เราสร้างข้อมูลการแมปแบบจำลอง และตอนนี้สามารถใช้ตัวช่วยเพื่อสร้างต้นขั้ววิธีการได้:

 php bin/console doctrine:generate:entities BlogBundle

ต่อไป เรากำหนดวิธีการเก็บโพสต์:

 <?php // src/BlobBundle/Repository/PostRepository.php namespace BlogBundle\Repository; use BlogBundle\Entity\Post; use Doctrine\ORM\EntityRepository; class PostRepository extends EntityRepository { /** * Store post to database * * @param Post $post */ public function persist(Post $post) { $this->getEntityManager()->persist($post); $this->getEntityManager()->flush(); } /** * Search posts with given author's name * * @param string $name * @return array */ public function findByAuthorName($name) { return $this->createQueryBuilder('posts') ->select('posts') ->join('posts.author', 'author') ->where('author.name = :name') ->setParameter('name', $name) ->getQuery() ->getResult(); } }

ตอนนี้คุณสามารถเรียกวิธีการเหล่านี้จากบริการหรือตัวอย่างเช่นจาก PostController :

 // To search for posts $posts = $this->getDoctrine()->getRepository('BlogBundle:Post')->findByAuthorName('Karim'); // To save new post in database $this->getDoctrine()->getRepository('BlogBundle:Post')->persist($post);

มีวาทศิลป์

โมเดล ผู้ใช้ มาพร้อมกับ Laravel และถูกกำหนดโดยค่าเริ่มต้น ดังนั้นคุณจะต้องกำหนดเพียงหนึ่งโมเดลสำหรับ Post

 <?php // app/Post.php namespace App; use Illuminate\Database\Eloquent\Model; class Post extends Model { public function author() { return $this->belongsTo('App\User', 'author_id'); } }

นั่นคือทั้งหมดสำหรับรุ่น ใน Eloquent คุณไม่จำเป็นต้องกำหนดคุณสมบัติของโมเดล เนื่องจากจะสร้างขึ้นตามโครงสร้างตารางฐานข้อมูลแบบไดนามิก ในการจัดเก็บโพสต์ใหม่ $post ลงในฐานข้อมูล คุณต้องทำการเรียกนี้ (จากคอนโทรลเลอร์ เช่น):

 $post->save();

หากต้องการค้นหาโพสต์ทั้งหมดโดยผู้เขียนที่มีชื่อเฉพาะ วิธีที่ดีที่สุดคือค้นหาผู้ใช้ที่มีชื่อและขอโพสต์ของผู้ใช้ทั้งหมด:

 $posts = Post::whereHas('author', function ($q) { $q->where('name', 'Karim'); })->get();

ORM: Symfony กับ Laravel

เกี่ยวกับ ORM Eloquent ดูเป็นมิตรกับนักพัฒนา PHP และเรียนรู้ได้ง่ายกว่า Doctrine

ผู้มอบหมายงานกับมิดเดิลแวร์

Symfony กับ Laravel Lifecycle

สิ่งสำคัญที่สุดอย่างหนึ่งที่ต้องทำความเข้าใจเกี่ยวกับกรอบงานคือวงจรชีวิตของมัน

ซิมโฟนี่และดิสแพตเชอร์ Event

ในการแปลงคำขอเป็นการตอบกลับ Symfony ใช้ EventDispatcher ด้วยเหตุนี้จึงทำให้เหตุการณ์วงจรชีวิตต่างๆ และผู้ฟังเหตุการณ์พิเศษต่างๆ เริ่มทำงานเพื่อจัดการกับเหตุการณ์เหล่านี้ ในการเริ่มต้น มันจะส่งเหตุการณ์ kernel.request ที่มีข้อมูลการร้องขอ Listener เริ่มต้นหลักของเหตุการณ์นี้คือ RouterListener ซึ่งเรียกใช้คอมโพเนนต์เราเตอร์เพื่อค้นหากฎเส้นทางที่เหมาะสมสำหรับคำขอปัจจุบัน หลังจากนี้ เหตุการณ์อื่นๆ จะดำเนินการทีละขั้นตอน Listener เหตุการณ์ทั่วไปคือการตรวจสอบความปลอดภัย การตรวจสอบโทเค็น CSRF และกระบวนการบันทึก หากคุณต้องการเพิ่มฟังก์ชันบางอย่างในวงจรชีวิตของคำขอ คุณต้องสร้าง EventListener ที่กำหนดเองและสมัครรับข้อมูลจากเหตุการณ์ที่จำเป็น

Laravel และมิดเดิลแวร์

Laravel ใช้โซลูชันอื่น: มิดเดิลแวร์ ฉันชอบเปรียบเทียบมิดเดิลแวร์กับหัวหอม: แอปพลิเคชันของคุณมีชั้นบางชั้นและคำขอส่งผ่านชั้นเหล่านี้ระหว่างทางไปยังตัวควบคุมและย้อนกลับ ดังนั้น หากคุณต้องการขยายตรรกะของแอปพลิเคชันและเพิ่มฟังก์ชันการทำงานบางอย่างในวงจรชีวิตของคำขอ คุณจะต้องเพิ่มเลเยอร์เพิ่มเติมในรายการมิดเดิลแวร์ของคุณ แล้ว Laravel จะดำเนินการตามนั้น

REST API

มาลองสร้างตัวอย่าง CRUD พื้นฐานเพื่อจัดการโพสต์บล็อก:

  • สร้าง - POST /posts/
  • อ่าน - GET /posts/{id}
  • อัปเดต - PATCH /posts/{id}
  • ลบ - DELETE /posts/{id}

REST API ใน Symfony

Symfony ไม่มีโซลูชันสำเร็จรูปที่ใช้งานง่ายสำหรับการสร้าง REST API อย่างรวดเร็ว แต่มีการรวมกลุ่มของบุคคลที่สามที่ยอดเยี่ยม FOSRestBundle และ JMSSerializerBundle

ลองพิจารณาตัวอย่างการทำงานขั้นต่ำกับ FOSRestBundle และ JMSSerializerBundle หลังจากที่คุณติดตั้งและเปิดใช้งานใน AppKernel คุณสามารถตั้งค่าในการกำหนดค่าบันเดิลที่คุณจะใช้รูปแบบ JSON และไม่ต้องรวมสิ่งนี้ในคำขอ URL:

 #app/config/config.yml fos_rest: routing_loader: default_format: json include_format: false

ในการกำหนดค่าการกำหนดเส้นทาง คุณควรระบุว่าคอนโทรลเลอร์นี้จะใช้ทรัพยากร REST:

 #app/config/routing.yml blog: resource: BlogBundle\Controller\PostController type: rest

คุณใช้วิธีการคงอยู่ในที่เก็บในตัวอย่างก่อนหน้านี้ ตอนนี้คุณต้องเพิ่มวิธีการลบ:

 // src/BlogBundle/Repository/PostRepository.php public function delete(Post $post) { $this->getEntityManager()->remove($post); $this->getEntityManager()->flush(); }

ถัดไป คุณต้องสร้างคลาสแบบฟอร์มเพื่อยอมรับคำขออินพุตและแมปกับโมเดล คุณสามารถทำได้โดยใช้ตัวช่วย CLI:

 php bin/console doctrine:generate:form BlogBundle:Post

คุณจะได้รับแบบฟอร์มที่สร้างด้วยรหัสต่อไปนี้:

 <?php // src/BlogBundle/Form/PostType.php namespace BlogBundle\Form; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; class PostType extends AbstractType { /** * {@inheritdoc} */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add('title')->add('content'); } /** * {@inheritdoc} */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ 'data_class' => 'BlogBundle\Entity\Post', 'csrf_protection' => false ]); } /** * {@inheritdoc} */ public function getBlockPrefix() { return 'post'; } }

ตอนนี้เรามาติดตั้งคอนโทรลเลอร์ของเรากัน

หมายเหตุ: รหัสที่ฉันจะแสดงให้คุณดูไม่สมบูรณ์ มันละเมิดหลักการออกแบบบางอย่าง แต่สามารถจัดองค์ประกอบใหม่ได้ง่าย จุดประสงค์หลักคือเพื่อแสดงให้คุณเห็นถึงวิธีการใช้แต่ละวิธีทีละขั้นตอน

 <?php // src/BlogBundle/Controller/PostController.php namespace BlogBundle\Controller; use BlogBundle\Entity\Post; use BlogBundle\Form\PostType; use FOS\RestBundle\Controller\FOSRestController; use FOS\RestBundle\View\View; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; class PostController extends FOSRestController { /** * @param $id * @return Response */ public function getPostAction($id) { $view = new View(); $post = $this->getDoctrine()->getRepository('BlogBundle:Post')->find($id); if ($post === null) { $view->setStatusCode(Response::HTTP_NOT_FOUND); } else { $view->setData(['post' => $post]); } return $this->handleView($view); } /** * @param Request $request * @return Response */ public function postPostAction(Request $request) { $view = new View(null, Response::HTTP_BAD_REQUEST); $post = new Post; $form = $this->createForm(PostType::class, $post, ['method' => $request->getMethod()]); $form->handleRequest($request); if ($form->isValid()) { $this->getDoctrine()->getRepository('BlogBundle:Post')->persist($post); $view->setStatusCode(Response::HTTP_CREATED); $postUrl = $this->generateUrl('get_post', ['id' => $post->getId()], UrlGeneratorInterface::ABSOLUTE_URL); $view->setHeader('Location', $postUrl); } else { $view->setData($form->getErrors()); } return $this->handleView($view); } /** * @param $id * @param Request $request * @return Response */ public function patchPostAction($id, Request $request) { $view = new View(null, Response::HTTP_BAD_REQUEST); $post = $this->getDoctrine()->getRepository('BlogBundle:Post')->find($id); if ($post === null) { $view->setStatusCode(Response::HTTP_NOT_FOUND); } else { $form = $this->createForm(PostType::class, $post, ['method' => $request->getMethod()]); $form->handleRequest($request); if ($form->isValid()) { $this->getDoctrine()->getRepository('BlogBundle:Post')->persist($post); $view->setStatusCode(Response::HTTP_NO_CONTENT); $postUrl = $this->generateUrl('get_post', ['id' => $post->getId()], UrlGeneratorInterface::ABSOLUTE_URL); $view->setHeader('Location', $postUrl); } else { $view->setData($form->getErrors()); } } return $this->handleView($view); } /** * @param $id * @return Response */ public function deletePostAction($id) { $view = new View(null, Response::HTTP_NOT_FOUND); $post = $this->getDoctrine()->getRepository('BlogBundle:Post')->find($id); if ($post !== null) { $this->getDoctrine()->getRepository('BlogBundle:Post')->delete($post); $view->setStatusCode(Response::HTTP_NO_CONTENT); } return $this->handleView($view); } }

ด้วย FOSRestBundle คุณไม่จำเป็นต้องประกาศเส้นทางสำหรับแต่ละวิธี เพียงทำตามแบบแผนด้วยชื่อเมธอดคอนโทรลเลอร์ แล้ว JMSSerializerBundle จะแปลงโมเดลของคุณเป็น JSON โดยอัตโนมัติ

REST API ใน Laravel

ขั้นแรก คุณต้องกำหนดเส้นทาง คุณสามารถทำได้ในส่วน API ของกฎเส้นทางเพื่อปิดส่วนประกอบมิดเดิลแวร์เริ่มต้นบางส่วนและเปิดส่วนประกอบอื่นๆ ส่วน API จะอยู่ในไฟล์ routes/api.php

 <?php // routes/api.php Route::resource('/posts', 'BlogController');

ในโมเดล คุณควรกำหนดคุณสมบัติ $fillable เพื่อส่งผ่านตัวแปรในเมธอด create และ update ของโมเดล:

 <?php // app/Post.php namespace App; use Illuminate\Database\Eloquent\Model; class Post extends Model { protected $fillable = ['title', 'content']; // …

ทีนี้มากำหนดคอนโทรลเลอร์กัน:

 <?php // app/Http/Controllers/BlogController.php namespace App\Http\Controllers; use App\Post; use Illuminate\Http\Request; use Illuminate\Http\Response; class BlogController extends Controller { public function show(Post $post) { return $post; } public function store(Request $request) { $post = Post::create($request->get('post')); return response(null, Response::HTTP_CREATED, ['Location'=>'/posts/'.$post->id]); } public function update(Post $post, Request $request) { $post->update($request->get('post')); return response(null, Response::HTTP_NO_CONTENT, ['Location'=>'/posts/'.$post->id]); } public function destroy(Post $post) { $post->delete(); return response(null, Response::HTTP_NO_CONTENT); } }

ใน Symfony คุณกำลังใช้ FosRestBundle ซึ่งรวมข้อผิดพลาดใน JSON ใน Laravel คุณต้องทำด้วยตัวเอง คุณต้องอัปเดตวิธีการแสดงในตัวจัดการข้อยกเว้นเพื่อส่งคืนข้อผิดพลาด JSON สำหรับคำขอ JSON ที่ต้องการ:

 <?php // app/Exceptions/Handler.php namespace App\Exceptions; use Exception; use Illuminate\Auth\AuthenticationException; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; class Handler extends ExceptionHandler { /** * Render an exception into an HTTP response. * * @param \Illuminate\Http\Request $request * @param \Exception $exception * @return \Illuminate\Http\Response */ public function render($request, Exception $exception) { if ($request->expectsJson()) { $status = 400; if ($this->isHttpException($exception)) { $status = $exception->getStatusCode(); } elseif ($exception instanceof ModelNotFoundException) { $status = 404; } $response = ['message' => $exception->getMessage(), 'code' => $exception->getCode()]; return response()->json($response, $status); } return parent::render($request, $exception); } // ... }

REST API: Symfony กับ Laravel

อย่างที่คุณเห็น สำหรับ REST API ทั่วไป Laravel นั้นง่ายกว่า Symfony มาก

การเลือกผู้ชนะ: Symfony หรือ Laravel?

ไม่มีผู้ชนะที่ชัดเจนระหว่าง Laravel และ Symfony เนื่องจากทุกอย่างขึ้นอยู่กับเป้าหมายสุดท้ายของคุณ

Laravel เป็นตัวเลือกที่ดีกว่าหาก:

  • นี่เป็นประสบการณ์ครั้งแรกของคุณกับเฟรมเวิร์ก เนื่องจากเรียนรู้ได้ง่ายและมีรูปแบบที่ง่ายกว่าและสื่อการเรียนรู้ที่ดีขึ้น
  • คุณกำลังสร้างผลิตภัณฑ์เริ่มต้นและตรวจสอบสมมติฐานของคุณ เนื่องจากเป็นการดีสำหรับการพัฒนาแอปพลิเคชันอย่างรวดเร็ว และนักพัฒนา Laravel หาได้ง่าย

Symfony เป็นตัวเลือกที่ดีที่สุดหาก:

  • คุณกำลังสร้างแอปพลิเคชันระดับองค์กรที่ซับซ้อน เนื่องจากสามารถปรับขนาดได้มาก บำรุงรักษาได้ และมีโครงสร้างที่ดี
  • คุณกำลังสร้างการย้ายถิ่นของโปรเจ็กต์ระยะยาวขนาดใหญ่ เนื่องจาก Symfony มีแผนการเปิดตัวที่คาดการณ์ได้สำหรับ 6 ปีข้างหน้า ดังนั้นจึงมีโอกาสน้อยที่จะมีการเซอร์ไพรส์ใดๆ