Composer “Could not load package ezsystems/ezplatform” Error

어제 저녁 laravel 8 업데이트 소식과 함께 테스트를 하려고 했는데, 갑작스럽게 composer에서

  [RuntimeException]
  Could not load package ezsystems/ezplatform in https://packagist.org: UnexpectedValueException] Could not parse version constraint dev-load-varnish-only-when-used as ^2.0@dev: Invalid version string "^2.0@dev"

  [UnexpectedValueException]
  Could not parse version constraint dev-load-varnish-only-when-used as ^2.0@dev: Invalidversion string "^2.0@dev"

이런 에러가 표시되면서 모든 프로젝트에서 작동이 되지 않았다.
진짜 laravel 지웟다 다시깔고, os 도 다시 설치해보다가 도저히 방법을 못찾겟던 와중

https://stackoverflow.com/questions/63801165/composer-could-not-load-package-invalid-version-string

composer 1.10.11이 망가졌다고 한다..
composer self-update를 통해 문제를 해결했다. (내 5시간..)

[Tip] Laravel with method specific columns

Laravel의 with method는 relation된 다른 테이블의 값을 함께 가져올 때 사용된다.

다만 이번 프로젝트에서 users 테이블의 연결된 logs의 timestamp 값 (created_at, updated_at)을 가져오고 싶었는데 hidden으로 감추기엔 모두 사용되는 값이였고,
그렇다고 모두 가져오기엔 필요없는 데이터와, 유저에게 표시되면 안되는 부분도 있어서 생성일만 가져오는게 제일인 상황에서 아래와 같이 코드를 짰었다.

// ...
public function history()
{
    // ...
    $logs = $user->logs;
    foreach ($logs as $log) {
        $user->log = [
            'created_at' => $log->created_at,
            'updated_at' => $log->updated_at,
        ];
    }
    // ...
    return $user;
}
// ...

리펙토링을 하는중에 가만 생각해보니 Raw로 쿼리를 짯을 땐 select로 원하는것만 가져오면되는데,
이걸 또 with으로 가져오다보니 select를 따로 하려면 callback을 짜야하는데 간단히 select 정도만 하면되는데 콜백으로 가독성이 더 떨어질까 생각하는 와중에 with에서 callback없이 원하는 column만 가져올 수 있는 방법을 찾앗다.

$user->with('logs:id,created_at,updated_at')->get()

with에 relation method명과 원하는 컬럼 명을 위와같은 포맷으로 전달해 주면 해당 결과만 표시되고 그외의 결과는 표시되지 않는다.

callback보다 보기 깔끔하고, 위에 코드보다 깨끗(?) 하다.

다만 주의해야 하는점이 반드시 ‘id‘값을 포함해야 한다. 예를 들어
$user->with('logs:created_at,updated_at')->get() 와 같이 작성하면 작동되지 않는다.
id 값을 기준으로 가져오기때문에 반드시 logs:id,를 넣어주어야한다.

[Tip] UTF-8 Bom 문제 해결방법

PHP 에서 string을 외부에서 받아와 json_decode 등을 할 때 아무 이유없이 syntax error를 반환 하는 경우가 있다.

갑자기 Syntax error..

이럴땐 여러가지 의심을 할 수 있는데, 이번 이슈의 경우 Data를 가져온 곳이 web임을 가만했을때 가장 추측할 수 있는 것은 UTF8-BOM이다.

BOM은 (Byte Oder Mark, BOM)으로 UTF-8, UTF-16을 구분하기 위해 윈도우에서 사용된다고 한다.

function removeUtf8Bom(string $string): string
{
    return str_replace("\xEF\xBB\xBF", '', $string);
}

이런 경우 간단히 위 함수를 통해 BOM을 제거 할 수 있다.

[laravel] GuLa library

소개

GuL(a)ibrary (이하 구라블러리 ㅎ)는 sir.kr에서 만든 그누보드와 영카트의 테이블의 모델들을 묶어둔 라이브러리입니다.
바로 DB를 바꿀순 없고 api, scheduler등 더 다양한 기능들을 기존 그누보드, 영카트를 유지하면서 laravel로 구현하기 위해 만들어졌습니다.

실은 그누보드로 만들어진 사이트 서로 연동 하는 작업하는데 raw php론 너무 힘들어서 만들엇습니다. ㅎ

특징

구라블러리는 그누보드와 Laravel에서 사용할 DB를 따로 분리하여 운영이 가능합니다.
구라브러리는 Laravel의 relation을 그누보드 DB에서 사용할수 있습니다.
구라브러리는 G5ModelFactory를 통해 모델이 존재하지 않는 경우 runtime상에서 인스턴스 생성이 가능합니다.
구라브러리는 그누보드에서 생성된 g5_write_ 테이블을 G5Member 모델을 통해 자동으로 연결해줍니다,

모두 구랍니다

설치

composer require silnex/gula

.env

GULA_DB_HOST=gnuboard.db.host
GULA_DB_PORT=3306 # (default: 3306)
GULA_DB_DATABASE=gnuboard_db
GULA_DB_USERNAME=gnuboard_db_user
GULA_DB_PASSWORD=gnuboard_db_pass
GULA_CHARSET=utf8mb4 # DB 문자셋 (default: utf8mb4)
GULA_COLLATION=utf8mb4_unicode_ci # DB 문자셋 (default: utf8mb4_unicode_ci)

위처럼 env를 설정하여 그누보드와 Laravel의 DB를 각각 운영하실 수 있습니다.

예제

<?php
// 그누보드
use SilNex\GuLa\Models\Gnu\G5Member;
$g5Member = new G5Member;
$g5Member->all();
$g5Member->where('mb_id', '=', 'admin');

// 영카트
use SilNex\GuLa\Models\Young\G5ShopItem;
$g5ShopItem = new G5ShopItem;
$g5ShopItem->all();

관계-Relationships

Laravel을 쓰면서 가장 마음에 들었엇고 또 가장 많이 사용하는 Relation도 넣었습니다.
다만 그누보드는 외래키가 따로 존재하지않고 mb_id 등 AI를 이용한 검색도 하지 않아 직접(…) 각각의 모델에 넣어줬습니다.

한땀한땀….
<?php
use SilNex\GuLa\Models\Gnu\G5Member;
$g5Member = new G5Member;

$admin = $g5Member->where('mb_id', '=', 'admin')->first();
$admin->g5Points()->get();

위에 구현을 통해 계정을 가져온 다음 계정의 포인트를 가져오는 식의 활용이 가능합니다.

G5ModelFactory

그누보드는 게시판에 따라 각각 테이블을 만드는데 이때 만들어진 테이블을 아래의 커스텀 모델로 만들어 사용할 수도 있지만, 코드상으로 factory를 통해 생성할 수 있습니다.

<?php
// Anywhere

use SilNex\GuLa\G5ModelFactory;

// ['connection', 'table_name']
$G5WriteFree = new G5ModelFactory(['gula', 'g5_write_free']);
$G5WriteFree->get();

그누보드 게시판 자동연동

구라이브러리는 그누보드에서 생성되는 g5_write_로 시작하는 게시판 테이블을 G5Member 모델을 통해서 자동으로 연동해줍니다.

<?php
use SilNex\GuLa\Models\Gnu\G5Member;

var_dump(class_exists("\SilNex\GuLa\Gnu\G5WriteFree")); // bool(false)

$g5Member = new G5Member;
$admin = $g5Member->where('mb_id', '=', 'admin')->first();
$admin->g5WriteFree()->first(); // g5_write_free의 mb_id가 admin인 게시글을 가져온다.

커스텀 모델

만약 직접 만드신 테이블 혹은 PG사의 테이블 처럼 이후에 추가되는 경우 직접 추가 하실 수 있습니다.

<?php
// App/CustomG5Model.php

namespace App;

use SilNex\GuLa\G5Model;

class CustomG5Table extends G5Model
{
    protected $table = 'custom_g5_table';

    // code here
}

커멘드 (개발중)

아직 개발중이긴 하지만 artisan 커맨드를 통해 쉽게 G5모델을 만들수 있게 개발중입니다.
대략적인 사용방법은 아래와 같습니다.

php artisan g5model:write free # app/G5Models/G5WriteFree.php 생성
php artisan g5model:pg # PG와 관련된 모델 생성


버그/아이디어 제보

직접 프로젝트에 사용하면서 만들다보니 제가 미처 테스트 못하는 부분은 Github 이슈를 통해서 제보해주시면 감사하겠습니다!

그누보드 Hook 사용법

최근 그누보드 업데이트 스크립트를 만들면서, 코어 코드가 수정되었을때 자동으로 업데이트 해주는 기능 구현에 애를 먹고 있다.
하지만 그누보드 5.4부터 새로 추가된 hook을 사용하면 코어코드에 대한 수정을 최소한으로 하거나 혹은 코어 수정없이 진행가능하기에 업데이트가 용이하다.

Hook

Hook은 단어 그대로 갈고리처럼 코드 중간에 갈고리를 걸어, 코드를 실행시켜주는 기능을 한다.
혹은 이벤트-리스너와 같은 기능을 한다고 생각해도 좋다.

그누보드가 Hook을 어떻게 구현 했는지 궁금하면, https://github.com/Josantonius/PHP-Hook 의 코드를 가져다 썻다고 하니 보도록 하고 사용 방법만 간단히 익혀보자.

구현 내용 및 분석

간단히 1대1 문의가 왓을 때 slack이나 기타 메신저로 알려주는 부분을 구현해보자.

먼저 그누보드에선 1대1문의를 작성할 때 /bbs/qawrite.php에서 작성을 한 뒤 /bbs/qawrite_update.php로 데이터를 전송해 DB에 저장한다.

이때 /bbs/qawrite_update.php파일 321줄에

<?php
# ...
run_event('qawrite_update', $qa_id, $write, $w, $qaconfig);
# ...

run_event 함수에서 qawrite_update 라는 이벤트를 $qa_id, $write, $w, $qaconfig 변수 4개와 함께 실행하고(일으키고)있다.

하지만 현재는 qawrite_update라는 이벤트에 아무런 내용이 없으므로 아무런 기능을 하지 않는다.
이제 바깥(코어 파일이 아닌 곳)에서 qawrite_update 이벤트가 실행될 때 slack 메시지를 보내는 기능을 추가해보자.

구현

그누보드는 기본적으로 /extend 폴더에 있는 파일은 외부 플러그인 취급하며, 따로 include('common.php');해주지 않아도 자동으로 .php로 끝나는 파일은 common.php가 로딩된 후 실행시켜준다. (common.php 659~667번째줄)

/extend/slack.notice.php

<?php
if (!defined('_GNUBOARD_')) exit; // 개별 페이지 접근 불가

add_event('qawrite_update', 'slack_notice', G5_HOOK_DEFAULT_PRIORITY, 4);

function slack_notice($qa_id, $write, $w, $qaconfig)
{
    global $g5;

    if($w == '' || $w == 'r') {
        $text = "";
        $qa = sql_fetch("SELECT * FROM `{$g5['qa_content_table']}` WHERE `qa_id` = {$qa_id}");
        $text .= "[{$qa['qa_category']}] {$qa['qa_subject']}\n\t=> {$qa['qa_content']}";

        $slack = new Slack();
        $slack = $slack->sendMessage($text);
    }
}

위 코드를 간략히 분석해보면
add_event >
1. qawrite_update 이벤트가 실행될 때, ‘slack_notice‘ 라는 함수를 실행해라. (Variable function)
2. qawrite_update이벤트가 실행될 때 등록된 함수들 중에서 G5_HOOK_DEFAULT_PRIORITY(8) 번째로 실행 해라. (이를 이용하면 순서의 의존적인 여러 함수를 순차적으로 실행 할 수 있다.)
3. 전달되는 파라미터는 총4개 이다.

function slack_notice >
1. 인자로 $qa_id, $write, $w, $qaconfig를 전달받고,
2. 만약 새로운 등록($w == '')이거나, 재문의($w == 'r') 일경우
3. 문의 카테고리(qa_category), 제목(qa_subject), 내용(qa_content)을 담아 slack으로 전송한다.

정리

이 처럼 미리 run_event 가 들어 있는 곳에 내용을 보고 코어 파일에 수정없이 기능을 추가할 수 있어서, 이후 업데이트할 때 변경된 파일을 일일히 신경쓰지 않고 바로바로 엎을 수 있다.

또한, 원하는 위치에 내용이 없다고 하더라고 해당부분에 run_event('원하는_이벤트_이름', $arg1, $arg2) 이렇게 한 줄만 추가해 주고 extend 같은 외부 파일에서 add_event()함수를 통해 실행해주면 코어 코드에 대한 수정을 최소화 할 수 있다.

마지막으로 위에서본 add_event ( 이벤트 추가 )와 run_event ( 이벤트 실행 )말고도

add_replace ( 내용 변경 )
run_replace ( 내용 변경 적용 )
delete_event ( 이벤트 추가된 것을 취소함 )
delete_replace ( 내용 변경된 것을 취소함 )와 같은 함수들이 있으니 그누보드 메뉴얼에서 내용을 확인하고 다양하게 사용해볼 수 있다.

마무리

곧 훈련소 가는데(크흡..) 가기전에 블로그 업데이트 한번 해고 싶어서 최근 유용하게 쓰고있는 그누보드 hook에 대한 내용을 정리해 봤습니다.
기분이 싱숭생숭하다보니 내용이 중구난방일 것같은데 잘못된 것이 있다면 댓글로 남겨주시면 훈련소 다녀와서(ㅎ) 수정하겠습니다.

PHPUnit에서 Laravel 에러 메시지 출력 패키지(Collision) 사용

원글 Collision PHPUnit Listener With Laravel

Collision‘는 Laravel에서 아래 이미지처럼 상세한 오류 내역을 출력해주는 패키지이다.

View image on Twitter
[사진] Nuno maduro 트위터

Laravel로 개발하면서 정말 좋은 기능임을 알고있었지만, phpunit과 같은 외부 테스트 도구들을 통해 테스트할 때 오류가 발생해도 아래 이미치 처럼 단순히 Exception stack만 보여줘 ‘Collision’ 처럼 자세한 내용을 제공 받지 못해 아쉬웠던 적이 더러 있었다.

하지만 작년 12월 31일 Collision 패키지 개발자가 트위터를 통해 PHPUnit adapter를 언급하면서 간단한 설정만으로 phpunit에서도 Collision 화면을 볼 수 있게 되었다.

기존 phpunit은 아래 이미지처럼 Exception이 일어나면 단순히 Exception stack만을 표시 했지만,

[laravel-new] phpunit 에러 메시지

phpunit.xml파일에 Collision 리스너를 추가한다.

<listeners>
    <listener class="NunoMaduro\Collision\Adapters\Phpunit\Listener" />
</listeners>
[laravel-new] Listener 추가 후 phpunit 에러 메시지

그러면 위와 같이 phpnuit에서도 깔끔하게 출력되며 좀 더 나은 디버깅 환경을 제공해준다.

[VSCode] NPM Rename Permission Error

NPM Rename permission error

VScode에서 WSL Remote를 지원하면서 NPM을 이용한 패키지 관리도 VSCode 상에서 진행을 하는중 https://github.com/Microsoft/WSL/issues/2097 이슈처럼 계속 Rename 권한 이슈때문에 npm install이 진행되지 않는다..

해결 방법은 단순한데 VSCode를 끄고 npm install을 진행하면 된다.
VSCode가 실행되는 중에 npm install을 사용하면 VSCode의 폴더를 캐싱하는 부분과 겹쳐지면서 파일 쓰기가 되지 않아 생기는 문제로 보여진다.

이 문제로 고생하는 사람이 꾀나 있는데 이 글이 도움이 되었으면 좋겠다.