Rinvex Repository is a simple, intuitive, and smart implementation of Active Repository with extremely flexible & granular caching system for Laravel, used to abstract the data layer, making applications more flexible to maintain.
dev-develop
branch. It's stable but not tagged yet since test suites isn't complete.
The Rinvex\Repository\Repositories\BaseRepository
is an abstract class with bare minimum that concrete implementations must extend.
The Rinvex\Repository\Repositories\EloquentRepository
is currently the only available repository implementation (more to come in the future and you can develop your own), it makes it easy to create new eloquent model instances and to manipulate them easily. To use EloquentRepository
your repository MUST extend it first:
namespace App\Repositories;
use Rinvex\Repository\Repositories\EloquentRepository;
class FooRepository extends EloquentRepository
{
protected $repositoryId = 'rinvex.repository.uniqueid';
protected $model = 'App\Models\User';
}
That's it, you're done! Yes, it's that simple.
But if you'd like more control over the container instance, or would like to pass model name dynamically you can alternatively do as follow:
namespace App\Repositories;
use Illuminate\Contracts\Container\Container;
use Rinvex\Repository\Repositories\EloquentRepository;
class FooRepository extends EloquentRepository
{
// Instantiate repository object with required data
public function __construct(Container $container)
{
$this->setContainer($container)
->setModel(\App\Models\User::class)
->setRepositoryId('rinvex.repository.uniqueid');
}
}
Now inside your controller, you can either instantiate the repository traditionally through $repository = new \App\Repositories\FooRepository();
or to use Laravel's awesome dependency injection and let the IoC do the magic:
namespace App\Http\Controllers;
use App\Repositories\FooRepository;
class BarController
{
// Inject `FooRepository` from the IoC
public function baz(FooRepository $repository)
{
// Find entity by primary key
$repository->find(1);
// Find all entities
$repository->findAll();
// Create a new entity
$repository->create(['name' => 'Example']);
}
}
Rinvex Repository Workflow - Create Repository
Rinvex Repository Workflow - Use In Controller
Mission accomplished! You're good to use this package right now!
Unless you need to dig deeper & know some advanced stuff, you can skip the following steps!
setContainer()
, getContainer()
setConnection()
, getConnection()
setModel()
, getModel()
setRepositoryId()
, getRepositoryId()
setCacheLifetime()
, getCacheLifetime()
setCacheDriver()
, getCacheDriver()
enableCacheClear()
, isCacheClearEnabled()
createModel()
forgetCache()
with()
where()
whereIn()
whereNotIn()
whereHas()
offset()
limit()
orderBy()
find()
findBy()
findFirst()
findAll()
paginate()
simplePaginate()
findWhere()
findWhereIn()
findWhereNotIn()
findWhereHas()
create()
update()
store()
delete()
beginTransaction()
commit()
rollBack()
The best and easiest way to install this package is through Composer.
This package fully compatible with Laravel 5.1.*
, 5.2.*
, and 5.3.*
.
While this package tends to be framework-agnostic, it embraces Laravel culture and best practices to some extent. It's tested mainly with Laravel but you still can use it with other frameworks or even without any framework if you want.
Open your application's composer.json
file and add the following line to the require
array:
"rinvex/laravel-repositories": "3.0.*"
Note: Make sure that after the required changes your
composer.json
file is valid by runningcomposer validate
.
On your terminal run composer install
or composer update
command according to your application's status to install the new requirements.
Note: Checkout Composer's Basic Usage documentation for further details.
Rinvex Repository package is framework-agnostic and as such can be integrated easily natively or with your favorite framework.
Integrating the package outside of a framework is incredibly easy, just require the vendor/autoload.php
file to autoload the package.
Note: Checkout Composer's Autoloading documentation for further details.
Run the following command on your terminal to publish config files:
php artisan vendor:publish --tag="rinvex-repository-config"
Note: Checkout Laravel's Configuration documentation for further details.
You are good to go. Integration is done and you can now use all the available methods, proceed to the Usage section for an example.
If you followed the previous integration steps, then your published config file reside at config/rinvex.repository.php
.
Config options are very expressive and self explanatory, as follows:
return [
/*
|--------------------------------------------------------------------------
| Models Directory
|--------------------------------------------------------------------------
|
| Here you may specify the default models directory, just write
| directory name, like 'Models' not the full path.
|
| Default: 'Models'
|
*/
'models' => 'Models',
/*
|--------------------------------------------------------------------------
| Caching Strategy
|--------------------------------------------------------------------------
*/
'cache' => [
/*
|--------------------------------------------------------------------------
| Cache Keys File
|--------------------------------------------------------------------------
|
| Here you may specify the cache keys file that is used only with cache
| drivers that does not support cache tags. It is mandatory to keep
| track of cache keys for later usage on cache flush process.
|
| Default: storage_path('framework/cache/rinvex.repository.json')
|
*/
'keys_file' => storage_path('framework/cache/rinvex.repository.json'),
/*
|--------------------------------------------------------------------------
| Cache Lifetime
|--------------------------------------------------------------------------
|
| Here you may specify the number of seconds that you wish the cache
| to be remembered before it expires. If you want the cache to be
| remembered forever, set this option to -1. 0 means disabled.
|
| Default: -1
|
*/
'lifetime' => -1,
/*
|--------------------------------------------------------------------------
| Cache Clear
|--------------------------------------------------------------------------
|
| Specify which actions would you like to clear cache upon success.
| All repository cached data will be cleared accordingly.
|
| Default: ['create', 'update', 'delete']
|
*/
'clear_on' => [
'create',
'update',
'delete',
],
/*
|--------------------------------------------------------------------------
| Cache Skipping URI
|--------------------------------------------------------------------------
|
| For testing purposes, or maybe some certain situations, you may wish
| to skip caching layer and get fresh data result set just for the
| current request. This option allows you to specify custom
| URL parameter for skipping caching layer easily.
|
| Default: 'skipCache'
|
*/
'skip_uri' => 'skipCache',
],
];
setContainer()
, getContainer()
The setContainer
method sets the IoC container instance, while getContainer
returns it:
// Set the IoC container instance
$repository->setContainer(new \Illuminate\Container\Container());
// Get the IoC container instance
$container = $repository->getContainer();
setConnection()
, getConnection()
The setConnection
method sets the connection associated with the repository, while getConnection
returns it:
// Set the connection associated with the repository
$repository->setConnection('mysql');
// Get the current connection for the repository
$connection = $repository->getConnection();
Note: The name passed to the
setConnection
method should correspond to one of the connections listed in yourconfig/database.php
configuration file.
setModel()
, getModel()
The setModel
method sets the repository model, while getModel
returns it:
// Set the repository model
$repository->setModel(\App\Models\User::class);
// Get the repository model
$repositoryModel = $repository->getModel();
setRepositoryId()
, getRepositoryId()
The setRepositoryId
method sets the repository identifier, while getRepositoryId
returns it (it could be anything you want, but must be unique per repository):
// Set the repository identifier
$repository->setRepositoryId('rinvex.repository.uniqueid');
// Get the repository identifier
$repositoryId = $repository->getRepositoryId();
setCacheLifetime()
, getCacheLifetime()
The setCacheLifetime
method sets the repository cache lifetime, while getCacheLifetime
returns it:
// Set the repository cache lifetime
$repository->setCacheLifetime(123);
// Get the repository cache lifetime
$cacheLifetime = $repository->getCacheLifetime();
setCacheDriver()
, getCacheDriver()
The setCacheDriver
method sets the repository cache driver, while getCacheDriver
returns it:
// Set the repository cache driver
$repository->setCacheDriver('redis');
// Get the repository cache driver
$cacheDriver = $repository->getCacheDriver();
enableCacheClear()
, isCacheClearEnabled()
The enableCacheClear
method enables repository cache clear, while isCacheClearEnabled
determines it's state:
// Enable repository cache clear
$repository->enableCacheClear(true);
// Disable repository cache clear
$repository->enableCacheClear(false);
// Determine if repository cache clear is enabled
$cacheClearStatus = $repository->isCacheClearEnabled();
createModel()
The createModel()
method creates a new repository model instance:
$repositoryModelInstance = $repository->createModel();
forgetCache()
The forgetCache()
method forgets the repository cache:
$repository->forgetCache();
with()
The with
method sets the relationships that should be eager loaded:
// Pass a string
$repository->with('relationship');
// Or an array
$repository->with(['relationship1', 'relationship2']);
where()
The where
method adds a basic where clause to the query:
$repository->where('slug', '=', 'example');
whereIn()
The whereIn
method adds a "where in" clause to the query:
$repository->whereIn('id', [1, 2, 5, 8]);
whereNotIn()
The whereNotIn
method adds a "where not in" clause to the query:
$repository->whereNotIn('id', [1, 2, 5, 8]);
whereHas()
The whereHas
method adds a "where has relationship" clause to the query:
use Illuminate\Database\Eloquent\Builder;
$repository->whereHas('attachments', function (Builder $builder) use ($attachment) {
$builder->where('attachment_id', $attachment->id);
});
Note: All of the
where*
methods are chainable & could be called multiple times in a single request. It will hold all where clauses in an array internally and apply them all before executing the query.
offset()
The offset
method sets the "offset" value of the query:
$repository->offset(5);
limit()
The limit
method sets the "limit" value of the query:
$repository->limit(9);
orderBy()
The orderBy
method adds an "order by" clause to the query:
$repository->orderBy('id', 'asc');
find()
The find
method finds an entity by it's primary key:
$entity = $repository->find(1);
findOrFail()
The findOrFail()
method finds an entity by its primary key or throw an exception:
$entity = $repository->findOrFail(1);
findOrNew()
The findOrNew()
method finds an entity by its primary key or return fresh entity instance:
$entity = $repository->findOrNew(1);
findBy()
The findBy
method finds an entity by one of it's attributes:
$entity = $repository->findBy('id', 1);
findFirst()
The findFirst
method finds first entity:
$firstEntity = $repository->findFirst();
findAll()
The findAll
method finds all entities:
$allEntities = $repository->findAll();
paginate()
The paginate
method paginates all entities:
$entitiesPagination = $repository->paginate(15, ['*'], 'page', 2);
As you can guess, this query the first 15 records, in the second page.
simplePaginate()
The simplePaginate
method paginates all entities into a simple paginator:
$entitiesSimplePagination = $repository->simplePaginate(15);
findWhere()
The findWhere
method finds all entities matching where conditions:
// Matching values with equal '=' operator
$repository->findWhere(['slug', '=', 'example']);
findWhereIn()
The findWhereIn
method finds all entities matching whereIn conditions:
$includedEntities = $repository->findwhereIn(['id', [1, 2, 5, 8]]);
findWhereNotIn()
The findWhereNotIn
method finds all entities matching whereNotIn conditions:
$excludedEntities = $repository->findWhereNotIn(['id', [1, 2, 5, 8]]);
findWhereHas()
The findWhereHas
method finds all entities matching whereHas conditions:
use Illuminate\Database\Eloquent\Builder;
$entities = $repository->findWhereHas(['attachments', function (Builder $builder) use ($attachment) {
$builder->where('attachment_id', $attachment->id);
}]);
Notes:
- The
findWhereHas
method will return a collection of entities that match the condition inside the closure. If you need to embed theattachments
relation, in this case, you'll need to callwith()
method before callingfindWhereHas()
like this:$repository->with('attachments')->findWhereHas([...]);
- Signature of all of the
findWhere
,findWhereIn
, andfindWhereNotIn
methods has been changed since v2.0.0.- All of the
findWhere
,findWhereIn
, andfindWhereNotIn
methods utilize thewhere
,whereIn
, andwhereNotIn
methods respectively, and thus takes first argument as an array of same parameters required by the later ones.- All of the
find*
methods are could be filtered with precedingwhere
clauses, which is chainable by the way. Allwhere
clauses been hold in an array internally and applied before executing the query. Check the following examples:
Example of filtered findAll
method:
$allFilteredEntities = $repository->where('slug', '=', 'example')->findAll();
Another example of filtered findFirst
method with chained clauses:
$allFilteredEntities = $repository->where('name', 'LIKE', '%TEST%')->where('slug', '=', 'example')->findFirst();
create()
The create
method creates a new entity with the given attributes:
$createdEntity = $repository->create(['name' => 'Example']);
update()
The update
method updates an entity with the given attributes:
$updatedEntity = $repository->update(1, ['name' => 'Example2']);
store()
The store
method stores the entity with the given attributes:
// Existing Entity
$storedEntity = $repository->store(1, ['name' => 'Example2']);
// New Entity
$storedEntity = $repository->store(null, ['name' => 'Example2']);
Note: This method is just an alias for both
create
&update
methods. It's useful in case where single form is used for both create & update processes.
delete()
The delete
method deletes an entity with the given id:
$deletedEntity = $repository->delete(1);
beginTransaction()
The beginTransaction
method starts a database transaction:
$repository->beginTransaction();
commit()
The commit
method commits a database transaction:
$repository->commit();
rollBack()
The rollback
method rollbacks a database transaction:
$repository->rollBack();
Notes:
- All
find*
methods take one more optional parameter for selected attributes.- All
set*
methods returns an instance of the current repository, and thus can be chained.create
,update
, anddelete
methods always return an array with two values, the first is action status whether it's success or fail as a boolean value, and the other is an instance of the model just operated upon.- It's recommended to set IoC container instance, repository model, and repository identifier explicitly through your repository constructor like the above example, but this package is smart enough to guess any missing requirements. Check Automatic Guessing Section
As a best practice, it's recommended to code for an interface, specifically for scalable projects. The following example explains how to do so.
First, create an interface (abstract) for every entity you've:
use Rinvex\Repository\Contracts\CacheableContract;
use Rinvex\Repository\Contracts\RepositoryContract;
interface UserRepositoryContract extends RepositoryContract, CacheableContract
{
//
}
Second, create a repository (concrete implementation) for every entity you've:
use Rinvex\Repository\Repositories\EloquentRepository;
class UserEloquentRepository extends EloquentRepository implements UserRepositoryContract
{
//
}
Now in a Laravel Service Provider bind both to the IoC (inside the register
method):
$this->app->bind(UserRepositoryContract::class, UserEloquentRepository::class)
This way we don't have to instantiate the repository manually, and it's easy to switch between multiple implementations. The IoC Container will take care of the required dependencies.
Note: Checkout Laravel's Service Providers and Service Container documentation for further details.
Since we're focusing on abstracting the data layer, and we're separating the abstract interface from the concrete implementation, it's easy to add your own implementation.
Say your domain model uses a web service, or a filesystem data store as it's data source, all you need to do is just extend the BaseRepository
class, that's it. See:
class FilesystemRepository extends BaseRepository
{
// Implement here all `RepositoryContract` methods that query/persist data to & from filesystem or whatever datastore
}
Repositories fire events at every action, like create
, update
, delete
. All fired events are prefixed with repository's identifier (you set before in your repository's constructor) like the following example:
For your convenience, the events suffixed with .entity.created
, .entity.updated
, or .entity.deleted
have listeners that take actions accordingly. Usually we need to flush cache -if enabled & exists- upon every success action.
There's one more event rinvex.repository.uniqueid.entity.cache.flushed
that's fired on cache flush. It has no listeners by default, but you may need to listen to it if you've model relations for further actions.
Here some conventions important to know while using this package. This package adheres to best practices trying to make development easier for web artisans, and thus it has some conventions for standardization and interoperability.
All Fired Events has a unique suffix, like .entity.created
for example. Note the .entity.
which is mandatory for automatic event listeners to subscribe to.
Default directory structure of any package uses Rinvex Repository is as follows:
├── config --> config files
|
├── database
| ├── factories --> database factory files
| ├── migrations --> database migration files
| └── seeds --> database seed files
|
├── resources
| └── lang
| └── en --> English language files
|
├── routes --> Routes files
| ├── api.php
| ├── console.php
| └── web.php
|
├── src --> self explanatory directories
| ├── Console
| | └── Commands
| |
| ├── Http
| | ├── Controllers
| | ├── Middleware
| | └── Requests
| |
| ├── Events
| ├── Exceptions
| ├── Facades
| ├── Jobs
| ├── Listeners
| ├── Models
| ├── Overrides
| ├── Policies
| ├── Providers
| ├── Repositories
| ├── Scopes
| ├── Support
| └── Traits
|
└── composer.json --> composer dependencies file
Note: Rinvex Repository adheres to PSR-4: Autoloader and expects other packages that uses it to adhere to the same standard as well. It's required for Automatic Guessing, such as when repository model is missing, it will be guessed automatically and resolved accordingly, and while that full directory structure might not required, it's the standard for all Rinvex packages.
While it's recommended to explicitly set IoC container, repository identifier, and repository model; This package is smart enough to guess any of these required data whenever missing.
app()
helper is used as a fallback if IoC container instance not provided explicitly.rinvex.repository.uniqueid
, but if it's missing fully qualified repository class name will be used (actually the value of static::class
).Rinvex\Demos\Repositories\ItemRepository
, so corresponding model supposed to be namespaced like this Rinvex\Demos\Models\Item
. That's how this packages guess the model if it's missing according to the Default Directory Structure.Rinvex Repository has a powerful, yet simple and granular caching system, that handles almost every edge case. While you can enable/disable your application's cache as a whole, you have the flexibility to enable/disable cache granularly for every individual query! That gives you the ability to except certain queries from being cached even if the method is normally cached by default or otherwise.
Let's see what caching levels we can control:
Checkout Laravel's Cache documentation for more details.
Change cache per query or disable it:
// Set cache lifetime for this individual query to 123 seconds
$repository->setCacheLifetime(123);
// Set cache lifetime for this individual query to forever
$repository->setCacheLifetime(-1);
// Disable cache for this individual query
$repository->setCacheLifetime(0);
Change cache driver per query:
// Set cache driver for this individual query to redis
$repository->setCacheDriver('redis');
Both setCacheLifetime
& setCacheDriver
methods are chainable:
// Change cache lifetime & driver on runtime
$repository->setCacheLifetime(123)->setCacheDriver('redis')->findAll();
// Use default cache lifetime & driver
$repository->findAll();
Unless disabled explicitly, cache is enabled for all repositories by default, and kept for as long as your rinvex.repository.cache.lifetime
config value, using default application's cache driver cache.default
(which could be changed per query as well).
Caching results is totally up to you, while all retrieval find*
methods have cache enabled by default, you can enable/disable cache for individual queries or control how it's being cached, for how long, and using which driver as you wish.
Lastly, you can skip cache for an individual request by passing the following query string in your URL skipCache=true
. You can modify this parameter to whatever name you may need through the rinvex.repository.cache.skip_uri
config option.
rinvex.repository.cache.keys_file
config option to change file path.Refer to the Changelog for a full history of the project.
The following support channels are available at your fingertips:
Thank you for considering contributing to this project! The contribution guide can be found in CONTRIBUTING.md.
Bug reports, feature requests, and pull requests are very welcome.
If you discover a security vulnerability within this project, please send an e-mail to help@rinvex.com. All security vulnerabilities will be promptly addressed.
Rinvex is a software solutions startup, specialized in integrated enterprise solutions for SMEs established in Alexandria, Egypt since June 2016. We believe that our drive The Value, The Reach, and The Impact is what differentiates us and unleash the endless possibilities of our philosophy through the power of software. We like to call it Innovation At The Speed Of Life. That’s how we do our share of advancing humanity.
This software is released under The MIT License (MIT).
(c) 2016-2020 Rinvex LLC, Some rights reserved.
About the Author My name is Nicolas Widart, and I'm a flexible enthusiastic software engineer and consultant. I'm a Laravel framework specialist; an open-source contributor and always on the lookout f
Laravel 是一套简洁、优雅的PHP Web开发框架(PHP Web Framework)。它可以让你从面条一样杂乱的代码中解脱出来;它可以帮你构建一个完美的网络APP,而且每行代码都可以简洁、富于表达力。 功能特点 1、语法更富有表现力 你知道下面这行代码里 “true” 代表什么意思么? $uri = Uri::create(‘some/uri’, array(), array(), tr
我需要空间/Laravel权限的帮助。当我试图分配它给我错误哎呀,看起来像出了问题。 错误 Connection.php第761行中的QueryExcema:SQLSTATE[23000]:完整性约束冲突:1048列role_id不能为空(SQL:插入到(,)值(9,))
Laravel 作为现在最流行的 PHP 框架,其中的知识较多,所以单独拿出来写一篇。 简述 Laravel 的生命周期 Laravel 采用了单一入口模式,应用的所有请求入口都是 public/index.php 文件。 注册类文件自动加载器 : Laravel通过 composer 进行依赖管理,无需开发者手动导入各种类文件,而由自动加载器自行导入。 创建服务容器:从 bootstrap/ap
简介 Laravel Scout 为 Eloquent 模型 全文搜索提供了简单的,基于驱动的解决方案。通过使用模型观察者,Scout 会自动同步 Eloquent 记录的搜索索引。 目前,Scout 自带一个 Algolia 驱动;不过,编写自定义驱动很简单, 你可以轻松的通过自己的搜索实现来扩展 Scout。 安装 首先,通过 Composer 包管理器来安装 Scout: composer
简介 Laravel 致力于让整个 PHP 开发体验变得愉快, 包括你的本地开发环境。 Vagrant 提供了一种简单,优雅的方式来管理和配置虚拟机。 Laravel Homestead 是一个官方预封装的 Vagrant box,它为你提供了一个完美的开发环境,而无需在本地机器安装 PHP 、Web 服务器和其他服务器软件。不用担心会搞乱你的操作系统!Vagrant boxes 是一次性的。如果
WebStack-Laravel 一个开源的网址导航网站项目,具备完整的前后台,您可以拿来制作自己的网址导航。 部署 克隆代码: git clone https://github.com/hui-ho/WebStack-Laravel.git 安装依赖: composer installphp artisan key:generate 编辑配置: cp .env.example .env ...D