본문 바로가기
코드이그나이터와 php7와 mysql

크레이의 라라벨 도전기 #7. 컨트롤러 + 모델(엘로퀀트 all) + 뷰

※ 이 게시글은 크레이의 IT개발 관련 성장기를 다루고 있습니다. 관련지식이 약간 있어야 이해되실 수 있습니다. 가벼운 마음으로 읽어보시면서 흥미가 생기고 의욕이 생긴다면? 개발자의 자질이 있으신 겁니다 :)

연휴기간 가족과 함께 지방에 휴식을 취하러 갈 때면 빈번하게 보게 되는 그 지역의 컨텐츠가 있습니다.
바로 '미래 우체통'인데요. 엽서를 작성해 미래의 나에게 또는 지인에게 부치면
1년 뒤든 10년 뒤든 그 때 우편물이 도착하는 그런 서비스인데요.
실제로 오는지는 확인되지 않았습니다 :)

인터넷에도 그런게 있으면 재미있지 않을까요?
게시글을 작성하면 그 내용이 오픈되는 기간은 길지 않게,
뭐.. 일주일이면 적당할 것 같습니다.
라라벨을 연습하며 지난 게시글에서 다루어봤던 '미래박스'를
어느 수준까지 사이드 프로젝트로 진행해보려 합니다.
관련 내용 공유드리지만 강좌 형식은 아니기 때문에 모두 이해하시기에는 난해한 부분이 있을 수 있습니다.

https://itadventure.tistory.com/607

 

크레이의 라라벨 도전기 #6. 마이그레이션

※ 이 게시글은 크레이의 IT개발 관련 성장기를 다루고 있습니다. 관련지식이 약간 있어야 이해되실 수 있습니다. 가벼운 마음으로 읽어보시면서 흥미가 생기고 의욕이 생긴다면? 개발자의 자질

itadventure.tistory.com

 

이번에는 부트 스트랩(CSS라이브러리)를 적용해보았습니다.
부트스트랩은 웬지 HTML 소스가 너저분해 지는 것 같아 그리 마음에 들지는 않았는데요.
이번에 사용법을 잠깐 익혀보니 익숙해지면 괜찮을것 같다는 생각이 들어 겸사 사용해보았습니다.

결과 화면은 아래와 같은데요.

글 목록에서 미래상자 안내 문구부분을 선택하면, 날짜가 경과된 상자글은 아래와 같이 게시글이 노출되지만

아직 날짜가 경과되지 않는 상자글은 '개봉전'이라는 문구만 노출됩니다.

이게 전부인데요. 게시글 작성 기능은 본 블로그 글을 먼저 작성해야 해서 아직 진행하지 않았습니다.
대신
샘플 데이터를 MYSQL에 2건 입력해 놓았습니다. 삽입 SQL문은 아래와 같습니다.

insert into `boxes` (`id`, `title`, `body`, `writer`, `usermail`, `pwd`, `opendate`, `clicks`, `created_at`, `updated_at`) 
values('1','라라벨의 힘!','라라벨은 우와하다!','쿠크닥스','coocue@1.com','1234','2023-01-16 23:38:25','1','2023-01-15 23:38:34','2023-01-15 23:38:38');
insert into `boxes` (`id`, `title`, `body`, `writer`, `usermail`, `pwd`, `opendate`, `clicks`, `created_at`, `updated_at`)
values('2','이미지 메이킹 영단어','영단어를 떠올려봐!','시원닥스','cool@2.com','1234','2023-01-23 23:32:39','0','2023-01-16 23:32:45',NULL);

목록 게시글의 내용이 화면에 보여지게 하려면 어떤 과정을 거칠까요?

고전 PHP

고전 php 출력방식은 아래와 같은데요.

1) 입력한 URL 에 해당하는 경로의 PHP 코드가 바로 실행됩니다.
2) db 테이블에서 게시글 내용을 읽기 시작합니다.
3) 게시글을 하나씩 읽으면서 화면에 바로 바로 뿌려줍니다.

위 방식은 고전 방식이긴 하나 나름 장점이 있습니다.
게시글의 데이터가 너무 많다면 비록 DB 에서 읽는 도중일지라도 연결을 끊지 않고 실시간으로 화면에 데이터를 뿌려줄 수 있는데요. 이를 롱 폴링(long polling)이라고 합니다. 이 방식은 사용하는 서버 메모리도 초경량 수준입니다.
웹소켓(websocket)이라는 기술이 나오기 전에는, 실시간 통신은 롱-폴링(long-polling) 또는 코멧(comet)이라는 방식을 이용하여 구현했습니다.

모던PHP

세월은 흐르고 하드웨어 성능도 향상, 인터넷 속도도 향상, PHP 언어도 버전이 올라가면서 그 기능이 향상되어 프레임워크를 도입하여 처리할 만큼 발전하였는데요.

라라벨 프레임워크에서는 게시글을 출력하기 위해 아래 여러 단계의 프로세스를 거칩니다.

1) 입력한 URL을 라우터가 분석, 컨트롤러 클래스를 호출합니다.
2) 컨트롤러 클래스는 DB클래스를 호출하여 화면에 출력할 게시글을 모두 읽어 놓습니다.
3) 컨트롤러 클래스는 읽어놓은 모든 게시글을 가공하고 뷰에 전달합니다.
4) 뷰는 전달받은 게시글을 규칙에 맞게 화면에 출력합니다.

데이터를 이곳 저곳에 뺑뺑이 돌린다는 점이 속도면에서는 상당히 비효율적이지만

요즈음 하드웨어 성능은 이 과정을 충분히 빠른 속도로 커버합니다.
그리고 무엇보다 프레임워크 코딩 방식에 익숙해지면 개발을 완수하는 속도가 빨라지는 생산성의 이점도 있습니다. 객체지향적 프로그래밍의 이점이기도 합니다.

라우터(Router)

이제 라라벨 코드를 살펴보겠습니다.
먼저 라우터에서 컨트롤러 클래스를 매칭해주어야 하며
routes/web.php 에 추가되는 소스코드는 아래와 같습니다.

Route::get('/box', 'BoxController@index');

위 코드는 http://IP주소/box 라는 URL 로 접근할 때 BoxController 컨트롤러의 index 라는 메소드로 연결해 줍니다. 라우터는 이것으로 끝이지요.

컨트롤러(Controller)

라라벨은 컨트롤러 소스를 명령어 한줄로 자동 작성할 수 있는 멋진 기능을 지원하는데요.
보통 이 명령으로 컨트롤러 소스의 기본 틀을 마련하고 그 안에 코딩을 해넣습니다.
리눅스 터미널에서 아래 명령어를 실행하면 컨트롤러 소스가 자동으로 생성되는데요.

php artisan make:controller BoxController

생성된 컨트롤러 소스는 app/Http/Controllers 폴더에 위치합니다.

이 파일을 처음 열어보면 들어있는 소스 코드 내용은 아래와 같은데요.

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class BoxController extends Controller
{
    //
}

여기에 index 메소드만 구현해 넣으면 되는 것이지요.
index 메소드는 아래와 같이 구현하였습니다.

public function index()
{
    $today=date("Y-m-d H:i:s");
    $boxes = Box::all();
    for($i=0;$i<count($boxes);++$i)
        if($today < $boxes[$i]->opendate)$boxes[$i]->body="<i class='bi bi-body-text'></i>개봉전";

    return view('box.index', 
        [
            'boxes'=>$boxes
        ]
    );
}

여기서 눈여겨 보실 부분은 아래 코드입니다.

$boxes = Box::all();

Box 라는 클래스의 all() 이라는 정적 메소드를 호출하겠다는 의미인데요.
PHP에서는 정적 메소드를 이용하면 싱글턴 을 별도 코드 없이 사용할 수 있습니다.
독자적으로 사용한다면 객체를 선언하지 않아도 된다는 의미이지요.
결국 아래 코드를 사용할 필요가 없습니다.

$object = new class();

모델(Model)

Box 클래스는 DB클래스입니다. 하나의 테이블을 다루는 모델 클래스인데요.
이 클래스도 소스 코드를 작성해야 하나 라라벨에서는 명령어 한줄로 기본 코드를 생성할 수 있습니다.
터미널에서 아래 코드를 작성한다면 말이지요.

php artisan make:model Box

그러면 app 폴더에 Box.php 모델 클래스 소스파일이 생성된 것을 확인할 수 있습니다.

자동생성된 소스코드의 내용은 아래와 같은데요.

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Box extends Model
{
    //
}

본 예제에서는 이 코드는 전혀 수정하지 않았습니다.
이 모델 클래스는 엘로퀀트(Eloquent)의 모델 클래스를 상속받기 때문에 엘로퀀트 모델 기능을 그대로 사용할 수 있습니다.

엘로퀀트 모델 클래스는 DB테이블의 기본적인 읽기, 쓰기, 수정, 삭제, 검색 기능을 지원하는데요.
all() 메소드도 그중 하나입니다. 테이블의 모든 데이터를 읽어오라~는 명령어이지요.

그렇다면 엘로퀀트는 어떤 테이블의 자료를 읽어올까요? 클래스명에서 그 힌트를 찾을 수 있습니다.
클래스명이 Box 이면 Box 를 소문자 box로 바꾸고 뒤에 복수형 es을 붙인, boxes 라는 테이블을 검색합니다.

다른 이 것이 항상 철칙은 아닙니다.
테이블명을 boxes 가 아닌 box 라고 지었고 이 테이블을 사용하려면 대체할 다른 방법이 있는데요.
클래스 내 $table 이라는 변수를 아래와 같이 선언해주면 됩니다. 그러면 테이블명을 box로 고정해서 사용할 수 있는 것이지요.

class Box extends Model
{
   protected $table = 'box';

내용 가공

이제 읽어온 게시글들을 뷰에 넘겨서 화면에 보여주기 전에 할 일이 한가지 있습니다.
바로 비공개 게시글의 내용인데요.
아직 기간이 경과되지 않은 게시글은 그 내용을 비공개로 해야 하기 때문입니다.
모델에서 처리할 수도 있고 컨트롤러에서 처리할 수도 있는데요.
여기서는 컨트롤러에서 처리하였습니다.

$today=date("Y-m-d H:i:s");
for($i=0;$i<count($boxes);++$i)
    if($today < $boxes[$i]->opendate)
        $boxes[$i]->body="<i class='bi bi-body-text'></i>개봉전";

참고로 <i class='bi bi-body-text'></i> 이라는 태그는 부트스트랩 아이콘 폰트이며,
이렇게 생겼습니다.

뷰(View)

이제 컨트롤러는 Box 모델에서 받아와 가공한 데이터를 뷰에 넘겨주게 되는데요.

return view('box.index', 
    [
        'boxes'=>$boxes
    ]
);

box.index 라는 뷰를 호출하게 되며,
이는 resources/views/box/index.blade.php 뷰에 데이터를 넘겨주겠다는 의미입니다.

최종 뷰 소스코드는 아래와 같습니다.
부트스트랩 기능 적용으로 코드량이 꽤 되는데요.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>미래박스</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
	<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.1/font/bootstrap-icons.css">
  </head>
  <body>
  
	<!-- As a heading -->
	<nav class="navbar navbar-dark bg-primary">
		<div class="container-fluid">
			<span class="navbar-brand mb-0 h1">미래 박스</span>
		</div>
	</nav>
  
	<div class="container text-center py-3">
		<div class="card" style="width: 18rem;margin:0 auto;">
		  <img src="img/fbox.png" class="card-img-top" alt="미래박스">
		  <div class="card-body">
			<h5 class="card-title">미래 박스</h5>
			<p class="card-text">일주일 후에 열려라! 친구들이 숨겨놓은 이야기를 만나보세요.</p>
			<!--<a href="#" class="btn btn-primary">Go somewhere</a>-->
		  </div>
		</div>

		<table class="table my-5 ">
		<tr class="table bg-primary text-white">
		<td>제목</td>
		<td>작성자</td>
		<td>작성일</td>
		<td>오픈일</td>
		</tr>
		<?php $keycnt=1; ?>
		@foreach ($boxes as $box)
		<tr>
		<td>{{ $box->title }}</td>
		<td>{{ $box->writer }}<br/>
		{{ $box->usermail }}</td>
		<td>{{ $box->created_at }}</td>
		<td>{{ $box->opendate }}</td>
		</tr>
		<tr>
		<td colspan=4>
		
			<div class="accordion" id="accordionPanelsStayOpenExample">
			  <div class="accordion-item">
				<h2 class="accordion-header" id="panelsStayOpen-headingOne">
				  <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#panelsStayOpen-collapse{{ $keycnt }}" aria-expanded="false" aria-controls="panelsStayOpen-collapseOne">
					<i class="bi bi-archive-fill mx-1"></i>
					미래 상자에는 무슨 내용이 들어 있을까요 ?
				  </button>
				</h2>
				<div id="panelsStayOpen-collapse{{ $keycnt }}" class="accordion-collapse collapse" aria-labelledby="panelsStayOpen-headingOne">
				  <div class="accordion-body">
					{!! $box->body !!}
				  </div>
				</div>			
			  </div>
			</div>
		</td>
		</tr>
		<?php $keycnt++; ?>
		@endforeach
		</table>

	</div>


    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4" crossorigin="anonymous"></script>
  </body>
</html>

부트 스트랩에 대해서는 좀 더 익힌 후 기회가 되면 다루어 보기로 하겠습니다.
아직은 완전히 익숙하려면 시간이 걸릴것 같습니다.

부트 스트랩을 제외하고 순수하게 데이터만 출력하는 소스를 간추리면 대략 아래와 같습니다.

<table>
<?php $keycnt=1; ?>
@foreach ($boxes as $box)
<tr>
<td>{{ $box->title }}</td>
<td>{{ $box->writer }}<br/>
{{ $box->usermail }}</td>
<td>{{ $box->created_at }}</td>
<td>{{ $box->opendate }}</td>
</tr>
<tr>
<td colspan=4>
  <button data-bs-target="..{{ $keycnt }}">
  미래 상자에는 무슨 내용이 들어 있을까요 ?
  </button>
  <div id="..{{ $keycnt }}">
    {!! $box->body !!}
  </div>
</td>
</tr>
<?php $keycnt++; ?>
@endforeach
</table>

정리

오늘은 게시글중 처음으로 한번에 MVC 패턴을 모두 다루어보았군요.
내용을 간추리면 아래와 같습니다. 총 4개의 파일이 하나의 페이지에 관여하는데요.

호출순서 : 라우터 -> 컨트롤러(Controller) -> 모델(Model) -> 뷰(View)

모델과 컨트롤러 소스는 아래 명령으로 자동으로 기본 코드를 작성할 수 있습니다.

php artisan make:model Box ( Box 모델을 생성 ) 
php artisan make:controller BoxController ( BoxController 컨트롤러를 생성 )

생성된 모델과 컨트롤러 코드는 각각 다음 폴더에 들어 있습니다.
모델은 기본 기능만 사용한다면 수정할 부분이 없으며 컨트롤러만 수정하면 됩니다.

/home/ec2-user/crayLaravel/app ( 모델이 들어 있는 폴더 )
/home/ec2-user/crayLaravel/app/Http/Controllers ( 컨트롤러가 들어 있는 폴더 )

컨트롤러에서는 box.index 라는 뷰를 호출하는데 뷰 파일은 자동 생성 기능이 없어 직접 파일을 만들어야 합니다. 아래 폴더를 생성하고 그 안에 index.blade.php 파일을 생성하여 내용을 구성하면 됩니다.

/home/ec2-user/crayLaravel/resources/views/box

라우터에서 URL 주소에 접근하면 컨트롤러로 향하도록 규칙이 되는 소스코드를 구성해 주면 끝입니다.

/home/ec2-user/crayLaravel/routes/web.php

마무~리

오늘도 필요하신 분께 도움이 되셨을지 모르겠습니다.
방문하시는 분들, 구독하시는 모든 분들,
설 연휴 잘 보내시고 2023년에는 하나님의 축복이 가정에 가득하시기를 소원합니다.
새해 복 많이 받으세요! 늘 관심 가져 주셔서 감사합니다!