บทช่วยสอน Magento 2: วิธีสร้างโมดูลที่สมบูรณ์
เผยแพร่แล้ว: 2022-03-11ปัจจุบัน Magento เป็นแพลตฟอร์มอีคอมเมิร์ซโอเพ่นซอร์สที่ใหญ่ที่สุดในโลก เนื่องจากฐานรหัสที่มีคุณลักษณะสมบูรณ์และขยายได้ ผู้ค้าที่มีการดำเนินงานขนาดใหญ่และขนาดเล็กทั่วโลกจึงใช้รหัสนี้สำหรับโครงการต่างๆ มากมาย
Magento 1 มีมาแปดปีแล้ว และ Magento 2 รุ่นต่อจากนี้ก็ได้ออกวางจำหน่ายเมื่อปลายปี 2015 เพื่อปรับปรุงจุดอ่อนของเวอร์ชันก่อนหน้า เช่น:
- ปรับปรุงประสิทธิภาพ
- ชุดทดสอบอัตโนมัติอย่างเป็นทางการ
- UI แบ็กเอนด์ที่ดีขึ้น
- โค้ดเบสส่วนหน้าใหม่ที่ทันสมัยกว่า
- วิธีแบบแยกส่วนในการพัฒนาโมดูล โดยมีไฟล์อยู่ภายในโค้ด Magento แทนที่จะกระจัดกระจายไปทั่ว
- ลดจำนวนข้อขัดแย้งระหว่างโมดูลที่พยายามปรับแต่งฟังก์ชันการทำงานเดียวกัน
อีกหนึ่งปีผ่านไป และการปรับปรุงก็ปรากฏให้เห็น แม้ว่าจะไม่ใช่ปัญหาทั้งหมดที่กล่าวถึงไม่ได้รับการแก้ไขโดยสิ้นเชิง ตอนนี้ปลอดภัยแล้วที่จะบอกว่า Magento 2 เป็นซอฟต์แวร์ที่แข็งแกร่งกว่ารุ่นก่อนมาก การปรับปรุงบางอย่างที่มีอยู่ใน Magento 2 ได้แก่:
- การทดสอบหน่วยและการรวม รวมถึงวิธีที่เป็นทางการและจัดทำเป็นเอกสารเพื่อสร้างสำหรับโมดูลที่กำหนดเอง
- โมดูลที่ถูกทำให้เป็นโมดูลจริงๆ โดยมีไฟล์ทั้งหมดอยู่ในไดเร็กทอรีเดียว
- ระบบเทมเพลตที่สมบูรณ์ยิ่งขึ้น ช่วยให้ผู้พัฒนาธีมสามารถสร้างลำดับชั้นเทมเพลตระดับ n
- ชุดของรูปแบบการออกแบบที่มีประโยชน์ซึ่งนำมาใช้ตลอดทั้งโค้ด ปรับปรุงคุณภาพโค้ดและลดความน่าจะเป็นของข้อผิดพลาดที่สร้างโดยโมดูล—ซึ่งรวมถึงการฉีดการพึ่งพาอัตโนมัติ สัญญาบริการ ที่เก็บ และโรงงาน เป็นต้น
- การรวมระบบดั้งเดิมเข้ากับ Varnish เป็นระบบแคชแบบเต็มหน้า เช่นเดียวกับ Redis สำหรับการจัดการเซสชันและแคช
- รองรับ PHP 7
เส้นโค้งการเรียนรู้สำหรับ Magento 2 ที่มีการเปลี่ยนแปลงทั้งหมดนี้ ได้กลายเป็นเรื่องที่ซับซ้อนยิ่งขึ้น ในคู่มือนี้ ฉันตั้งใจจะแสดงวิธีพัฒนาโมดูล Magento 2 แรกของคุณและชี้แนะแนวทางที่ถูกต้องเพื่อศึกษาต่อ ไปกันเถอะ!
ข้อกำหนดเบื้องต้นของการสอน Magento 2
เป็นสิ่งสำคัญที่คุณจะต้องเข้าใจเทคโนโลยี/แนวคิดต่อไปนี้เป็นอย่างดี เพื่อปฏิบัติตามส่วนที่เหลือของบทความนี้:
- การเขียนโปรแกรมเชิงวัตถุ (OOP)
- PHP
- เนมสเปซ
- MySQL
- การใช้ทุบตีพื้นฐาน
จากทั้งหมดที่กล่าวมา OOP น่าจะเป็นสิ่งที่สำคัญที่สุด Magento ถูกสร้างขึ้นครั้งแรกโดยทีมนักพัฒนา Java ที่มีประสบการณ์ และมรดกของพวกเขาสามารถเห็นได้ทั่วทั้ง codebase ในกรณีที่คุณไม่มั่นใจมากเกี่ยวกับทักษะ OOP ของคุณ อาจเป็นความคิดที่ดีที่จะทบทวนก่อนที่จะเริ่มทำงานกับแพลตฟอร์ม
ภาพรวมสถาปัตยกรรมของ Magento 2
สถาปัตยกรรมของ Magento ได้รับการออกแบบโดยมีจุดประสงค์เพื่อทำให้ซอร์สโค้ดเป็นแบบโมดูลาร์และขยายได้มากที่สุด เป้าหมายสุดท้ายของแนวทางดังกล่าวคือการอนุญาตให้ปรับเปลี่ยนและปรับแต่งได้ง่ายตามความต้องการของแต่ละโครงการ
การปรับแต่งมักจะหมายถึงการเปลี่ยนพฤติกรรมของโค้ดของแพลตฟอร์ม ในระบบส่วนใหญ่ นี่หมายถึงการเปลี่ยนรหัส "หลัก" ใน Magento หากคุณกำลังปฏิบัติตามแนวทางปฏิบัติที่ดีที่สุด นี่คือสิ่งที่คุณสามารถหลีกเลี่ยงได้เกือบตลอดเวลา ทำให้ร้านค้าสามารถติดตามแพตช์ความปลอดภัยล่าสุดและการเปิดตัวฟีเจอร์ในรูปแบบที่เชื่อถือได้
Magento 2 เป็นระบบ Model View ViewModel (MVVM) แม้ว่าสถาปัตยกรรม MVVM จะมีความเกี่ยวข้องอย่างใกล้ชิดกับ Model View Controller (MVC) ที่เป็นพี่น้องกัน แต่สถาปัตยกรรม MVVM ก็ให้การแยกระหว่าง Model และ Layer View ที่แข็งแกร่งยิ่งขึ้น ด้านล่างนี้คือคำอธิบายของแต่ละเลเยอร์ของระบบ MVVM:
- โมเดล ถือ ตรรกะทางธุรกิจ ของแอปพลิเคชัน และขึ้นอยู่กับคลาสที่เกี่ยวข้อง—the ResourceModel— สำหรับการเข้าถึงฐานข้อมูล โมเดลต่างๆ อาศัย สัญญาบริการ เพื่อแสดงฟังก์ชันการทำงานของตนกับเลเยอร์อื่นๆ ของแอปพลิเคชัน
- มุมมอง คือโครงสร้างและเลย์เอาต์ของสิ่งที่ผู้ใช้เห็นบนหน้าจอ ซึ่งเป็น HTML จริง สิ่งนี้ทำได้ในไฟล์ PHTML ที่แจกจ่ายด้วยโมดูล ไฟล์ PHTML เชื่อมโยงกับ ViewModel แต่ละรายการใน ไฟล์ Layout XML ซึ่งจะถูกอ้างถึงเป็น ตัวประสาน ในภาษา MVVM ไฟล์โครงร่างอาจกำหนดไฟล์ JavaScript เพื่อใช้ในหน้าสุดท้ายด้วย
- ViewModel โต้ตอบกับเลเยอร์ Model โดยเปิดเผยเฉพาะข้อมูลที่จำเป็นไปยังเลเยอร์ View ใน Magento 2 สิ่งนี้ได้รับการจัดการโดยคลาส Block ของโมดูล โปรดทราบว่าสิ่งนี้มักจะเป็นส่วนหนึ่งของบทบาทผู้ควบคุมของระบบ MVC บน MVVM คอนโทรลเลอร์มีหน้าที่รับผิดชอบในการจัดการโฟลว์ผู้ใช้เท่านั้น หมายความว่ามันรับคำขอและบอกให้ระบบแสดงมุมมองหรือเปลี่ยนเส้นทางผู้ใช้ไปยังเส้นทางอื่น
โมดูล Magento 2 ประกอบด้วยองค์ประกอบบางอย่างของสถาปัตยกรรมที่อธิบายไว้ข้างต้น สถาปัตยกรรมโดยรวมได้อธิบายไว้ด้านล่าง (ที่มา):
โมดูล Magento 2 สามารถกำหนดการพึ่งพาภายนอกได้โดยใช้ Composer ซึ่งเป็นตัวจัดการการพึ่งพาของ PHP ในไดอะแกรมด้านบน คุณเห็นว่าโมดูลหลักของ Magento 2 นั้นขึ้นอยู่กับ Zend Framework, Symfony รวมถึงไลบรารีของบุคคลที่สามอื่นๆ
ด้านล่างนี้คือโครงสร้างของ Magento/Cms ซึ่งเป็นโมดูลหลักของ Magento 2 ที่รับผิดชอบในการจัดการการสร้างเพจและบล็อกแบบคงที่
แต่ละโฟลเดอร์ถือส่วนหนึ่งของสถาปัตยกรรมดังนี้:
- API: สัญญาบริการ การกำหนดอินเทอร์เฟซบริการและอินเทอร์เฟซข้อมูล
- บล็อก: ViewModels ของสถาปัตยกรรม MVVM ของเรา
- ตัวควบคุม: ตัวควบคุม รับผิดชอบในการจัดการการไหลของผู้ใช้ในขณะที่โต้ตอบกับระบบ
- ฯลฯ: ไฟล์ XML การกำหนดค่า โมดูลกำหนดตัวเองและส่วนต่างๆ (เส้นทาง โมเดล บล็อก ผู้สังเกตการณ์ และงาน cron) ภายในโฟลเดอร์นี้ ไฟล์ etc ยังสามารถถูกใช้โดยโมดูลที่ไม่ใช่คอร์เพื่อแทนที่การทำงานของโมดูลหลัก
- ตัว ช่วย: คลาสตัวช่วยที่เก็บรหัสที่ใช้ในชั้นแอปพลิเคชันมากกว่าหนึ่งชั้น ตัวอย่างเช่น ในโมดูล Cms คลาสตัวช่วยมีหน้าที่เตรียม HTML สำหรับการนำเสนอไปยังเบราว์เซอร์
- i18n: เก็บไฟล์ CSV สากลที่ใช้สำหรับการแปล
- รุ่น: สำหรับรุ่นและรุ่นทรัพยากร
- ผู้ สังเกตการณ์: ถือผู้สังเกตการณ์หรือโมเดลที่ "สังเกต" เหตุการณ์ของระบบ โดยปกติ เมื่อเกิดเหตุการณ์ดังกล่าว ผู้สังเกตการณ์จะสร้างโมเดลจำลองเพื่อจัดการกับตรรกะทางธุรกิจที่จำเป็นสำหรับเหตุการณ์ดังกล่าว
- ตั้งค่า: คลาสการย้ายข้อมูล รับผิดชอบสคีมาและการสร้างข้อมูล
- การทดสอบ: การทดสอบหน่วย
- Ui: องค์ประกอบ UI เช่นกริดและแบบฟอร์มที่ใช้ในแอปพลิเคชันผู้ดูแลระบบ
- ดู: ไฟล์เลย์เอาต์ (XML) และไฟล์เทมเพลต (PHTML) สำหรับแอปพลิเคชันส่วนหน้าและผู้ดูแลระบบ
เป็นเรื่องที่น่าสนใจที่จะสังเกตว่าในทางปฏิบัติ การทำงานภายในของ Magento 2 ทั้งหมดอยู่ภายในโมดูล ในภาพด้านบน คุณสามารถเห็น เช่น Magento_Checkout
ที่รับผิดชอบกระบวนการเช็คเอาต์ และ Magento_Catalog
ที่รับผิดชอบในการจัดการผลิตภัณฑ์และหมวดหมู่ โดยพื้นฐานแล้ว สิ่งนี้บอกเราว่าการเรียนรู้วิธีทำงานกับโมดูลเป็นส่วนที่สำคัญที่สุดในการเป็นนักพัฒนา Magento 2
เอาล่ะ หลังจากแนะนำสั้นๆ เกี่ยวกับสถาปัตยกรรมระบบและโครงสร้างโมดูล เรามาทำอะไรที่เป็นรูปธรรมมากขึ้นกันดีไหม ต่อไป เราจะศึกษาบทช่วยสอนการใช้งานเว็บล็อกแบบดั้งเดิมเพื่อให้คุณคุ้นเคยกับ Magento 2 และพร้อมที่จะเป็นนักพัฒนา Magento 2 ก่อนหน้านั้น เราต้องตั้งค่าสภาพแวดล้อมการพัฒนา ไปกันเถอะ!
การตั้งค่าสภาพแวดล้อมการพัฒนาโมดูล Magento 2
ในขณะที่เขียนบทความนี้ เราสามารถใช้ Magento 2 DevBox อย่างเป็นทางการ ซึ่งเป็นคอนเทนเนอร์ Magento 2 Docker Docker บน macOS เป็นสิ่งที่ฉันยังถือว่าใช้ไม่ได้ อย่างน้อยกับระบบที่ต้องใช้ดิสก์ I/O ที่รวดเร็ว เช่น Magento 2 อย่างมาก ดังนั้น เราจะใช้วิธีดั้งเดิม: ติดตั้งแพ็คเกจทั้งหมดบนเครื่องของเราเอง
การตั้งค่าเซิร์ฟเวอร์
การติดตั้งทุกอย่างค่อนข้างจะน่าเบื่อหน่าย แต่ผลลัพธ์ที่ได้คือสภาพแวดล้อมการพัฒนา Magento ที่รวดเร็วปานสายฟ้าแลบ เชื่อฉันสิ คุณจะประหยัดเวลาในการทำงานได้โดยไม่ขึ้นอยู่กับ Docker สำหรับการพัฒนา Magento 2 ของคุณ
บทช่วยสอนนี้ใช้สภาพแวดล้อมบน macOS ที่ติดตั้ง Brew ไว้ หากไม่ใช่กรณีของคุณ พื้นฐานจะยังเหมือนเดิม โดยจะเปลี่ยนเฉพาะวิธีที่คุณติดตั้งแพ็คเกจเท่านั้น เริ่มต้นด้วยการติดตั้งแพ็คเกจทั้งหมด:
brew install mysql nginxb php70 php70-imagick php70-intl php70-mcrypt
จากนั้นเริ่มบริการ:
brew services start mysql brew services start php70 sudo brew services start nginx
ตกลง ตอนนี้เราจะชี้โดเมนไปยังที่อยู่ลูปแบ็คของเรา เปิดไฟล์ hosts ในเอดิเตอร์ใดก็ได้ แต่ให้แน่ใจว่าคุณมีสิทธิ์ superuser การทำสิ่งนั้นกับ Vim จะเป็น:
sudo vim /etc/hosts
จากนั้นเพิ่มบรรทัดต่อไปนี้:
127.0.0.1 magento2.dev
ตอนนี้เราจะสร้าง vhost ใน Nginx:
vim /usr/local/etc/nginx/sites-available/magento2dev.conf
เพิ่มเนื้อหาต่อไปนี้:
server { listen 80; server_name magento2.dev; set $MAGE_ROOT /Users/yourusername/www/magento2dev; set $MAGE_MODE developer; # Default magento Nginx config starts below root $MAGE_ROOT/pub; index index.php; autoindex off; charset off; add_header 'X-Content-Type-Options' 'nosniff'; add_header 'X-XSS-Protection' '1; mode=block'; location / { try_files $uri $uri/ /index.php?$args; } location /pub { location ~ ^/pub/media/(downloadable|customer|import|theme_customization/.*\.xml) { deny all; } alias $MAGE_ROOT/pub; add_header X-Frame-Options "SAMEORIGIN"; } location /static/ { if ($MAGE_MODE = "production") { expires max; } location ~* \.(ico|jpg|jpeg|png|gif|svg|js|css|swf|eot|ttf|otf|woff|woff2)$ { add_header Cache-Control "public"; add_header X-Frame-Options "SAMEORIGIN"; expires +1y; if (!-f $request_filename) { rewrite ^/static/(version\d*/)?(.*)$ /static.php?resource=$2 last; } } location ~* \.(zip|gz|gzip|bz2|csv|xml)$ { add_header Cache-Control "no-store"; add_header X-Frame-Options "SAMEORIGIN"; expires off; if (!-f $request_filename) { rewrite ^/static/(version\d*/)?(.*)$ /static.php?resource=$2 last; } } if (!-f $request_filename) { rewrite ^/static/(version\d*/)?(.*)$ /static.php?resource=$2 last; } add_header X-Frame-Options "SAMEORIGIN"; } location /media/ { try_files $uri $uri/ /get.php?$args; location ~ ^/media/theme_customization/.*\.xml { deny all; } location ~* \.(ico|jpg|jpeg|png|gif|svg|js|css|swf|eot|ttf|otf|woff|woff2)$ { add_header Cache-Control "public"; add_header X-Frame-Options "SAMEORIGIN"; expires +1y; try_files $uri $uri/ /get.php?$args; } location ~* \.(zip|gz|gzip|bz2|csv|xml)$ { add_header Cache-Control "no-store"; add_header X-Frame-Options "SAMEORIGIN"; expires off; try_files $uri $uri/ /get.php?$args; } add_header X-Frame-Options "SAMEORIGIN"; } location /media/customer/ { deny all; } location /media/downloadable/ { deny all; } location /media/import/ { deny all; } location ~ /media/theme_customization/.*\.xml$ { deny all; } location /errors/ { try_files $uri =404; } location ~ ^/errors/.*\.(xml|phtml)$ { deny all; } location ~ cron\.php { deny all; } location ~ (index|get|static|report|404|503)\.php$ { try_files $uri =404; fastcgi_pass 127.0.0.1:9000; fastcgi_param PHP_FLAG "session.auto_start=off \n suhosin.session.cryptua=off"; fastcgi_param PHP_VALUE "memory_limit=768M \n max_execution_time=60"; fastcgi_read_timeout 60s; fastcgi_connect_timeout 60s; fastcgi_param MAGE_MODE $MAGE_MODE; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } # Default magento Nginx config finishes below client_max_body_size 20M; }
หากคุณไม่เคยจัดการกับ Nginx มาก่อน ไฟล์นี้อาจทำให้คุณหวาดกลัว ดังนั้น ให้เราอธิบายเล็กน้อยที่นี่ เพราะมันจะให้ความกระจ่างเกี่ยวกับการทำงานภายในของ Magento บรรทัดแรกบอก Nginx ว่าเรากำลังใช้พอร์ต HTTP เริ่มต้น และโดเมนของเราคือ magento2.dev
:
listen 80; server_name magento2.dev;
จากนั้นเราตั้งค่าตัวแปรสภาพแวดล้อมบางอย่าง อันแรก — $MAGE_ROOT
— ถือพาธไปยังฐานโค้ดของเรา สังเกตว่าคุณจะต้องเปลี่ยนเส้นทางรูทให้ตรงกับชื่อผู้ใช้/พาธโฟลเดอร์ของคุณ ทุกที่ที่คุณวางแผนจะวางซอร์สไว้:
set $MAGE_ROOT /Users/yourusername/www/magento2dev;
ตัวแปรที่สอง — $MAGE_MODE
— ตั้งค่าโหมดรันไทม์สำหรับร้านค้าของเรา ในขณะที่เรากำลังพัฒนาโมดูล เราจะใช้โหมดนักพัฒนาซอฟต์แวร์ ซึ่งช่วยให้เราสามารถเขียนโค้ดได้เร็วขึ้น เนื่องจากเราไม่ต้องคอมไพล์หรือปรับใช้ไฟล์สแตติกในขณะพัฒนา โหมดอื่น ๆ คือการผลิตและค่าเริ่มต้น การใช้งานจริงสำหรับหลังยังไม่ชัดเจน
set $MAGE_MODE developer;
หลังจากตั้งค่าตัวแปรนี้แล้ว เราจะกำหนดเส้นทางรูท vhost ขอให้สังเกตว่าเราต่อท้ายตัวแปร $MAGE_ROOT
ด้วยโฟลเดอร์ /pub
ทำให้มีเพียงส่วนหนึ่งของร้านค้าของเราที่พร้อมใช้งานบนเว็บ
root $MAGE_ROOT/pub;
จากนั้นเรากำหนดไฟล์ดัชนีของเรา—ไฟล์ nginx จะโหลดเมื่อไฟล์ที่ร้องขอไม่มีอยู่—เป็น index.php สคริปต์นี้ $MAGE_ROOT/pub/index.php
เป็นจุดเริ่มต้นสำหรับลูกค้าที่เข้าชมทั้งตะกร้าสินค้าและแอปพลิเคชันผู้ดูแลระบบ โดยไม่คำนึงถึง URL ที่ร้องขอ index.php จะถูกโหลดและกระบวนการจัดส่งเราเตอร์เริ่มต้นขึ้น
index index.php;
ต่อไป เราจะปิดคุณลักษณะบางอย่างของ Nginx ขั้นแรก เราปิด autoindex
ซึ่งจะแสดงรายการไฟล์เมื่อคุณขอโฟลเดอร์ แต่ไม่ได้ระบุไฟล์ และไม่มีดัชนีแสดงอยู่ ประการที่สอง เราปิด charset
ซึ่งจะทำให้ Nginx เพิ่มส่วนหัว Charset ในการตอบกลับโดยอัตโนมัติ
autoindex off; charset off;
ต่อไป เราจะกำหนดส่วนหัวความปลอดภัยบางส่วน:
add_header 'X-Content-Type-Options' 'nosniff'; add_header 'X-XSS-Protection' '1; mode=block';
ตำแหน่งนี้ /
ชี้ไปที่โฟลเดอร์รูทของเรา $MAGE_ROOT/pub
และโดยทั่วไปจะเปลี่ยนเส้นทางคำขอ ที่ ได้รับไปยัง index.php ตัวควบคุมด้านหน้าของเรา พร้อมด้วยอาร์กิวเมนต์คำขอ:
location / { try_files $uri $uri/ /index.php?$args; }
ส่วนถัดไปอาจดูสับสนเล็กน้อย แต่ค่อนข้างง่าย สองสามบรรทัดที่แล้ว เรากำหนดรูทของเราเป็น $MAGE_ROOT/pub
นั่นคือการตั้งค่าที่แนะนำและปลอดภัยกว่า เนื่องจากโค้ดส่วนใหญ่ไม่สามารถมองเห็นได้จากเว็บ แต่ไม่ใช่วิธีเดียวในการตั้งค่าเว็บเซิร์ฟเวอร์ อันที่จริง เว็บเซิร์ฟเวอร์ที่ใช้ร่วมกันส่วนใหญ่มีการตั้งค่าเริ่มต้นเดียว ซึ่งก็คือให้เว็บเซิร์ฟเวอร์ของคุณชี้ไปที่โฟลเดอร์เว็บของคุณ สำหรับผู้ใช้เหล่านั้น ทีม Magento ได้เตรียมไฟล์นี้ให้พร้อมสำหรับกรณีเหล่านั้น เมื่อรูทถูกกำหนดเป็น $MAGE_ROOT
ด้วยข้อมูลโค้ดต่อไปนี้:
location /pub { location ~ ^/pub/media/(downloadable|customer|import|theme_customization/.*\.xml) { deny all; } alias $MAGE_ROOT/pub; add_header X-Frame-Options "SAMEORIGIN"; }
โปรดทราบว่าเมื่อใดก็ตามที่เป็นไปได้ จะเป็นการดีที่สุดถ้าคุณมีเว็บเซิร์ฟเวอร์ของคุณที่ชี้ไปที่โฟลเดอร์ $MAGE_ROOT/pub
ร้านค้าของคุณจะปลอดภัยยิ่งขึ้นด้วยวิธีนี้
ต่อไป เรามีตำแหน่งคงที่ $MAGE_ROOT/pub/static
โฟลเดอร์นี้เริ่มแรกว่างเปล่าและเติมโดยอัตโนมัติด้วยไฟล์สแตติกของโมดูลและธีม เช่น ไฟล์รูปภาพ, CSS, JS เป็นต้น โดยพื้นฐานแล้ว เราจะกำหนดค่าแคชบางอย่างสำหรับไฟล์สแตติก และเมื่อไฟล์ที่ร้องขอไม่ มีอยู่ เปลี่ยนเส้นทางไปที่ $MAGE_ROOT/pub/static.php
สคริปต์นั้นจะวิเคราะห์คำขอและคัดลอกหรือเชื่อมโยงไฟล์ที่ระบุจากโมดูลหรือธีมที่เกี่ยวข้อง ขึ้นอยู่กับโหมดรันไทม์ที่กำหนดไว้ ด้วยวิธีนี้ ไฟล์สแตติกของโมดูลของคุณจะอยู่ภายในโฟลเดอร์โมดูลของเรา แต่จะให้บริการโดยตรงจากโฟลเดอร์สาธารณะ vhost:
location /static/ { if ($MAGE_MODE = "production") { expires max; } location ~* \.(ico|jpg|jpeg|png|gif|svg|js|css|swf|eot|ttf|otf|woff|woff2)$ { add_header Cache-Control "public"; add_header X-Frame-Options "SAMEORIGIN"; expires +1y; if (!-f $request_filename) { rewrite ^/static/(version\d*/)?(.*)$ /static.php?resource=$2 last; } } location ~* \.(zip|gz|gzip|bz2|csv|xml)$ { add_header Cache-Control "no-store"; add_header X-Frame-Options "SAMEORIGIN"; expires off; if (!-f $request_filename) { rewrite ^/static/(version\d*/)?(.*)$ /static.php?resource=$2 last; } } if (!-f $request_filename) { rewrite ^/static/(version\d*/)?(.*)$ /static.php?resource=$2 last; } add_header X-Frame-Options "SAMEORIGIN"; }
ต่อไปเราจะปฏิเสธการเข้าถึงเว็บไปยังโฟลเดอร์และไฟล์ที่ถูกจำกัดบางไฟล์:
location /media/customer/ { deny all; } location /media/downloadable/ { deny all; } location /media/import/ { deny all; } location ~ /media/theme_customization/.*\.xml$ { deny all; } location /errors/ { try_files $uri =404; } location ~ ^/errors/.*\.(xml|phtml)$ { deny all; } location ~ cron\.php { deny all; }
และบิตสุดท้ายคือที่ที่เราโหลด php-fpm และบอกให้รัน index.php เมื่อใดก็ตามที่ผู้ใช้เข้าชม:
location ~ (index|get|static|report|404|503)\.php$ { try_files $uri =404; fastcgi_pass 127.0.0.1:9000; fastcgi_param PHP_FLAG "session.auto_start=off \n suhosin.session.cryptua=off"; fastcgi_param PHP_VALUE "memory_limit=768M \n max_execution_time=60"; fastcgi_read_timeout 60s; fastcgi_connect_timeout 60s; fastcgi_param MAGE_MODE $MAGE_MODE; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; }
ด้วยวิธีนี้ ให้บันทึกไฟล์แล้วเปิดใช้งานโดยพิมพ์คำสั่งต่อไปนี้:
ln -s /usr/local/etc/nginx/sites-available/magento2dev.conf \ /usr/local/etc/nginx/sites-enabled/magento2dev.conf sudo brew services restart nginx
วิธีการติดตั้ง Magento 2
ตกลง ณ จุดนี้เครื่องของคุณตรงตามข้อกำหนดของ Magento 2 ขาดเฉพาะตัวสัตว์ร้ายเท่านั้น ตรงไปที่เว็บไซต์ Magento และสร้างบัญชีหากคุณยังไม่มี หลังจากนั้นไปที่หน้าดาวน์โหลดและดาวน์โหลดเวอร์ชันล่าสุด (2.1.5 ในขณะที่เขียน):
เลือกรูปแบบ .tar.bz2 และดาวน์โหลด จากนั้นดำเนินการแตกไฟล์และตั้งค่าการอนุญาตโฟลเดอร์และไฟล์ที่ถูกต้องสำหรับ Magento 2 เพื่อให้สามารถทำงานได้:
mkdir ~/www/magento2dev cd ~/www/magento2dev tar -xjf ~/Downloads/Magento-CE-2.1.5-2017-02-20-05-39-14.tar.bz2 find var vendor pub/static pub/media app/etc -type f -exec chmod u+w {} \; find var vendor pub/static pub/media app/etc -type d -exec chmod u+w {} \; chmod u+x bin/magento
ในการติดตั้งตารางฐานข้อมูลและสร้างไฟล์การกำหนดค่าที่จำเป็น เราจะเรียกใช้คำสั่งนี้จากเทอร์มินัล:
./bin/magento setup:install --base-url=http://magento2.dev/ \ --db-host=127.0.0.1 --db-name=magento2 --db-user=root \ --db-password=123 --admin-firstname=Magento --admin-lastname=User \ [email protected] --admin-user=admin \ --admin-password=admin123 --language=en_US --currency=USD \ --timezone=America/Chicago --use-rewrites=1 --backend-frontname=admin
อย่าลืมเปลี่ยนชื่อฐานข้อมูล ( db-name
) ผู้ใช้ ( db-user
) และรหัสผ่าน ( db-password
) ให้ตรงกับที่คุณใช้ระหว่างการติดตั้ง MySQL เท่านั้น! คำสั่งนี้จะติดตั้งโมดูลทั้งหมดของ Magento 2 สร้างตารางที่จำเป็นและไฟล์การกำหนดค่า หลังจากเสร็จสิ้น เปิดเบราว์เซอร์ของคุณและไปที่ http://magento2.dev/ คุณควรเห็นการติดตั้ง Magento 2 ใหม่ทั้งหมดโดยใช้ธีม Luma ที่เป็นค่าเริ่มต้น:
หากคุณไปที่ http://magento2.dev/admin คุณจะเห็นหน้าเข้าสู่ระบบแอปพลิเคชันผู้ดูแลระบบ:
จากนั้นใช้ข้อมูลรับรองด้านล่างเพื่อเข้าสู่ระบบ:
ผู้ใช้: admin รหัสผ่าน: admin123
ในที่สุด เราก็พร้อมที่จะเริ่มเขียนโค้ดของเราแล้ว!
การสร้างโมดูล Magento 2 แรกของเรา
เพื่อให้โมดูลของเราสมบูรณ์ เราจะต้องสร้างไฟล์ต่อไปนี้ และฉันจะแนะนำคุณตลอดกระบวนการทั้งหมด เราต้องการ:
- ไฟล์การลงทะเบียนต้นแบบบางส่วน เพื่อให้ Magento รับรู้ถึงโมดูลบล็อกของเรา
- ไฟล์อินเทอร์เฟซหนึ่งไฟล์ เพื่อกำหนดสัญญาข้อมูลของเราสำหรับ Post
- Post Model เพื่อเป็นตัวแทนของ Post ในโค้ดของเรา โดยใช้อินเทอร์เฟซข้อมูล Post
- Post Resource Model เพื่อเชื่อมโยง Post Model กับฐานข้อมูล
- Post Collection เพื่อดึงข้อมูลหลายโพสต์พร้อมกันจากฐานข้อมูลด้วยความช่วยเหลือของ Resource Model
- คลาสการย้ายสองคลาส เพื่อตั้งค่าสคีมาตารางและเนื้อหา
- สองการกระทำ: หนึ่งรายการเพื่อแสดงรายการโพสต์ทั้งหมดและอีกรายการหนึ่งเพื่อแสดงแต่ละโพสต์เป็นรายบุคคล
- ไฟล์ Blocks, Views และ Layout แต่ละไฟล์สองไฟล์: ไฟล์ละหนึ่งไฟล์สำหรับการดำเนินการรายการ และอีกไฟล์หนึ่งสำหรับมุมมอง
อันดับแรก มาดูโครงสร้างโฟลเดอร์ซอร์สโค้ดหลักอย่างรวดเร็ว เพื่อให้เราสามารถกำหนดตำแหน่งที่จะวางโค้ดของเราได้ วิธีที่เราติดตั้งมีโค้ดหลัก Magento 2 ทั้งหมด พร้อมด้วยการพึ่งพาทั้งหมด ซึ่งอยู่ภายในโฟลเดอร์ vendor
ของผู้แต่ง
การลงทะเบียนโมดูลของเรา
เราจะเก็บรหัสของเราไว้ในโฟลเดอร์ที่แยกจากกัน app/code
ชื่อของโมดูลทั้งหมดอยู่ในรูปแบบ Namespace_ModuleName
และตำแหน่งบนระบบไฟล์ต้องสะท้อนถึงชื่อนั้น app/code/Namespace/ModuleName
สำหรับตัวอย่างนี้ ตามรูปแบบนั้น เราจะตั้งชื่อโมดูลของเรา Toptal_Blog
และวางไฟล์ของเราไว้ใต้ app/code/Toptal/Blog
ไปข้างหน้าและสร้างโครงสร้างโฟลเดอร์นั้น
ตอนนี้ เราต้องสร้างไฟล์ต้นแบบสองสามไฟล์เพื่อให้โมดูลของเราลงทะเบียนกับ Magento ขั้นแรก สร้าง app/code/Toptal/Blog/composer.json
:
{}
ไฟล์นี้จะโหลดโดย Composer ทุกครั้งที่คุณเรียกใช้ แม้ว่าเราจะไม่ได้ใช้ Composer กับโมดูลของเราจริงๆ แต่เราก็ต้องสร้างมันขึ้นมาเพื่อให้ Composer มีความสุข
ตอนนี้เราจะลงทะเบียนโมดูลของเรากับ Magento ไปข้างหน้าและสร้าง app/code/Toptal/Blog/registration.php
:
<?php \Magento\Framework\Component\ComponentRegistrar::register( \Magento\Framework\Component\ComponentRegistrar::MODULE, 'Toptal_Blog', __DIR__ );
ที่นี่ เรากำลังเรียกวิธีการ register
ของคลาส ComponentRegistrar
โดยส่งพารามิเตอร์สองตัว: สตริง 'module'
ซึ่งเป็นประเภทของส่วนประกอบที่เรากำลังลงทะเบียน และชื่อโมดูล 'Toptal_Blog'
เรา ด้วยข้อมูลดังกล่าว ตัวโหลดอัตโนมัติของ Magento จะรับรู้เนมสเปซของเราและจะรู้ว่าจะค้นหาคลาสและไฟล์ XML ของเราที่ใด
สิ่งที่น่าสนใจอย่างหนึ่งที่ควรสังเกตคือ เรามีประเภทของส่วนประกอบ ( MODULE
) ที่ส่งเป็นพารามิเตอร์ไปยังฟังก์ชัน \Magento\Framework\Component\ComponentRegistrar::register
ไม่เพียงแต่เราสามารถลงทะเบียนโมดูลเท่านั้น เราสามารถลงทะเบียนส่วนประกอบประเภทอื่นๆ ได้ ตัวอย่างเช่น ธีม ไลบรารีภายนอก และชุดภาษาได้รับการลงทะเบียนโดยใช้วิธีการเดียวกันนี้
ต่อไป ให้เราสร้างไฟล์การลงทะเบียนล่าสุด app/code/Toptal/Blog/etc/module.xml
:
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Module/etc/module.xsd"> <module name="Toptal_Blog" setup_version="0.1.0"> <sequence> <module name="Magento_Directory" /> <module name="Magento_Config" /> </sequence> </module> </config>
ไฟล์นี้เก็บข้อมูลที่สำคัญบางอย่างเกี่ยวกับโมดูลของเรา พวกเขาเป็น:
- ชื่อโมดูลปรากฏขึ้นอีกครั้ง โดยเปิดเผยชื่อโมดูลของเราต่อการกำหนดค่าวีโอไอพี
- เวอร์ชันการตั้งค่า Magento ซึ่ง Magento จะใช้เพื่อตัดสินใจว่าจะเรียกใช้สคริปต์การย้ายฐานข้อมูลเมื่อใด
- การพึ่งพาโมดูลของเรา—ในขณะที่เรากำลังเขียนโมดูลอย่างง่าย เราพึ่งพาโมดูลหลักสองโมดูลเท่านั้น:
Magento_Directory
และMagento_Config
ตอนนี้ เรามีโมดูลที่ Magento 2 ควรจะจดจำได้แล้ว มาตรวจสอบกันโดยใช้ Magento 2 CLI
ก่อนอื่น เราต้องปิดการใช้งานแคชของวีโอไอพี กลไกแคชของ Magento สมควรได้รับบทความที่อุทิศให้กับตัวเอง ในขณะนี้ ในขณะที่เรากำลังพัฒนาโมดูลและต้องการให้ Magento รับรู้การเปลี่ยนแปลงของเราทันทีโดยไม่จำเป็นต้องล้างแคชตลอดเวลา เราจะปิดการใช้งานมัน จากบรรทัดคำสั่ง ให้รัน:
./bin/magento cache:disable
มาดูกันว่า Magento ทราบถึงการแก้ไขของเราแล้วหรือไม่ โดยดูจากสถานะของโมดูล เพียงเรียกใช้คำสั่งต่อไปนี้:
./bin/magento module:status
ผลลัพธ์จากอันสุดท้ายควรคล้ายกับ:
โมดูลของเราอยู่ที่นั่น แต่ตามที่แสดงออกมา มันยังคงถูกปิดใช้งานอยู่ หากต้องการเปิดใช้งาน ให้เรียกใช้:
./bin/magento module:enable Toptal_Blog
ที่ควรจะได้ทำมัน เพื่อให้แน่ใจ คุณสามารถเรียก module:status
อีกครั้งและค้นหาชื่อโมดูลของเราในรายการที่เปิดใช้งาน:
การจัดการการจัดเก็บข้อมูล
ตอนนี้เราได้เปิดใช้งานโมดูลของเราแล้ว เราต้องสร้างตารางฐานข้อมูลที่เก็บโพสต์ในบล็อกของเรา นี่คือสคีมาสำหรับตารางที่เราต้องการสร้าง:
สนาม | พิมพ์ | โมฆะ | สำคัญ | ค่าเริ่มต้น |
---|---|---|---|---|
post_id | int(10) ไม่ได้ลงนาม | ไม่ | ปรี | โมฆะ |
ชื่อ | ข้อความ | ไม่ | โมฆะ | |
เนื้อหา | ข้อความ | ไม่ | โมฆะ | |
สร้าง_at | การประทับเวลา | ไม่ | CURRENT_TIMESTAMP |
เราบรรลุสิ่งนี้โดยการสร้างคลาส InstallSchema
ซึ่งรับผิดชอบการจัดการการติดตั้งการโยกย้ายสคีมาของเรา ไฟล์ตั้งอยู่ที่ app/code/Toptal/Blog/Setup/InstallSchema.php
และมีเนื้อหาดังต่อไปนี้:
<?php namespace Toptal\Blog\Setup; use \Magento\Framework\Setup\InstallSchemaInterface; use \Magento\Framework\Setup\ModuleContextInterface; use \Magento\Framework\Setup\SchemaSetupInterface; use \Magento\Framework\DB\Ddl\Table; /** * Class InstallSchema * * @package Toptal\Blog\Setup */ class InstallSchema implements InstallSchemaInterface { /** * Install Blog Posts table * * @param SchemaSetupInterface $setup * @param ModuleContextInterface $context */ public function install(SchemaSetupInterface $setup, ModuleContextInterface $context) { $setup->startSetup(); $tableName = $setup->getTable('toptal_blog_post'); if ($setup->getConnection()->isTableExists($tableName) != true) { $table = $setup->getConnection() ->newTable($tableName) ->addColumn( 'post_id', Table::TYPE_INTEGER, null, [ 'identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true ], 'ID' ) ->addColumn( 'title', Table::TYPE_TEXT, null, ['nullable' => false], 'Title' ) ->addColumn( 'content', Table::TYPE_TEXT, null, ['nullable' => false], 'Content' ) ->addColumn( 'created_at', Table::TYPE_TIMESTAMP, null, ['nullable' => false, 'default' => Table::TIMESTAMP_INIT], 'Created At' ) ->setComment('Toptal Blog - Posts'); $setup->getConnection()->createTable($table); } $setup->endSetup(); } }
หากคุณวิเคราะห์วิธีการ install
คุณจะสังเกตเห็นว่ามันสร้างตารางของเราและเพิ่มคอลัมน์ทีละรายการ

เพื่อกำหนดว่าจะเรียกใช้การโยกย้ายสคีมาเมื่อใด Magento จะเก็บตารางที่มีเวอร์ชันการตั้งค่าปัจจุบันทั้งหมดสำหรับแต่ละโมดูล และเมื่อใดก็ตามที่เวอร์ชันของโมดูลเปลี่ยนแปลง คลาสการย้ายข้อมูลจะถูกเริ่มต้น ตารางนี้คือ setup_module
และหากคุณดูเนื้อหาของตารางนั้น คุณจะเห็นว่ายังไม่มีการอ้างอิงถึงโมดูลของเราจนถึงตอนนี้ ดังนั้นให้เราเปลี่ยนสิ่งนั้น จากเทอร์มินัล ให้เรียกใช้คำสั่งต่อไปนี้:
./bin/magento setup:upgrade
ซึ่งจะแสดงรายการโมดูลทั้งหมดและสคริปต์การย้ายข้อมูลที่ได้รับการดำเนินการ รวมทั้งโมดูลของเรา:
จากไคลเอนต์ MySQL ที่คุณต้องการ คุณสามารถตรวจสอบว่าตารางถูกสร้างขึ้นจริงหรือไม่:
และที่ตาราง setup_module
ตอนนี้มีการอ้างอิงถึงโมดูล สคีมา และเวอร์ชันข้อมูลของเรา:
ตกลงแล้วการอัปเกรดสคีมาล่ะ มาเพิ่มโพสต์ในตารางนั้นผ่านการอัปเกรดเพื่อแสดงวิธีการทำเช่นนั้น ขั้นแรก ให้ชนไฟล์ setup_version
ในไฟล์ etc/module.xml
ของเรา:
ตอนนี้เราสร้างไฟล์ app/code/Toptal/Blog/Setup/UpgradeData.php
ซึ่งรับผิดชอบการย้ายข้อมูล (ไม่ใช่สคีมา):
<?php namespace Toptal\Blog\Setup; use \Magento\Framework\Setup\UpgradeDataInterface; use \Magento\Framework\Setup\ModuleContextInterface; use \Magento\Framework\Setup\ModuleDataSetupInterface; /** * Class UpgradeData * * @package Toptal\Blog\Setup */ class UpgradeData implements UpgradeDataInterface { /** * Creates sample blog posts * * @param ModuleDataSetupInterface $setup * @param ModuleContextInterface $context * @return void */ public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context) { $setup->startSetup(); if ($context->getVersion() && version_compare($context->getVersion(), '0.1.1') < 0 ) { $tableName = $setup->getTable('toptal_blog_post'); $data = [ [ 'title' => 'Post 1 Title', 'content' => 'Content of the first post.', ], [ 'title' => 'Post 2 Title', 'content' => 'Content of the second post.', ], ]; $setup ->getConnection() ->insertMultiple($tableName, $data); } $setup->endSetup(); } }
คุณจะเห็นว่ามันคล้ายกับคลาสการติดตั้งของเรามาก ความแตกต่างเพียงอย่างเดียวคือมันใช้ UpgradeDataInterface
แทน InstallSchemaInterface
และวิธีการหลักเรียกว่า upgrade
ด้วยวิธีนี้ คุณจะตรวจสอบเวอร์ชันที่ติดตั้งของโมดูลปัจจุบัน และเมื่อมีขนาดเล็กกว่าของคุณ ให้ดำเนินการเปลี่ยนแปลงที่จำเป็นเพื่อให้เสร็จสิ้น ในตัวอย่างของเรา เรากำลังตรวจสอบว่าเวอร์ชันปัจจุบันมีขนาดเล็กกว่า 0.1.1 ในบรรทัดต่อไปนี้โดยใช้ฟังก์ชัน version_compare
ไม่:
if ($context->getVersion() && version_compare($context->getVersion(), '0.1.1') < 0 ) {
การเรียก $context->getVersion()
จะคืนค่า 0.1.0 เมื่อมีการเรียกคำสั่ง setup:upgrade
CLI เป็นครั้งแรก จากนั้นข้อมูลตัวอย่างจะถูกโหลดไปยังฐานข้อมูล และเวอร์ชันของเราจะถูกกระแทกไปที่ 0.1.1 เพื่อให้ทำงานได้ ให้ดำเนินการ setup:upgrade
:
./bin/magento setup:upgrade
แล้วตรวจสอบผลลัพธ์ที่ตารางโพสต์:
และที่ตาราง setup_module
:
โปรดสังเกตว่า แม้ว่าเราจะเพิ่มข้อมูลลงในตารางของเราโดยใช้กระบวนการย้ายข้อมูล แต่ก็สามารถเปลี่ยนแปลงสคีมาได้เช่นกัน กระบวนการนี้เหมือนกัน คุณจะใช้ UpgradeSchemaInterface
แทน UpgradeDataInterface
เท่านั้น
การกำหนดแบบจำลองสำหรับโพสต์
ต่อไป ถ้าคุณจำภาพรวมสถาปัตยกรรมของเราได้ Building Block ถัดไปของเราคือ ResourceModel ของบล็อกโพสต์ Resource Model นั้นง่ายมาก และเพียงแค่ระบุตารางที่ Model จะ "เชื่อมต่อ" กับพร้อมกับว่าคีย์หลักคืออะไร เราจะสร้าง ResourceModel ของเราที่ app/code/Toptal/Blog/Model/ResourceModel/Post.php
โดยมีเนื้อหาดังนี้:
<?php namespace Toptal\Blog\Model\ResourceModel; use \Magento\Framework\Model\ResourceModel\Db\AbstractDb; class Post extends AbstractDb { /** * Post Abstract Resource Constructor * @return void */ protected function _construct() { $this->_init('toptal_blog_post', 'post_id'); } }
การดำเนินการ ResourceModel ทั้งหมด ยกเว้นว่าคุณต้องการสิ่งที่แตกต่างจากการดำเนินการ CRUD ปกติ จะได้รับการจัดการโดยคลาสพาเรนต์ของ AbstractDb
นอกจากนี้เรายังต้องการ ResourceModel อีกชุดหนึ่ง ซึ่งเป็นคอลเล็กชัน คอลเล็กชันจะรับผิดชอบในการสืบค้นฐานข้อมูลสำหรับโพสต์หลายรายการโดยใช้ ResourceModel ของเรา และส่งชุดของ Models กลับคืนมาซึ่งสร้างอินสแตนซ์และเติมข้อมูล เราสร้างไฟล์ app/code/Toptal/Blog/Model/ResourceModel/Post/Collection.php
ด้วยเนื้อหาต่อไปนี้:
<?php namespace Toptal\Blog\Model\ResourceModel\Post; use \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection; class Collection extends AbstractCollection { /** * Remittance File Collection Constructor * @return void */ protected function _construct() { $this->_init('Toptal\Blog\Model\Post', 'Toptal\Blog\Model\ResourceModel\Post'); } }
สังเกตว่าใน Constructor เราแค่พูดถึง Model ซึ่งจะเป็นตัวแทนของโพสต์ตลอดทั้งโค้ดของเรา และ ResourceModel ซึ่งจะดึงข้อมูลที่ฐานข้อมูล
ส่วนที่ขาดหายไปสำหรับเลเยอร์นี้คือ Post Model เอง โมเดลควรมีแอตทริบิวต์ทั้งหมดที่เรากำหนดไว้ในสคีมาของเรา พร้อมด้วยตรรกะทางธุรกิจใดๆ ที่คุณอาจต้องการ ตามรูปแบบของ Magento 2 เราจำเป็นต้องสร้าง Data Interface ที่โมเดลของเราจะขยายออกไป เราวางอินเทอร์เฟซที่ app/code/Toptal/Blog/Api/Data/PostInterface.php
และควรมีชื่อเขตข้อมูลของตารางพร้อมกับวิธีการเข้าถึง:
<?php namespace Toptal\Blog\Api\Data; interface PostInterface { /**#@+ * Constants for keys of data array. Identical to the name of the getter in snake case */ const POST_; const TITLE = 'title'; const CONTENT = 'content'; const CREATED_AT = 'created_at'; /**#@-*/ /** * Get Title * * @return string|null */ public function getTitle(); /** * Get Content * * @return string|null */ public function getContent(); /** * Get Created At * * @return string|null */ public function getCreatedAt(); /** * Get ID * * @return int|null */ public function getId(); /** * Set Title * * @param string $title * @return $this */ public function setTitle($title); /** * Set Content * * @param string $content * @return $this */ public function setContent($content); /** * Set Crated At * * @param int $createdAt * @return $this */ public function setCreatedAt($createdAt); /** * Set ID * * @param int $id * @return $this */ public function setId($id); }
ตอนนี้ถึงการใช้งานโมเดลที่ app/code/Toptal/Blog/Model/Post.php
เราจะสร้างวิธีการที่กำหนดไว้ที่อินเทอร์เฟซ นอกจากนี้ เราจะระบุแท็กแคชผ่านค่าคงที่ CACHE_TAG
และที่ตัวสร้าง เราจะระบุ ResourceModel ที่จะรับผิดชอบในการเข้าถึงฐานข้อมูลสำหรับโมเดลของเรา
<?php namespace Toptal\Blog\Model; use \Magento\Framework\Model\AbstractModel; use \Magento\Framework\DataObject\IdentityInterface; use \Toptal\Blog\Api\Data\PostInterface; /** * Class File * @package Toptal\Blog\Model * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Post extends AbstractModel implements PostInterface, IdentityInterface { /** * Cache tag */ const CACHE_TAG = 'toptal_blog_post'; /** * Post Initialization * @return void */ protected function _construct() { $this->_init('Toptal\Blog\Model\ResourceModel\Post'); } /** * Get Title * * @return string|null */ public function getTitle() { return $this->getData(self::TITLE); } /** * Get Content * * @return string|null */ public function getContent() { return $this->getData(self::CONTENT); } /** * Get Created At * * @return string|null */ public function getCreatedAt() { return $this->getData(self::CREATED_AT); } /** * Get ID * * @return int|null */ public function getId() { return $this->getData(self::POST_ID); } /** * Return identities * @return string[] */ public function getIdentities() { return [self::CACHE_TAG . '_' . $this->getId()]; } /** * Set Title * * @param string $title * @return $this */ public function setTitle($title) { return $this->setData(self::TITLE, $title); } /** * Set Content * * @param string $content * @return $this */ public function setContent($content) { return $this->setData(self::CONTENT, $content); } /** * Set Created At * * @param string $createdAt * @return $this */ public function setCreatedAt($createdAt) { return $this->setData(self::CREATED_AT, $createdAt); } /** * Set ID * * @param int $id * @return $this */ public function setId($id) { return $this->setData(self::POST_ID, $id); } }
Creating Views
Now we are moving one layer up, and will start the implementation of our ViewModel and Controller. To define a route in the front-end (shopping cart) application, we need to create the file app/code/Toptal/Blog/etc/frontend/routes.xml
with the following contents:
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd"> <router> <route frontName="blog"> <module name="Toptal_Blog"/> </route> </router> </config>
List of Posts at the Index Page
Here, we are basically telling Magento that our module, Toptal_Blog
, will be responsible for responding to routes under http://magento2.dev/blog (notice the frontName attribute of the route). Next up is the action, at app/code/Toptal/Blog/Controller/Index/Index.php
:
<?php namespace Toptal\Blog\Controller\Index; use \Magento\Framework\App\Action\Action; use \Magento\Framework\View\Result\PageFactory; use \Magento\Framework\View\Result\Page; use \Magento\Framework\App\Action\Context; use \Magento\Framework\Exception\LocalizedException; class Index extends Action { /** * @var PageFactory */ protected $resultPageFactory; /** * @param Context $context * @param PageFactory $resultPageFactory * * @codeCoverageIgnore * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( Context $context, PageFactory $resultPageFactory ) { parent::__construct( $context ); $this->resultPageFactory = $resultPageFactory; } /** * Prints the blog from informed order id * @return Page * @throws LocalizedException */ public function execute() { $resultPage = $this->resultPageFactory->create(); return $resultPage; } }
Our action is defining two methods. Let us take a closer look at them:
The constructor method simply sends the
$context
parameter to its parent method, and sets the$resultPageFactory
parameter to an attribute for later use. At this point it is useful to know the Dependency Injection design pattern, as that is what is happening here. In Magento 2's case we have automatic dependency injection. This means that whenever a class instantiation occurs, Magento will automatically try to instantiate all of the class constructor parameters (dependencies) and inject it for you as constructor parameters. It identifies which classes to instantiate for each parameter by inspecting the type hints, in this caseContext
andPageFactory
.The
execute
method is responsible for the action execution itself. In our case, we are simply telling Magento to render its layout by returning aMagento\Framework\View\Result\Page
object. This will trigger the layout rendering process, which we will create in a bit.
Now you should see a blank page at the url http://magento2.dev/blog/index/index. We still need to define the layout structure for that route, and its corresponding Block (our ViewModel) and the template file which will present the data to our user.
The layout structure for the front-end application is defined under view/frontend/layout
, and the file name must reflect our route. As our route is blog/index/index
, the layout file for that route will be app/code/Toptal/Blog/view/frontend/layout/blog_index_index.xml
:
<?xml version="1.0"?> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="2columns-left" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceContainer name="content"> <block class="Toptal\Blog\Block\Posts" name="posts.list" template="Toptal_Blog::post/list.phtml" /> </referenceContainer> </body> </page>
Here, we must define three very important structures in the Magento layout structure: Blocks, Containers, and Templates.
Blocks are the ViewModel part of our MVVM architecture, which was explained in earlier sections. They are the building blocks of our template structure.
Containers contain and output Blocks. They hold blocks together in nice hierarchical structures, and help in making things make sense when the layout for a page is being processed.
Templates are PHMTL (mixed HTML and PHP) files used by a special type of block in Magento. You can make calls to methods of a
$block
variable from within a template. The variable is always defined in the template context. You will be invoking your Block's methods by doing so, and thus allowing you to pull information from the ViewModel layer to the actual presentation.
With that extra information at hand, we can analyze the XML layout structure above. This layout structure is basically telling Magento that, when a request is made to the blog/index/index
route, a Block of the type Toptal\Blog\Block\Posts
is to be added to the content
container, and the template which will be used to render it is Toptal_blog::post/list.phtml
.
This leads us to the creation of our two remaining files. Our Block, located at app/code/Toptal/Blog/Block/Posts.php
:
<?php namespace Toptal\Blog\Block; use \Magento\Framework\View\Element\Template; use \Magento\Framework\View\Element\Template\Context; use \Toptal\Blog\Model\ResourceModel\Post\Collection as PostCollection; use \Toptal\Blog\Model\ResourceModel\Post\CollectionFactory as PostCollectionFactory; use \Toptal\Blog\Model\Post; class Posts extends Template { /** * CollectionFactory * @var null|CollectionFactory */ protected $_postCollectionFactory = null; /** * Constructor * * @param Context $context * @param PostCollectionFactory $postCollectionFactory * @param array $data */ public function __construct( Context $context, PostCollectionFactory $postCollectionFactory, array $data = [] ) { $this->_postCollectionFactory = $postCollectionFactory; parent::__construct($context, $data); } /** * @return Post[] */ public function getPosts() { /** @var PostCollection $postCollection */ $postCollection = $this->_postCollectionFactory->create(); $postCollection->addFieldToSelect('*')->load(); return $postCollection->getItems(); } /** * For a given post, returns its url * @param Post $post * @return string */ public function getPostUrl( Post $post ) { return '/blog/post/view/id/' . $post->getId(); } }
คลาสนี้ค่อนข้างเรียบง่าย และมีวัตถุประสงค์เพื่อโหลดโพสต์ที่จะแสดงเท่านั้น และจัดเตรียมเมธอด getPostUrl
ให้กับเทมเพลต มีบางสิ่งที่ต้องสังเกตแม้ว่า
หากคุณจำได้ เรายังไม่ได้กำหนด Toptal\Blog\Model\ResourceModel\Post\CollectionFactory
เรากำหนดเฉพาะ Toptal\Blog\Model\ResourceModel\Post\Collection
แล้วมันทำงานอย่างไร? สำหรับทุกคลาสที่คุณกำหนดในโมดูลของคุณ Magento 2 จะสร้าง Factory ให้คุณโดยอัตโนมัติ โรงงานมีสองวิธี: create
ซึ่งจะส่งคืนอินสแตนซ์ใหม่สำหรับการเรียกแต่ละครั้ง และ get
ซึ่งจะส่งคืนอินสแตนซ์เดียวกันทุกครั้งที่เรียกใช้—ใช้เพื่อนำรูปแบบซิงเกิลตันไปใช้
พารามิเตอร์ที่สามของ Block ของเรา $data
เป็นอาร์เรย์เสริม เนื่องจากเป็นอุปกรณ์เสริมและไม่มีคำใบ้จึงจะไม่ถูกฉีดโดยระบบฉีดอัตโนมัติ สิ่งสำคัญคือต้องสังเกตว่าพารามิเตอร์ ตัว สร้างที่เป็นทางเลือกต้อง อยู่ ในตำแหน่งสุดท้ายในพารามิเตอร์เสมอ ตัวอย่างเช่น ตัวสร้างของ Magento\Framework\View\Element\Template
ซึ่งเป็นคลาสหลักของเรา มีพารามิเตอร์เหล่านี้:
public function __construct( Template\Context $context, array $data = [] ) { ...
เนื่องจากเราต้องการเพิ่ม CollectionFactory
ของเราในพารามิเตอร์คอนสตรัคเตอร์หลังจากขยายคลาสเทมเพลต เราจึงต้องดำเนินการ ก่อน พารามิเตอร์ทางเลือก มิฉะนั้น การฉีดจะไม่ทำงาน:
public function __construct( Context $context, PostCollectionFactory $postCollectionFactory, array $data = [] ) { ...
ที่เมธอด getPosts
ซึ่งจะเข้าถึงได้ในภายหลังโดยเทมเพลตของเรา เราเพียงแค่เรียกวิธีการ create
จาก PostCollectionFactory
ซึ่งจะส่งคืน PostCollection
ใหม่ให้เรา และทำให้เราสามารถดึงโพสต์จากฐานข้อมูลและส่งไปยังคำตอบของเรา
และเพื่อให้การจัดวางเส้นทางนี้เสร็จสิ้น นี่คือเทมเพลต PHTML ของเรา app/code/Toptal/Blog/view/frontend/templates/post/list.phtml
:
<?php /** @var Toptal\Blog\Block\Posts $block */ ?> <h1>Toptal Posts</h1> <?php foreach($block->getPosts() as $post): ?> <?php /** @var Toptal\Blog\Model\Post */ ?> <h2><a href="<?php echo $block->getPostUrl($post);?>"><?php echo $post->getTitle(); ?></a></h2> <p><?php echo $post->getContent(); ?></p> <?php endforeach; ?>
โปรดสังเกตว่าที่นี่เราสามารถเห็นเลเยอร์ View ที่เข้าถึง ModelView ของเรา ( $block->getPosts()
) ซึ่งจะใช้ ResourceModel (คอลเลกชัน) เพื่อดึงโมเดลของเรา ( Toptal\Blog\Model\Post
) จากฐานข้อมูล ในทุกเทมเพลต เมื่อใดก็ตามที่คุณต้องการเข้าถึงเมธอดของบล็อก จะมีการกำหนดตัวแปร $block
และรอการเรียกของคุณ
ในตอนนี้ คุณควรจะสามารถเห็นรายการโพสต์ได้โดยเพียงแค่กดปุ่มเส้นทางของเราอีกครั้ง
กำลังดูโพสต์ส่วนบุคคล
ตอนนี้ หากคุณคลิกที่ชื่อโพสต์ คุณจะได้ 404 มาแก้ไขกัน เมื่อโครงสร้างทั้งหมดของเราเข้าที่ มันจึงค่อนข้างง่าย เราจะต้องสร้างสิ่งต่อไปนี้เท่านั้น:
- การดำเนินการใหม่ รับผิดชอบในการจัดการคำขอไปยัง
blog/post/view
เส้นทาง - บล็อกเพื่อแสดงโพสต์
- เทมเพลต PHTML ที่รับผิดชอบมุมมองเอง
- ไฟล์เลย์เอาต์สำหรับเส้นทางบล็อก/โพสต์/ดู โดยนำส่วนสุดท้ายเหล่านี้มารวมกัน
การดำเนินการใหม่ของเราค่อนข้างง่าย มันจะได้รับ id
พารามิเตอร์จากคำขอและลงทะเบียนที่ Magento core registry ซึ่งเป็นที่เก็บข้อมูลส่วนกลางสำหรับข้อมูลที่มีอยู่ในรอบคำขอเดียว การทำเช่นนี้จะทำให้ ID พร้อมใช้งานสำหรับบล็อกในภายหลัง ไฟล์ควรอยู่ที่ app/code/Toptal/Blog/Controller/Post/View.php
และนี่คือเนื้อหา:
<?php namespace Toptal\Blog\Controller\Post; use \Magento\Framework\App\Action\Action; use \Magento\Framework\View\Result\PageFactory; use \Magento\Framework\View\Result\Page; use \Magento\Framework\App\Action\Context; use \Magento\Framework\Exception\LocalizedException; use \Magento\Framework\Registry; class View extends Action { const REGISTRY_KEY_POST_; /** * Core registry * @var Registry */ protected $_coreRegistry; /** * @var PageFactory */ protected $_resultPageFactory; /** * @param Context $context * @param Registry $coreRegistry * @param PageFactory $resultPageFactory * * @codeCoverageIgnore * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( Context $context, Registry $coreRegistry, PageFactory $resultPageFactory ) { parent::__construct( $context ); $this->_coreRegistry = $coreRegistry; $this->_resultPageFactory = $resultPageFactory; } /** * Saves the blog id to the register and renders the page * @return Page * @throws LocalizedException */ public function execute() { $this->_coreRegistry->register(self::REGISTRY_KEY_POST_ID, (int) $this->_request->getParam('id')); $resultPage = $this->_resultPageFactory->create(); return $resultPage; } }
โปรดสังเกตว่าเราได้เพิ่มพารามิเตอร์ $coreRegistry
ลงใน __construct
ของเรา และบันทึกเป็นแอตทริบิวต์เพื่อใช้ในภายหลัง ที่วิธี execute
การ เราดึงพารามิเตอร์ id
จากคำขอและลงทะเบียน นอกจากนี้เรายังใช้ค่าคงที่คลาส self::REGISTRY_KEY_POST_ID
เป็นกุญแจสำคัญในการลงทะเบียนและเราจะใช้ค่าคงที่เดียวกันนี้ที่บล็อกของเราเพื่ออ้างถึง id ในรีจิสทรี
ให้เราสร้างบล็อกที่ app/code/Toptal/Blog/Block/View.php
โดยมีเนื้อหาดังต่อไปนี้:
<?php namespace Toptal\Blog\Block; use \Magento\Framework\Exception\LocalizedException; use \Magento\Framework\View\Element\Template; use \Magento\Framework\View\Element\Template\Context; use \Magento\Framework\Registry; use \Toptal\Blog\Model\Post; use \Toptal\Blog\Model\PostFactory; use \Toptal\Blog\Controller\Post\View as ViewAction; class View extends Template { /** * Core registry * @var Registry */ protected $_coreRegistry; /** * Post * @var null|Post */ protected $_post = null; /** * PostFactory * @var null|PostFactory */ protected $_postFactory = null; /** * Constructor * @param Context $context * @param Registry $coreRegistry * @param PostFactory $postCollectionFactory * @param array $data */ public function __construct( Context $context, Registry $coreRegistry, PostFactory $postFactory, array $data = [] ) { $this->_postFactory = $postFactory; $this->_coreRegistry = $coreRegistry; parent::__construct($context, $data); } /** * Lazy loads the requested post * @return Post * @throws LocalizedException */ public function getPost() { if ($this->_post === null) { /** @var Post $post */ $post = $this->_postFactory->create(); $post->load($this->_getPostId()); if (!$post->getId()) { throw new LocalizedException(__('Post not found')); } $this->_post = $post; } return $this->_post; } /** * Retrieves the post id from the registry * @return int */ protected function _getPostId() { return (int) $this->_coreRegistry->registry( ViewAction::REGISTRY_KEY_POST_ID ); } }
ที่บล็อกการดู เรากำหนดวิธีการที่ได้รับการป้องกัน _getPostId
ซึ่งจะดึงข้อมูล ID โพสต์จากรีจิสตรีหลัก เมธอด getPost
สาธารณะจะทำให้ขี้เกียจโหลดโพสต์และโยนข้อยกเว้นหากไม่มีโพสต์ การแสดงข้อยกเว้นที่นี่จะทำให้ Magento แสดงหน้าจอข้อผิดพลาดเริ่มต้น ซึ่งอาจไม่ใช่ทางออกที่ดีที่สุดในกรณีเช่นนี้ แต่เราจะเก็บวิธีนี้ไว้เพื่อความง่าย
ไปยังเทมเพลต PHTML ของเรา เพิ่ม app/code/Toptal/Blog/view/frontend/templates/post/view.phtml
ด้วยเนื้อหาต่อไปนี้:
<?php /** @var Toptal\Blog\Block\View $block */ ?> <h1><?php echo $block->getPost()->getTitle(); ?></h1> <p><?php echo $block->getPost()->getContent(); ?></p>
ดีและเรียบง่ายเพียงแค่เข้าถึงวิธีดูบล็อก getPost
ที่เราสร้างไว้ก่อนหน้านี้
และเพื่อนำมารวมกัน เราสร้างไฟล์เลย์เอาต์สำหรับเส้นทางใหม่ของเราที่ app/code/Toptal/Blog/view/frontend/layout/blog_post_view.xml
พร้อมเนื้อหาต่อไปนี้:
<?xml version="1.0"?> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="2columns-left" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceContainer name="content"> <block class="Toptal\Blog\Block\View" name="post.view" template="Toptal_Blog::post/view.phtml" /> </referenceContainer> </body> </page>
สิ่งนี้ทำในสิ่งเดียวกันกับที่เราทำมาก่อน มันเพียงเพิ่ม Toptal\Blog\Block\View
ไปยังคอนเทนเนอร์ content
โดยมี Toptal_Blog::post/view.phtml
เป็นเทมเพลตที่เกี่ยวข้อง
หากต้องการดูการใช้งานจริง เพียงกำหนดเบราว์เซอร์ของคุณไปที่ http://magento2.dev/blog/post/view/id/1 เพื่อโหลดโพสต์ให้สำเร็จ คุณควรเห็นหน้าจอดังตัวอย่างด้านล่าง:
และอย่างที่คุณเห็น หลังจากที่สร้างโครงสร้างเริ่มต้นของเรา การเพิ่มคุณสมบัติให้กับแพลตฟอร์มนั้นง่ายมาก และโค้ดเริ่มต้นส่วนใหญ่ของเราถูกนำมาใช้ซ้ำในกระบวนการ
ในกรณีที่คุณต้องการทดสอบโมดูลอย่างรวดเร็ว นี่คือผลงานทั้งหมดของเรา
จะไปจากที่นี่ที่ไหน
หากคุณติดตามฉันมาจนถึงที่นี่ ยินดีด้วย! ฉันมั่นใจว่าคุณใกล้จะเป็นนักพัฒนา Magento 2 แล้ว เราได้พัฒนาโมดูลที่กำหนดเอง Magento 2 ที่ค่อนข้างล้ำหน้า และถึงแม้ว่ามันจะเรียบง่ายในฟีเจอร์ของมัน แต่ก็ยังครอบคลุมพื้นที่มากมาย
มีบางสิ่งที่ละทิ้งจากบทความนี้เพื่อความเรียบง่าย หากต้องการชื่อไม่กี่:
- ผู้ดูแลระบบแก้ไขแบบฟอร์มและกริดเพื่อจัดการเนื้อหาบล็อกของเรา
- หมวดหมู่บล็อก แท็ก และความคิดเห็น
- ที่เก็บข้อมูลและสัญญาบริการสองสามฉบับที่เราสร้างขึ้นได้
- บรรจุโมดูลเป็นส่วนขยาย Magento 2
ต่อไปนี้คือลิงก์ที่มีประโยชน์ซึ่งคุณสามารถเพิ่มพูนความรู้ของคุณให้ลึกซึ้งยิ่งขึ้นไม่ว่าในกรณีใด:
- บล็อก Alan Storm บน Magento 2 — Alan Storm อาจมีเนื้อหาเกี่ยวกับการสอนมากที่สุดเมื่อต้องการเรียนรู้ Magento
- บล็อกของ Alan Kent
- เอกสาร Magento: Magento 2 Dev Docs
ฉันได้ให้คำแนะนำอย่างครอบคลุมเกี่ยวกับแง่มุมที่เกี่ยวข้องทั้งหมดเกี่ยวกับวิธีสร้างโมดูลใน Magento 2 และทรัพยากรเพิ่มเติมบางส่วนที่คุณต้องการ ตอนนี้ก็ขึ้นอยู่กับคุณแล้วที่จะเขียนโค้ด หรือมุ่งไปที่ความคิดเห็นหากต้องการแสดงความคิดเห็น