• 레거시 PHP5.1으로 프레임워크를 만들어보자

    2023. 5. 16.

    by. mason.jeong

     

    GitHub - meju7015/uframework: PHP 5.1 에서도 사용할수 있는 MVC, 라라벨 컨셉의 프레임워크

    PHP 5.1 에서도 사용할수 있는 MVC, 라라벨 컨셉의 프레임워크. Contribute to meju7015/uframework development by creating an account on GitHub.

    github.com

     

    제가 개발자로 첫 회사에 취직을 하고 보니 이 회사의 대부분의 서비스가 php5.1~5.4 버전을 사용하고 있었습니다. 사실 php를 잘 알지 못했기 때문에 처음에는 그러려니 했습니다. 객체지향과는 거리가 먼, 그리고 5.1~5.4 버전에서는 딱히 프레임워크라고 할만한 것도 없었고 해 봐야 CMS라는 게시판 생성기가 판을 치고 있더라고요. 정말 이 사실에 깜짝 놀랐습니다. 

     

    저는 php라는 언어를 가지고 무언가 해본 적이 없었기 때문에 php 쪽이 이 정도로 엉망일 줄은 몰랐습니다. 데이터베이스 커넥션을 담당하는 함수만 100가지가 넘었고 memcache, redis를 다루는 함수 또한 몇십 가지가 되었습니다. 하나의 똑같은 기능을 하는 코어 코드가 수많은 개발자들을 거치면서 똑같은 기능을 하는 다른 함수가 쌓이고 쌓여 발생한 일이었습니다. 계속해서 옛날 코드는 들어내고 체계적인 모습을 갖추려고 많이 노력은 하고 있긴 했지만 결국 새로운 개발자가 와서 작업을 하려면 많은 코드를 파악하는데 시간이 오래 걸리고 코드를 짜다 보면 체계를 벗어나는 일이 종종 생기기 마련이었습니다.

     

    CMS라는 어느 정도의 절차가 있었지만 알게 뭡니까? php는 그러한 절차를 모두 무시할 수 있게 해주는 대단한 녀석이었습니다. CMS의 코어는 어딘가에 박혀있을 뿐 기능을 하지 않은지 오래였습니다. 

     

    아무튼!

    이렇게 반복되는 문제점을 나름대로 고쳐보고 싶었습니다. 1년도 안된 새내기였지만 나름대로 고민을 해가며 이문제를 해결해 보려고 했습니다. 제가 군대있을때 서버실에서 서블렛 JSP를 몇 번 수정하고 했던 경험이 있는데 그때 MVC 패턴이라는 디자인 패턴을 공부했었습니다. mvc 패턴이 잘 동작하려면 잘 만들어진 프레임워크가 있어야 했습니다. 그래서 php진영의 심포니, 라라벨, 스프링부트 등을 참고해 가며 개발자가 mvc 패턴을 잘 이용할 수 있도록 프레임워크를 만들게 되었습니다.

     

     


     

    모델 (Model)

    기존의 코드에서 데이터베이스를 사용하기 위해 어떤 사용자는 mysql_connect를 사용하고 어떤 사용자는 mysql_pconnect 를, 어떤 사용자는 mysqli_connect, pdo를... 일관성이 없었습니다. 모든 상황에 적절하게 함수를 이용한 것 같지도 않았습니다. 사용자는 데이터를 crud 하기 위해 커넥션부터 쿼리, 해제까지 모든 과정을 직접 컨트롤했지만 mvc에서는 그럴 필요가 없습니다. 커넥션은 베이스 모델에서 관리하며 커넥션에 관련된 설정은 환경 변수를 읽어와 주입해 줄 수 있도록 하였습니다. connect라는 인스턴스를 만들어서 커넥션을 구분할 수 있게 하였고 각각의 커넥션은 용도에 맞게 설정할 수 있도록 했습니다. (readonly connect, write only 등) 커넥션이라는 클래스는 하나의 세션에서 한 번만 인스턴스가 될 수 있도록 팩터링 해주었습니다.

     

    기존에는 데이터베이스에 쿼리를 하기 위해서 텍스트나 밸류 매핑을 이용해서 쿼리를 했었는데 역시나 일관성이 떨어집니다 어떤 부분에서는 텍스트에서 밸류를 직접 치환해주는 경우가 있으면 어떤 부분에서는 pdo에 값을 매핑하여 pdo에서 치환하게 해 주었습니다. 이 부분에 일관성을 부여하기 위해 pdo를 이용, query DSL 형태로 사용할 수 있는 빌더를 제공했습니다. 쿼리 빌더는 인스턴스 자체를 쿼리로 파싱 하기 위한 클래스이며 쿼리빌더에 모델을 주입하여 메서드 형태로 쿼리를 만들 수 있는 기능입니다.

     

    쿼리빌더 사용 예

    $this->builder
        ->table('Users')
        ->column(
            Array(
                'User',
                'Password'
            )
        )
        ->where(
            Array(
                Array('User', '=', 'root'),
                Array('Password', '=', ''),
            )
        )
        ->whereOr(
            Array('Email', '=', '*')
        )
        ->find();

     

    쿼리빌더에 대한 분석은 따로 포스팅하도록 하겠습니다.

     

    쿼리 빌더를 사용하니 기존에 커넥션 > 쿼리 > 값 매핑 > 커넥션 해제 단계를 모두 건너뛰고 dsl 만 컨트롤 하면 되니 나머지 과정에 대한 제어를 프레임워크에서 처리해 줄 수 있었습니다. 개발자가 별도의 커넥션을 이용해서 강제로 커넥션을 열어버리게 되면 어쩔 수 없지만 어쨌든 모델이라는 큰 틀에서 쿼리빌더의 역할은 데이터베이스의 일관된 사용성을 제공할 수 있었습니다.

     

     

    뷰 (View)

    php는 사실 뷰 템플릿의 역할이 강하기 때문에 php가 뷰 템플릿이라고 보아도 될것 같습니다. php파일에는 html코드와 css 등을 넣는 것이 가능하고 반대로 html 파일에 php코드를 넣는 것도 가능합니다. 따라서 mvc패턴에서 처럼 프레임워크가 뷰를 생성하고 컨트롤러에서 데이터를 바인딩해주는 방식이 아닌 언제, 어디서라도 뷰의 역할을 하는 php파일을 직접 호출하거나 include 또는 echo, print 등을 통해 그려주기만 하면 뷰가 생성되었습니다. 그러다 보니 뷰 하나가 라우팅, 컨트롤러, 모델의 역할을 하는 경우도 있었고 어떤 경우는 그냥 마크업의 역할만 하는 경우도 있었습니다. 마찬가지로 일관성이 떨어져 작업자의 방식에 따라 방향성이 결정되었어요. 또한 php 뷰를 사용하게 되면 라우팅 경로를 설정할 필요 없지 php가 있는 페이지 자체를 호출하면 되기 때문에. 라우팅에 프로젝트의 경로 구조가 노출된다는 단점도 있었기 때문에 이를 개선해 보고 싶었습니다.

     

    그러나 막상 뷰의 역할이 크다보니 라우팅에서부터 모델까지의 역할을 분리해야 했고 첫 번째로 프로젝트의 경로 구조를 노출시키지 않도록 rewrite에 대응할 수 있는 라우팅 기능을 만들어야 했습니다.

     

    콘셉트는 라라벨을 하여, 라라벨의 싱글톤-파사드 패턴을 이용, 사용자가 라우트를 생성하고 컨트롤러와 매칭해 줄 수 있도록 제공하고 싶었습니다. 또한 restfull API 등을 지원하고 싶었고, 그러기 위해서는 라우팅-모델 간 매칭이 필요했습니다. (싱글톤-파사드 패턴은 다른 포스팅에서 자세히 다뤄보겠습니다.)

     

    HomeRoute.php

    <?php
    
    Router::get('/', 'HomeController.index')
        ->get('/home', 'HomeController.homes');
    
    Router::post('/login', 'HomeController.login');
    
    Router::post('/users/{id}', 'UserController.findUser');

     

    코어 라우터는 이러한 라우터를 수집하여 컨트롤러와 메소드의 존재 여부, 또는 경로 파라미터 여부를 체크하여 컨트롤러를 인스턴스화하고 메서드를 실행시켰습니다. 만약 경로 파라미터가 있는 경우 순서대로 컨트롤러의 파라미터로 전달할 수 있도록 했습니다. 따라서 restfull API를 설계할 수 있게 되었고, 전체 프로젝트 구조를 노출시키지 않고 뷰를 그려줄 수 있는 구조가 되었습니다. 

     

    요청이 컨트롤러에 도착하면 컨트롤러는 기본 컨트롤러를 상속받게 되는데, 기본 컨트롤러에는 기본적으로 view를 generate 할수 있는 기능이 포함되어 있습니다. 뷰는 php파일로 생성하지만 뷰 안에는 컨트롤러에서 주입되는 변수를 이용할 수 있습니다. 컨트롤러에서 모델을 통해 데이터를 가져오게 되면 데이터를 뷰에 주입하여 뷰에서 데이터를 그려주는 방식입니다. 따라서 뷰는 라우팅, 데이터베이스 참조, 커넥션 해제, 응답 등의 코드를 포함할 필요가 없이 단순 뷰를 표현하는 기능에만 집중할 수 있게 되었습니다.

     

     

    컨트롤러 (Controller)

    컨트롤러는 실제 비즈니스 로직이 동잘할 수 있도록 사용자의 코드를 입력받아 사용자가 원하는 자원을 이용하여 데이터를 핸들링하고 뷰를 생성해 줄 수 있도록 모델과 뷰 인스턴스를 제공하고 세션등을 관리해 주어야 합니다. 요청에서 주입되는 헤더를 파싱 하여 사용자가 원하는 형태로 사용할 수 있게 가공해 주고, 세션 드라이버를 환경 변수로 주입받아 선택할 수 있게 하였습니다. 

     

    라우터에서 컨트롤러에 관련된 데이터를 생성하여 주입해주는데 주입 시 컨트롤러가 인스턴스화됩니다. 인스턴스화된 컨트롤러는 사용자의 bootstrap이라는 함수를 통해 dispatch 되는데 이때 사용자의 세션 데이터를 파싱 하여 유저 데이터를 가져와 reuqest 객체를 생성하고 라라벨의 request->user처럼 동작하게 구성했습니다.

     

     

    이 외에도 오류 처리를 위한 디버거(라기엔 조금 애매한) 와 jquery에 의존적이지 않은 ajax 통신 모듈, CLI를 지원하기 위한 커맨드 구현(artisan 콘셉트) 다양한 기능이 있습니다. 자세한 내용은 깃허브 저장소에서 소스코드로 확인해보실 수 있습니다. 다만 공부의 목적으로 만들어진 프레임워크이다 보니 실사용을 하기에는 애매한 부분이 많아서 지금은 굳이 php를 써야 한다면 라라벨을 많이 사용하는 것 같습니다. 이 프레임워크는 극한의 환경 php5.4 이하.. euc-kr 등을 이용하는 서버에서는 유용할 수 있을 것 같지만 지금은 그런 환경이 거의 없겠죠...?

     

    프레임워크를 제작해보면서 MVC 패턴과 싱글톤, 파사드, 메서드 체이닝 등에 대해서 많이 알게 되었고 깊은 공부가 되었던 것 같습니다. 이 당시 면접질문에 꼭 등장했던 restfull API, ORM, MVC 등을 직접 구현해 보니 이해 수준이 깊어진 것을 많이 느꼈습니다. 크게 어려웠던 프로젝트는 아니었어서 초반 1~2년 차의 개발자들이 한번 도전해 보면 좋을 것 같다는 생각입니다.

     

    질문 있으시다면 댓글로 알려주세요!

    감사합니다.

     

     

     

     

     

     

     

     

     

     

     

     

     

    댓글