[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 이슈를 통해서 제보해주시면 감사하겠습니다!

[PHP] Multiple for(each), while escape

TL;TR;

PHP는 중접된 반복문을 원하는 개수만큼 탈출할 수 있는,
break 2;continue 2;를 지원한다.

다중 for문

PHP가 아니더라도 어디에든 다중 for문을 사용하는 것을 본적이 있을 것이다.
물론, for문이 중첩되어있는걸 보면 경기를 일으키는 사람도 있지만,
어쩔 수 없이 써야할수도 있고, 지식의 한계와 마감의 촉박함이 다중 for문을 만들어 낼 수 있는 부분은 어쩔수 없다.

뭐 그렇다치고, 모던 PHP그룹을 보던중 신기한(?) 문법을 발견해 정리겸 글을 남긴다.

어마아머한 3중 for문

<?php
$list_0 = [0, 1];
$list_1 = [2, 3];
$list_2 = [4, 5];
foreach ($list_0 as $v_0) {
    echo $v_0;
    foreach ($list_1 as $v_1) {
        echo $v_1;
        foreach ($list_2 as $v_2) {
            echo $v_2;
        }
    }
}

보통 이런 for문을 보면 현기증 부터오고 개발자의 얼굴을 보고 싶어지지만, 일단 넘어가기로 하고, 해당 코드를 실행하면 02453451245345 가 출력된다.

여기서 만약 3번째 for문 에서 break; 를 하게되면 당연스럽게도 2번째 for문으로 넘어가게된다.

하지만 3번째 for문 이후 첫번째 for문으로 넘어가기 위해선 아래와 같이 해주어야 한다.

<?php
$list_0 = [0, 1];
$list_1 = [2, 3];
$list_2 = [4, 5];
foreach ($list_0 as $v_0) {
    echo $v_0;
    foreach ($list_1 as $v_1) {
        echo $v_1;
        foreach ($list_2 as $v_2) {
            echo $v_2;
            break;
        }
        break;
    }
}

지금과 같이 단순한 경우엔 문제가 없지만, “만약 $v_2가 짝수이면 첫번째 for문으로 가고 홀수이면 그냥 진행 하고 싶다.”과 같은 조건문이 생기게 된다면 아래와 같이 복잡한 코드가 될것이다.

<?php
$list_0 = [0, 1];
$list_1 = [2, 3];
$list_2 = [4, 5];
foreach ($list_0 as $v_0) {
    echo $v_0;
    foreach ($list_1 as $v_1) {
        echo $v_1;
        foreach ($list_2 as $v_2) {
            echo $v_2;
            if ($v_2 % 2 === 0) break;
        }
        if ($v_2 % 2 === 0) break;
    }
}

if ($v_2 % 2 === 0) break;가 반복되면서 안그래도 복잡한 코드를 더욱 복잡하게 만들고있다. 또 이후에 홀수일때 break 하고 싶다면, 해당 구문을 모두 찾아 바꿔줘야하는 번거로움까지 있다.

이럴 때 간단히 원하는 개수만큼 break; 해주는 php문법인 break 2;를 사용할 수 있다.

<?php
$list_0 = [0, 1];
$list_1 = [2, 3];
$list_2 = [4, 5];
foreach ($list_0 as $v_0) {
    echo $v_0;
    foreach ($list_1 as $v_1) {
        echo $v_1;
        foreach ($list_2 as $v_2) {
            echo $v_2;
            if ($v_2 % 2 === 0) break 2;
        }
    }
}

이렇게 하면 만약 $v_2가 짝수일때 break 2;를 통해 바로 첫번째 for문까지 바로 점프하게 된다.

물론 break말고 continue도 위와같이 중첩된 반복문에서 원하는 개수만큼의 반복문을 탈출 할 수 있는 continue 2;문법을 지원한다.

[PHP] Distinction Null and Undefined

TL;TR

<?php array_key_exists('value', get_defined_vars()) ?>


대게 주의에선 PHP에선 Null과 Undefined를 구별하지 못한다고 알려져 있다.
나 또한 Null과 Undefined를 구별할 수 있는 방법이 없다고 생각했지만, 성능상의 이슈를 제외하고, PHP에서도 Null과 Undefined를 구분 할 수 있다.

<?php 
    $a = null;
    var_dump(isset($a), isset($b));
    // Result : bool(false) bool(false)
    var_dump($a, $b);
    // Result : NULL NULL
?>

PHP에서 변수의 유효성을 확인하기 위해 가장 많이 사용되는 함수인 isset은 해당변수가 ‘정의 되어 있지 않’거나 변수의 값이 ‘Null’인 경우에 false를 반환한다.

즉, php의 변수의 유효성을 판단하기 위한 함수은 isset은 ‘정의되지 않음’과 ‘Null’을 구별 하지 못한한다.

또한, 6번째 줄의 결과값을 보면 $a$b의 출력값은 NULL로 동일하다.
(물론 php Notice가 b는 선언되어있지 않다고 알경고를 띄워주기는 한다.)

JS와 같이 php와 같이 공부를 많이하는 언어의 경우
undefined라는 타입이 있으며, value == undefined와 같이 사용이 가능하기에
php에선 구분이 불가능하다 라고 알려진것같다.
(적어도 나는 이렇게 생각했다…)

하지만 PHP에서도 undefined와 Null을 구별할 수 있는데 이는 get_defined_vars()를 사용해 현재 선언된 변수들의 목록에서 찾는 방법이다.

<?php
    $a = null;
    var_dump(array_key_exists('a', get_defined_vars()), array_key_exists('b', get_defined_vars()));
    // Result : bool(true) bool(false)

    unset($a);
    var_dump(array_key_exists('a', get_defined_vars()));
    // Result : bool(false)

퍼포먼스 상으론 그렇게 좋은 방식은 아닌것 같지만, 이를 통해서 해당 변수가 정의되어있는지 단순히 null인지 확인 할 수 있는 확실한 방법이다.
(이외의 다른 방법이 있다면 댓글로..)

[Laravel] artisan 자동완성 (bash_completion)

source : https://gitlab.com/balping/artisan-bash-completion

기본적으로 자동완성 기능이 켜져 있는(. /etc/bash_completion)이 작동 중이라는 가정하에 작동한다.

TL; DR
$ composer require balping/artisan-bash-completion
$ echo 'alias artisan="php artisan"' >> ~/.bash_aliases
$ echo '. ~/.bash_aliases' >> ~/.bashrc
$ sudo cp ~/{vendor path}/vendor/balping/artisan-bash-completion/artisan /etc/bash_completion.d/artisan
$ source .bashrc

결과물

silnex@uServer-For-Laravel:~/blog$ artisan [tab] [tab]
app:name             db:seed              list                 make:factory         make:observer        migrate              optimize             queue:listen         schedule:finish      view:cache
auth:clear-resets    down                 list:forBash         make:job             make:policy          migrate:fresh        optimize:clear       queue:restart        schedule:run         view:clear
cache:clear          dump-server          make:auth            make:listener        make:provider        migrate:install      package:discover     queue:retry          serve
cache:forget         env                  make:channel         make:mail            make:request         migrate:refresh      preset               queue:table          session:table
cache:table          event:generate       make:command         make:middleware      make:resource        migrate:reset        queue:failed         queue:work           storage:link
clear-compiled       help                 make:controller      make:migration       make:rule            migrate:rollback     queue:failed-table   route:cache          tinker
config:cache         inspire              make:event           make:model           make:seeder          migrate:status       queue:flush          route:clear          up
config:clear         key:generate         make:exception       make:notification    make:test            notifications:table  queue:forget         route:list           vendor:publish
silnex@uServer-For-Laravel:~/blog$ artisan