J-한솔넷

Laravel 용 패키지 만들기, 삽질기 본문

프로그래밍/PHP

Laravel 용 패키지 만들기, 삽질기

jhansol 2024. 6. 2. 04:11

정말 오랜만에 포스팅을 하는것 같습니다. 이번 포스트는 제가 구상중인 사이트 프로젝트의 일부인 다국어 지원 서비스를 구축하기 위해 언어 검출 및 서비스의 인터페이스와 콘텐츠 언어 출력을 설정하는 역할을 하는 설치 가능한 패키지 개발과정에 관련된 것입니다.
이 포스트의 코드는 Github에 공개할 예정입니다.

지금까지 10여년을 PHP로 개발을 해왔지만 Composer를 이용한 설치용 패키지를 만들어보기는 처음입니다. 몇번에 걸쳐 시행착오도 격고, 구글링, Copilot, ChatGPT에 질문하고 해서 겨우 제가 원하는 단계까지 왔습니다. 이 과정의 기억을 박제하고자 합니다.

composeer.json 생성

우선 아래와 같이 laravel-language-detect 폴더를 만들고, 폴더로 이동하여 'composer init' 명령을 실행했습니다.

mkdir laravel-language-detect
cd laravel-language-detect
composer init

그러면 아래와 같이 패키지와 관련된 정보를 입력하는 프롬프트가 출력됩니다. 아래와 같이 입력했습니다.

Package name (<vendor>/<name>) [root/laravel-language-detect]: j-hansol/laravel-language-detect
Author [n to skip]: SungHyun Jang
Minimum Stability []: dev
Package Type (e.g. library, project, metapackage, composer-plugin) []: library
License []: MIT

Define your dependencies.

Would you like to define your dependencies (require) interactively [yes]? no
Would you like to define your dev dependencies (require-dev) interactively [yes]? no
Add PSR-4 autoload mapping? Maps namespace "JHansol\LaravelLanguageDetect" to the entered relative path. [src/, n to skip]:

{
    "name": "j-hansol/laravel-language-detect",
    "description": "This is a package for language detection and configuration for Laravel.",
    "type": "library",
    "license": "MIT",
    "autoload": {
        "psr-4": {
            "JHansol\\LaravelLanguageDetect\\": "src/"
        }
    },
    "authors": [
        {
            "name": "SungHyun Jang"
        }
    ],
    "minimum-stability": "dev",
    "require": {}
}

Do you confirm generation [yes]? yes

위와 같이 composer.json 파일에 몇가지를 더 추가하거나 수정했습니다.

테스터 케이스 네임스페이스 설정

지금은 사용하고 있지 않지만 다음에 테스트까지 고려하기 위해 미리 추가해두었습니다. 이 내용은 autoload 아래에 추가했습니다.

    "autoload-dev": {
        "psr-4": {
            "JHansol\\LaravelLanguageDetect\\Tests\\": "tests/"
        }
    },

authors 수정

authors 부분에 이름만 들어 있는데, 여기에 역할과, 이메일 주소를 추가했습니다.

    "authors": [
        {
            "role": "Developer",
            "name": "SungHyun Jang",
            "email": "p....2@gmail.com"
        }
    ],

Composer 관련 설정 추가

아래와 같이 컴포저가 패키지관리를 위한 설정을 추가했습니다.

    "minimum-stability": "dev",
    "prefer-stable": true,
    "config": {
        "sort-packages": true,
        "preferred-install": "dist",
        "optimize-autoloader": true,
        "allow-plugins": {
            "kylekatarnls/update-helper": true
        }
    },

required 항목 수정

이 내용은 생성과정에서 패키지를 검색하는 부분이 있으나 제가 아직 사용법을 잘 몰라 아래와 같이 수정했습니다. 이본적으로 이 패키지는 PHP 8.1과 라라벨 프레임워크인 laravel/framework 11.0... 버전에 의존하는 것을 추가해주었습니다.

    "require": {
        "php": "^8.2",
        "laravel/framework": "^11.0"
    },

라라벨용 패키지 정보 추가

라라벨은 최처 설치 후, 패키지 설치 후, Autoload 파일 갱신 후에 라라벨용 패키지를 검출하는 스크립트가 실행됩니다. 아래 내용은 이 때 필요한 것으로 서비스 프로바이더와 파사드 등의 클래스명을 등록해두면 서비스 실행 시에 자동으로 로드될 수 있도록 합니다.

    "extra": {
        "laravel": {
            "providers": [
                "JHansol\\LaravelLanguageDetect\\LanguageDetectServiceProvider"
            ]
        }
    }

패키지 코딩

```src``` 폴더에 언어를 검출하고 설정하는 클래스와 클래스를 컨테이너에 등록하는 서비스 프로바이더를 만들어 두었습니다.

LanguageDetect.php

이 코드는 추후 수정될 것입니다.

<?php

namespace JHansol\LaravelLanguageDetect;

use Illuminate\Foundation\Application;
use Illuminate\Http\Request;

class LanguageDetect {
    private Application $application;
    private Request $request;

    function __construct(Application $application) {
        $this->application = $application;
    }

    public function setRightLocale() : void  {
        $this->request = $this->application->make('request');

        $able_locales = config('language-detect.locales');
        $default_locale = config('language-detect.default_locale');
        $locale_segment = config('language-detect.locale_segment');

        $temp_locale = $this->request->segment($locale_segment);
        $target_locale = null;

        if($temp_locale && in_array($temp_locale, $able_locales)) $target_locale = $temp_locale;
        if(!$target_locale) $target_locale = $default_locale;

        $this->application->setLocale($target_locale);
    }
}

LanguageDetectServiceProvider.php

<?php
namespace JHansol\LaravelLanguageDetect;

use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Foundation\Application;
use Illuminate\Support\ServiceProvider;

class LanguageDetectServiceProvider extends ServiceProvider {
    public function register(): void {
        $this->app->singleton('language_detect', function(Application $application) {
            return new LanguageDetect($application);
        });

        $config = __DIR__.'/config/language_detect.php';
        $this->mergeConfigFrom(
            $config, 'language-detect'
        );
    }

    public function boot(): void {
        $config = __DIR__.'/config/language_detect.php';
        $this->publishes(
            [$config => config_path('language_detect.php')],
            ['language-detect', 'language-detect:config']
        );

        try {
            $language_detect = $this->app->make('language_detect');
            $language_detect->setRightLocale();
        } catch (BindingResolutionException $e) {}
    }
}

기타 환경과 관련 파일도 있으나 여기서는 생략하도록 하겠습니다. 이렇게 하여 필요한 구성이 완료되었습니다.
위 코드를 Github 저장소에 푸시했습니다.

삽질 부분에서 또 언급하겠지만 composer에 의해 패키지가 원할하게 관리되려면 버전을 표시하는 태그를 추가해야 합니다.

라라벨 프로젝트에 적용 삽질

패키지는 무리없이 만들었는데, 여기서 애를 먹었습니다. 패키지가 설치되기는 했어나 라라벨 패키지로 인식하는 부분에서 많은 시행착오가 있었습니다. 여기서는 그 과정을 모두 적을 수는 없고, 주된 원인만 적고 넘어가겠습니다.

Custom 패키지 저장소 설정 삽질

공식 패키지 저장소에 등록된 것이 아니라면 반드시 'repositories' 항목에 해당 정보가 지정되어야 합니다. 저장소 등록을 구글링하여 아래와 같이 등록을 했었습니다.

    "repositories": [
        {
            "type":"package",
            "package": {
                "name": "j-hansol/laravel-language-detect",
                "version":"1.0.0",
                "source": {
                    "url": "https://github.com/j-hansol/language-detect.git",
                    "type": "git",
                    "reference":"main"
                }
            }
        }
    ],

위와 같이 하고 아래와 같이 composer를 이용하여 설치를 했습니다. 성공은 했습니다.

composer require j-hansol/laravel-language-detect

하지만 라라벨 패키지로 인식이 않었습니다. 결격 라라벨의 php artisan package:discover --ansi 콘솔 커멘드를 디버깅을 통해 저의 패키지에 어떤 문제가 있는지 체크를 해봤습니다. 이 명령은 아래 코드에 의해 실행됩니다.

<?php

namespace Illuminate\Foundation\Console;

use Illuminate\Console\Command;
use Illuminate\Foundation\PackageManifest;
use Symfony\Component\Console\Attribute\AsCommand;

#[AsCommand(name: 'package:discover')]
class PackageDiscoverCommand extends Command
{
    /**
     * The console command signature.
     *
     * @var string
     */
    protected $signature = 'package:discover';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Rebuild the cached package manifest';

    /**
     * Execute the console command.
     *
     * @param  \Illuminate\Foundation\PackageManifest  $manifest
     * @return void
     */
    public function handle(PackageManifest $manifest)
    {
        $this->components->info('Discovering packages');

        $manifest->build();

        collect($manifest->manifest)
            ->keys()
            ->each(fn ($description) => $this->components->task($description))
            ->whenNotEmpty(fn () => $this->newLine());
    }
}

그 중에서도 패키지를 검출하는 것은 이 코드입니다.

$manifest->build();

메소드는

    public function build()
    {
        $packages = [];

        if ($this->files->exists($path = $this->vendorPath.'/composer/installed.json')) {
            $installed = json_decode($this->files->get($path), true);

            $packages = $installed['packages'] ?? $installed;
        }

        $ignoreAll = in_array('*', $ignore = $this->packagesToIgnore());

        $this->write(collect($packages)->mapWithKeys(function ($package) {
            return [$this->format($package['name']) => $package['extra']['laravel'] ?? []];
        })->each(function ($configuration) use (&$ignore) {
            $ignore = array_merge($ignore, $configuration['dont-discover'] ?? []);
        })->reject(function ($configuration, $package) use ($ignore, $ignoreAll) {
            return $ignoreAll || in_array($package, $ignore);
        })->filter()->all());
    }

외 코드에서 write() 메소드 안의 코드가 패키지를 검출(필터링)하는 코드인데, 저의 패키지가 이 과정에서 재외되는 것을 확인했습니다. 그리고 패키지 설치와 관련된 정보를 composer/installed.json' 파일의 내용을 바탕으로 하는 것을 알고, 해당 파일을 열어보니 라라벨 패키지와 관련된 내용은 없고, reposotories 항목에 기록된 내용이 저장되어 있었습니다. 전혀 엉뚱한 내용이 저장되어 있으니 인식이 안된 것이었습니다.

        {
            "name": "j-hansol/laravel-language-detect",
            "version": "1.0.0",
            "version_normalized": "1.0.0.0",
            "source": {
                "type": "git",
                "url": "https://github.com/j-hansol/language-detect.git",
                "reference": "main"
            },
            "type": "library",
            "installation-source": "source",
            "install-path": "../j-hansol/laravel-language-detect"
        },

삽질 끝에 패키지 저장소 수정

결국 저장소정보 설정이 잘 못되었다는 것인데, 구글링, Copilot, ChatGPT에게 질문했습니다. 앞에 것들은 전혀 도움이 안되었고, 마지막 ChatGPT가 매우 큰 도움이 되었습니다. ChatGPT의 조력에 따라

    "repositories": [
        {
            "url": "https://github.com/j-hansol/language-detect.git",
            "type": "vcs",
            "reference":"main"
        }
    ],

패키지 저장소 버전 태그 추가

패키기 저장소에 버전을 나타내는 태그가 없으면 위와 같이 수정한 수 설치를 시도하면 아래와 같은 오류가 출력됩니다.

Could not find a version of package j-hansol/laravel-language-detect matching your minimum-stability (stable). Require it with an explicit version constraint allowing its desired stability. 

그래서 아래와 같이 태그를 추가해서 푸시했습니다.

git tag 1.0.0
git push origin 1.0.0

설치 성공, 패키지 인식

드디어 아래의 명령으로 설치되고, 패키지도 정상적으로 인식되었습니다.

composer require j-hansol/laravel-language-detect

..... 이전 생략 .....

> @php artisan package:discover --ansi

   INFO  Discovering packages.  

  j-hansol/laravel-language-detect ......................................................................................... DONE
  laravel/breeze ........................................................................................................... DONE
  laravel/sail ............................................................................................................. DONE
  laravel/tinker ........................................................................................................... DONE
  livewire/livewire ........................................................................................................ DONE
  livewire/volt ............................................................................................................ DONE
  nesbot/carbon ............................................................................................................ DONE
  nunomaduro/collision ..................................................................................................... DONE
  nunomaduro/termwind ...................................................................................................... DONE
  spatie/laravel-ignition .................................................................................................. DONE

패키지와 관련된 파일을 퍼블리싱하는 명령에서 시비스 프로바이더가 검색됩니다.

php artisan vendor:publish

 ┌ Which provider or tag's files would you like to publish? ────────┐
 │ jH                                                                        │
 ├────────────────────────────────────┤
 │   Provider: JHansol\LaravelLanguageDetect\LanguageDetectServiceProvider   │
  ─────────────────────────────────────┘

태그도 잘 검색됩니다.

php artisan vendor:publish

┌ Which provider or tag's files would you like to publish? ────────┐  
│ language │  
├ ────────────────────────────────────┤  
│ Provider: JHansol\\LaravelLanguageDetect\\LanguageDetectServiceProvider │  
│ Tag: language-detect │  
│ Tag: language-detect:config │  
└───────────────── ───────────────────┘  

'프로그래밍 > PHP' 카테고리의 다른 글

Nginx 기반 Docker 개발환경 만들기, 삽질기  (0) 2024.06.18
삽질의 연속  (0) 2024.04.20
메모리 프로파일링?  (0) 2023.11.09
ChatGPT를 이용한 코딩?  (0) 2023.10.26
WSL2를 쓰야하나!!!  (0) 2023.09.14