Skip to content

Commit df0f657

Browse files
author
nejc
committed
feat: add simple and advanced repository stubs, improve generator, update README with usage and customization instructions
1 parent 5c85fe1 commit df0f657

21 files changed

+686
-11
lines changed

README.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,85 @@ The MIT License (MIT). Please see [License File](LICENSE.md) for more informatio
124124
## Laravel Package Boilerplate
125125

126126
This package was generated using the [Laravel Package Boilerplate](https://laravelpackageboilerplate.com).
127+
128+
## Modern Repository Example with Traits
129+
130+
You can enhance your repositories by mixing in traits for extra features like soft deletes, caching, and multi-database support.
131+
132+
### Example: `app/Repositories/UserRepository.php`
133+
134+
```php
135+
namespace App\Repositories;
136+
137+
use App\Models\User;
138+
use Laravelplus\RepositoryPattern\BaseRepository;
139+
use Laravelplus\RepositoryPattern\Traits\SoftDeletes;
140+
use Laravelplus\RepositoryPattern\Traits\Cacheable;
141+
use Laravelplus\RepositoryPattern\Traits\MultiDatabase;
142+
143+
class UserRepository extends BaseRepository implements \Laravelplus\RepositoryPattern\Contracts\MultiDatabaseInterface
144+
{
145+
use SoftDeletes, Cacheable, MultiDatabase;
146+
147+
protected static string $modelClass = User::class;
148+
// Optionally configure $table, $primaryKey, $connection, etc.
149+
}
150+
```
151+
152+
### Using Trait Methods
153+
154+
```php
155+
$userRepo = new UserRepository();
156+
$userRepo->softDelete($userId); // Soft delete a user
157+
$userRepo->restore($userId); // Restore a soft-deleted user
158+
$userRepo->cacheAll(30); // Cache all users for 30 minutes
159+
$userRepo->runOnConnection('mysql2', fn($db) => $db->table('users')->get());
160+
```
161+
162+
## Available Traits
163+
164+
- `SoftDeletes`: Adds soft delete, restore, and onlyTrashed methods.
165+
- `Cacheable`: Adds cacheAll for caching results.
166+
- `Loggable`: Adds logAction for logging repository actions.
167+
- `Eventable`: Adds fireEvent for dispatching events.
168+
- `ValidatesData`: Adds validate for validating data before create/update.
169+
- `Searchable`: Adds search for column-based LIKE search.
170+
- `Sortable`: Adds sortBy for sorting results.
171+
- `HasRelationships`: Adds withRelations for eager loading relationships.
172+
- `MultiDatabase`: Adds runOnConnection and crossConnectionQuery for multi-database support.
173+
174+
Mix and match these traits in your repositories as needed!
175+
176+
## Repository Generator Command
177+
178+
You can quickly generate repository classes using the built-in Artisan command:
179+
180+
### Simple Repository
181+
182+
Generate a basic repository (no traits or interface):
183+
184+
```bash
185+
php artisan make:repository User
186+
```
187+
188+
This uses the `stubs/repository.simple.stub` template.
189+
190+
### Repository with Traits and Interface
191+
192+
Generate a repository with traits and/or an interface:
193+
194+
```bash
195+
php artisan make:repository User --traits=SoftDeletes,Cacheable --interface=MultiDatabaseInterface
196+
```
197+
198+
This uses the `stubs/repository.stub` template and will insert the specified traits and interface.
199+
200+
### Customizing Stubs
201+
202+
You can publish the stubs to your application and customize them as needed:
203+
204+
```bash
205+
php artisan vendor:publish --tag=config
206+
```
207+
208+
This will copy the stubs to your `stubs/` directory, where you can edit them to fit your project's needs.

config/config.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
<?php
22

3+
declare(strict_types=1);
4+
35
/*
46
* You can place your custom package configuration in here.
57
*/
68
return [
79

8-
];
10+
];

src/BaseRepository.php

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Laravelplus\RepositoryPattern;
6+
7+
use BadMethodCallException;
8+
use Illuminate\Database\Eloquent\Collection;
9+
use Illuminate\Database\Eloquent\Model;
10+
use Illuminate\Pagination\LengthAwarePaginator;
11+
use Illuminate\Support\Facades\DB;
12+
use Laravelplus\RepositoryPattern\Contracts\RepositoryInterface;
13+
14+
abstract class BaseRepository implements RepositoryInterface
15+
{
16+
/**
17+
* The model instance.
18+
*
19+
* @var Model
20+
*/
21+
protected $model;
22+
23+
// Static properties for configuration
24+
protected static string $modelClass = '';
25+
26+
protected static string $table = '';
27+
28+
protected static string $connection = 'mysql';
29+
30+
protected static string $primaryKey = 'id';
31+
32+
protected static array $relations = [];
33+
34+
protected static array $casts = [];
35+
36+
protected static array $hidden = [];
37+
38+
/**
39+
* BaseRepository constructor.
40+
*/
41+
public function __construct(?Model $model = null)
42+
{
43+
$modelClass = static::$modelClass ?: Model::class;
44+
$this->model = $model ?? new $modelClass();
45+
$this->table = static::$table ?: $this->model->getTable();
46+
$defaultConnection = config('database.default');
47+
$conn = static::$connection ?: $this->model->getConnectionName();
48+
$this->connection = ($conn && $conn !== $defaultConnection) ? $conn : null;
49+
}
50+
51+
protected function getQuery()
52+
{
53+
if ($this->connection) {
54+
return DB::connection($this->connection)->table($this->table);
55+
}
56+
57+
return DB::table($this->table);
58+
}
59+
60+
public function __get($name)
61+
{
62+
if ($name === 'table') {
63+
return $this->getQuery();
64+
}
65+
if ($name === 'model') {
66+
return $this->model;
67+
}
68+
if ($name === 'relations') {
69+
return static::$relations;
70+
}
71+
if ($name === 'casts') {
72+
return static::$casts;
73+
}
74+
75+
return $this->$name;
76+
}
77+
78+
public function __call($method, $arguments)
79+
{
80+
if (isset(static::$relations[$method])) {
81+
$relation = static::$relations[$method];
82+
$defaultConnection = config('database.default');
83+
if (is_string($relation) && class_exists($relation)) {
84+
return new $relation();
85+
}
86+
if (is_string($relation)) {
87+
$connection = static::$connection;
88+
if (!$connection || $connection === $defaultConnection) {
89+
return DB::table($relation);
90+
}
91+
92+
return DB::connection($connection)->table($relation);
93+
}
94+
if (is_array($relation) && isset($relation['table'])) {
95+
$connection = $relation['connection'] ?? static::$connection;
96+
$primaryKey = $relation['primaryKey'] ?? 'id';
97+
$foreignKey = $relation['foreignKey'] ?? null;
98+
$query = (!$connection || $connection === $defaultConnection)
99+
? DB::table($relation['table'])
100+
: DB::connection($connection)->table($relation['table']);
101+
if ($foreignKey && isset($arguments[0])) {
102+
$query->where($foreignKey, $arguments[0]);
103+
}
104+
105+
return $query;
106+
}
107+
}
108+
throw new BadMethodCallException("Relation or method '{$method}' not defined in " . static::class);
109+
}
110+
111+
protected function hideFields($result)
112+
{
113+
$hidden = static::$hidden;
114+
if (empty($hidden)) {
115+
return $result;
116+
}
117+
if ($result instanceof \Illuminate\Support\Collection || is_array($result)) {
118+
return collect($result)->map(function ($item) use ($hidden) {
119+
foreach ($hidden as $field) {
120+
if (is_array($item) && array_key_exists($field, $item)) {
121+
unset($item[$field]);
122+
} elseif (is_object($item) && property_exists($item, $field)) {
123+
unset($item->$field);
124+
}
125+
}
126+
127+
return $item;
128+
});
129+
}
130+
if (is_object($result) || is_array($result)) {
131+
foreach ($hidden as $field) {
132+
if (is_array($result) && array_key_exists($field, $result)) {
133+
unset($result[$field]);
134+
} elseif (is_object($result) && property_exists($result, $field)) {
135+
unset($result->$field);
136+
}
137+
}
138+
}
139+
140+
return $result;
141+
}
142+
143+
/**
144+
* Get all records.
145+
*/
146+
public function all(): Collection
147+
{
148+
$results = $this->model->all();
149+
150+
return $this->hideFields($results);
151+
}
152+
153+
/**
154+
* Find a record by ID.
155+
*/
156+
public function find(int|string $id): ?Model
157+
{
158+
$result = $this->model->where(static::$primaryKey, $id)->first();
159+
160+
return $this->hideFields($result);
161+
}
162+
163+
/**
164+
* Create a new record.
165+
*/
166+
public function create(array $data): Model
167+
{
168+
return $this->model->create($data);
169+
}
170+
171+
/**
172+
* Update a record by ID.
173+
*/
174+
public function update(int|string $id, array $data): ?Model
175+
{
176+
$model = $this->find($id);
177+
if ($model) {
178+
$model->update($data);
179+
180+
return $model;
181+
}
182+
183+
return null;
184+
}
185+
186+
/**
187+
* Delete a record by ID.
188+
*/
189+
public function delete(int|string $id): bool
190+
{
191+
return (bool) $this->model->where(static::$primaryKey, $id)->delete();
192+
}
193+
194+
public function paginate(int $perPage = 15): LengthAwarePaginator
195+
{
196+
return $this->model->paginate($perPage);
197+
}
198+
199+
public function findBy(string $field, mixed $value): ?Model
200+
{
201+
return $this->model->where($field, $value)->first();
202+
}
203+
204+
// Utility methods are now available via RepositoryService:
205+
// - RepositoryService::map($results, fn($item) => ...)
206+
// - RepositoryService::mapWithKeys($results, fn($item) => ...)
207+
// - RepositoryService::modifyFields($results, ['field' => fn($v, $item) => ...])
208+
// - RepositoryService::runOnConnection('mysql2', fn($db) => ...)
209+
// - RepositoryService::crossConnectionQuery(...)
210+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Laravelplus\RepositoryPattern\Contracts;
6+
7+
interface MultiDatabaseInterface
8+
{
9+
public function runOnConnection(string $connection, callable $callback);
10+
11+
public function crossConnectionQuery(
12+
string $connA, string $tableA,
13+
string $connB, string $tableB,
14+
string $keyA, string $keyB
15+
);
16+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Laravelplus\RepositoryPattern\Contracts;
6+
7+
use Illuminate\Database\Eloquent\Collection;
8+
use Illuminate\Database\Eloquent\Model;
9+
use Illuminate\Pagination\LengthAwarePaginator;
10+
11+
interface RepositoryInterface
12+
{
13+
public function all(): Collection;
14+
15+
public function paginate(int $perPage = 15): LengthAwarePaginator;
16+
17+
public function find(int|string $id): ?Model;
18+
19+
public function findBy(string $field, mixed $value): ?Model;
20+
21+
public function create(array $data): Model;
22+
23+
public function update(int|string $id, array $data): ?Model;
24+
25+
public function delete(int|string $id): bool;
26+
27+
public function map($results, callable $callback);
28+
29+
public function mapWithKeys($results, callable $callback);
30+
31+
public function modifyFields($results, array $modifiers);
32+
33+
public function runOnConnection(string $connection, callable $callback);
34+
35+
public function crossConnectionQuery(
36+
string $connA, string $tableA,
37+
string $connB, string $tableB,
38+
string $keyA, string $keyB
39+
);
40+
}

0 commit comments

Comments
 (0)