Skip to content

Commit c6df6f4

Browse files
committed
自原專案抽離
0 parents  commit c6df6f4

File tree

10 files changed

+357
-0
lines changed

10 files changed

+357
-0
lines changed

.editorconfig

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
end_of_line = lf
6+
insert_final_newline = true
7+
indent_style = space
8+
indent_size = 4
9+
trim_trailing_whitespace = true
10+
11+
[*.md]
12+
trim_trailing_whitespace = false
13+
14+
[*.{yml,yaml}]
15+
indent_size = 2

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/vendor
2+
/coverage
3+
.env
4+
.php_cs.cache
5+
.phpunit.result.cache
6+
composer.lock

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# A2Workspace/Laravel-Database-Patcher
2+
3+
一個基於專案的資料庫補丁管理工具。
4+
5+
6+
## Installation | 安裝
7+
8+
此套件尚未發布到 **Packagist** 需透過下列方法安裝:
9+
10+
```
11+
composer config repositories.laravel-database-patcher vcs https://github.com/A2Workspace/laravel-database-patcher.git
12+
composer require "a2workspace/laravel-database-patcher:*"
13+
```
14+
15+
## Usage | 如何使用
16+
17+
現在你可以使用 `db:patch` [Artisan 命令](https://laravel.com/docs/9.x/artisan)來管理資料庫遷移補丁。該命令將會讀取 `database/patches` 下的 [Migrations 遷移檔](https://laravel.com/docs/9.x/migrations)
18+
19+
```
20+
php artisan db:patch
21+
```

composer.json

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"name": "a2workspace/laravel-database-patcher",
3+
"description": "一個基於專案的資料庫補丁管理工具",
4+
"license": "MIT",
5+
"authors": [
6+
{
7+
"name": "Shishamou",
8+
"email": "shishatw225@gmail.com"
9+
}
10+
],
11+
"require": {
12+
"php": "^7.4|^8.0"
13+
},
14+
"require-dev": {
15+
"orchestra/testbench": "6.x"
16+
},
17+
"autoload": {
18+
"psr-4": {
19+
"A2Workspace\\DatabasePatcher\\": "src/"
20+
}
21+
},
22+
"autoload-dev": {
23+
"psr-4": {
24+
"Tests\\": "tests/"
25+
}
26+
},
27+
"extra": {
28+
"laravel": {
29+
"providers": [
30+
"A2Workspace\\DatabasePatcher\\ServiceProvider"
31+
]
32+
}
33+
}
34+
}

phpunit.xml.dist

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<phpunit
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
bootstrap="vendor/autoload.php"
5+
backupGlobals="false"
6+
backupStaticAttributes="false"
7+
colors="true"
8+
verbose="true"
9+
convertErrorsToExceptions="true"
10+
convertNoticesToExceptions="true"
11+
convertWarningsToExceptions="true"
12+
processIsolation="false"
13+
stopOnFailure="false"
14+
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
15+
>
16+
<testsuites>
17+
<testsuite name="Unit">
18+
<directory suffix="Test.php">./tests/Unit</directory>
19+
</testsuite>
20+
<testsuite name="Feature">
21+
<directory suffix="Test.php">./tests/Feature</directory>
22+
</testsuite>
23+
</testsuites>
24+
<php>
25+
<env name="DB_CONNECTION" value="testing"/>
26+
<env name="APP_KEY" value="base64:2fl+Ktvkfl+Fuz4Qp/A75G2RTiWVA/ZoKZvp6fiiM10="/>
27+
</php>
28+
</phpunit>

publishes/.gitignore

Whitespace-only changes.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
class AddPriorityToProductsTable extends Migration
8+
{
9+
/**
10+
* Run the migrations.
11+
*
12+
* @return void
13+
*/
14+
public function up()
15+
{
16+
Schema::table('products', function (Blueprint $table) {
17+
$table->tinyInteger('priority')->comment('優先度')
18+
->default(0);
19+
});
20+
}
21+
22+
/**
23+
* Reverse the migrations.
24+
*
25+
* @return void
26+
*/
27+
public function down()
28+
{
29+
Schema::table('products', function (Blueprint $table) {
30+
if (Schema::hasColumn($table->getTable(), 'priority')) {
31+
$table->dropColumn('priority');
32+
}
33+
});
34+
}
35+
}

publishes/patches/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Stubs
2+
3+
此目錄用來放置 `patches` 檔案。
4+
5+
`artisan db:patch` 將會讀取當前目錄的遷移檔案。

src/Commands/DbPatchCommand.php

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
<?php
2+
3+
namespace A2Workspace\DatabasePatcher\Commands;
4+
5+
use Illuminate\Console\Command;
6+
use Illuminate\Support\Collection;
7+
use Illuminate\Support\Str;
8+
use Illuminate\Filesystem\Filesystem;
9+
use Symfony\Component\Finder\Finder;
10+
use Symfony\Component\Finder\SplFileInfo;
11+
12+
class DbPatchCommand extends Command
13+
{
14+
/**
15+
* The name and signature of the console command.
16+
*
17+
* @var string
18+
*/
19+
protected $signature = 'db:patch {filter? : 指定或搜尋檔案}
20+
{--r|revert : Revert the patch file}';
21+
22+
/**
23+
* The console command description.
24+
*
25+
* @var string
26+
*/
27+
protected $description = '執行資料庫補丁檔';
28+
29+
/**
30+
* The filesystem instance.
31+
*
32+
* @var \Illuminate\Filesystem\Filesystem
33+
*/
34+
protected $files;
35+
36+
/**
37+
* 要排除的檔案
38+
*
39+
* @var string[]
40+
*/
41+
protected array $excludedNames = [
42+
'README.md',
43+
];
44+
45+
/**
46+
* Execute the console command.
47+
*
48+
* @return int
49+
*/
50+
public function handle()
51+
{
52+
$file = $this->determinePatchFile();
53+
if (!$file) {
54+
return 1;
55+
}
56+
57+
$path = $file->getRealPath();
58+
$path = Str::after($path, base_path());
59+
60+
// 這邊我們判斷
61+
// 若為回復模式則呼叫滾回命令並傳入補丁檔案路徑
62+
if ($this->option('revert')) {
63+
$this->info("Running: php artisan migrate:rollback --path={$path}");
64+
65+
$this->call('migrate:rollback', [
66+
'--path' => $path,
67+
]);
68+
}
69+
70+
// 呼叫遷移命令並傳入補丁檔案路徑
71+
else {
72+
$this->info("Running: php artisan migrate --path={$path}");
73+
74+
$this->call('migrate', [
75+
'--path' => $path,
76+
]);
77+
}
78+
79+
return 0;
80+
}
81+
82+
/**
83+
* 決定要被使用的檔案
84+
*
85+
* @return \Symfony\Component\Finder\SplFileInfo|null
86+
*/
87+
protected function determinePatchFile()
88+
{
89+
// 取得 patches 目錄的檔案列表,若結果為空則提前終止
90+
$files = $this->getFileList();
91+
if ($files->isEmpty()) {
92+
$this->warn('找不到任何補丁檔案');
93+
94+
return null;
95+
}
96+
97+
// 這邊處理有輸入 filter 參數的場合。
98+
if ($inputFilter = $this->getFilterInput()) {
99+
$filtered = $files->filter(function (SplFileInfo $file) use ($inputFilter) {
100+
return Str::contains($file->getRelativePathname(), $inputFilter);
101+
});
102+
103+
if ($filtered->isEmpty()) {
104+
$this->error('找不到符合的補丁檔案');
105+
106+
return null;
107+
}
108+
109+
$files = $filtered;
110+
}
111+
112+
return $this->choiceFromFileList('選擇補丁檔案', $files);
113+
}
114+
115+
/**
116+
* @return string
117+
*/
118+
protected function getFilterInput()
119+
{
120+
return trim($this->argument('filter'));
121+
}
122+
123+
/**
124+
* @return \Illuminate\Support\Collection<int, \Symfony\Component\Finder\SplFileInfo>
125+
*/
126+
protected function getFileList(): Collection
127+
{
128+
$paths = [database_path('patches')];
129+
130+
return collect($paths)
131+
->map(fn ($path) => $this->getFileListInDirectory($path))
132+
->collapse();
133+
}
134+
135+
/**
136+
* 回傳指令目錄下的檔案。排除目錄的與 $excludedNames 指定的檔案。
137+
*
138+
* @param string $path
139+
* @return \Symfony\Component\Finder\SplFileInfo[]
140+
*/
141+
protected function getFileListInDirectory(string $path): array
142+
{
143+
$finder = Finder::create()
144+
->filter(function (SplFileInfo $file) {
145+
return !in_array($file->getRelativePathname(), $this->excludedNames);
146+
})
147+
->files()
148+
->in($path)
149+
->depth(0)
150+
->sortByName();
151+
152+
return iterator_to_array($finder, false);
153+
}
154+
155+
/**
156+
* 讓使用者自檔案列表中選取一個。
157+
*
158+
* @param string $question
159+
* @param \Illuminate\Support\Collection $files
160+
* @return \Symfony\Component\Finder\SplFileInfo
161+
*/
162+
protected function choiceFromFileList($question, Collection $files): SplFileInfo
163+
{
164+
$formattedFiles = $files->map(function (SplFileInfo $file) {
165+
$label = $file->getRelativePathname();
166+
167+
// 加上符號隔開避免 choice 時索引與檔案名稱混淆 (這應該是 Symfony 的 bug 待查證)
168+
$label = "-> {$label}";
169+
170+
return [$label, $file];
171+
});
172+
173+
$options = $formattedFiles->pluck(0)->toArray();
174+
175+
$input = $this->choice($question, $options);
176+
177+
return $formattedFiles->first(function ($value) use ($input) {
178+
return $value[0] === $input;
179+
})[1];
180+
}
181+
}

src/ServiceProvider.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace A2Workspace\DatabasePatcher;
4+
5+
use Illuminate\Support\ServiceProvider as IlluminateServiceProvider;
6+
7+
class ServiceProvider extends IlluminateServiceProvider
8+
{
9+
/**
10+
* Bootstrap the application services.
11+
*
12+
* @return void
13+
*/
14+
public function boot()
15+
{
16+
$this->publishes([
17+
__DIR__ . '/../publishes/patches' => database_path('patches'),
18+
], '@a2workspace/laravel-database-patcher');
19+
}
20+
21+
/**
22+
* Register the application services.
23+
*
24+
* @return void
25+
*/
26+
public function register()
27+
{
28+
$this->commands([
29+
Commands\DbPatchCommand::class,
30+
]);
31+
}
32+
}

0 commit comments

Comments
 (0)