Skip to content

Commit 7066ef4

Browse files
authored
Update trace command to increase the reusability and testability (#318)
1 parent 7c9a32e commit 7066ef4

File tree

5 files changed

+321
-232
lines changed

5 files changed

+321
-232
lines changed

src/BlueprintServiceProvider.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ function ($app) {
6565
$this->app->bind(
6666
'command.blueprint.trace',
6767
function ($app) {
68-
return new TraceCommand($app['files']);
68+
return new TraceCommand($app['files'], app(Tracer::class));
6969
}
7070
);
7171
$this->app->bind(

src/Commands/TraceCommand.php

Lines changed: 12 additions & 231 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,8 @@
33
namespace Blueprint\Commands;
44

55
use Blueprint\Blueprint;
6-
use Blueprint\EnumType;
7-
use Doctrine\DBAL\Types\Type;
6+
use Blueprint\Tracer;
87
use Illuminate\Console\Command;
9-
use Illuminate\Database\Eloquent\Model;
108
use Illuminate\Filesystem\Filesystem;
119
use Illuminate\Support\Str;
1210

@@ -29,15 +27,19 @@ class TraceCommand extends Command
2927
/** @var Filesystem $files */
3028
protected $files;
3129

30+
/** @var Tracer */
31+
private $tracer;
32+
3233
/**
3334
* @param Filesystem $files
34-
* @param \Illuminate\Contracts\View\Factory $view
35+
* @param Tracer $tracer
3536
*/
36-
public function __construct(Filesystem $files)
37+
public function __construct(Filesystem $files, Tracer $tracer)
3738
{
3839
parent::__construct();
3940

4041
$this->files = $files;
42+
$this->tracer = $tracer;
4143
}
4244

4345
/**
@@ -47,236 +49,15 @@ public function __construct(Filesystem $files)
4749
*/
4850
public function handle()
4951
{
50-
$definitions = [];
51-
foreach ($this->appClasses() as $class) {
52-
$model = $this->loadModel($class);
53-
if (is_null($model)) {
54-
continue;
55-
}
56-
57-
$definitions[$this->relativeClassName($model)] = $this->translateColumns($this->mapColumns($this->extractColumns($model)));
58-
}
52+
$blueprint = resolve(Blueprint::class);
53+
$definitions = $this->tracer->execute($blueprint, $this->files);
5954

6055
if (empty($definitions)) {
6156
$this->error('No models found');
62-
63-
return;
64-
}
65-
66-
$blueprint = new Blueprint();
67-
68-
$cache = [];
69-
if ($this->files->exists('.blueprint')) {
70-
$cache = $blueprint->parse($this->files->get('.blueprint'));
71-
}
72-
73-
$cache['models'] = $definitions;
74-
75-
$this->files->put('.blueprint', $blueprint->dump($cache));
76-
77-
$this->info('Traced ' . count($definitions) . ' ' . Str::plural('model', count($definitions)));
78-
}
79-
80-
private function appClasses()
81-
{
82-
$dir = Blueprint::appPath();
83-
84-
if (config('blueprint.models_namespace')) {
85-
$dir .= '/' . str_replace('\\', '/', config('blueprint.models_namespace'));
86-
}
87-
88-
if (!$this->files->exists($dir)) {
89-
return [];
90-
}
91-
92-
return array_map(function (\SplFIleInfo $file) {
93-
return str_replace(
94-
[Blueprint::appPath() . '/', '/'],
95-
[config('blueprint.namespace') . '\\', '\\'],
96-
$file->getPath() . '/' . $file->getBasename('.php')
97-
);
98-
}, $this->files->allFiles($dir));
99-
}
100-
101-
private function loadModel(string $class)
102-
{
103-
if (!class_exists($class)) {
104-
return null;
105-
}
106-
107-
$reflectionClass = new \ReflectionClass($class);
108-
if (
109-
!$reflectionClass->isSubclassOf(\Illuminate\Database\Eloquent\Model::class) ||
110-
(class_exists('Jenssegers\Mongodb\Eloquent\Model') &&
111-
$reflectionClass->isSubclassOf('Jenssegers\Mongodb\Eloquent\Model'))
112-
) {
113-
return null;
114-
}
115-
116-
return $this->laravel->make($class);
117-
}
118-
119-
private function extractColumns(Model $model)
120-
{
121-
$table = $model->getConnection()->getTablePrefix() . $model->getTable();
122-
$schema = $model->getConnection()->getDoctrineSchemaManager();
123-
124-
if (!Type::hasType('enum')) {
125-
Type::addType('enum', EnumType::class);
126-
$databasePlatform = $schema->getDatabasePlatform();
127-
$databasePlatform->registerDoctrineTypeMapping('enum', 'enum');
128-
}
129-
130-
$database = null;
131-
if (strpos($table, '.')) {
132-
[$database, $table] = explode('.', $table);
133-
}
134-
135-
$columns = $schema->listTableColumns($table, $database);
136-
137-
$uses_enums = collect($columns)->contains(function ($column) {
138-
return $column->getType() instanceof \Blueprint\EnumType;
139-
});
140-
141-
if ($uses_enums) {
142-
$definitions = $model->getConnection()->getDoctrineConnection()->fetchAll($schema->getDatabasePlatform()->getListTableColumnsSQL($table, $database));
143-
144-
collect($columns)->filter(function ($column) {
145-
return $column->getType() instanceof \Blueprint\EnumType;
146-
})->each(function (&$column, $key) use ($definitions) {
147-
$definition = collect($definitions)->where('Field', $key)->first();
148-
149-
$column->options = \Blueprint\EnumType::extractOptions($definition['Type']);
150-
});
151-
}
152-
153-
return $columns;
154-
}
155-
156-
/**
157-
* @param \Doctrine\DBAL\Schema\Column[] $columns
158-
*/
159-
private function mapColumns($columns)
160-
{
161-
return collect($columns)
162-
->map([self::class, 'columns'])
163-
->toArray();
164-
}
165-
166-
public static function columns(\Doctrine\DBAL\Schema\Column $column, string $key)
167-
{
168-
$attributes = [];
169-
170-
$type = self::translations($column->getType()->getName());
171-
172-
if (in_array($type, ['decimal', 'float'])) {
173-
if ($column->getPrecision()) {
174-
$type .= ':' . $column->getPrecision();
175-
}
176-
if ($column->getScale()) {
177-
$type .= ',' . $column->getScale();
178-
}
179-
} elseif ($type === 'string' && $column->getLength()) {
180-
if ($column->getLength() !== 255) {
181-
$type .= ':' . $column->getLength();
182-
}
183-
} elseif ($type === 'text') {
184-
if ($column->getLength() > 65535) {
185-
$type = 'longtext';
186-
}
187-
} elseif ($type === 'enum' && !empty($column->options)) {
188-
$type .= ':' . implode(',', $column->options);
189-
}
190-
191-
// TODO: guid/uuid
192-
193-
$attributes[] = $type;
194-
195-
if ($column->getUnsigned()) {
196-
$attributes[] = 'unsigned';
197-
}
198-
199-
if (!$column->getNotnull()) {
200-
$attributes[] = 'nullable';
201-
}
202-
203-
if ($column->getAutoincrement()) {
204-
$attributes[] = 'autoincrement';
205-
}
206-
207-
if (!is_null($column->getDefault())) {
208-
$attributes[] = 'default:' . $column->getDefault();
209-
}
210-
211-
return implode(' ', $attributes);
212-
}
213-
214-
private static function translations(string $type)
215-
{
216-
static $mappings = [
217-
'array' => 'string',
218-
'bigint' => 'biginteger',
219-
'binary' => 'binary',
220-
'blob' => 'binary',
221-
'boolean' => 'boolean',
222-
'date' => 'date',
223-
'date_immutable' => 'date',
224-
'dateinterval' => 'date',
225-
'datetime' => 'datetime',
226-
'datetime_immutable' => 'datetime',
227-
'datetimetz' => 'datetimetz',
228-
'datetimetz_immutable' => 'datetimetz',
229-
'decimal' => 'decimal',
230-
'enum' => 'enum',
231-
'float' => 'float',
232-
'guid' => 'string',
233-
'integer' => 'integer',
234-
'json' => 'json',
235-
'object' => 'string',
236-
'simple_array' => 'string',
237-
'smallint' => 'smallinteger',
238-
'string' => 'string',
239-
'text' => 'text',
240-
'time' => 'time',
241-
'time_immutable' => 'time',
242-
];
243-
244-
return $mappings[$type] ?? 'string';
245-
}
246-
247-
private function translateColumns(array $columns)
248-
{
249-
if (isset($columns['id']) && strpos($columns['id'], 'autoincrement') !== false && strpos($columns['id'], 'integer') !== false) {
250-
unset($columns['id']);
251-
}
252-
253-
if (isset($columns[Model::CREATED_AT]) && isset($columns[Model::UPDATED_AT])) {
254-
if (strpos($columns[Model::CREATED_AT], 'datetimetz') !== false) {
255-
$columns['timestampstz'] = 'timestampsTz';
256-
}
257-
258-
unset($columns[Model::CREATED_AT]);
259-
unset($columns[Model::UPDATED_AT]);
260-
}
261-
262-
if (isset($columns['deleted_at'])) {
263-
if (strpos($columns['deleted_at'], 'datetimetz') !== false) {
264-
$columns['softdeletestz'] = 'softDeletesTz';
265-
}
266-
267-
unset($columns['deleted_at']);
268-
}
269-
270-
return $columns;
271-
}
272-
273-
private function relativeClassName($model)
274-
{
275-
$name = Blueprint::relativeNamespace(get_class($model));
276-
if (config('blueprint.models_namespace')) {
277-
return $name;
57+
} else {
58+
$this->info('Traced ' . count($definitions) . ' ' . Str::plural('model', count($definitions)));
27859
}
27960

280-
return ltrim(str_replace(config('blueprint.models_namespace'), '', $name), '\\');
61+
return 0;
28162
}
28263
}

0 commit comments

Comments
 (0)