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

implement make repository command #17

Merged
merged 4 commits into from
Mar 17, 2024
Merged
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
12 changes: 12 additions & 0 deletions config/repository.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
<?php

return [
/*
|--------------------------------------------------------------------------
| Repository Paths
|--------------------------------------------------------------------------
|
| This option defines the path where your repositories are located. This allows the
| application to dynamically resolve repository paths based on your configuration,
| making it easy to organize and manage your application's data access layer.
|
*/
'path' => app_path('Repositories'), // Default repository path

/*
|--------------------------------------------------------------------------
| Default Pagination Settings
Expand Down
91 changes: 91 additions & 0 deletions src/Commands/MakeRepository.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php

declare(strict_types=1);

namespace Salehhashemi\Repository\Commands;

use Illuminate\Console\Command;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Str;

class MakeRepository extends Command
{
protected $signature = 'make:repository {name : The (fully qualified) name of the model}';

protected $description = 'Creates a new repository, interface, and filter for the specified model';

public function __construct(private readonly Filesystem $filesystem)
{
parent::__construct();
}

public function handle(): void
{
$name = $this->argument('name');
salehhashemi1992 marked this conversation as resolved.
Show resolved Hide resolved

if (Str::contains($name, '\\')) {
$modelName = Str::afterLast($name, '\\');
$modelNamespace = Str::beforeLast($name, '\\');
} else {
$modelName = Str::studly(class_basename($name));
$modelNamespace = app()->getNamespace().'Models';
}

// Configuration-based paths
$repositoryPath = config('repository.path', app_path('Repositories'));

$paths = [
"{$repositoryPath}/{$modelName}Repository.php" => 'repository.stub',
"{$repositoryPath}/Contracts/{$modelName}RepositoryInterface.php" => 'repository-interface.stub',
"{$repositoryPath}/Filters/{$modelName}Filter.php" => 'filter.stub',
];

foreach ($paths as $filePath => $stubName) {
$this->ensureDirectoryExists(dirname($filePath));

$replacements = [
'{{namespace}}' => str_replace('/', '\\', $this->pathToNamespace(dirname($filePath))),
'{{modelName}}' => $modelName,
'{{modelNamespace}}' => $modelNamespace,
'{{name}}' => $modelName,
];

$content = $this->getStubContent($stubName, $replacements);
$this->filesystem->put($filePath, $content);

$this->info("Created: {$filePath}");
}
}

protected function ensureDirectoryExists(string $path): void
{
if (! $this->filesystem->isDirectory($path)) {
$this->filesystem->makeDirectory($path, 0755, true);
}
}

protected function pathToNamespace(string $path): string
{
$appNamespace = app()->getNamespace();
$relativePath = str_replace(app_path(), '', $path);
$namespace = str_replace(['/', '\\'], '\\', $relativePath);
$namespace = ltrim($namespace, '\\');

return trim($appNamespace.$namespace, '\\');
}

protected function getStubContent(string $stubName, array $replacements): string
{
$stubPath = __DIR__."/stubs/{$stubName}";
if (! file_exists($stubPath)) {
throw new \RuntimeException("Stub not found: {$stubPath}");

Check warning on line 81 in src/Commands/MakeRepository.php

View check run for this annotation

Codecov / codecov/patch

src/Commands/MakeRepository.php#L81

Added line #L81 was not covered by tests
}

$content = (string) file_get_contents($stubPath);
foreach ($replacements as $key => $value) {
$content = str_replace($key, $value, $content);
}

return $content;
}
}
27 changes: 27 additions & 0 deletions src/Commands/stubs/filter.stub
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);

namespace {{namespace}};

use Salehhashemi\Repository\BaseFilter;
use Illuminate\Database\Eloquent\Builder;
use {{modelNamespace}}\{{modelName}};

class {{name}}Filter extends BaseFilter
{
/**
* Apply filters to the query builder based on a set of parameters.
*/
public function applyFilter(array $queryParams): Builder
{
return $this->getQuery();
}

/**
* The Eloquent model class that this filter applies to.
*/
protected function getModelClass(): string
{
return {{modelName}}::class;
}
}
16 changes: 16 additions & 0 deletions src/Commands/stubs/repository-interface.stub
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);

namespace {{namespace}};

use Salehhashemi\Repository\Contracts\RepositoryInterface;
use Salehhashemi\Repository\Contracts\SearchableRepositoryInterface;

/**
* @method \{{modelNamespace}}\{{modelName}}|null findOne(int|string $primaryKey = null)
* @method \{{modelNamespace}}\{{modelName}} findOneOrFail(int|string $primaryKey = null)
*/
interface {{name}}RepositoryInterface extends RepositoryInterface, SearchableRepositoryInterface
{
// ...
}
34 changes: 34 additions & 0 deletions src/Commands/stubs/repository.stub
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);

namespace {{namespace}};

use {{namespace}}\Contracts\{{name}}RepositoryInterface;
use {{modelNamespace}}\{{modelName}};
use {{namespace}}\Filters\{{name}}Filter;
use Salehhashemi\Repository\BaseEloquentRepository;
use Salehhashemi\Repository\Traits\Searchable;

class {{name}}Repository extends BaseEloquentRepository implements {{name}}RepositoryInterface
{
use Searchable;

/**
* Get the filter manager for this repository.
*/
protected function getFilterManager(): {{name}}Filter
{
$filterManager = new {{name}}Filter();
$filterManager->setQuery($this->getQuery());

return $filterManager;
}

/**
* {@inheritdoc}
*/
protected function getModelClass(): string
{
return {{modelName}}::class;
}
}
6 changes: 3 additions & 3 deletions src/Contracts/RepositoryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
interface RepositoryInterface
{
/**
* It applies the conditions, criteria, relations, and order by to the query,
* It applies the conditions, criteria, relations, and order-by clauses to the query,
* then gets the first result and resets the query
*/
public function findOne(int|string|null $primaryKey = null): ?Model;
Expand All @@ -32,8 +32,8 @@ public function findAll(array $options = []): EloquentCollection;
/**
* It returns a collection of key-value pairs from the database.
*
* @param string|null $key The key to use for the list.
* @param string|null $value The field to use as the value of the select list.
* @param string|null $key The key to use for the list.
* @param string|null $value The field to use as the value of the select list.
* @return \Illuminate\Support\Collection A collection of key value pairs.
*/
public function findList(?string $key = null, ?string $value = null): Collection;
Expand Down
7 changes: 7 additions & 0 deletions src/RepositoryServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Salehhashemi\Repository;

use Illuminate\Support\ServiceProvider;
use Salehhashemi\Repository\Commands\MakeRepository;

class RepositoryServiceProvider extends ServiceProvider
{
Expand All @@ -15,6 +16,12 @@ public function boot(): void
$this->publishes([
__DIR__.'/../config/repository.php' => config_path('repository.php'),
], 'config');

if ($this->app->runningInConsole()) {
$this->commands([
MakeRepository::class,
]);
}
}

/**
Expand Down
64 changes: 64 additions & 0 deletions tests/MakeRepositoryCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

namespace Salehhashemi\Repository\Tests;

use Illuminate\Support\Facades\File;

class MakeRepositoryCommandTest extends BaseTest
{
protected string $model = 'TestModel';

protected string $basePath;

protected function setUp(): void
{
parent::setUp();

$this->basePath = config('repository.path', app_path('Repositories'));
}

protected function tearDown(): void
{
// Cleanup: Remove the directories and files created during the test
File::deleteDirectory($this->basePath);

parent::tearDown();
}

public function testRepositoryStubGeneration(): void
{
$this->artisan('make:repository', ['name' => $this->model]);

$expectedFiles = [
"{$this->basePath}/{$this->model}Repository.php",
"{$this->basePath}/Contracts/{$this->model}RepositoryInterface.php",
"{$this->basePath}/Filters/{$this->model}Filter.php",
];

foreach ($expectedFiles as $file) {
$this->assertFileExists($file);
}
}

public function testRepositoryStubGenerationWithFQCN(): void
{
$fqcn = 'App\Models\Special\\'.$this->model;

$this->artisan('make:repository', ['name' => $fqcn]);

$expectedNamespace = 'namespace App\Repositories';

$expectedFiles = [
"{$this->basePath}/{$this->model}Repository.php",
"{$this->basePath}/Contracts/{$this->model}RepositoryInterface.php",
"{$this->basePath}/Filters/{$this->model}Filter.php",
];

foreach ($expectedFiles as $file) {
$this->assertFileExists($file);

$content = file_get_contents($file);
$this->assertStringContainsString($expectedNamespace, $content);
}
}
}
Loading