Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add model:prune console command #1263

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions modules/system/ServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,8 @@ protected function registerConsole()
$this->registerConsoleCommand('create.settings', \System\Console\CreateSettings::class);
$this->registerConsoleCommand('create.test', \System\Console\CreateTest::class);

$this->registerConsoleCommand('model.prune', Console\PruneCommand::class);

$this->registerConsoleCommand('winter.up', \System\Console\WinterUp::class);
$this->registerConsoleCommand('winter.down', \System\Console\WinterDown::class);
$this->registerConsoleCommand('winter.update', \System\Console\WinterUpdate::class);
Expand Down
75 changes: 75 additions & 0 deletions modules/system/console/PruneCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

namespace System\Console;

use Exception;
use Illuminate\Database\Console\PruneCommand as BasePruneCommand;
use Illuminate\Database\Eloquent\MassPrunable;
use Illuminate\Database\Eloquent\Prunable;
use Illuminate\Support\Collection;
use Winter\Storm\Support\Facades\Event;
use System\Helpers\ModelFinder;

class PruneCommand extends BasePruneCommand
{
/**
* {@inheritDoc}
*/
protected function models(): Collection
{
if (! empty($models = $this->option('model'))) {
return collect($models)->filter(function ($model) {
return class_exists($model);
})->values();
}

$except = $this->option('except');

return collect($this->findModels())
->when(! empty($except), function ($models) use ($except) {
return $models->reject(function ($model) use ($except) {
return in_array($model, $except);
});
})->filter(function ($model) {
return class_exists($model) && $this->isPrunable($model);
})->values();
}

/**
* {@inheritDoc}
*/
protected function isPrunable($model): bool
{
try {
$uses = class_uses_recursive($model);
} catch (Exception $e) {
return false;
}

return in_array(Prunable::class, $uses) || in_array(MassPrunable::class, $uses);
}

/**
* Find all models.
*/
protected function findModels(): array
{
/**
* @event system.console.model.prune.findModels
* Give the opportunity to return an array of Models to prune.
*
* Example usage:
*
* Event::listen('system.console.model.prune.findModels', function () {
* return ['example model' => '\System\Models\File'];
* });
*
*/
$models = Event::fire('system.console.model.prune.findModels', [$this], true);
if (is_array($models)) {
return $models;
}

return ModelFinder::findModels();
}
}
57 changes: 57 additions & 0 deletions modules/system/helpers/ModelFinder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

namespace System\Helpers;

use Winter\Storm\Support\Facades\File;
use Illuminate\Support\Str;
use Symfony\Component\Finder\Finder;

class ModelFinder
{
/**
* Find all models in core and active plugins.
*
* @return Collection
*/
public static function findModels(): array
{
$models = [];
$models[] = static::findModuleModels();
$models[] = static::findActivePluginsModels();

return collect($models)->flatten()->all();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we doing any sort of checking to make sure the classes are actual model classes?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really, no.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@LukeTowers But I do search only under /models/ folders

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we doing any sort of checking to make sure the classes are actual model classes?

Any suggestions ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mjauvin if the intention is to find out if it's a model without loading it, you'll have to use something like PHP Parser to statically analyze the file.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bennothommo I think searching for all class files under models is good enough for the purpose, more logic can be added by the code using the ModelFinder helper once it gets the list of class files as this is more efficient.

}

public static function findModuleModels(): array
{
$modulesPath = base_path() . '/modules';

$models = collect(Finder::create()->in($modulesPath)->path('/models/')->notPath('/tests/')->files()->name('/^[A-Z]{1}.+\.php$/'))
->map(function ($model) use ($modulesPath) {
$modelPath = str_replace(['/', '.php'], ['\\', ''], Str::after($model->getRealPath(), realpath($modulesPath).DIRECTORY_SEPARATOR));
return ucwords($modelPath, '\\');
});

return $models->values()->all();
}

public static function findActivePluginsModels(): array
{
$models = [];
$pm = \System\Classes\PluginManager::instance();

$pluginsPaths = collect($pm->getPlugins())->map(function ($plugin) use ($pm) {
return $pm->getPluginPath($plugin);
})->filter(function ($path) {
return File::exists($path . '/models');
})->each(function ($path) use (&$models) {
$modelPaths = Finder::create()->in($path . '/models')->files()->name('/^[A-Z]{1}.+\.php$/');
$models[] = collect($modelPaths)->map(function ($model) {
$modelPath = str_replace(['/', '.php'], ['\\', ''], Str::after($model->getRealPath(), plugins_path().DIRECTORY_SEPARATOR));
return ucwords($modelPath, '\\');
})->all();
});

return collect($models)->flatten()->all();
}
}
Loading