Skip to main content

플러그인 개발 가이드

📝 초안 (Draft)

이 문서는 검토 중입니다. 내용이 변경될 수 있습니다.

새로운 플러그인을 개발하는 방법을 설명합니다.

개요

Multi-SaaS Kit 플러그인은 독립적인 기능 모듈로, Core 기능을 확장합니다. 이 가이드에서는 플러그인 개발의 전체 과정을 다룹니다.


플러그인 구조

기본 폴더 구조

packages/plugins/{PluginName}/
├── plugin.json # 메타데이터 (필수)
├── README.md # 문서
├── src/
│ ├── {PluginName}Plugin.php # 메인 플러그인 클래스 (필수)
│ ├── {PluginName}ServiceProvider.php # 서비스 프로바이더 (필수)
│ ├── Contracts/ # 인터페이스
│ │ └── {Name}ServiceInterface.php
│ ├── Services/ # 서비스 로직
│ │ └── {Name}Service.php
│ ├── Config/ # 설정 파일
│ │ └── {plugin-slug}.php
│ ├── Models/ # 모델 (필요시)
│ ├── Events/ # 이벤트 (필요시)
│ └── Listeners/ # 리스너 (필요시)
├── database/
│ └── migrations/ # 마이그레이션
├── resources/
│ └── views/ # 뷰 (필요시)
└── tests/
├── Unit/
└── Feature/

Step 1: plugin.json 작성

플러그인 메타데이터를 정의합니다.

{
"slug": "my-awesome-plugin",
"name": "My Awesome Plugin",
"description": "Adds awesome features to Multi-SaaS Kit",
"version": "1.0.0",
"author": "Your Name",
"license": "MIT",
"tier": "L0",
"price": {
"model": "free",
"amount": 0
},
"dependencies": {
"core": ">=1.1.0",
"extensions": []
},
"providers": [
"MyAwesomePluginServiceProvider"
]
}

필수 필드

필드설명예시
slug고유 식별자 (소문자, 하이픈)"my-awesome-plugin"
name표시 이름"My Awesome Plugin"
version시맨틱 버전"1.0.0"
tier플러그인 티어"L0", "L1", "business", "enterprise"
providers서비스 프로바이더 클래스명["MyPluginServiceProvider"]

가격 모델

// 무료
"price": { "model": "free", "amount": 0 }

// 일회성 구매
"price": { "model": "one_time", "amount": 49.00 }

// 구독
"price": { "model": "subscription", "amount": 9.99, "period": "monthly" }

Step 2: 플러그인 클래스 작성

BasePlugin을 상속하여 메인 플러그인 클래스를 작성합니다.

<?php

declare(strict_types=1);

namespace App\Plugins\MyAwesomePlugin;

use App\Core\Base\Plugin\BasePlugin;
use App\Core\Base\Plugin\Enums\PluginTier;
use App\Core\Base\Plugin\Enums\PriceModel;

class MyAwesomePlugin extends BasePlugin
{
protected string $slug = 'my-awesome-plugin';
protected string $name = 'My Awesome Plugin';
protected string $description = 'Adds awesome features';
protected string $version = '1.0.0';

/**
* 플러그인 부팅 시 실행
*/
public function boot(): void
{
// 라우트 등록, 이벤트 리스너 등록 등
}

/**
* 서비스 등록
*/
public function register(): void
{
// 서비스 컨테이너에 바인딩
}

/**
* 플러그인 설치 시 실행
*/
public function install(): void
{
// 마이그레이션 실행, 초기 데이터 시드 등
}

/**
* 플러그인 제거 시 실행
*/
public function uninstall(): void
{
// 정리 작업
}

public function getTier(): PluginTier
{
return PluginTier::L0;
}

public function getPriceModel(): PriceModel
{
return PriceModel::FREE;
}

public function getDependencies(): array
{
return [
'core' => '>=1.1.0',
];
}
}

Step 3: ServiceProvider 작성

Laravel ServiceProvider를 작성하여 서비스를 등록합니다.

<?php

declare(strict_types=1);

namespace App\Plugins\MyAwesomePlugin;

use App\Plugins\MyAwesomePlugin\Contracts\AwesomeServiceInterface;
use App\Plugins\MyAwesomePlugin\Services\AwesomeService;
use Illuminate\Support\ServiceProvider;

class MyAwesomePluginServiceProvider extends ServiceProvider
{
/**
* Register services
*/
public function register(): void
{
// 설정 파일 병합
$this->mergeConfigFrom(
__DIR__.'/Config/my-awesome-plugin.php',
'my-awesome-plugin'
);

// 서비스 바인딩 (싱글톤)
$this->app->singleton(
AwesomeServiceInterface::class,
AwesomeService::class
);
}

/**
* Bootstrap services
*/
public function boot(): void
{
// 플러그인 비활성화 시 스킵
if (! config('my-awesome-plugin.enabled', false)) {
return;
}

// 마이그레이션 로드
$this->loadMigrationsFrom(__DIR__.'/../database/migrations');

// 뷰 로드 (필요시)
$this->loadViewsFrom(__DIR__.'/../resources/views', 'awesome');

// 라우트 로드 (필요시)
$this->loadRoutesFrom(__DIR__.'/../routes/web.php');

// 설정 퍼블리싱
if ($this->app->runningInConsole()) {
$this->publishes([
__DIR__.'/Config/my-awesome-plugin.php'
=> config_path('my-awesome-plugin.php'),
], 'my-awesome-plugin-config');
}
}

/**
* 제공하는 서비스 목록
*/
public function provides(): array
{
return [
AwesomeServiceInterface::class,
];
}
}

Step 4: Interface 및 Service 작성

Contract (Interface)

<?php

declare(strict_types=1);

namespace App\Plugins\MyAwesomePlugin\Contracts;

interface AwesomeServiceInterface
{
/**
* 주요 기능 메서드
*/
public function doSomethingAwesome(string $input): array;

/**
* 설정 조회
*/
public function getSettings(): array;
}

Service 구현

<?php

declare(strict_types=1);

namespace App\Plugins\MyAwesomePlugin\Services;

use App\Plugins\MyAwesomePlugin\Contracts\AwesomeServiceInterface;

class AwesomeService implements AwesomeServiceInterface
{
protected array $defaultSettings = [
'feature_a' => true,
'feature_b' => false,
'limit' => 100,
];

public function doSomethingAwesome(string $input): array
{
$settings = $this->getSettings();

// 비즈니스 로직 구현
return [
'success' => true,
'result' => "Processed: {$input}",
];
}

public function getSettings(): array
{
return array_merge(
$this->defaultSettings,
config('my-awesome-plugin', [])
);
}
}

Step 5: 설정 파일

<?php

// src/Config/my-awesome-plugin.php

return [
/*
|--------------------------------------------------------------------------
| Plugin Enable
|--------------------------------------------------------------------------
*/
'enabled' => env('MY_AWESOME_PLUGIN_ENABLED', true),

/*
|--------------------------------------------------------------------------
| Feature Settings
|--------------------------------------------------------------------------
*/
'feature_a' => true,
'feature_b' => false,
'limit' => 100,

/*
|--------------------------------------------------------------------------
| SaaS Customization
|--------------------------------------------------------------------------
| SaaS별로 다른 설정이 필요한 경우
*/
'saas_override' => [
// 'saas-slug' => ['limit' => 200],
],
];

Step 6: 마이그레이션

테이블 프리픽스 규칙

플러그인 테이블은 plg_{name}_ 프리픽스를 사용합니다.

<?php

// database/migrations/2026_01_01_000001_create_plg_awesome_logs_table.php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
public function up(): void
{
Schema::create('plg_awesome_logs', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->string('action');
$table->json('data')->nullable();
$table->timestamps();

$table->index('user_id');
$table->index('created_at');
});
}

public function down(): void
{
Schema::dropIfExists('plg_awesome_logs');
}
};

프리픽스 규칙

영역프리픽스예시
Corecore_core_plugins
Extensionsext_ext_guardian_relationships
Pluginsplg_{slug}_plg_awesome_logs

Step 7: 테스트 작성

Unit Test

<?php

namespace App\Plugins\MyAwesomePlugin\Tests\Unit;

use App\Plugins\MyAwesomePlugin\Services\AwesomeService;
use PHPUnit\Framework\TestCase;

class AwesomeServiceTest extends TestCase
{
private AwesomeService $service;

protected function setUp(): void
{
parent::setUp();
$this->service = new AwesomeService();
}

public function test_do_something_awesome_returns_success(): void
{
$result = $this->service->doSomethingAwesome('test input');

$this->assertTrue($result['success']);
$this->assertStringContainsString('test input', $result['result']);
}

public function test_get_settings_returns_default_settings(): void
{
$settings = $this->service->getSettings();

$this->assertArrayHasKey('feature_a', $settings);
$this->assertArrayHasKey('limit', $settings);
}
}

Feature Test

<?php

namespace App\Plugins\MyAwesomePlugin\Tests\Feature;

use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;

class AwesomeFeatureTest extends TestCase
{
use RefreshDatabase;

public function test_plugin_can_be_enabled(): void
{
config(['my-awesome-plugin.enabled' => true]);

$this->assertTrue(config('my-awesome-plugin.enabled'));
}
}

Extensions 의존성 (L1 플러그인)

L1 플러그인은 Extensions에 의존합니다.

// MyL1Plugin.php
public function getTier(): PluginTier
{
return PluginTier::L1;
}

public function getRequiredExtensions(): array
{
return ['guardian']; // Guardian Extension 필요
}

public function getDependencies(): array
{
return [
'core' => '>=1.1.0',
];
}

의존성 체크

// PluginManager가 자동으로 체크
$missing = $pluginManager->checkDependencies('my-l1-plugin');

if (!empty($missing)) {
// Extension: guardian
// 의존성 미충족
}

플러그인 등록

자동 발견

// AppServiceProvider.php
public function boot(): void
{
app(PluginManager::class)->discover();
}

수동 등록

// config/app.php
'providers' => [
// ...
App\Plugins\MyAwesomePlugin\MyAwesomePluginServiceProvider::class,
],

모범 사례

1. Interface 사용

// Good: Interface로 추상화
$this->app->singleton(
AwesomeServiceInterface::class,
AwesomeService::class
);

// Bad: 구현체 직접 바인딩
$this->app->singleton(AwesomeService::class);

2. 설정으로 활성화 제어

public function boot(): void
{
// 비활성화 시 부팅 스킵
if (! config('my-plugin.enabled', false)) {
return;
}
// ...
}

3. 프로젝트 코드 직접 참조 금지

// Bad: 프로젝트 모델 직접 참조
use App\Models\User;

// Good: Interface 사용 또는 config로 주입
$userClass = config('core.models.user', \App\Models\User::class);

4. 이벤트 기반 확장

// 플러그인에서 이벤트 발생
event(new AwesomeActionPerformed($data));

// 프로젝트에서 리스너 등록
Event::listen(AwesomeActionPerformed::class, MyCustomListener::class);

관련 문서