├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── composer.json ├── phpspec.yml ├── phpunit.xml ├── src ├── config │ └── context.php └── rtroncoso │ ├── facades │ └── Context.php │ ├── libraries │ └── Context.php │ ├── middleware │ └── ContextMiddleware.php │ ├── providers │ ├── ContextServiceProvider.php │ ├── DefaultServiceProvider.php │ └── DummyServiceProvider.php │ └── services │ └── NamespaceBuilder.php └── tests ├── .gitkeep ├── TestCase.php ├── libraries └── ContextTest.php └── services └── NamespaceBuilderTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore Idea 2 | .idea 3 | 4 | /bootstrap/compiled.php 5 | .env.*.php 6 | .env.php 7 | .env 8 | 9 | /vendor 10 | composer.phar 11 | composer.lock 12 | .DS_Store 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.5 5 | - 5.6 6 | - hhvm 7 | 8 | before_script: 9 | - travis_retry composer self-update 10 | - travis_retry composer install --prefer-source --no-interaction --dev 11 | - travis_retry composer dump-autoload 12 | 13 | script: phpunit 14 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Laravel Context 2 | =============== 3 | 4 | [![Total Downloads](https://poser.pugx.org/rtroncoso/laravel-context/d/total.svg)](https://packagist.org/packages/rtroncoso/laravel-context) 5 | [![Build Status](https://travis-ci.org/rtroncoso/Laravel-Context.svg?branch=master)](https://travis-ci.org/rtroncoso/Laravel-Context) 6 | [![License](https://poser.pugx.org/rtroncoso/laravel-context/license.svg)](https://packagist.org/packages/rtroncoso/laravel-context) 7 | 8 | This simple yet powerful package will help you load different Service Providers depending in which context you are. Contexts can be setup using `context` middleware in your route groups or the `Context` facade. 9 | 10 | It supports both **Laravel 5.1.x** _(release: ^2.0.0)_ and **Laravel 5.0.x** _(release: ^1.0.0)_ 11 | 12 | ## What's it for? 13 | Let's say you have 2 contexts in your application: an **Administration Panel** and a **RESTful WebService**. This are certainly two completely different contexts as in one context you'll maybe want to get all resources (i.e. including trashed) and in the other one you want only the active ones. 14 | 15 | This is when Service Providers come in really handy, the only problem is that Laravel doesn't come with an out of the box solution for loading different Service Providers for different contexts. 16 | 17 | This package gives you the possibility to register your different repositories to a single interface and bind them through your Context's Service Provider, using Laravel's amazing IoC Container to resolve which concrete implementation we need to bind depending on which context we are on. 18 | 19 | ## Installation Instructions 20 | To install this package you'll simply need to add this line to your composer.json file: 21 | 22 | ### Laravel 5.0.x 23 | ``` 24 | "require": { 25 | "rtroncoso/laravel-context": "^1.0.0" 26 | } 27 | ``` 28 | 29 | ### Laravel 5.1.x 30 | ``` 31 | "require": { 32 | "rtroncoso/laravel-context": "^2.0.0" 33 | } 34 | ``` 35 | 36 | After the installation is done, you need to add some stuff to `config/app.php`: 37 | 38 | At the end of your `providers` array add the following: 39 | 40 | ```php 41 | 'providers' => [ 42 | ... 43 | 'Cupona\Providers\ContextServiceProvider', 44 | ] 45 | ``` 46 | 47 | At the end of your `aliases` array add the following: 48 | 49 | ```php 50 | 'aliases' => [ 51 | ... 52 | 'Cupona\Facades\Context', 53 | ] 54 | ``` 55 | 56 | Then you need to add the context middleware the `$routeMiddleware` array in your `App/Http/Kernel.php` file: 57 | 58 | ```php 59 | protected $routeMiddleware => [ 60 | ... 61 | 'context' => 'Cupona\Middleware\ContextMiddleware', 62 | ] 63 | ``` 64 | 65 | And last but not least, run this command to publish context configuration file: 66 | 67 | $ php artisan vendor:publish --provider="Cupona\Providers\ContextServiceProvider" --tag="config" 68 | 69 | ## Package Usage 70 | So, here's the fun part! You have two ways of using this package, you can either load different contexts through `routes/route groups actions` or you can use the `Context` facade. 71 | 72 | ### Routes & Route Groups 73 | Given the case you want your contexts to be loaded depending on which route or route group you are, you'll simply have to state which context you'll need to load in your `routes.php` file and create your Service Provider. 74 | 75 | Let's see an example, if you want your `/admin` routes to load the `backend` context, you'll need to set up your `routes.php` file like this: 76 | 77 | #### Laravel 5.1.x: 78 | ```php 79 | Route::group(['prefix' => 'admin', 'middleware' => 'context:backend', function() { 80 | 81 | // Your contextually loaded routes go here 82 | 83 | }]); 84 | ``` 85 | 86 | #### Laravel 5.0.x: 87 | ```php 88 | Route::group(['prefix' => 'admin', 'middleware' => 'context', 'context' => 'backend', function() { 89 | 90 | // Your contextually loaded routes go here 91 | 92 | }]); 93 | ``` 94 | 95 | And your `BackendServiceProvider` should look something like this: 96 | 97 | ```php 98 | app->bind('Your\\Interfaces\\UserRepositoryInterface', 'Your\\Repositories\\Backend\\UserRepository'); 112 | 113 | // Make more awesome bindings here! 114 | } 115 | 116 | } 117 | ``` 118 | 119 | By doing this you've defined your very own contexts in your application, this can be very helpful as you can make sure that when you are in an end user context **they will never** see your resources as in an administration context. 120 | 121 | >Note: By default the backend context Service Provider will be loaded in the namespace `Contextual\Providers`, you can edit this in the configuration file provided by this package (`config/context.php`) 122 | 123 | ### Context Facade 124 | If you want you can use this package's `Context` facade and dinamycally load and check which context you are in. 125 | 126 | #### Loading a Context 127 | ```php 128 | Context::load('context'); 129 | ``` 130 | 131 | #### Checking currently loaded Context 132 | ```php 133 | $currentContext = Context::current(); 134 | ``` 135 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rtroncoso/laravel-context", 3 | "description": "Provides Service Provider binding depending on a context", 4 | "homepage": "https://github.com/rtroncoso/laravel-context", 5 | "license": "WTFPL (Do What The Fuck You Want To Public License)", 6 | "keywords": [ 7 | "laravel", 8 | "contextual", 9 | "service", 10 | "providers", 11 | "binding" 12 | ], 13 | "authors": [ 14 | { 15 | "name": "Rodrigo Troncoso", 16 | "email": "rod.tronco@gmail.com" 17 | } 18 | ], 19 | "require": { 20 | "php": ">=5.4.0", 21 | "illuminate/support": "5.1.*" 22 | }, 23 | "require-dev": { 24 | "phpunit/phpunit": "^4.6", 25 | "phpspec/phpspec": "^2.2", 26 | "benconstable/phpspec-laravel": "~2.0", 27 | "orchestra/testbench": "3.1.*" 28 | }, 29 | "autoload": { 30 | "classmap" : [ 31 | "src/" 32 | ], 33 | "psr-0": { 34 | "Cupona\\": "src/" 35 | } 36 | }, 37 | "minimum-stability": "dev" 38 | } 39 | -------------------------------------------------------------------------------- /phpspec.yml: -------------------------------------------------------------------------------- 1 | suites: 2 | main: 3 | namespace: Cupona 4 | psr4_prefix: Cupona 5 | src_path: src 6 | 7 | extensions: 8 | - PhpSpec\Laravel\Extension\LaravelExtension 9 | 10 | laravel_extension: 11 | testing_environment: 'testing' -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | ./tests/ 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/config/context.php: -------------------------------------------------------------------------------- 1 | 'default', 16 | 17 | /* 18 | |-------------------------------------------------------------------------- 19 | | Matcher 20 | |-------------------------------------------------------------------------- 21 | | 22 | | Sets the matcher to be used by the NamespaceBuilder. It will 23 | | search for any {ClassName} matches and replace them with the 24 | | passed context (converting your context into PSR-1 StudlyCaps) 25 | | 26 | */ 27 | 'matcher' => 'Contextual\\Providers\\{ClassName}ServiceProvider', 28 | 29 | /* 30 | |-------------------------------------------------------------------------- 31 | | Custom Service Providers 32 | |-------------------------------------------------------------------------- 33 | | 34 | | Here you can preload all your custom service 35 | | providers, given a key-value pair for each 36 | | one of them (context => concrete) 37 | | 38 | */ 39 | 'custom' => [ 40 | 'test' => 'Contextual\\Providers\\TestServiceProvider', 41 | ] 42 | 43 | ]; -------------------------------------------------------------------------------- /src/rtroncoso/facades/Context.php: -------------------------------------------------------------------------------- 1 | app = $app; 29 | $this->builder = $builder; 30 | } 31 | 32 | /** 33 | * Gets the current context 34 | * 35 | * @return string 36 | */ 37 | public function current() 38 | { 39 | return $this->current; 40 | } 41 | 42 | /** 43 | * Loads a service provider depending on the context passed 44 | * to this method 45 | * 46 | * @param mixed $context 47 | * @param $matcher 48 | * @return \Illuminate\Support\ServiceProvider 49 | */ 50 | public function load($context = null, $matcher = null) 51 | { 52 | if(is_null($context)) { 53 | $context = $this->app['config']['context.default']; 54 | } 55 | 56 | if(is_null($matcher)) { 57 | $matcher = $this->app['config']['context.matcher']; 58 | } 59 | 60 | return $this->build($context, $matcher); 61 | } 62 | 63 | /** 64 | * Builds a provider string and loads it into our application 65 | * 66 | * @param $context 67 | * @param $matcher 68 | * @return \Illuminate\Support\ServiceProvider|null 69 | */ 70 | private function build($context, $matcher) 71 | { 72 | // Save our current context 73 | $this->current = $context; 74 | 75 | $provider = $this->getPreloadedProvider($context) ?: 76 | $this->builder->build($this->current, $matcher); 77 | 78 | return $this->validProvider($provider) ? 79 | $this->app->register($provider) : null; 80 | } 81 | 82 | /** 83 | * Gets a preloaded Service Provider from our config string 84 | * or else return null if not found 85 | * 86 | * @param $context 87 | * @return string 88 | */ 89 | private function getPreloadedProvider($context) 90 | { 91 | $config = $this->app['config']['context.custom']; 92 | 93 | if(!is_null($config)) { 94 | return array_key_exists($context, $config) ? $config[$context] : null; 95 | } 96 | 97 | return null; 98 | } 99 | 100 | /** 101 | * Will check if the provider given is instantiable, 102 | * if not it will throw ReflectionException 103 | * 104 | * @param $provider 105 | * @return ReflectionClass 106 | * @throws \ReflectionException 107 | */ 108 | private function validProvider($provider) 109 | { 110 | return new ReflectionClass($provider); 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /src/rtroncoso/middleware/ContextMiddleware.php: -------------------------------------------------------------------------------- 1 | route()->getAction(); 28 | $context = $context ?: $this->getContext($actions); 29 | 30 | Context::load($context); 31 | 32 | return $next($request); 33 | } 34 | 35 | /** 36 | * Gets a context from the actions in the request 37 | * 38 | * @param $actions 39 | * @return mixed 40 | */ 41 | public function getContext($actions) 42 | { 43 | if (array_key_exists('context', $actions)) { 44 | return $actions['context']; 45 | } 46 | 47 | return null; 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/rtroncoso/providers/ContextServiceProvider.php: -------------------------------------------------------------------------------- 1 | publishes([ 26 | __DIR__.'/../../config/context.php' => config_path('context.php') 27 | ], 'config'); 28 | 29 | $this->app->bind('context', 'Cupona\\Libraries\\Context'); 30 | } 31 | 32 | /** 33 | * Register the service provider. 34 | * 35 | * @return void 36 | */ 37 | public function register() 38 | { 39 | // 40 | } 41 | 42 | /** 43 | * Get the services provided by the provider. 44 | * 45 | * @return array 46 | */ 47 | public function provides() 48 | { 49 | return []; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/rtroncoso/providers/DefaultServiceProvider.php: -------------------------------------------------------------------------------- 1 | applyMatcher($className, $matcher); 27 | 28 | return $this->stripBackslashes($classPath); 29 | } 30 | 31 | /** 32 | * Applies a class name to a given matcher 33 | * 34 | * @param $className 35 | * @param null $matcher 36 | * @return mixed 37 | */ 38 | private function applyMatcher($className, $matcher = null) 39 | { 40 | if(is_null($matcher)) { 41 | return ucwords($className); 42 | } 43 | 44 | return preg_replace($this->pattern, ucwords($className), $matcher); 45 | } 46 | 47 | /** 48 | * Removes trailing or leading backslashes to the given 49 | * Class Path 50 | * 51 | * @param $classPath 52 | * @return string 53 | */ 54 | private function stripBackslashes($classPath) 55 | { 56 | return preg_replace(['/(\\\+)$/', '/^(\\\+)/'], '', $classPath); 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /tests/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rtroncoso/Laravel-Context/4e8fbd57ea2b48686d06524efcba09f4ad9d8bb9/tests/.gitkeep -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | set('context', [ 20 | 'default' => 'default', 21 | 'matcher' => 'Contextual\\Providers\\{ClassName}ServiceProvider', 22 | 'custom' => [ 23 | 'test' => 'Contextual\\Providers\\DummyServiceProvider' 24 | ] 25 | ]); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /tests/libraries/ContextTest.php: -------------------------------------------------------------------------------- 1 | app->make('Cupona\\Libraries\\Context'); 11 | 12 | $this->assertInstanceOf('Cupona\\Libraries\\Context', $context); 13 | } 14 | 15 | /** 16 | * @test 17 | */ 18 | public function it_should_load_the_default_service_provider() 19 | { 20 | $context = $this->app->make('Cupona\\Libraries\\Context'); 21 | $provider = $context->load(); 22 | 23 | $this->assertInstanceOf('Contextual\\Providers\\DefaultServiceProvider', $provider); 24 | $this->assertArrayHasKey('Contextual\\Providers\\DefaultServiceProvider', $this->app->getLoadedProviders()); 25 | $this->assertEquals('default', $context->current()); 26 | } 27 | 28 | /** 29 | * @test 30 | */ 31 | public function it_should_load_a_dummy_service_provider() 32 | { 33 | $context = $this->app->make('Cupona\\Libraries\\Context'); 34 | $provider = $context->load('dummy'); 35 | 36 | $this->assertInstanceOf('Contextual\\Providers\\DummyServiceProvider', $provider); 37 | $this->assertArrayHasKey('Contextual\\Providers\\DummyServiceProvider', $this->app->getLoadedProviders()); 38 | $this->assertEquals('dummy', $context->current()); 39 | } 40 | 41 | /** 42 | * @test 43 | */ 44 | public function it_should_save_the_current_context() 45 | { 46 | $context = $this->app->make('Cupona\\Libraries\\Context'); 47 | $provider = $context->load(); 48 | 49 | $this->assertInstanceOf('Contextual\\Providers\\DefaultServiceProvider', $provider); 50 | $this->assertEquals('default', $context->current()); 51 | } 52 | 53 | /** 54 | * @test 55 | */ 56 | public function it_should_load_a_preloaded_provider() 57 | { 58 | $context = $this->app->make('Cupona\\Libraries\\Context'); 59 | $provider = $context->load('test'); 60 | 61 | $this->assertInstanceOf('Contextual\\Providers\\DummyServiceProvider', $provider); 62 | $this->assertArrayHasKey('Contextual\\Providers\\DummyServiceProvider', $this->app->getLoadedProviders()); 63 | $this->assertEquals('test', $context->current()); 64 | } 65 | 66 | /** 67 | * @test 68 | * @expectedException ReflectionException 69 | */ 70 | public function it_should_not_load_an_unexisting_provider() 71 | { 72 | $context = $this->app->make('Cupona\\Libraries\\Context'); 73 | $context->load('nonExisting'); 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /tests/services/NamespaceBuilderTest.php: -------------------------------------------------------------------------------- 1 | app->make('Cupona\\Services\\NamespaceBuilder'); 14 | $fullName = $builder->build($class, $matcher); 15 | 16 | $this->assertEquals('Cupona\\Providers\\DefaultServiceProvider', $fullName); 17 | } 18 | 19 | /** 20 | * @test 21 | */ 22 | public function it_should_build_a_class_name_without_a_matcher() 23 | { 24 | $class = 'withoutMatcher'; 25 | 26 | $builder = $this->app->make('Cupona\\Services\\NamespaceBuilder'); 27 | $fullName = $builder->build($class); 28 | 29 | $this->assertEquals('WithoutMatcher', $fullName); 30 | } 31 | 32 | } 33 | --------------------------------------------------------------------------------