Magento 2 튜토리얼: 완전한 모듈을 구축하는 방법

게시 됨: 2022-03-11

Magento는 현재 세계에서 가장 큰 오픈 소스 전자 상거래 플랫폼입니다. 기능이 풍부하고 확장 가능한 코드 기반으로 인해 전 세계의 크고 작은 작업을 수행하는 판매자가 다양한 프로젝트에 사용하고 있습니다.

Magento 1은 출시된 지 8년이 되었고 후속 버전인 Magento 2가 2015년 말에 출시되어 다음과 같은 이전 버전의 약점을 개선했습니다.

  • 향상된 성능
  • 공식 자동화 테스트 제품군
  • 향상된 백엔드 UI
  • 새롭고 보다 현대적인 프론트엔드 코드베이스
  • 여기저기 흩어져 있는 대신 Magento 코드 내부에 포함된 파일을 사용하여 모듈을 개발하는 보다 모듈화된 방법
  • 동일한 기능을 사용자 정의하려는 모듈 간의 충돌 감소

양식화된 Magento 2 로고

앞으로 1년 조금 더 지나면 언급된 모든 문제가 완전히 해결된 것은 아니지만 개선이 눈에 보입니다. 이제 Magento 2가 이전 버전보다 훨씬 더 강력한 소프트웨어라고 말할 수 있습니다. Magento 2의 개선 사항 중 일부는 다음과 같습니다.

  • 사용자 지정 모듈용으로 생성하는 공식 및 문서화된 방법을 포함한 단위 및 통합 테스트
  • 모든 파일이 하나의 단일 디렉토리에 배치되는 실제로 모듈화된 모듈
  • 테마 개발자가 n-레벨 템플릿 계층 구조를 생성할 수 있도록 하는 보다 풍부한 템플릿 시스템
  • 코드 전체에 걸쳐 채택된 일련의 유용한 디자인 패턴은 코드 품질을 개선하고 모듈에서 생성되는 오류 가능성을 줄입니다. 여기에는 몇 가지 예를 들면 자동 종속성 주입, 서비스 계약, 리포지토리 및 팩토리가 포함됩니다.
  • 세션 및 캐시 처리를 위한 Redis뿐만 아니라 전체 페이지 캐싱 시스템으로서 Varnish에 기본 통합
  • PHP 7 지원

이러한 모든 변경 사항으로 Magento 2의 학습 곡선은 더욱 가파르게 되었습니다. 이 가이드에서는 첫 번째 Magento 2 모듈을 개발하는 방법을 보여주고 연구를 계속할 수 있는 올바른 방향을 제시하고자 합니다. 가자!

Magento 2 튜토리얼 전제 조건

이 문서의 나머지 부분을 따르려면 다음 기술/개념을 잘 이해하는 것이 중요합니다.

  • 객체 지향 프로그래밍(OOP)
  • PHP
  • 네임스페이스
  • MySQL
  • 기본 배쉬 사용법

위의 모든 것 중에서 OOP가 아마도 가장 중요한 것일 것입니다. Magento는 처음에 경험 많은 Java 개발자 팀에 의해 만들어졌으며 그들의 유산은 코드베이스 전체에서 확실히 볼 수 있습니다. OOP 기술에 대해 자신이 없는 경우 플랫폼 작업을 시작하기 전에 OOP 기술을 검토하는 것이 좋습니다.

Magento 2의 아키텍처 개요

Magento의 아키텍처는 소스 코드를 가능한 한 모듈화하고 확장할 수 있도록 설계되었습니다. 이 접근 방식의 최종 목표는 각 프로젝트의 요구 사항에 따라 쉽게 조정하고 사용자 지정할 수 있도록 하는 것입니다.

사용자 정의는 일반적으로 플랫폼 코드의 동작을 변경하는 것을 의미합니다. 대부분의 시스템에서 이는 "핵심" 코드를 변경하는 것을 의미합니다. Magento에서 모범 사례를 따르고 있다면 이는 대부분의 시간을 피할 수 있는 일이므로 매장에서 안정적인 방식으로 최신 보안 패치 및 기능 릴리스를 최신 상태로 유지할 수 있습니다.

Magento 2는 MVVM( Model View ViewModel ) 시스템입니다. 형제 MVC(모델 보기 컨트롤러)와 밀접하게 관련되어 있지만 MVVM 아키텍처는 모델과 보기 계층 간에 보다 강력한 분리를 제공합니다. 다음은 MVVM 시스템의 각 계층에 대한 설명입니다.

  • 모델 은 응용 프로그램의 비즈니스 논리 를 보유하고 데이터베이스 액세스를 위해 연결된 클래스(ResourceModel)에 종속됩니다. 모델은 서비스 계약 에 의존하여 기능을 애플리케이션의 다른 계층에 노출합니다.
  • 보기 는 사용자가 화면에서 보는 것, 즉 실제 HTML의 구조와 레이아웃입니다. 이것은 모듈과 함께 배포되는 PHTML 파일에서 달성됩니다. PHTML 파일은 MVVM 언어에서 바인더 라고 하는 레이아웃 XML 파일 의 각 ViewModel에 연결됩니다. 레이아웃 파일은 최종 페이지에서 사용할 JavaScript 파일을 할당할 수도 있습니다.
  • ViewModel 은 Model 레이어와 상호 작용하여 View 레이어에 필요한 정보만 노출합니다. Magento 2에서 이것은 모듈의 Block 클래스에 의해 처리됩니다. 이것은 일반적으로 MVC 시스템의 컨트롤러 역할의 일부였습니다. MVVM에서 컨트롤러는 사용자 흐름 처리만 담당합니다. 즉, 요청을 수신하고 시스템에 보기를 렌더링하거나 사용자를 다른 경로로 리디렉션하도록 지시합니다.

Magento 2 모듈은 위에서 설명한 아키텍처의 전부는 아니지만 일부 요소로 구성됩니다. 전체 아키텍처는 아래에 설명되어 있습니다(출처).

전체 Magento 2 아키텍처 다이어그램

Magento 2 모듈은 PHP의 종속성 관리자인 Composer를 사용하여 외부 종속성을 차례로 정의할 수 있습니다. 위의 다이어그램에서 Magento 2 핵심 모듈은 Zend Framework, Symfony 및 기타 타사 라이브러리에 의존한다는 것을 알 수 있습니다.

다음은 페이지 및 정적 블록 생성을 처리하는 Magento 2 코어 모듈인 Magento/Cms의 구조입니다.

Magento/Cms 모듈의 디렉토리 레이아웃

각 폴더에는 다음과 같이 아키텍처의 한 부분이 있습니다.

  • API: 서비스 계약, 서비스 인터페이스 및 데이터 인터페이스 정의
  • 블록: MVVM 아키텍처의 ViewModels
  • 컨트롤러: 시스템과 상호 작용하는 동안 사용자의 흐름을 처리하는 컨트롤러
  • 등: 구성 XML 파일 - 모듈은 이 폴더 내에서 자체 및 해당 부분(경로, 모델, 블록, 관찰자 ​​및 크론 작업)을 정의합니다. etc 파일은 비핵심 모듈에서 핵심 모듈의 기능을 재정의하는 데 사용할 수도 있습니다.
  • 도우미: 둘 이상의 응용 프로그램 계층에서 사용되는 코드를 보유하는 도우미 클래스입니다. 예를 들어 Cms 모듈에서 도우미 클래스는 브라우저에 표시할 HTML을 준비하는 역할을 합니다.
  • i18n: 번역에 사용되는 국제화 CSV 파일 보유
  • 모델: 모델 및 ResourceModel의 경우
  • 옵저버: 시스템 이벤트를 "관찰"하는 옵저버 또는 모델을 보유합니다. 일반적으로 이러한 이벤트가 발생하면 관찰자는 모델을 인스턴스화하여 해당 이벤트에 필요한 비즈니스 로직을 처리합니다.
  • 설정: 스키마 및 데이터 생성을 담당하는 마이그레이션 클래스
  • 테스트: 단위 테스트
  • Ui: 관리 애플리케이션에서 사용되는 그리드 및 양식과 같은 UI 요소
  • 보기: 프론트 엔드 및 관리 애플리케이션을 위한 레이아웃(XML) 파일 및 템플릿(PHTML) 파일

실제로 Magento 2의 모든 내부 작동이 모듈 내부에 있다는 사실도 흥미롭습니다. 위 이미지에서 예를 들어 체크아웃 프로세스를 담당하는 Magento_Catalog 과 제품 및 카테고리 처리를 담당하는 Magento_Checkout 를 볼 수 있습니다. 기본적으로 이것이 우리에게 말해주는 것은 모듈 작업 방법을 배우는 것이 Magento 2 개발자가 되는 데 가장 중요한 부분이라는 것입니다.

좋습니다. 시스템 아키텍처와 모듈 구조에 대해 비교적 간략하게 소개한 후에 좀 더 구체적으로 해 볼까요? 다음으로, Magento 2에 익숙해지고 Magento 2 개발자가 될 수 있도록 전통적인 웹로그 튜토리얼을 살펴보겠습니다. 그 전에 개발 환경을 설정해야 합니다. 가자!

Magento 2 모듈 개발 환경 설정

이 글을 쓰는 시점에서 우리는 Magento 2 Docker 컨테이너인 공식 Magento 2 DevBox를 사용할 수 있었습니다. macOS의 Docker는 최소한 Magento 2와 같은 빠른 디스크 I/O에 크게 의존하는 시스템에서는 여전히 사용할 수 없다고 생각하는 것입니다. 따라서 우리는 전통적인 방식으로 할 것입니다. 모든 패키지를 기본적으로 자체 머신에 설치합니다.

서버 설정

모든 것을 설치하는 것은 확실히 좀 더 지루하지만 최종 결과는 번개처럼 빠른 Magento 개발 환경이 될 것입니다. 나를 믿으십시오. Magento 2 개발을 위해 Docker에 의존하지 않음으로써 작업 시간을 절약할 수 있습니다.

이 튜토리얼은 Brew가 설치된 macOS 환경을 가정합니다. 그렇지 않은 경우 기본 사항은 그대로 유지되며 패키지를 설치하는 방식만 변경됩니다. 모든 패키지 설치부터 시작하겠습니다.

 brew install mysql nginxb php70 php70-imagick php70-intl php70-mcrypt

그런 다음 서비스를 시작합니다.

 brew services start mysql brew services start php70 sudo brew services start nginx

자, 이제 도메인을 루프백 주소로 지정하겠습니다. 아무 편집기에서나 호스트 파일을 열지만 수퍼유저 권한이 있는지 확인하십시오. Vim으로 그렇게 하면 다음과 같습니다.

 sudo vim /etc/hosts

그런 다음 다음 줄을 추가합니다.

 127.0.0.1 magento2.dev

이제 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;

이 변수를 설정한 후 가상 호스트 루트 경로를 정의합니다. $MAGE_ROOT 변수에 /pub 폴더를 접미사로 지정하여 스토어의 일부만 웹에서 사용할 수 있도록 합니다.

 root $MAGE_ROOT/pub;

그런 다음 인덱스 파일(요청된 파일이 존재하지 않을 때 nginx가 로드할 파일)을 index.php로 정의합니다. $MAGE_ROOT/pub/index.php 스크립트는 장바구니와 관리 애플리케이션을 모두 방문하는 고객을 위한 기본 진입점입니다. 요청된 URL에 관계없이 index.php가 로드되고 라우터 디스패치 프로세스가 시작됩니다.

 index index.php;

다음으로 일부 Nginx 기능을 끕니다. 먼저, 폴더를 요청할 때 파일 목록을 표시하지만 파일을 지정하지 않고 색인이 없는 autoindex 를 끕니다. 둘째, Nginx가 자동으로 응답에 Charset 헤더를 추가할 수 있도록 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)을 다운로드합니다.

Magento 2 다운로드 페이지

.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/로 이동합니다. 기본 Luma 테마로 Magento 2 새로 설치가 표시되어야 합니다.

기본 Luma 테마의 홈 페이지

http://magento2.dev/admin으로 이동하면 관리자 애플리케이션 로그인 페이지가 표시되어야 합니다.

관리자 애플리케이션 로그인 페이지

그런 다음 아래 자격 증명을 사용하여 로그인합니다.

사용자: admin 비밀번호: admin123

드디어 코드 작성을 시작할 준비가 되었습니다!

첫 Magento 2 모듈 만들기

모듈을 완료하려면 다음 파일을 만들어야 하며 전체 프로세스를 안내해 드리겠습니다. 다음이 필요합니다.

  • Magento가 블로그 모듈을 인식하도록 하는 몇 가지 상용구 등록 파일
  • Post에 대한 데이터 계약을 정의하기 위한 하나의 인터페이스 파일
  • Post 데이터 인터페이스를 구현하는 코드 전체에서 Post를 나타내는 Post 모델
  • 포스트 모델을 데이터베이스에 연결하기 위한 포스트 리소스 모델
  • 리소스 모델의 도움으로 데이터베이스에서 한 번에 여러 게시물을 검색하는 게시물 컬렉션
  • 테이블 스키마 및 콘텐츠를 설정하기 위한 두 가지 마이그레이션 클래스
  • 두 가지 작업: 모든 게시물을 나열하는 작업과 각 게시물을 개별적으로 표시하는 작업
  • Blocks, Views 및 Layout 파일 각각 2개: 목록 작업에 대해 각각 하나씩, 보기에 대해 각각 하나씩

먼저 핵심 소스 코드 폴더 구조를 간단히 살펴보고 코드를 배치할 위치를 정의할 수 있습니다. 우리가 설치한 방식에는 모든 Magento 2 핵심 코드와 모든 종속성이 있으며 작곡가의 vendor 폴더 안에 있습니다.

Magento 2 코어 코드의 디렉토리 레이아웃

모듈 등록

코드를 별도의 폴더인 app/code 에 보관할 것입니다. 모든 모듈의 이름은 Namespace_ModuleName 형식이며 파일 시스템의 해당 위치는 이 예제의 경우 해당 이름( app/code/Namespace/ModuleName )을 반영해야 합니다. 이 패턴에 따라 모듈 이름을 Toptal_Blog 로 지정하고 파일을 app/code/Toptal/Blog 아래에 배치합니다. 계속해서 해당 폴더 구조를 만드십시오.

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__ );

여기에서 ComponentRegistrar 클래스의 register 메소드를 호출하여 두 개의 매개변수를 전송합니다. 등록하려는 구성요소의 유형인 문자열 'module' 과 모듈 이름 'Toptal_Blog' . 이 정보를 사용하여 Magento의 자동 로더는 우리의 네임스페이스를 인식하고 클래스 및 XML 파일을 찾을 위치를 알게 됩니다.

여기서 주목해야 할 흥미로운 점은 \Magento\Framework\Component\ComponentRegistrar::register 함수에 매개변수로 전송되는 구성요소 유형( MODULE )이 있다는 것입니다. 모듈을 등록할 수 있을 뿐만 아니라 다른 종류의 구성 요소도 등록할 수 있습니다. 예를 들어 테마, 외부 라이브러리 및 언어 팩도 동일한 방법으로 등록됩니다.

계속해서 마지막 등록 파일인 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 설정 버전입니다.
  • 모듈의 종속성 - 간단한 모듈을 작성하면서 Magento_DirectoryMagento_Config 라는 두 가지 Magento 핵심 모듈에만 의존합니다.

이제 Magento 2에서 인식할 수 있는 모듈이 생겼습니다. Magento 2 CLI를 사용하여 확인하겠습니다.

먼저 Magento의 캐시를 비활성화해야 합니다. Magento의 캐시 메커니즘은 자체 전용 기사가 필요합니다. 당분간은 모듈을 개발 중이고 항상 캐시를 지울 필요 없이 Magento에서 변경 사항을 즉시 인식하기를 원하므로 간단히 비활성화합니다. 명령줄에서 다음을 실행합니다.

 ./bin/magento cache:disable

그런 다음 Magento가 모듈의 상태를 보고 수정 사항을 이미 알고 있는지 봅시다. 다음 명령을 실행하기만 하면 됩니다.

 ./bin/magento module:status

마지막 결과는 다음과 유사해야 합니다.

Toptal_Blog 모듈이 비활성화되어 있음을 보여주는 status 명령의 출력

우리 모듈이 존재하지만 출력에서 ​​볼 수 있듯이 여전히 비활성화되어 있습니다. 활성화하려면 다음을 실행하십시오.

 ./bin/magento module:enable Toptal_Blog

그렇게 했어야 했다. 확실히 하기 위해 module:status 다시 호출하고 활성화된 목록에서 모듈의 이름을 찾을 수 있습니다.

활성화된 Toptal_Blog 모듈을 보여주는 status 명령의 출력

데이터 저장 처리

이제 모듈을 활성화했으므로 블로그 게시물을 보관하는 데이터베이스 테이블을 만들어야 합니다. 생성하려는 테이블의 스키마는 다음과 같습니다.

유형 없는 열쇠 기본
post_id int(10) 부호 없는 아니요 PRI 없는
제목 텍스트 아니요 없는
콘텐츠 텍스트 아니요 없는
만든_에 타임스탬프 아니요 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 클라이언트에서 테이블이 실제로 생성되었는지 확인할 수 있습니다.

MySQL 클라이언트에서 테이블 시연

그리고 setup_module 테이블에는 이제 모듈, 해당 스키마 및 데이터 버전에 대한 참조가 있습니다.

setup_module 테이블의 내용

좋습니다. 스키마 업그레이드는 어떻습니까? 그 방법을 보여주기 위해 업그레이드를 통해 해당 테이블에 몇 가지 게시물을 추가해 보겠습니다. 먼저 etc/module.xml 파일에서 setup_version 을 범프하십시오.

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(); } }

Install 클래스와 매우 유사함을 알 수 있습니다. 유일한 차이점은 InstallSchemaInterface 대신 UpgradeDataInterface 를 구현하고 주요 메서드를 upgrade 라고 한다는 것입니다. 이 방법을 사용하면 현재 모듈의 설치된 버전을 확인하고 사용자 버전보다 작은 경우 완료하는 데 필요한 변경 사항을 실행합니다. 이 예에서는 version_compare 함수를 사용하여 다음 줄에서 현재 버전이 0.1.1보다 작은지 확인합니다.

 if ($context->getVersion() && version_compare($context->getVersion(), '0.1.1') < 0 ) {

$context->getVersion() 호출은 setup:upgrade CLI 명령이 처음 호출될 때 0.1.0을 반환합니다. 그런 다음 샘플 데이터가 데이터베이스에 로드되고 버전이 0.1.1로 변경됩니다. 이것을 실행하려면 setup:upgrade 를 실행하십시오.

 ./bin/magento setup:upgrade

그런 다음 posts 테이블에서 결과를 확인하십시오.

우리 테이블의 내용

그리고 setup_module 테이블에서:

setup_module 테이블의 업데이트된 내용

마이그레이션 프로세스를 사용하여 테이블에 데이터를 추가했지만 스키마도 변경할 수 있었습니다. 프로세스는 동일합니다. UpgradeSchemaInterface 대신 UpgradeDataInterface 만 사용합니다.

게시물에 대한 모델 정의

계속해서 아키텍처 개요를 기억한다면 다음 빌딩 블록은 블로그 게시물 ResourceModel이 될 것입니다. 자원 모델은 매우 간단하며 기본 키가 무엇인지와 함께 모델이 "연결"할 테이블을 간단히 설명합니다. 다음 내용으로 app/code/Toptal/Blog/Model/ResourceModel/Post.php 에서 ResourceModel을 생성합니다.

 <?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'); } }

일반적인 CRUD 작업과 다른 것이 필요한 경우를 제외하고 모든 ResourceModel 작업은 AbstractDb 상위 클래스에서 처리됩니다.

또 다른 ResourceModel인 Collection도 필요합니다. Collection은 ResourceModel을 사용하여 데이터베이스에 여러 게시물을 쿼리하고 인스턴스화되고 정보로 채워진 일련의 모델을 다시 전달하는 역할을 합니다. 다음 내용 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'); } }

생성자에서 코드 전체에 걸쳐 게시물 엔터티를 나타내는 Model과 데이터베이스에서 정보를 가져올 ResourceModel을 간단히 언급합니다.

이 레이어에서 누락된 부분은 포스트 모델 자체입니다. 모델은 필요한 비즈니스 로직과 함께 스키마에 정의한 모든 속성을 보유해야 합니다. Magento 2의 패턴에 따라 모델이 확장될 데이터 인터페이스를 생성해야 합니다. 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); } }

보기 만들기

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 case Context and PageFactory .

  • The execute method is responsible for the action execution itself. In our case, we are simply telling Magento to render its layout by returning a Magento\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는 자동으로 공장을 생성합니다. 팩토리에는 두 가지 메서드가 있습니다. 각 호출에 대해 새 인스턴스를 반환하는 create 와 호출될 때마다 항상 동일한 인스턴스를 반환하는 get - Singleton 패턴을 구현하는 데 사용됩니다.

Block의 세 번째 매개변수인 $data 는 선택적 배열입니다. 선택사항이며 타입 힌트가 없기 때문에 자동 주입 시스템에서 주입되지 않습니다. 선택적 생성자 매개변수는 항상 매개변수의 마지막에 위치해야 합니다. 예를 들어, 부모 클래스인 Magento\Framework\View\Element\Template 의 생성자는 다음 매개변수를 갖습니다.

 public function __construct( Template\Context $context, array $data = [] ) { ...

Template 클래스를 확장한 후 생성자 매개변수에 CollectionFactory 를 추가하고 싶었기 때문에 선택적 매개변수 보다 먼저 추가해야 했습니다. 그렇지 않으면 주입이 작동하지 않습니다.

 public function __construct( Context $context, PostCollectionFactory $postCollectionFactory, array $data = [] ) { ...

나중에 템플릿에서 액세스할 getPosts 메서드에서 PostCollectionFactory 에서 create 메서드를 호출하기만 하면 새로운 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; ?>

여기에서 ModelView( $block->getPosts() )에 액세스하는 View 레이어를 볼 수 있으며, 이 레이어는 ResourceModel(컬렉션)을 사용하여 데이터베이스에서 모델( Toptal\Blog\Model\Post )을 가져옵니다. 모든 템플릿에서 블록의 메서드에 액세스하려고 할 때마다 $block 변수가 정의되고 호출을 기다리고 있습니다.

이제 경로를 다시 누르기만 하면 게시물 목록을 볼 수 있습니다.

게시물 목록을 보여주는 색인 페이지

개별 게시물 보기

이제 게시물 제목을 클릭하면 404가 나오므로 수정해 보겠습니다. 모든 구조가 제자리에 있으면 이것은 매우 간단해집니다. 다음을 생성하기만 하면 됩니다.

  • blog/post/view 경로에 대한 요청 처리를 담당하는 새로운 작업
  • 게시물을 렌더링하는 블록
  • 보기 자체를 담당하는 PHTML 템플릿
  • 블로그/게시물/보기 경로에 대한 레이아웃 파일로, 이 마지막 부분을 함께 묶습니다.

우리의 새로운 행동은 아주 간단합니다. 요청에서 매개변수 id 를 수신하고 단일 요청 주기 동안 사용할 수 있는 정보의 중앙 저장소인 Magento 코어 레지스트리에 등록하기만 하면 됩니다. 이렇게 하면 나중에 블록에서 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>

멋지고 간단합니다. 이전에 만든 View 블록 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\Viewcontent 컨테이너에 추가하고 Toptal_Blog::post/view.phtml 을 연결된 템플릿으로 사용합니다.

작동하는 모습을 보려면 브라우저에서 http://magento2.dev/blog/post/view/id/1로 이동하여 게시물을 성공적으로 로드하세요. 아래와 같은 화면이 표시되어야 합니다.

개별 게시물을 표시하는 페이지

그리고 보시다시피 초기 구조를 만든 후 플랫폼에 기능을 추가하는 것은 정말 간단하며 초기 코드의 대부분은 프로세스에서 재사용됩니다.

모듈을 빠르게 테스트하고 싶다면 다음은 우리 작업의 전체 결과입니다.

여기에서 갈 곳

여기까지 팔로우하셨다면 축하드립니다! 나는 당신이 Magento 2 개발자가 되기에 아주 가깝다고 확신합니다. 우리는 꽤 발전된 Magento 2 사용자 정의 모듈을 개발했으며 기능은 간단하지만 많은 부분을 커버했습니다.

이 글에서는 편의를 위해 일부 내용을 생략했습니다. 몇 가지 예를 들면 다음과 같습니다.

  • 관리자는 블로그 콘텐츠를 관리하기 위해 양식 및 그리드를 편집합니다.
  • 블로그 카테고리, 태그 및 댓글
  • 우리가 설정할 수 있었던 저장소 및 몇 가지 서비스 계약
  • Magento 2 확장으로 모듈 패키징

어쨌든 다음은 지식을 더욱 심화할 수 있는 몇 가지 유용한 링크입니다.

  • Magento 2의 Alan Storm 블로그 — Alan Storm은 아마도 Magento 학습에 관한 가장 교훈적인 콘텐츠를 가지고 있을 것입니다.
  • 앨런 켄트의 블로그
  • Magento 문서: Magento 2 Dev Docs

Magento 2에서 모듈을 생성하는 방법의 모든 관련 측면에 대한 포괄적인 소개와 필요한 경우 몇 가지 추가 리소스를 제공했습니다. 이제 코딩을 하는 것은 여러분에게 달려 있으며, 의견을 듣고 싶다면 댓글을 참고하세요.