Laravel Eloquent의 모델 관계 이해하기

모델과 그 관계는 Laravel Eloquent의 핵심입니다. 그들이 당신에게 어려움을 주거나 간단하고 친근하며 완전한 가이드를 찾을 수 없다면 여기에서 시작하세요!

프로그래밍 기사의 반대편에 앉아 있는 작가는 플랫폼이 제공하는 전문성/위신의 아우라를 가장하거나 폭파하기 쉽습니다. 하지만 솔직히 말해 난 너무 힘들었어 라라벨 배우기, 내 첫 풀스택 프레임워크였기 때문입니다. 한 가지 이유는 내가 직장에서 사용하지 않고 호기심에서 탐색했기 때문입니다. 그래서 나는 시도하고, 결론을 내리고, 혼란스러워하고, 포기하고, 결국 모든 것을 잊을 것입니다. 5-6번은 이 작업이 이해되기 시작했을 것입니다(물론 설명서는 도움이 되지 않습니다).

그러나 여전히 말이 되지 않는 것은 Eloquent였습니다. 또는 적어도 모델 간의 관계(Eloquent는 완전히 학습하기에는 너무 크기 때문입니다). 실제 프로젝트는 훨씬 더 복잡하기 때문에 모델링 작성자 및 블로그 게시물의 예는 농담입니다. 슬프게도 공식 문서는 매우 동일한(또는 유사한) 예제를 사용합니다. 또는 유용한 기사/자원을 발견했더라도 설명이 너무 형편없거나 너무 심하게 누락되어 아무 소용이 없었습니다.

(그나저나 나는 이전에 공식 문서를 공격했다는 공격을 받은 적이 있으므로 비슷한 생각을 가지고 있다면 여기 내 표준 답변이 있습니다. Django 문서를 확인한 다음 저에게 말하십시오.)

결국, 조금씩 뭉쳐지고 의미가 생겼습니다. 마침내 프로젝트를 제대로 모델링하고 모델을 편안하게 사용할 수 있었습니다. 그러던 어느 날 이 작업을 더 즐겁게 만들어주는 깔끔한 컬렉션 트릭을 발견했습니다. 이 기사에서는 가장 기본적인 것부터 시작하여 실제 프로젝트에서 접하게 될 모든 가능한 사용 사례를 포함하여 모든 것을 다룰 것입니다.

Eloquent 모델 관계가 어려운 이유는 무엇입니까?

안타깝게도 모델을 제대로 이해하지 못하는 Laravel 개발자가 너무 많습니다.

하지만 왜?

오늘날에도 Laravel에 대한 과정, 기사 및 비디오가 폭발적으로 증가하고 있지만 전반적인 이해가 부족합니다. 중요한 포인트이고 반성할 가치가 있다고 생각합니다.

나에게 묻는다면 Eloquent 모델 관계는 전혀 어렵지 않다고 말할 것입니다. 적어도 “하드”의 정의 관점에서 볼 때. 라이브 스키마 마이그레이션은 어렵습니다. 새로운 템플릿 엔진을 작성하는 것은 어렵습니다. Laravel의 핵심에 코드를 기여하는 것은 어렵습니다. 이것에 비해 ORM을 배우고 사용합니다. . . 글쎄, 그것은 어려울 수 없습니다! 🤭🤭

실제로 일어나는 일은 Laravel을 배우는 PHP 개발자가 Eloquent를 어렵게 찾는 것입니다. 이것이 근본적인 문제이며 제 생각에는 이에 기여하는 몇 가지 요인이 있습니다(가혹하고 인기 없는 의견 경고!).

  • Laravel 이전에 대부분의 PHP 개발자를 위한 프레임워크 노출은 CodeIgniter였습니다(여전히 살아 있는, 그건 그렇고, 더 Laravel/CakePHP와 비슷해지더라도). 이전 CodeIgniter 커뮤니티(있는 경우)에서 “모범 사례”는 필요한 곳에 SQL 쿼리를 직접 고정하는 것이었습니다. 그리고 오늘날 새로운 CodeIgniter가 있지만 습관은 이어졌습니다. 결과적으로 Laravel을 배울 때 ORM에 대한 아이디어는 PHP 개발자에게 100% 생소합니다.
  • Yii, CakePHP 등과 같은 프레임워크에 노출된 아주 적은 비율의 PHP를 버리고 나머지는 코어 PHP 또는 WordPress와 같은 환경에서 작업하는 데 사용됩니다. 그리고 여기서도 OOP 기반 사고방식은 존재하지 않으므로 프레임워크, 서비스 컨테이너, 디자인 패턴, ORM이 있습니다. . . 이들은 외계인 개념입니다.
  • PHP 세계에는 지속적인 학습의 개념이 거의 또는 전혀 없습니다. 일반 개발자는 관계형 데이터베이스를 사용하고 문자열로 작성된 쿼리를 발행하는 단일 서버 설정으로 작업하는 것을 좋아합니다. 비동기식 프로그래밍, 웹 소켓, HTTP 2/3, Linux(Docker는 잊어라), 단위 테스트, 도메인 기반 설계 — 이러한 것들은 압도적인 비율의 PHP 개발자에게는 모두 생소한 아이디어입니다. 그 결과 Eloquent를 만났을 때 편안하다고 느낄 정도로 새롭고 도전적인 것을 읽는 것은 일어나지 않습니다.
  • 데이터베이스와 모델링에 대한 전반적인 이해도 부족합니다. 데이터베이스 설계는 Eloquent 모델과 불가분의 관계로 직접 연결되어 있기 때문에 난이도가 높아집니다.

전 세계적으로 가혹하고 일반화하려는 것이 아닙니다. 훌륭한 PHP 개발자도 있고 그 중 다수가 있지만 전체 비율은 매우 낮습니다.

이 글을 읽고 있다면 이 모든 장벽을 넘어 Laravel을 만나고 Eloquent를 엉망으로 만들었다는 의미입니다.

축하합니다! 👏

거의 다 왔습니다. 모든 빌딩 블록이 제자리에 있으며 올바른 순서와 세부 사항으로 살펴보기만 하면 됩니다. 즉, 데이터베이스 수준에서 시작하겠습니다.

데이터베이스 모델: 관계 및 카디널리티

간단하게 하기 위해 이 기사 전체에서 관계형 데이터베이스로만 작업한다고 가정해 보겠습니다. 한 가지 이유는 ORM이 원래 관계형 데이터베이스용으로 개발되었기 때문입니다. 다른 이유는 RDBMS가 여전히 압도적인 인기를 누리고 있기 때문입니다.

데이터 모델

먼저 데이터 모델을 더 잘 이해해 봅시다. 모델(또는 더 정확히 말하면 데이터 모델)의 아이디어는 데이터베이스에서 나옵니다. 데이터베이스도, 데이터도, 데이터 모델도 없습니다. 데이터 모델이란 무엇입니까? 간단히 말해 데이터를 저장/구조화하기로 결정한 방식입니다. 예를 들어, 전자 상거래 상점에서는 하나의 거대한 테이블에 모든 것을 저장할 수 있습니다(끔찍한 관행이지만 슬프게도 PHP 세계에서는 드문 일이 아닙니다). 그것이 당신의 데이터 모델이 될 것입니다. 데이터를 20개의 기본 테이블과 16개의 연결 테이블로 분할할 수도 있습니다. 데이터 모델이기도 합니다.

또한 데이터베이스에서 데이터가 구성되는 방식이 프레임워크의 ORM에서 배열되는 방식과 100% 일치할 필요는 없습니다. 그러나 개발할 때 한 가지 더 염두에 두어야 할 사항이 없도록 가능한 한 가깝게 유지하려는 노력이 항상 있습니다.

카디널리티

카디널리티라는 용어도 빨리 알아봅시다. 느슨하게 말하면 “카운트”를 나타냅니다. 그래서, 1, 2, 3 . . . 모두 무언가의 카디널리티가 될 수 있습니다. 이야기의 끝. 계속 움직이자!

관계

이제 모든 유형의 시스템에 데이터를 저장할 때마다 데이터 포인트가 서로 관련될 수 있는 방법이 있습니다. 이것이 추상적이고 지루하게 들린다는 것을 알지만 조금만 참아주십시오. 서로 다른 데이터 항목이 연결되는 방식을 관계라고 합니다. 아이디어를 완전히 이해했는지 확인하기 위해 데이터베이스가 아닌 몇 가지 예를 먼저 살펴보겠습니다.

  • 모든 것을 배열에 저장하는 경우 가능한 관계 중 하나는 다음 데이터 항목이 이전 인덱스보다 1만큼 큰 인덱스에 있다는 것입니다.
  • 데이터를 이진 트리에 저장하는 경우 가능한 관계 중 하나는 왼쪽의 자식 트리가 항상 부모 노드보다 작은 값을 갖는다는 것입니다(트리를 그런 식으로 유지하도록 선택한 경우).
  • 데이터를 동일한 길이의 배열 배열로 저장하면 행렬을 모방할 수 있으며 그 속성은 데이터의 관계가 됩니다.

따라서 데이터 맥락에서 “관계”라는 단어는 고정된 의미를 갖지 않습니다. 실제로 두 사람이 동일한 데이터를 보고 있다면 두 가지 매우 다른 데이터 관계(안녕하세요, 통계!)를 식별할 수 있으며 둘 다 유효할 수 있습니다.

관계형 데이터베이스

지금까지 논의한 모든 용어를 바탕으로 마침내 웹 프레임워크(Laravel)의 모델에 직접 연결되는 관계형 데이터베이스에 대해 이야기할 수 있습니다. 우리 대부분에게 사용되는 기본 데이터베이스는 MySQL, MariaDB, PostgreSQL, MSSQL, SQL Server, SQLite 등입니다. 우리는 또한 이것이 RDBMS라고 불리는 것을 막연하게 알 수 있지만 우리 대부분은 그것이 실제로 무엇을 의미하고 왜 그것이 중요한지 잊어버렸습니다.

RDBMS의 “R”은 물론 Relational을 의미합니다. 이것은 임의로 선택한 용어가 아닙니다. 이를 통해 이러한 데이터베이스 시스템이 저장된 데이터 간의 관계와 효율적으로 작동하도록 설계되었다는 사실을 강조합니다. 사실 여기서 “관계”는 엄밀한 수학적 의미를 가지고 있으며 개발자가 그것에 대해 신경 쓸 필요는 없지만 엄격한 수학적 의미가 있음을 아는 것이 도움이 됩니다. 기초 이러한 유형의 데이터베이스 아래에 있습니다.

SQL 및 NoSQL에 대해 알아보려면 다음 리소스를 살펴보세요.

알겠습니다. 경험을 통해 RDBMS의 데이터가 테이블로 저장된다는 것을 알고 있습니다. 그렇다면 관계는 어디에 있습니까?

RDBMS의 관계 유형

이것은 아마도 Laravel과 모델 관계의 전체 주제에서 가장 중요한 부분일 것입니다. 이것을 이해하지 못한다면 Eloquent는 결코 말이 되지 않을 것이므로 다음 몇 분 동안 주의를 기울이시기 바랍니다(그렇게 어렵지도 않습니다).

RDBMS를 사용하면 데이터베이스 수준에서 데이터 간의 관계를 가질 수 있습니다. 즉, 이러한 관계는 비실용적/상상적/주관적이지 않으며 동일한 결과로 다른 사람들이 생성하거나 추론할 수 있습니다.

동시에 RDBMS에는 이러한 관계를 생성하고 적용할 수 있는 다음과 같은 특정 기능/도구가 있습니다.

  • 기본 키
  • 외래 키
  • 제약

이 기사가 데이터베이스 과정이 되는 것을 원하지 않으므로 이러한 개념이 무엇인지 알고 있다고 가정하겠습니다. 그렇지 않거나 자신감이 불안정한 경우 이 친근한 동영상을 추천합니다(전체 시리즈를 자유롭게 살펴보세요).

공교롭게도 이러한 RDBMS 스타일 관계는 실제 응용 프로그램에서 발생하는 가장 일반적인 관계이기도 합니다(소셜 네트워크는 테이블 모음이 아닌 그래프로 가장 잘 모델링되기 때문에 항상 그런 것은 아닙니다). 따라서 그것들을 하나씩 살펴보고 유용할 수 있는 부분을 이해하려고 노력합시다.

일대일 관계

거의 모든 웹 애플리케이션에는 사용자 계정이 있습니다. 또한 다음은 사용자 및 계정에 대해 (일반적으로 말해서) 사실입니다.

  • 사용자는 하나의 계정만 가질 수 있습니다.
  • 계정은 한 명의 사용자만 소유할 수 있습니다.

예, 한 사람이 다른 이메일로 가입하여 두 개의 계정을 만들 수 있다고 주장할 수 있지만 웹 애플리케이션의 관점에서 볼 때 두 사람은 서로 다른 계정을 가진 서로 다른 두 사람입니다. 예를 들어 애플리케이션은 한 계정의 데이터를 다른 계정에 표시하지 않습니다.

이 모든 헤어 분할이 의미하는 바는 애플리케이션에서 이와 같은 상황이 있고 관계형 데이터베이스를 사용하고 있다면 이를 일대일 관계로 설계해야 한다는 것입니다. 아무도 당신에게 인위적으로 강요하지 않는다는 점에 유의하십시오. 비즈니스 도메인에는 분명한 상황이 있고 당신은 우연히 관계형 데이터베이스를 사용하고 있습니다. . . 이 두 조건이 모두 충족되어야만 일대일 관계에 도달할 수 있습니다.

이 예(사용자 및 계정)의 경우 스키마를 생성할 때 이 관계를 구현할 수 있는 방법은 다음과 같습니다.

CREATE TABLE users(
    id INT NOT NULL AUTO_INCREMENT,
    email VARCHAR(100) NOT NULL,
    password VARCHAR(100) NOT NULL,
    PRIMARY KEY(id)
);

CREATE TABLE accounts(
    id INT NOT NULL AUTO_INCREMENT,
    role VARCHAR(50) NOT NULL,
    PRIMARY KEY(id),
    FOREIGN KEY(id) REFERENCES users(id)
);

여기서 트릭을 알아차리셨나요? 일반적으로 앱을 빌드할 때 매우 드문 일이지만 계정 테이블에는 필드 ID가 기본 키와 외래 키로 설정되어 있습니다! 외래 키 속성은 이를 사용자 테이블(물론 🙄)에 연결하는 반면 기본 키 속성은 id 열을 고유하게 만듭니다. 진정한 일대일 관계입니다!

물론 이 관계의 충실도는 보장되지 않습니다. 예를 들어, 계정 테이블에 단일 항목을 추가하지 않고 200명의 새 사용자를 추가하는 것을 막을 수 있는 것은 없습니다. 그렇게 하면 1:0 관계로 끝납니다! 🤭🤭 하지만 순수한 구조의 범위 내에서 그것이 우리가 할 수 있는 최선입니다. 계정 없이 사용자를 추가하지 않으려면 데이터베이스 트리거 또는 Laravel에 의해 시행되는 유효성 검사의 형태로 일종의 프로그래밍 논리의 도움을 받아야 합니다.

  램이란? 알아야 할 모든 것

스트레스를 받기 시작했다면 아주 좋은 조언이 있습니다.

  • 천천히 해. 필요한 만큼 천천히. 이 기사와 오늘 즐겨찾기에 추가한 15개의 다른 기사를 끝내려고 하지 말고 이 기사에 충실하십시오. 그것이 필요한 경우 3, 4, 5일이 걸리도록 하십시오. 목표는 Eloquent 모델 관계를 목록에서 영원히 제거하는 것입니다. 이전에 기사에서 기사로 이동하여 수백 시간을 낭비했지만 도움이 되지 않았습니다. 그래서 이번에는 다른 일을 하십시오. 😇
  • 이 글은 Laravel Eloquent에 관한 것이지만, 그 모든 것은 훨씬 나중에 나옵니다. 모든 것의 기초는 데이터베이스 스키마이므로 우리는 먼저 이를 올바르게 하는 데 초점을 맞춰야 합니다. 데이터베이스 수준에서만 작업할 수 없다면(세상에 프레임워크가 없다고 가정) 모델과 관계가 완전히 이해되지 않을 것입니다. 따라서 지금은 Laravel을 잊어 버리십시오. 완전히. 지금은 데이터베이스 설계에 대해서만 이야기하고 수행하고 있습니다. 예, 때때로 라라벨 참조를 만들겠지만, 여러분의 임무는 그것들이 여러분을 위해 그림을 복잡하게 만든다면 그것들을 완전히 무시하는 것입니다.
  • 나중에 데이터베이스와 그들이 제공하는 것에 대해 조금 더 읽으십시오. MongoDB의 인덱스, 성능, 트리거, 기본 데이터 구조 및 해당 동작, 캐싱, 관계. . . 다룰 수 있는 접선 주제는 엔지니어로서 도움이 될 것입니다. 프레임워크 모델은 유령 껍데기일 뿐임을 기억하십시오. 플랫폼의 실제 기능은 기본 데이터베이스에서 나옵니다.

일대다 관계

당신이 이것을 깨달았는지 모르겠지만 이것은 우리 모두가 일상 업무에서 직관적으로 만드는 관계의 유형입니다. 예를 들어 사용자 테이블에 외래 키를 저장하기 위해 주문 테이블(가상 예제)을 만들 때 사용자와 주문 간에 일대다 관계를 만듭니다. 왜 그런 겁니까? 글쎄, 누가 얼마나 많이 가질 수 있는지의 관점에서 다시 살펴보십시오. 한 사용자는 하나 이상의 주문을 가질 수 있으며 이는 거의 모든 전자 상거래가 작동하는 방식입니다. 그리고 반대쪽에서 볼 때 관계는 주문이 한 사용자에게만 속할 수 있다고 말하며 이는 또한 많은 의미가 있습니다.

데이터 모델링, RDBMS 서적 및 시스템 문서에서 이 상황은 다음과 같이 다이어그램으로 표현됩니다.

일종의 삼지창을 만드는 세 개의 선이 보이시나요? 이것은 “많은”의 기호이므로 이 다이어그램은 한 사용자가 많은 주문을 가질 수 있음을 나타냅니다.

그런데 우리가 반복적으로 접하는 이러한 “다수” 및 “일” 수는 관계의 카디널리티라고 하는 것입니다(이전 섹션에서 이 단어를 기억하십니까?). 다시 말하지만, 이 기사에서는 이 용어가 사용되지 않지만 인터뷰나 추가 읽기 중에 이 용어가 나올 경우 개념을 아는 데 도움이 됩니다.

간단하죠? 그리고 실제 SQL의 관점에서 이 관계를 생성하는 것도 간단합니다. 사실 일대일 관계의 경우보다 훨씬 간단합니다!

CREATE TABLE users( 
    id INT NOT NULL AUTO_INCREMENT, 
    email VARCHAR(100) NOT NULL, 
    password VARCHAR(100) NOT NULL, 
    PRIMARY KEY(id) 
);

CREATE TABLE orders( 
    id INT NOT NULL AUTO_INCREMENT, 
    user_id INT NOT NULL, 
    description VARCHAR(50) NOT NULL, 
    PRIMARY KEY(id), 
    FOREIGN KEY(user_id) REFERENCES users(id) 
);

주문 테이블은 각 주문에 대한 사용자 ID를 저장합니다. 주문 테이블의 사용자 ID가 고유해야 한다는 제약(제한)이 없기 때문에 단일 ID를 여러 번 반복할 수 있음을 의미합니다. 이것이 일대다 관계를 만드는 것이며 그 아래에 숨겨진 신비한 마법이 아닙니다. 사용자 ID는 주문 테이블에 멍청한 방식으로 저장되며 SQL에는 일대다, 일대일 등의 개념이 없습니다. 하지만 일단 이런 방식으로 데이터를 저장하면 일대다 관계가 있다고 생각할 수 있습니다.

바라건대, 이제 말이 됩니다. 또는 적어도 이전보다 더 의미가 있습니다. 😅 다른 것과 마찬가지로 이것은 단순한 연습의 문제이며 실제 상황에서 이 작업을 4-5번 수행하면 생각조차 하지 않을 것임을 기억하십시오.

다대다 관계

실제로 발생하는 다음 유형의 관계는 소위 다대다 관계입니다. 다시 한 번, 프레임워크에 대해 걱정하거나 데이터베이스에 뛰어들기 전에 실제 아날로그인 책과 저자를 생각해 봅시다. 좋아하는 작가를 생각해 보세요. 그들은 한 권 이상의 책을 썼습니다, 그렇죠? 동시에 여러 작가가 한 권의 책에서 공동 작업하는 것을 보는 것은 꽤 흔한 일입니다(적어도 논픽션 장르에서는). 그래서 한 작가가 많은 책을 쓸 수 있고, 여러 작가가 한 권의 책을 쓸 수 있습니다. 두 엔터티(책과 저자) 사이에서 이는 다대다 관계를 형성합니다.

이제 도서관이나 책, 저자가 포함된 실제 앱을 만들 가능성이 거의 없다는 점을 감안하여 몇 가지 예를 더 생각해 보겠습니다. B2B 환경에서 제조업체는 공급업체로부터 품목을 주문하고 송장을 받습니다. 송장에는 여러 항목이 포함되며 각 항목에는 공급된 수량과 항목이 나열됩니다. 예를 들어, 5인치 파이프 조각 x 200 등입니다. 이 상황에서 항목과 송장은 다대다 관계를 가집니다(생각해 보고 스스로 확신하십시오). 차량 관리 시스템에서 차량과 운전자는 유사한 관계를 갖게 됩니다. 전자 상거래 사이트에서 즐겨찾기 또는 위시리스트와 같은 기능을 고려하면 사용자와 제품은 다대다 관계를 가질 수 있습니다.

충분합니다. 이제 SQL에서 이 다대다 관계를 만드는 방법은 무엇입니까? 일대다 관계가 작동하는 방식에 대한 지식을 기반으로 두 테이블의 다른 테이블에 대한 외래 키를 저장해야 한다고 생각하고 싶을 수 있습니다. 그러나 이렇게 하려고 하면 큰 문제에 봉착합니다. 책의 저자가 다대다 관계를 가져야 하는 이 예를 살펴보십시오.

언뜻 보기에는 모든 것이 괜찮아 보입니다. 책은 정확히 다대다 방식으로 저자에게 매핑됩니다. 그러나 저자 테이블 데이터를 자세히 살펴보십시오. 책 ID 12와 13은 모두 Peter M.(저자 ID 2)에 의해 작성되었기 때문에 항목을 반복할 수밖에 없습니다. 이제 authors 테이블에 데이터 무결성 문제가 있을 뿐만 아니라(적절한 표준화 그리고 그 모든 것) id 열의 값이 이제 반복됩니다. 이것은 우리가 선택한 설계에서 기본 키 열이 없을 수 있고(기본 키는 중복 값을 가질 수 없기 때문에) 모든 것이 무너진다는 것을 의미합니다.

분명히 이를 위한 새로운 방법이 필요하며, 다행히도 이 문제는 이미 해결되었습니다. 외래 키를 두 테이블에 직접 저장하면 문제가 발생하므로 RDBMS에서 다대다 관계를 만드는 올바른 방법은 소위 “조인 테이블”을 만드는 것입니다. 아이디어는 기본적으로 두 개의 원래 테이블을 그대로 두고 다대다 매핑을 보여주기 위해 세 번째 테이블을 만드는 것입니다.

조인 테이블을 포함하도록 실패한 예제를 다시 실행해 보겠습니다.

급격한 변화가 있었습니다.

  • authors 테이블의 열 수가 줄어듭니다.
  • books 테이블의 열 수가 줄어듭니다.
  • 더 이상 반복할 필요가 없으므로 authors 테이블의 행 수가 줄어듭니다.
  • 저자 ID가 어떤 책 ID에 연결되어 있는지에 대한 정보를 포함하는 authors_books라는 새 테이블이 나타났습니다. 조인 테이블의 이름은 무엇이든 지정할 수 있지만 관례에 따라 는 단순히 밑줄을 사용하여 표가 나타내는 두 테이블을 조인한 결과입니다.

조인 테이블에는 기본 키가 없으며 대부분의 경우 두 테이블의 ID인 두 개의 열만 포함됩니다. 이전 예제에서 외래 키 열을 제거하고 이 새 테이블에 붙여 넣은 것과 거의 같습니다. 기본 키가 없으므로 모든 관계를 기록하는 데 필요한 만큼의 반복이 있을 수 있습니다.

이제 조인 테이블이 관계를 명확하게 표시하는 방법을 눈으로 볼 수 있지만 응용 프로그램에서 어떻게 액세스합니까? 비밀은 조인 테이블이라는 이름에 연결되어 있습니다. 이것은 SQL 쿼리에 대한 과정이 아니므로 깊이 들어가지는 않겠지만 아이디어는 특정 저자의 모든 책을 하나의 효율적인 쿼리로 원하는 경우 동일한 순서로 테이블을 SQL 조인한다는 것입니다. 저자, authors_books 및 책. authors 및 authors_books 테이블은 각각 ​​id 및 author_id 열에 조인되고 authors_books 및 books 테이블은 각각 ​​book_id 및 id 열에 조인됩니다.

네, 지쳤습니다. 그러나 긍정적인 측면을 살펴보십시오. Eloquent 모델을 다루기 전에 필요한 모든 이론/기초 작업을 완료했습니다. 그리고 이 모든 것이 선택 사항이 아님을 상기시켜 드리겠습니다! 데이터베이스 디자인을 모르면 영원히 Eloquent 혼란에 빠질 것입니다. 게다가 Eloquent가 무엇을 하든 무엇을 하려고 하든 이러한 데이터베이스 수준의 세부 정보를 완벽하게 미러링하므로 RDBMS에서 도망치면서 Eloquent를 배우려는 시도가 헛수고인 이유를 쉽게 알 수 있습니다.

Laravel Eloquent에서 모델 관계 생성

마지막으로 약 70,000마일을 우회한 후 Eloquent, 모델 및 모델 생성/사용 방법에 대해 이야기할 수 있는 지점에 도달했습니다. 이제 우리는 기사의 이전 부분에서 모든 것이 데이터베이스와 데이터를 모델링하는 방법에서 시작된다는 것을 배웠습니다. 이것은 내가 새로운 프로젝트를 시작하는 하나의 완전한 예를 사용해야 한다는 것을 깨달았습니다. 동시에 저는 이 예가 블로그와 저자 또는 책과 서가(실제이기도 하지만 죽도록 행해졌음)에 관한 것이 아니라 실제 세계에 있기를 바랍니다.

부드러운 장난감을 파는 가게를 상상해 봅시다. 또한 시스템에서 사용자, 주문, 송장, 항목, 범주, 하위 범주 및 트랜잭션의 네 가지 엔터티를 식별할 수 있는 요구 사항 문서가 제공되었다고 가정해 보겠습니다. 예, 더 복잡할 수 있지만 문제는 제쳐두고 문서에서 앱으로 이동하는 방법에 집중하겠습니다.

시스템의 주요 엔터티가 식별되면 지금까지 논의한 데이터베이스 관계 측면에서 이들이 서로 어떻게 관련되어 있는지 생각해야 합니다. 내가 생각할 수있는 것은 다음과 같습니다.

  • 사용자 및 주문: 일대다.
  • 주문 및 송장: 일대일. 나는 이것이 잘리고 말린 것이 아니라는 것을 알고 있으며 비즈니스 도메인에 따라 일대다, 다대일 또는 다대다 관계가 있을 수 있습니다. 그러나 평균적인 소규모 전자 상거래 상점의 경우 한 번의 주문으로 인해 하나의 인보이스가 발생하고 그 반대의 경우도 마찬가지입니다.
  • 주문 및 항목: 다대다.
  • 항목 및 범주: 다대일. 다시 말하지만 이것은 대규모 전자 상거래 사이트에서는 그렇지 않지만 소규모 운영이 있습니다.
  • 범주 및 하위 범주: 일대다. 다시 말하지만, 이것과 모순되는 대부분의 실제 사례를 찾을 수 있지만 Eloquent는 있는 그대로 충분히 어렵기 때문에 데이터 모델링을 더 어렵게 만들지 맙시다!
  • 주문 및 거래: 일대다. 또한 내 선택에 대한 정당성으로 다음 두 가지 사항을 추가하고 싶습니다. 1) 트랜잭션과 인보이스 간의 관계도 추가할 수 있었습니다. 데이터 모델링 결정 일뿐입니다. 2) 여기서 일대다인 이유는 무엇입니까? 뭐, 주문 결제가 어떤 이유로 실패하고 다음에 성공하는 것이 일반적입니다. 이 경우 해당 주문에 대해 생성된 두 개의 트랜잭션이 있습니다. 실패한 트랜잭션을 표시할지 여부는 비즈니스 결정이지만 중요한 데이터를 캡처하는 것은 항상 좋은 생각입니다.

다른 관계가 있습니까? 더 많은 관계가 가능하지만 실용적이지는 않습니다. 예를 들어 사용자가 많은 트랜잭션을 가지고 있으므로 그들 사이에 관계가 있어야 한다고 말할 수 있습니다. 여기서 깨달아야 할 점은 이미 간접적인 관계가 있다는 것입니다. 사용자 -> 주문 -> 거래, 그리고 일반적으로 말하면 RDBMS는 테이블 조인에 있어 짐승이기 때문에 충분합니다. 둘째, 이 관계를 생성한다는 것은 거래 테이블에 user_id 열을 추가하는 것을 의미합니다. 가능한 모든 직접 관계에 대해 이 작업을 수행하면 데이터베이스에 훨씬 더 많은 부하가 추가되고(특히 UUID가 사용되고 인덱스를 유지 관리하는 경우 더 많은 스토리지의 형태로) 전체 시스템이 연결됩니다. 물론 비즈니스에서 트랜잭션 데이터가 필요하다고 말하고 1.5초 이내에 필요하다고 말하면 해당 관계를 추가하고 작업 속도를 높일 수 있습니다(상충, 상충…).

  Word 2010에서 페이지를 삭제하는 방법

그리고 이제 신사숙녀 여러분, 실제 코드를 작성할 때가 왔습니다!

Laravel 모델 관계 — 코드가 포함된 실제 예제

이 기사의 다음 단계는 손을 더럽히지만 유용한 방법에 관한 것입니다. 이전 전자 상거래 예제와 동일한 데이터베이스 엔터티를 선택하고 Laravel을 설치하는 즉시 Laravel의 모델이 생성되고 연결되는 방식을 볼 것입니다!

당연히 개발 환경이 설정되어 있고 종속성 관리를 위해 Composer를 설치 및 사용하는 방법을 알고 있다고 가정합니다.

$ composer global require laravel/installer -W
$ laravel new model-relationships-study

이 두 콘솔 명령은 Laravel 설치 프로그램을 설치합니다(이미 이전 버전이 설치되어 있으므로 업그레이드에 -W 부분이 사용됨). 궁금한 점이 있으시면 글을 쓰는 시점에서 설치된 Laravel 버전은 8.5.9입니다. 당황하고 업그레이드해야합니까? 우리 애플리케이션의 맥락에서 Laravel 5와 Laravel 8 사이에 큰 변화가 없을 것으로 예상하기 때문에 이에 대해 조언하지 않습니다. 몇 가지 사항이 변경되어 이 문서(예: Model Factory)에 영향을 주지만 코드를 이식할 수 있을 것입니다.

우리는 이미 데이터 모델과 그 관계를 생각했기 때문에 모델을 생성하는 부분은 사소할 것입니다. 그리고 데이터베이스 스키마에 100% 의존하기 때문에 데이터베이스 스키마를 미러링하는 방법도 볼 수 있습니다(지금은 망가진 레코드처럼 들립니다!).

즉, 데이터베이스에 적용할 모든 모델에 대한 마이그레이션(및 모델 파일)을 먼저 생성해야 합니다. 나중에 모델에 대해 작업하고 관계를 추가할 수 있습니다.

그렇다면 어떤 모델로 시작해야 할까요? 물론 가장 단순하고 연결이 가장 적은 것입니다. 우리의 경우 이것은 사용자 모델을 의미합니다. 라라벨은 이 모델과 함께 제공되기 때문에 (그리고 그것 없이는 작동할 수 없습니다 🤣) 마이그레이션 파일을 수정하고 우리의 간단한 필요에 맞게 모델을 정리합시다.

마이그레이션 클래스는 다음과 같습니다.

class CreateUsersTable extends Migration
{
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
        });
    }
}

실제로 프로젝트를 빌드하는 것이 아니기 때문에 암호, is_active 등을 입력할 필요가 없습니다. 사용자 테이블에는 id와 사용자 이름이라는 두 개의 열만 있습니다.

다음에 범주에 대한 마이그레이션을 생성하겠습니다. Laravel을 사용하면 단일 명령으로도 모델을 생성할 수 있는 편리함을 제공하므로 모델 파일을 지금은 다루지 않겠지만 이점을 활용할 것입니다.

$ php artisan make:model Category -m
Model created successfully.
Created Migration: 2021_01_26_093326_create_categories_table

마이그레이션 클래스는 다음과 같습니다.

class CreateCategoriesTable extends Migration
{
    public function up()
    {
        Schema::create('categories', function (Blueprint $table) {
            $table->id();
            $table->string('name');
        });
    }
}

down() 함수가 없다는 사실에 놀라더라도 놀라지 마십시오. 실제로 열이나 테이블을 삭제하거나 열 유형을 변경하면 복구할 수 없는 데이터 손실이 발생하므로 거의 사용하지 않습니다. 개발 과정에서 전체 데이터베이스를 삭제한 다음 마이그레이션을 다시 실행하게 됩니다. 하지만 우리는 빗나갔으니 돌아가서 다음 엔터티를 다루겠습니다. 하위 카테고리는 카테고리와 직결되기 때문에 다음에 하는 것이 좋을 것 같습니다.

$ php artisan make:model SubCategory -m
Model created successfully.
Created Migration: 2021_01_26_140845_create_sub_categories_table

자, 이제 마이그레이션 파일을 채워봅시다:

class CreateSubCategoriesTable extends Migration
{
    public function up()
    {
        Schema::create('sub_categories', function (Blueprint $table) {
            $table->id();
            $table->string('name');

            $table->unsignedBigInteger('category_id');
            $table->foreign('category_id')
                ->references('id')
                ->on('categories')
                ->onDelete('cascade');
        });
    }
}

보시다시피 카테고리 테이블의 ID를 저장할 category_id라는 별도의 열을 여기에 추가합니다. 추측에 대한 보상은 없으며 데이터베이스 수준에서 일대다 관계를 생성합니다.

이제 아이템 차례입니다.

$ php artisan make:model Item -m
Model created successfully.
Created Migration: 2021_01_26_141421_create_items_table

그리고 마이그레이션:

class CreateItemsTable extends Migration
{
    public function up()
    {
        Schema::create('items', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->text('description');
            $table->string('type');
            $table->unsignedInteger('price');
            $table->unsignedInteger('quantity_in_stock');

            $table->unsignedBigInteger('sub_category_id');
            $table->foreign('sub_category_id')
                ->references('id')
                ->on('sub_categories')
                ->onDelete('cascade');
        });
    }
}

일을 다르게 해야 한다고 생각한다면 괜찮습니다. 두 사람이 정확히 동일한 스키마와 아키텍처를 생각해내는 경우는 거의 없습니다. 일종의 모범 사례인 한 가지에 유의하십시오. 가격을 정수로 저장했습니다.

왜요?

글쎄요, 사람들은 플로트 분할을 처리하는 것이 데이터베이스 측에서 추악하고 오류가 발생하기 쉽다는 것을 깨달았기 때문에 가장 작은 통화 단위로 가격을 저장하기 시작했습니다. 예를 들어 USD로 운영하는 경우 여기의 가격 필드는 센트를 나타냅니다. 시스템 전체에서 값과 계산은 센트 단위입니다. 사용자에게 표시하거나 이메일로 PDF를 보낼 때만 100으로 나누고 반올림합니다. 똑똑하군, 응?

어쨌든 항목이 다대일 관계의 하위 범주에 연결되어 있음에 유의하십시오. 카테고리에도 연결되어 있습니다. . . 하위 범주를 통해 간접적으로. 우리는 이 모든 체조의 견고한 시연을 보게 되겠지만 지금은 개념을 이해하고 100% 명확하게 해야 합니다.

다음은 Order 모델과 마이그레이션입니다.

$ php artisan make:model Order -m
Model created successfully.
Created Migration: 2021_01_26_144157_create_orders_table

간결함을 위해 마이그레이션의 중요한 필드 중 일부만 포함하겠습니다. 내 말은, 주문의 세부 사항에는 많은 것들이 포함될 수 있지만 모델 관계의 개념에 집중할 수 있도록 몇 가지로 제한할 것입니다.

class CreateOrdersTable extends Migration
{
    public function up()
    {
        Schema::create('orders', function (Blueprint $table) {
            $table->id();
            $table->string('status');
            $table->unsignedInteger('total_value');
            $table->unsignedInteger('taxes');
            $table->unsignedInteger('shipping_charges');

            $table->unsignedBigInteger('user_id');
            $table->foreign('user_id')
                ->references('id')
                ->on('users')
                ->onDelete('cascade');
        });
    }
}

괜찮아 보이지만 잠깐만! 이 주문의 항목은 어디에 있습니까? 앞에서 설정한 것처럼 주문과 항목 사이에는 다대다 관계가 있으므로 간단한 외래 키가 작동하지 않습니다. 해결책은 소위 조인 테이블 또는 중간 테이블입니다. 즉, 주문과 항목 간의 다대다 매핑을 저장하려면 조인 테이블이 필요합니다. 이제 Laravel 세계에는 시간을 절약하기 위해 따르는 기본 제공 규칙이 있습니다. 두 테이블 이름의 단수형을 사용하여 새 테이블을 만들고 사전 순서로 배치하고 밑줄을 사용하여 결합하면 Laravel은 자동으로 조인 테이블로 인식합니다.

우리의 경우 조인 테이블은 item_order라고 합니다(사전에서 “item”이라는 단어는 “order” 앞에 옵니다). 또한 앞에서 설명한 것처럼 이 조인 테이블에는 일반적으로 각 테이블에 대한 외래 키인 두 개의 열만 포함됩니다.

여기에서 모델 + 마이그레이션을 생성할 수 있지만 모델은 메타에 가깝기 때문에 절대 사용되지 않습니다. 따라서 Laravel에서 새로운 마이그레이션을 생성하고 무엇이 무엇인지 알려줍니다.

$ php artisan make:migration create_item_order_table --create="item_order"
Created Migration: 2021_01_27_093127_create_item_order_table

그 결과 새로운 마이그레이션이 생성되며 다음과 같이 변경됩니다.

class CreateItemOrderTable extends Migration
{
    public function up()
    {
        Schema::create('item_order', function (Blueprint $table) {
            $table->unsignedBigInteger('order_id');
            $table->foreign('order_id')
                ->references('id')
                ->on('orders')
                ->onDelete('cascade');
            
            $table->unsignedBigInteger('item_id');
            $table->foreign('item_id')
                ->references('id')
                ->on('items')
                ->onDelete('cascade');    
        });
    }
}

Eloquent 메소드 호출을 통해 이러한 관계에 실제로 액세스하는 방법은 나중에 다룰 주제이지만 먼저 이러한 외래 키를 손으로 열심히 만들어야 합니다. 이것이 없으면 Eloquent도 없고 Laravel에 “똑똑한” 것도 없습니다. 🙂

우리는 아직있다? 음, 거의 . . .

걱정할 모델이 몇 개 더 있습니다. 첫 번째는 인보이스 테이블이며 주문과 일대일 관계로 만들기로 결정했음을 기억할 것입니다.

$ php artisan make:model Invoice -m
Model created successfully.
Created Migration: 2021_01_27_101116_create_invoices_table

이 기사의 초기 섹션에서 우리는 일대일 관계를 강화하는 한 가지 방법이 자식 테이블의 기본 키를 외래 키로 만드는 것임을 보았습니다. 실제로 이 지나치게 신중한 접근 방식을 취하는 사람은 거의 없으며 사람들은 일반적으로 일대다 관계에 대해 스키마를 설계합니다. 내 의견은 중간 접근 방식이 더 낫다는 것입니다. 외래 키를 고유하게 만들고 상위 모델의 ID가 반복되지 않도록 했습니다.

class CreateInvoicesTable extends Migration
{
    public function up()
    {
        Schema::create('invoices', function (Blueprint $table) {
            $table->id();
            $table->timestamp('raised_at')->nullable();
            $table->string('status');
            $table->unsignedInteger('totalAmount');

            $table->unsignedBigInteger('order_id')->unique();
            $table->foreign('order_id')
                ->references('id')
                ->on('orders')
                ->onDelete('cascade')
                ->unique();
        });
    }
}

그리고 예, 저는 이 인보이스 테이블에 많은 것이 누락되어 있음을 열 번째로 알고 있습니다. 그러나 여기에서 우리의 초점은 전체 데이터베이스를 설계하는 것이 아니라 모델 관계가 작동하는 방식을 보는 것입니다.

자, 이제 시스템의 최종 마이그레이션을 생성해야 하는 시점에 도달했습니다(희망합니다!). 이제 초점은 트랜잭션 모델에 있으며 이전에 주문 모델과 연결되어 있다고 결정했습니다. 그건 그렇고, 여기 당신을 위한 연습이 있습니다. 트랜잭션 모델을 인보이스 모델에 대신 연결해야 합니까? 왜 그리고 왜 안 됩니까? 🙂

$ php artisan make:model Transaction -m
Model created successfully.
Created Migration: 2021_01_31_145806_create_transactions_table

그리고 마이그레이션:

class CreateTransactionsTable extends Migration
{
    public function up()
    {
        Schema::create('transactions', function (Blueprint $table) {
            $table->id();
            $table->timestamp('executed_at');
            $table->string('status');
            $table->string('payment_mode');
            $table->string('transaction_reference')->nullable();

            $table->unsignedBigInteger('order_id');
            $table->foreign('order_id')
                ->references('id')
                ->on('orders')
                ->onDelete('cascade');
        });
    }
}

휴! 그것은 힘든 일이었습니다. . . 마이그레이션을 실행하고 데이터베이스의 관점에서 우리가 어떻게 하고 있는지 봅시다.

$ php artisan migrate:fresh
Dropped all tables successfully.
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (3.45ms)
Migrating: 2021_01_26_093326_create_categories_table
Migrated:  2021_01_26_093326_create_categories_table (2.67ms)
Migrating: 2021_01_26_140845_create_sub_categories_table
Migrated:  2021_01_26_140845_create_sub_categories_table (3.83ms)
Migrating: 2021_01_26_141421_create_items_table
Migrated:  2021_01_26_141421_create_items_table (6.09ms)
Migrating: 2021_01_26_144157_create_orders_table
Migrated:  2021_01_26_144157_create_orders_table (4.60ms)
Migrating: 2021_01_27_093127_create_item_order_table
Migrated:  2021_01_27_093127_create_item_order_table (3.05ms)
Migrating: 2021_01_27_101116_create_invoices_table
Migrated:  2021_01_27_101116_create_invoices_table (3.95ms)
Migrating: 2021_01_31_145806_create_transactions_table
Migrated:  2021_01_31_145806_create_transactions_table (3.54ms)

주님을 찬양합니다! 🙏🏻🙏🏻 우리는 시련의 순간을 살아남은 것 같습니다.

이제 모델 관계 정의로 넘어갈 준비가 되었습니다! 이를 위해 이전에 만든 목록으로 돌아가 모델(테이블) 간의 직접적인 관계 유형을 설명해야 합니다.

우선 사용자와 주문 간에 일대다 관계가 있음을 확인했습니다. 주문의 마이그레이션 파일로 이동하여 거기에서 user_id 필드의 존재를 확인하여 이를 확인할 수 있습니다. 이 필드는 관계를 생성하는 것입니다. 우리가 설정하려는 모든 관계는 먼저 데이터베이스에서 존중되어야 하기 때문입니다. 나머지(Eloquent 구문과 어떤 함수를 작성할 위치)는 순수한 형식일 뿐입니다.

즉, 관계는 이미 존재합니다. 런타임에 사용할 수 있도록 Eloquent에 지시하기만 하면 됩니다. User 모델에 속한다고 선언하는 Order 모델부터 시작하겠습니다.

<?php

namespace AppModels;

use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;

class Order extends Model
{
    use HasFactory;

    public function user() {
        return $this->belongsTo(User::class);
    }
}

구문은 사용자에게 친숙해야 합니다. 우리는 이 주문을 소유한 사용자에 액세스하는 역할을 하는 user()라는 함수를 선언합니다(함수 이름은 무엇이든 될 수 있습니다. 중요한 것은 반환하는 것입니다). 잠시 다시 생각해 보십시오. 데이터베이스와 외래 키가 없다면 $this->belongsTo와 같은 명령문은 의미가 없습니다. Laravel이 해당 user_id를 사용하여 동일한 id를 가진 사용자를 조회하고 반환할 수 있는 것은 주문 테이블에 외래 키가 있기 때문입니다. 데이터베이스의 협력 없이는 라라벨 자체만으로는 허공에서 관계를 생성할 수 없습니다.

이제 사용자의 주문에 액세스하기 위해 $user->orders를 작성할 수 있는 것도 좋을 것입니다. 즉, User 모델로 이동하여 이 일대다 관계의 “다” 부분에 대한 함수를 작성해야 합니다.

<?php

namespace AppModels;

use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;

class User extends Model
{
    use HasFactory;
    public $timestamps = false;

    public function orders() {
        return $this->hasMany(Order::class);
    }
}

예, 이 자습서에서는 다른 모든 기능이 필요하지 않기 때문에 기본 사용자 모델을 크게 수정했습니다. 어쨌든 User 클래스에는 이제 한 명의 사용자가 여러 주문과 연결될 수 있다고 말하는 orders()라는 메서드가 있습니다. ORM 세계에서 우리는 여기서 orders() 관계가 Order 모델에서 가졌던 user() 관계의 반대라고 말합니다.

  PowerPoint에서 이미지를 흐리게 하는 방법

하지만, 잠깐! 이 관계는 어떻게 작동합니까? 즉, 사용자 테이블에서 주문 테이블로 나가는 다중 연결이 있는 데이터베이스 수준에는 아무 것도 없습니다.

실제로 기존 연결이 있으며, 주문 테이블에 저장된 외래 키 참조 자체만으로도 충분합니다! 이것은 우리가 $user->orders와 같은 것을 말할 때 Laravel은 orders() 함수를 조회하고 주문 테이블에 외래 키가 있음을 확인합니다. 그런 다음 일종의 SELECT * FROM 주문 WHERE user_id = 23을 수행하고 쿼리 결과를 컬렉션으로 반환합니다. 물론 ORM을 갖는 요점은 SQL을 잊는 것이지만 기본 기반이 SQL 쿼리를 실행하는 RDBMS라는 사실을 완전히 잊어서는 안됩니다.

다음으로 일대일 관계가 있는 주문 및 송장 모델을 살펴보겠습니다.

<?php

namespace AppModels;

use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;

class Order extends Model
{
    use HasFactory;
    public $timestamps = false;

    public function user() {
        return $this->belongsTo(User::class);
    }

    public function invoice() {
        return $this->hasOne(Invoice::class);
    }
}

송장 모델:

<?php

namespace AppModels;

use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;

class Invoice extends Model
{
    use HasFactory;
    public $timestamps = false;

    public function order() {
        return $this->belongsTo(Order::class);
    }
}

거의 Eloquent 수준뿐만 아니라 데이터베이스 수준에서도 일반적인 일대다 관계입니다. 일대일로 유지되는지 확인하기 위해 몇 가지 검사를 추가했습니다.

이제 다른 유형의 관계인 주문과 항목 간의 다대다 관계에 도달했습니다. 기본 키 간의 매핑을 저장하는 item_order라는 중간 테이블을 이미 생성했음을 상기하십시오. 이 정도가 올바르게 수행되었다면 관계를 정의하고 작업하는 것이 간단합니다. 라라벨 문서에 따라 다대다 관계를 정의하려면 메소드가 dependsToMany() 인스턴스를 반환해야 합니다.

따라서 항목 모델에서:

<?php

namespace AppModels;

use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;

class Item extends Model
{
    use HasFactory;
    public $timestamps = false;

    public function orders() {
        return $this->belongsToMany(Order::class);
    }
}

놀랍게도 역의 관계는 거의 동일합니다.

class Order extends Model
{
    /* ... other code */
    
    public function items() {
        return $this->belongsToMany(Item::class);
    }
}

그리고 그게 다야! 명명 규칙을 올바르게 따르는 한 Laravel은 매핑과 위치를 추론할 수 있습니다.

관계의 세 가지 기본 유형(일대일, 일대다, 다대다)을 모두 다루었으므로 다른 모델에 대한 메서드 작성을 중단할 것입니다. 같은 줄. 대신, 이러한 모델에 대한 공장을 생성하고, 일부 더미 데이터를 생성하고, 이러한 관계가 작동하는 것을 확인하십시오!

어떻게 하죠? 글쎄, 빠르고 더러운 경로를 택하고 모든 것을 기본 시더 파일에 던지자. 그런 다음 마이그레이션을 실행할 때 시더도 실행합니다. 내 DatabaseSeeder.php 파일은 다음과 같습니다.

<?php

namespace DatabaseSeeders;

use IlluminateDatabaseSeeder;
use AppModelsCategory;
use AppModelsSubCategory;
use AppModelsItem;
use AppModelsOrder;
use AppModelsInvoice;
use AppModelsUser;
use Faker;

class DatabaseSeeder extends Seeder
{
    public function run()
    {
        $faker = FakerFactory::create();

        // Let's make two users
        $user1 = User::create(['name' => $faker->name]);
        $user2 = User::create(['name' => $faker->name]);

        // Create two categories, each having two subcategories
        $category1 = Category::create(['name' => $faker->word]);
        $category2 = Category::create(['name' => $faker->word]);

        $subCategory1 = SubCategory::create(['name' => $faker->word, 'category_id' => $category1->id]);
        $subCategory2 = SubCategory::create(['name' => $faker->word, 'category_id' => $category1->id]);

        $subCategory3 = SubCategory::create(['name' => $faker->word, 'category_id' => $category2->id]);
        $subCategory4 = SubCategory::create(['name' => $faker->word, 'category_id' => $category2->id]);

        // After categories, well, we have items
        // Let's create two items each for sub-category 2 and 4
        $item1 = Item::create([
            'sub_category_id' => 2,
            'name' => $faker->name,
            'description' => $faker->text,
            'type' => $faker->word,
            'price' => $faker->randomNumber(2),
            'quantity_in_stock' => $faker->randomNumber(2),
        ]);

        $item2 = Item::create([
            'sub_category_id' => 2,
            'name' => $faker->name,
            'description' => $faker->text,
            'type' => $faker->word,
            'price' => $faker->randomNumber(3),
            'quantity_in_stock' => $faker->randomNumber(2),
        ]);

        $item3 = Item::create([
            'sub_category_id' => 4,
            'name' => $faker->name,
            'description' => $faker->text,
            'type' => $faker->word,
            'price' => $faker->randomNumber(4),
            'quantity_in_stock' => $faker->randomNumber(2),
        ]);

        $item4 = Item::create([
            'sub_category_id' => 4,
            'name' => $faker->name,
            'description' => $faker->text,
            'type' => $faker->word,
            'price' => $faker->randomNumber(1),
            'quantity_in_stock' => $faker->randomNumber(3),
        ]);

        // Now that we have users and items, let's make user1 place a couple of orders
        $order1 = Order::create([
            'status' => 'confirmed',
            'total_value' => $faker->randomNumber(3),
            'taxes' => $faker->randomNumber(1),
            'shipping_charges' => $faker->randomNumber(2),
            'user_id' => $user1->id
        ]);

        $order2 = Order::create([
            'status' => 'waiting',
            'total_value' => $faker->randomNumber(3),
            'taxes' => $faker->randomNumber(1),
            'shipping_charges' => $faker->randomNumber(2),
            'user_id' => $user1->id
        ]);

        // now, assigning items to orders
        $order1->items()->attach($item1);
        $order1->items()->attach($item2);
        $order1->items()->attach($item3);
        
        $order2->items()->attach($item1);
        $order2->items()->attach($item4);

        // and finally, create invoices
        $invoice1 = Invoice::create([
            'raised_at' => $faker->dateTimeThisMonth(),
            'status' => 'settled',
            'totalAmount' => $faker->randomNumber(3),
            'order_id' => $order1->id,
        ]);
    }
}

이제 데이터베이스를 다시 설정하고 시드합니다.

$ php artisan migrate:fresh --seed
Dropped all tables successfully.
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (43.81ms)
Migrating: 2021_01_26_093326_create_categories_table
Migrated:  2021_01_26_093326_create_categories_table (2.20ms)
Migrating: 2021_01_26_140845_create_sub_categories_table
Migrated:  2021_01_26_140845_create_sub_categories_table (4.56ms)
Migrating: 2021_01_26_141421_create_items_table
Migrated:  2021_01_26_141421_create_items_table (5.79ms)
Migrating: 2021_01_26_144157_create_orders_table
Migrated:  2021_01_26_144157_create_orders_table (6.40ms)
Migrating: 2021_01_27_093127_create_item_order_table
Migrated:  2021_01_27_093127_create_item_order_table (4.66ms)
Migrating: 2021_01_27_101116_create_invoices_table
Migrated:  2021_01_27_101116_create_invoices_table (6.70ms)
Migrating: 2021_01_31_145806_create_transactions_table
Migrated:  2021_01_31_145806_create_transactions_table (6.09ms)
Database seeding completed successfully.

괜찮은! 이제 이 기사의 마지막 부분에서 이러한 관계에 액세스하고 지금까지 배운 모든 것을 확인합니다. 이 섹션이 가볍고 재미있는 섹션이 될 것이라는 사실에 기뻐할 것입니다.

이제 가장 재미있는 Laravel 구성 요소인 Tinker 인터랙티브 콘솔을 시작하겠습니다!

$ php artisan tinker
Psy Shell v0.10.6 (PHP 8.0.0 — cli) by Justin Hileman
>>>

Laravel Eloquent에서 일대일 모델 관계에 액세스하기

자, 먼저 주문 및 송장 모델에서 일대일 관계에 액세스해 보겠습니다.

>>> $order = Order::find(1);
[!] Aliasing 'Order' to 'AppModelsOrder' for this Tinker session.
=> AppModelsOrder {#4108
     id: 1,
     status: "confirmed",
     total_value: 320,
     taxes: 5,
     shipping_charges: 12,
     user_id: 1,
   }
>>> $order->invoice
=> AppModelsInvoice {#4004
     id: 1,
     raised_at: "2021-01-21 19:20:31",
     status: "settled",
     totalAmount: 314,
     order_id: 1,
   }

뭔가 알아? 데이터베이스 수준에서 수행된 방식을 기억하십시오. 추가 제약 조건이 없다면 이 관계는 일대다 관계입니다. 따라서 라라벨은 객체의 집합(또는 단 하나의 객체)을 결과로 반환할 수 있었고 이는 기술적으로 정확할 것입니다. 하지만 . . . 우리는 Laravel에게 일대일 관계라고 말했고 결과는 하나의 Eloquent 인스턴스입니다. 반대 관계에 액세스할 때 동일한 일이 어떻게 발생하는지 확인하십시오.

$invoice = Invoice::find(1);
[!] Aliasing 'Invoice' to 'AppModelsInvoice' for this Tinker session.
=> AppModelsInvoice {#3319
     id: 1,
     raised_at: "2021-01-21 19:20:31",
     status: "settled",
     totalAmount: 314,
     order_id: 1,
   }
>>> $invoice->order
=> AppModelsOrder {#4042
     id: 1,
     status: "confirmed",
     total_value: 320,
     taxes: 5,
     shipping_charges: 12,
     user_id: 1,
   }

Laravel Eloquent에서 일대다 모델 관계에 접근하기

사용자와 주문 간에는 일대다 관계가 있습니다. 이제 “땜질”하고 출력을 봅시다.

>>> User::find(1)->orders;
[!] Aliasing 'User' to 'AppModelsUser' for this Tinker session.
=> IlluminateDatabaseEloquentCollection {#4291
     all: [
       AppModelsOrder {#4284
         id: 1,
         status: "confirmed",
         total_value: 320,
         taxes: 5,
         shipping_charges: 12,
         user_id: 1,
       },
       AppModelsOrder {#4280
         id: 2,
         status: "waiting",
         total_value: 713,
         taxes: 4,
         shipping_charges: 80,
         user_id: 1,
       },
     ],
   }
>>> Order::find(1)->user
=> AppModelsUser {#4281
     id: 1,
     name: "Dallas Kshlerin",
   }

정확히 예상한 대로 사용자의 주문에 액세스하면 레코드 모음이 생성되는 반면 역순으로 하나의 단일 사용자 개체만 생성됩니다. 즉, 일대다입니다.

Laravel Eloquent에서 다대다 모델 관계에 접근하기

이제 다대다 관계를 살펴보겠습니다. 항목과 주문 간에는 다음과 같은 관계가 있습니다.

>>> $item1 = Item::find(1);
[!] Aliasing 'Item' to 'AppModelsItem' for this Tinker session.
=> AppModelsItem {#4253
     id: 1,
     name: "Russ Kutch",
     description: "Deserunt voluptatibus omnis ut cupiditate doloremque. Perspiciatis officiis odio et accusantium alias aut. Voluptatum provident aut ut et.",
     type: "adipisci",
     price: 26,
     quantity_in_stock: 65,
     sub_category_id: 2,
   }
>>> $order1 = Order::find(1);
=> AppModelsOrder {#4198
     id: 1,
     status: "confirmed",
     total_value: 320,
     taxes: 5,
     shipping_charges: 12,
     user_id: 1,
   }
>>> $order1->items
=> IlluminateDatabaseEloquentCollection {#4255
     all: [
       AppModelsItem {#3636
         id: 1,
         name: "Russ Kutch",
         description: "Deserunt voluptatibus omnis ut cupiditate doloremque. Perspiciatis officiis odio et accusantium alias aut. Voluptatum provident aut ut et.",
         type: "adipisci",
         price: 26,
         quantity_in_stock: 65,
         sub_category_id: 2,
         pivot: IlluminateDatabaseEloquentRelationsPivot {#4264
           order_id: 1,
           item_id: 1,
         },
       },
       AppModelsItem {#3313
         id: 2,
         name: "Mr. Green Cole",
         description: "Maxime beatae porro commodi fugit hic. Et excepturi natus distinctio qui sit qui. Est non non aut necessitatibus aspernatur et aspernatur et. Voluptatem possimus consequatur exercitationem et.",
         type: "pariatur",
         price: 381,
         quantity_in_stock: 82,
         sub_category_id: 2,
         pivot: IlluminateDatabaseEloquentRelationsPivot {#4260
           order_id: 1,
           item_id: 2,
         },
       },
       AppModelsItem {#4265
         id: 3,
         name: "Brianne Weissnat IV",
         description: "Delectus ducimus quia voluptas fuga sed eos esse. Rerum repudiandae incidunt laboriosam. Ea eius omnis autem. Cum pariatur aut voluptas sint aliquam.",
         type: "non",
         price: 3843,
         quantity_in_stock: 26,
         sub_category_id: 4,
         pivot: IlluminateDatabaseEloquentRelationsPivot {#4261
           order_id: 1,
           item_id: 3,
         },
       },
     ],
   }
>>> $item1->orders
=> IlluminateDatabaseEloquentCollection {#4197
     all: [
       AppModelsOrder {#4272
         id: 1,
         status: "confirmed",
         total_value: 320,
         taxes: 5,
         shipping_charges: 12,
         user_id: 1,
         pivot: IlluminateDatabaseEloquentRelationsPivot {#4043
           item_id: 1,
           order_id: 1,
         },
       },
       AppModelsOrder {#4274
         id: 2,
         status: "waiting",
         total_value: 713,
         taxes: 4,
         shipping_charges: 80,
         user_id: 1,
         pivot: IlluminateDatabaseEloquentRelationsPivot {#4257
           item_id: 1,
           order_id: 2,
         },
       },
     ],
   }

이 출력은 읽기에 약간 어지러울 수 있지만 item1은 order1 항목의 일부이고 그 반대의 경우도 마찬가지입니다. 이것이 우리가 설정한 방식입니다. 매핑을 저장하는 중간 테이블도 살펴보겠습니다.

>>> use DB;
>>> DB::table('item_order')->select('*')->get();
=> IlluminateSupportCollection {#4290
     all: [
       {#4270
         +"order_id": 1,
         +"item_id": 1,
       },
       {#4276
         +"order_id": 1,
         +"item_id": 2,
       },
       {#4268
         +"order_id": 1,
         +"item_id": 3,
       },
       {#4254
         +"order_id": 2,
         +"item_id": 1,
       },
       {#4267
         +"order_id": 2,
         +"item_id": 4,
       },
     ],
   }

결론

그래, 이게 다야, 정말! 정말 긴 글이지만 도움이 되셨기를 바랍니다. Laravel 모델에 대해 알아야 할 모든 것이 있습니까?

슬프게도, 아닙니다. 토끼 구멍은 정말 정말 깊으며 다형성 관계 및 성능 튜닝과 같은 더 많은 도전적인 개념이 있으며 Laravel 개발자로 성장함에 따라 직면하게 될 것입니다. 지금은 이 문서에서 다루는 내용이 대략적으로 말해서 70%의 개발자에게 충분합니다. 지식을 업그레이드해야 할 필요성을 느끼기까지는 정말 오랜 시간이 걸릴 것입니다.

이러한 주의 사항은 치우고 가장 중요한 통찰력을 가져가시기 바랍니다. 흑마법이나 프로그래밍에서 불가능한 것은 없습니다. 단지 우리가 토대와 사물이 어떻게 만들어지는지 이해하지 못하기 때문에 어려움을 겪고 좌절감을 느낍니다.

그래서 . . . ?

자신에게 투자하세요! 코스, 서적, 기사, 기타 프로그래밍 커뮤니티(Python이 제 1위 권장 사항임) — 찾을 수 있는 모든 리소스를 사용하고 느리더라도 정기적으로 사용하십시오. 머지 않아 전체를 망칠 가능성이 있는 인스턴스의 수가 크게 줄어들 것입니다.

좋아, 충분한 설교. 좋은 하루 되세요! 🙂