├── .gitignore ├── .travis.yml ├── README.md ├── composer.json ├── composer.lock ├── phpunit.xml ├── src ├── Facades │ └── Tenant.php ├── Middlewares │ └── TenantDatabase.php ├── Providers │ └── TenantServiceProvider.php ├── Routing │ └── UrlGenerator.php ├── TenantManager.php └── config │ └── tenant.php └── tests ├── .gitkeep ├── TenantManagerTest.php ├── stubs └── configuration_test_stub.php └── temp └── .gitkeep /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | **/.DS_Store 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.6 5 | 6 | before_script: 7 | - composer self-update 8 | - composer install --prefer-source --no-interaction --dev 9 | 10 | script: phpunit -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Tenant Subdomínio 2 | [![Build Status](https://travis-ci.org/dlimars/laravel-tenant-subdomain.svg)](https://travis-ci.org/dlimars/laravel-tenant-subdomain) 3 | [![Latest Stable Version](https://poser.pugx.org/dlimars/laravel-tenant-subdomain/v/stable)](https://packagist.org/packages/dlimars/laravel-tenant-subdomain) 4 | [![Total Downloads](https://poser.pugx.org/dlimars/laravel-tenant-subdomain/downloads)](https://packagist.org/packages/dlimars/laravel-tenant-subdomain) 5 | [![Latest Unstable Version](https://poser.pugx.org/dlimars/laravel-tenant-subdomain/v/unstable)](https://packagist.org/packages/dlimars/laravel-tenant-subdomain) 6 | [![License](https://poser.pugx.org/dlimars/laravel-tenant-subdomain/license)](https://packagist.org/packages/dlimars/laravel-tenant-subdomain) 7 | 8 | Este pacote irá auxiliar na organização de clientes em subdomínios usando Laravel. 9 | 10 | ## Instalação 11 | Adicione no seu `composer.json` 12 | 13 | ```js 14 | "require": { 15 | //.. 16 | "dlimars/laravel-tenant-subdomain": "^1.0" 17 | }, 18 | ``` 19 | 20 | ou execute em seu terminal 21 | ``` 22 | composer require dlimars/laravel-tenant-subdomain 23 | ``` 24 | 25 | adicione o provider e o facade em `config/app.php`: 26 | 27 | ```php 28 | 'providers' => [ 29 | // outros providers 30 | Dlimars\Tenant\Providers\TenantServiceProvider::class, 31 | ], 32 | 33 | 'aliases' => [ 34 | // outros aliases 35 | 'Tenant' => Dlimars\Tenant\Facades\Tenant::class, 36 | ] 37 | ``` 38 | 39 | adicione o middleware em `app/Http/Kernel.php` 40 | 41 | ```php 42 | protected $routeMiddleware = [ 43 | // outros middlewares 44 | 'tenant.database' => \Dlimars\Tenant\Middlewares\TenantDatabase::class 45 | ]; 46 | ``` 47 | 48 | Após isso, abra seu console e execute: `php artisan vendor:publish`, modifique o arquivo `config/tenant.php` para sua necessidade, abra seu arquivo `.env` e adicione: 49 | 50 | ``` 51 | APP_HOST=domain.com 52 | TENANT_SUBDOMAIN_ARGUMENT=_account_ 53 | ``` 54 | 55 | ## Uso 56 | 57 | para gerar rotas de subdominio, utilize da seguinte forma: 58 | 59 | ```php 60 | // Tenant::getFullDomain() retorna algo como '{_account_}.domain.com' 61 | 62 | Route::group(['domain' => Tenant::getFullDomain()], function () { 63 | Route::get('subdomain-teste/{id}', ['as' => 'subdomain-teste', function($subdomain, $id){ 64 | return route('subdomain-teste', ['123']); 65 | }]); 66 | }); 67 | ``` 68 | 69 | para gerar rotas para a aplicação principal (que não seja subdominio), utilize da seguinte forma 70 | 71 | ```php 72 | // Tenant::getDomain() retorna algo como 'domain.com' 73 | 74 | Route::group(['domain' => Tenant::getDomain()], function () { 75 | Route::get('domain-teste/{id}', ['as' => 'domain-teste', function($id){ 76 | return route('domain-teste', ['123']); 77 | }]); 78 | }); 79 | 80 | // isso impede que rotas do dominio possam ser acessadas através do subdominio 81 | ``` 82 | 83 | ## Carregando as configurações de banco de acordo com o subdominio 84 | 85 | os arquivos de configurações de banco serão lidos por padrão, dentro da pasta `config/tenant`, com o exemplo de conteudo: 86 | 87 | ```php 88 | return [ 89 | 'driver' => 'mysql', 90 | 'host' => 'host', 91 | 'database' => 'db_subdomain', 92 | 'username' => 'user_subdomain', 93 | 'password' => 'user_password', 94 | 'charset' => 'utf8', 95 | 'collation' => 'utf8_unicode_ci', 96 | 'prefix' => '', 97 | 'strict' => false, 98 | ]; 99 | ``` 100 | 101 | o arquivo é lido e adicionado como conexão padrão `tenant`, isso é feito via Middleware, em todas as rotas que irão utilizar base de dados própria, use o middleware `tenant.database`: 102 | 103 | ```php 104 | Route::group(['domain' => Tenant::getFullDomain(), 'middleware' => ['tenant.database']], function () { 105 | Route::get('domain-teste/{id}', ['as' => 'domain-teste', function($id){ 106 | return route('domain-teste', ['123']); 107 | }]); 108 | }); 109 | ``` 110 | 111 | Supondo que o usuário acesse `http://beltrano.domain.com`, a configuração a ser carregada deverá estar em `/config/tenants/beltrano.php` (isso é configurável) 112 | 113 | 114 | ### Criar configurações de banco 115 | 116 | Para criar uma nova configuração de banco, use da seguinte forma: 117 | 118 | ```php 119 | $config = [ 120 | 'foo' => 'bar' 121 | ]; 122 | 123 | Tenant::makeDatabaseConfigFile('foo', $config); 124 | ``` 125 | 126 | isso irá gerar um arquivo dentro de `config/tenants` com o nome de `foo.php` (ou como/onde for definido na configuração), com o seguinte conteúdo 127 | 128 | ```php 129 | return [ 130 | 'foo' => 'bar' 131 | ]; 132 | ``` 133 | 134 | ### Excluir configurações de banco 135 | 136 | Para excluir um arquivo de configuração, apenas execute da seguinte maneira: 137 | 138 | ```php 139 | Tenant::dropDatabaseConfigFile('foo'); 140 | ``` -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dlimars/laravel-tenant-subdomain", 3 | "description": "Pacote para facilitar criação de rotas e troca de banco de dados baseado no subdominio da sua aplicação", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "Daniel Lima", 8 | "email": "daniel@docode.com.br" 9 | } 10 | ], 11 | "minimum-stability": "dev", 12 | "require": { 13 | "illuminate/routing": "5.*", 14 | "illuminate/config": "5.*" 15 | }, 16 | "autoload": { 17 | "psr-4": { 18 | "Dlimars\\Tenant\\": "src/" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "hash": "799169eaf5a1f155e0f2072806d8e7f5", 8 | "content-hash": "e8282caa55d168b6dcdbde13458e3003", 9 | "packages": [ 10 | { 11 | "name": "doctrine/inflector", 12 | "version": "dev-master", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/doctrine/inflector.git", 16 | "reference": "90b2128806bfde671b6952ab8bea493942c1fdae" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/doctrine/inflector/zipball/90b2128806bfde671b6952ab8bea493942c1fdae", 21 | "reference": "90b2128806bfde671b6952ab8bea493942c1fdae", 22 | "shasum": "" 23 | }, 24 | "require": { 25 | "php": ">=5.3.2" 26 | }, 27 | "require-dev": { 28 | "phpunit/phpunit": "4.*" 29 | }, 30 | "type": "library", 31 | "extra": { 32 | "branch-alias": { 33 | "dev-master": "1.1.x-dev" 34 | } 35 | }, 36 | "autoload": { 37 | "psr-0": { 38 | "Doctrine\\Common\\Inflector\\": "lib/" 39 | } 40 | }, 41 | "notification-url": "https://packagist.org/downloads/", 42 | "license": [ 43 | "MIT" 44 | ], 45 | "authors": [ 46 | { 47 | "name": "Roman Borschel", 48 | "email": "roman@code-factory.org" 49 | }, 50 | { 51 | "name": "Benjamin Eberlei", 52 | "email": "kontakt@beberlei.de" 53 | }, 54 | { 55 | "name": "Guilherme Blanco", 56 | "email": "guilhermeblanco@gmail.com" 57 | }, 58 | { 59 | "name": "Jonathan Wage", 60 | "email": "jonwage@gmail.com" 61 | }, 62 | { 63 | "name": "Johannes Schmitt", 64 | "email": "schmittjoh@gmail.com" 65 | } 66 | ], 67 | "description": "Common String Manipulations with regard to casing and singular/plural rules.", 68 | "homepage": "http://www.doctrine-project.org", 69 | "keywords": [ 70 | "inflection", 71 | "pluralize", 72 | "singularize", 73 | "string" 74 | ], 75 | "time": "2015-11-06 14:35:42" 76 | }, 77 | { 78 | "name": "illuminate/config", 79 | "version": "5.2.x-dev", 80 | "source": { 81 | "type": "git", 82 | "url": "https://github.com/illuminate/config.git", 83 | "reference": "29d25fa086eac092a54859b3cbf7580299fc101e" 84 | }, 85 | "dist": { 86 | "type": "zip", 87 | "url": "https://api.github.com/repos/illuminate/config/zipball/29d25fa086eac092a54859b3cbf7580299fc101e", 88 | "reference": "29d25fa086eac092a54859b3cbf7580299fc101e", 89 | "shasum": "" 90 | }, 91 | "require": { 92 | "illuminate/contracts": "5.2.*", 93 | "illuminate/filesystem": "5.2.*", 94 | "illuminate/support": "5.2.*", 95 | "php": ">=5.5.9" 96 | }, 97 | "type": "library", 98 | "extra": { 99 | "branch-alias": { 100 | "dev-master": "5.2-dev" 101 | } 102 | }, 103 | "autoload": { 104 | "psr-4": { 105 | "Illuminate\\Config\\": "" 106 | } 107 | }, 108 | "notification-url": "https://packagist.org/downloads/", 109 | "license": [ 110 | "MIT" 111 | ], 112 | "authors": [ 113 | { 114 | "name": "Taylor Otwell", 115 | "email": "taylorotwell@gmail.com" 116 | } 117 | ], 118 | "description": "The Illuminate Config package.", 119 | "homepage": "http://laravel.com", 120 | "time": "2015-06-22 20:36:58" 121 | }, 122 | { 123 | "name": "illuminate/container", 124 | "version": "5.2.x-dev", 125 | "source": { 126 | "type": "git", 127 | "url": "https://github.com/illuminate/container.git", 128 | "reference": "17192553b2d14baa0af98ff2ff77a3f91ab06232" 129 | }, 130 | "dist": { 131 | "type": "zip", 132 | "url": "https://api.github.com/repos/illuminate/container/zipball/17192553b2d14baa0af98ff2ff77a3f91ab06232", 133 | "reference": "17192553b2d14baa0af98ff2ff77a3f91ab06232", 134 | "shasum": "" 135 | }, 136 | "require": { 137 | "illuminate/contracts": "5.2.*", 138 | "php": ">=5.5.9" 139 | }, 140 | "type": "library", 141 | "extra": { 142 | "branch-alias": { 143 | "dev-master": "5.2-dev" 144 | } 145 | }, 146 | "autoload": { 147 | "psr-4": { 148 | "Illuminate\\Container\\": "" 149 | } 150 | }, 151 | "notification-url": "https://packagist.org/downloads/", 152 | "license": [ 153 | "MIT" 154 | ], 155 | "authors": [ 156 | { 157 | "name": "Taylor Otwell", 158 | "email": "taylorotwell@gmail.com" 159 | } 160 | ], 161 | "description": "The Illuminate Container package.", 162 | "homepage": "http://laravel.com", 163 | "time": "2015-12-23 01:00:15" 164 | }, 165 | { 166 | "name": "illuminate/contracts", 167 | "version": "5.2.x-dev", 168 | "source": { 169 | "type": "git", 170 | "url": "https://github.com/illuminate/contracts.git", 171 | "reference": "65ba0830f51da1cbaa1df38ff955c9df228daf7f" 172 | }, 173 | "dist": { 174 | "type": "zip", 175 | "url": "https://api.github.com/repos/illuminate/contracts/zipball/65ba0830f51da1cbaa1df38ff955c9df228daf7f", 176 | "reference": "65ba0830f51da1cbaa1df38ff955c9df228daf7f", 177 | "shasum": "" 178 | }, 179 | "require": { 180 | "php": ">=5.5.9" 181 | }, 182 | "type": "library", 183 | "extra": { 184 | "branch-alias": { 185 | "dev-master": "5.2-dev" 186 | } 187 | }, 188 | "autoload": { 189 | "psr-4": { 190 | "Illuminate\\Contracts\\": "" 191 | } 192 | }, 193 | "notification-url": "https://packagist.org/downloads/", 194 | "license": [ 195 | "MIT" 196 | ], 197 | "authors": [ 198 | { 199 | "name": "Taylor Otwell", 200 | "email": "taylorotwell@gmail.com" 201 | } 202 | ], 203 | "description": "The Illuminate Contracts package.", 204 | "homepage": "http://laravel.com", 205 | "time": "2015-12-19 14:25:38" 206 | }, 207 | { 208 | "name": "illuminate/filesystem", 209 | "version": "5.2.x-dev", 210 | "source": { 211 | "type": "git", 212 | "url": "https://github.com/illuminate/filesystem.git", 213 | "reference": "a2d2815b8dcd825377d5c52c75170fcad7a9b9cb" 214 | }, 215 | "dist": { 216 | "type": "zip", 217 | "url": "https://api.github.com/repos/illuminate/filesystem/zipball/a2d2815b8dcd825377d5c52c75170fcad7a9b9cb", 218 | "reference": "a2d2815b8dcd825377d5c52c75170fcad7a9b9cb", 219 | "shasum": "" 220 | }, 221 | "require": { 222 | "illuminate/contracts": "5.2.*", 223 | "illuminate/support": "5.2.*", 224 | "php": ">=5.5.9", 225 | "symfony/finder": "2.8.*|3.0.*" 226 | }, 227 | "suggest": { 228 | "league/flysystem": "Required to use the Flysystem local and FTP drivers (~1.0).", 229 | "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (~1.0).", 230 | "league/flysystem-rackspace": "Required to use the Flysystem Rackspace driver (~1.0)." 231 | }, 232 | "type": "library", 233 | "extra": { 234 | "branch-alias": { 235 | "dev-master": "5.2-dev" 236 | } 237 | }, 238 | "autoload": { 239 | "psr-4": { 240 | "Illuminate\\Filesystem\\": "" 241 | } 242 | }, 243 | "notification-url": "https://packagist.org/downloads/", 244 | "license": [ 245 | "MIT" 246 | ], 247 | "authors": [ 248 | { 249 | "name": "Taylor Otwell", 250 | "email": "taylorotwell@gmail.com" 251 | } 252 | ], 253 | "description": "The Illuminate Filesystem package.", 254 | "homepage": "http://laravel.com", 255 | "time": "2015-12-20 15:49:15" 256 | }, 257 | { 258 | "name": "illuminate/http", 259 | "version": "5.2.x-dev", 260 | "source": { 261 | "type": "git", 262 | "url": "https://github.com/illuminate/http.git", 263 | "reference": "e9b5d0843ff6d5bde4ed316797478cbed9677faa" 264 | }, 265 | "dist": { 266 | "type": "zip", 267 | "url": "https://api.github.com/repos/illuminate/http/zipball/e9b5d0843ff6d5bde4ed316797478cbed9677faa", 268 | "reference": "e9b5d0843ff6d5bde4ed316797478cbed9677faa", 269 | "shasum": "" 270 | }, 271 | "require": { 272 | "illuminate/session": "5.2.*", 273 | "illuminate/support": "5.2.*", 274 | "php": ">=5.5.9", 275 | "symfony/http-foundation": "2.8.*|3.0.*", 276 | "symfony/http-kernel": "2.8.*|3.0.*" 277 | }, 278 | "type": "library", 279 | "extra": { 280 | "branch-alias": { 281 | "dev-master": "5.2-dev" 282 | } 283 | }, 284 | "autoload": { 285 | "psr-4": { 286 | "Illuminate\\Http\\": "" 287 | } 288 | }, 289 | "notification-url": "https://packagist.org/downloads/", 290 | "license": [ 291 | "MIT" 292 | ], 293 | "authors": [ 294 | { 295 | "name": "Taylor Otwell", 296 | "email": "taylorotwell@gmail.com" 297 | } 298 | ], 299 | "description": "The Illuminate Http package.", 300 | "homepage": "http://laravel.com", 301 | "time": "2015-12-20 20:04:47" 302 | }, 303 | { 304 | "name": "illuminate/pipeline", 305 | "version": "5.2.x-dev", 306 | "source": { 307 | "type": "git", 308 | "url": "https://github.com/illuminate/pipeline.git", 309 | "reference": "0ba4095c46fdc26da52811395e94f49f0b68b896" 310 | }, 311 | "dist": { 312 | "type": "zip", 313 | "url": "https://api.github.com/repos/illuminate/pipeline/zipball/0ba4095c46fdc26da52811395e94f49f0b68b896", 314 | "reference": "0ba4095c46fdc26da52811395e94f49f0b68b896", 315 | "shasum": "" 316 | }, 317 | "require": { 318 | "illuminate/contracts": "5.2.*", 319 | "illuminate/support": "5.2.*", 320 | "php": ">=5.5.9" 321 | }, 322 | "type": "library", 323 | "extra": { 324 | "branch-alias": { 325 | "dev-master": "5.2-dev" 326 | } 327 | }, 328 | "autoload": { 329 | "psr-4": { 330 | "Illuminate\\Pipeline\\": "" 331 | } 332 | }, 333 | "notification-url": "https://packagist.org/downloads/", 334 | "license": [ 335 | "MIT" 336 | ], 337 | "authors": [ 338 | { 339 | "name": "Taylor Otwell", 340 | "email": "taylorotwell@gmail.com" 341 | } 342 | ], 343 | "description": "The Illuminate Pipeline package.", 344 | "homepage": "http://laravel.com", 345 | "time": "2015-12-10 18:01:38" 346 | }, 347 | { 348 | "name": "illuminate/routing", 349 | "version": "5.2.x-dev", 350 | "source": { 351 | "type": "git", 352 | "url": "https://github.com/illuminate/routing.git", 353 | "reference": "7bb8d2cdab4d3a364e60fa7434767d81662a1c15" 354 | }, 355 | "dist": { 356 | "type": "zip", 357 | "url": "https://api.github.com/repos/illuminate/routing/zipball/aac4973f37d686c056a5c9b03ab03e66140a3d9a", 358 | "reference": "7bb8d2cdab4d3a364e60fa7434767d81662a1c15", 359 | "shasum": "" 360 | }, 361 | "require": { 362 | "illuminate/container": "5.2.*", 363 | "illuminate/contracts": "5.2.*", 364 | "illuminate/http": "5.2.*", 365 | "illuminate/pipeline": "5.2.*", 366 | "illuminate/session": "5.2.*", 367 | "illuminate/support": "5.2.*", 368 | "php": ">=5.5.9", 369 | "symfony/debug": "2.8.*|3.0.*", 370 | "symfony/http-foundation": "2.8.*|3.0.*", 371 | "symfony/http-kernel": "2.8.*|3.0.*", 372 | "symfony/routing": "2.8.*|3.0.*" 373 | }, 374 | "suggest": { 375 | "illuminate/console": "Required to use the make commands (5.2.*)." 376 | }, 377 | "type": "library", 378 | "extra": { 379 | "branch-alias": { 380 | "dev-master": "5.2-dev" 381 | } 382 | }, 383 | "autoload": { 384 | "psr-4": { 385 | "Illuminate\\Routing\\": "" 386 | } 387 | }, 388 | "notification-url": "https://packagist.org/downloads/", 389 | "license": [ 390 | "MIT" 391 | ], 392 | "authors": [ 393 | { 394 | "name": "Taylor Otwell", 395 | "email": "taylorotwell@gmail.com" 396 | } 397 | ], 398 | "description": "The Illuminate Routing package.", 399 | "homepage": "http://laravel.com", 400 | "time": "2015-12-21 16:45:17" 401 | }, 402 | { 403 | "name": "illuminate/session", 404 | "version": "5.2.x-dev", 405 | "source": { 406 | "type": "git", 407 | "url": "https://github.com/illuminate/session.git", 408 | "reference": "905217482c16fb68862db4ff2f428ec3ff78d8dc" 409 | }, 410 | "dist": { 411 | "type": "zip", 412 | "url": "https://api.github.com/repos/illuminate/session/zipball/81bd4edc19e05323f5283bc8af7947045486860a", 413 | "reference": "905217482c16fb68862db4ff2f428ec3ff78d8dc", 414 | "shasum": "" 415 | }, 416 | "require": { 417 | "illuminate/contracts": "5.2.*", 418 | "illuminate/support": "5.2.*", 419 | "nesbot/carbon": "~1.20", 420 | "php": ">=5.5.9", 421 | "symfony/finder": "2.8.*|3.0.*", 422 | "symfony/http-foundation": "2.8.*|3.0.*" 423 | }, 424 | "suggest": { 425 | "illuminate/console": "Required to use the session:table command (5.2.*)." 426 | }, 427 | "type": "library", 428 | "extra": { 429 | "branch-alias": { 430 | "dev-master": "5.2-dev" 431 | } 432 | }, 433 | "autoload": { 434 | "psr-4": { 435 | "Illuminate\\Session\\": "" 436 | } 437 | }, 438 | "notification-url": "https://packagist.org/downloads/", 439 | "license": [ 440 | "MIT" 441 | ], 442 | "authors": [ 443 | { 444 | "name": "Taylor Otwell", 445 | "email": "taylorotwell@gmail.com" 446 | } 447 | ], 448 | "description": "The Illuminate Session package.", 449 | "homepage": "http://laravel.com", 450 | "time": "2015-12-08 15:10:00" 451 | }, 452 | { 453 | "name": "illuminate/support", 454 | "version": "5.2.x-dev", 455 | "source": { 456 | "type": "git", 457 | "url": "https://github.com/illuminate/support.git", 458 | "reference": "2aa2d981d251b309f3cb2d57ac95fed0cb17df88" 459 | }, 460 | "dist": { 461 | "type": "zip", 462 | "url": "https://api.github.com/repos/illuminate/support/zipball/9b0bf9de6deab284958df1738c427377ed23546b", 463 | "reference": "2aa2d981d251b309f3cb2d57ac95fed0cb17df88", 464 | "shasum": "" 465 | }, 466 | "require": { 467 | "doctrine/inflector": "~1.0", 468 | "ext-mbstring": "*", 469 | "illuminate/contracts": "5.2.*", 470 | "php": ">=5.5.9" 471 | }, 472 | "suggest": { 473 | "illuminate/filesystem": "Required to use the composer class (5.2.*).", 474 | "jeremeamia/superclosure": "Required to be able to serialize closures (~2.2).", 475 | "paragonie/random_compat": "Provides a compatible interface like PHP7's random_bytes() in PHP 5 projects (~1.1).", 476 | "symfony/polyfill-php56": "Required to use the hash_equals function on PHP 5.5 (~1.0).", 477 | "symfony/process": "Required to use the composer class (2.8.*|3.0.*).", 478 | "symfony/var-dumper": "Improves the dd function (2.8.*|3.0.*)." 479 | }, 480 | "type": "library", 481 | "extra": { 482 | "branch-alias": { 483 | "dev-master": "5.2-dev" 484 | } 485 | }, 486 | "autoload": { 487 | "psr-4": { 488 | "Illuminate\\Support\\": "" 489 | }, 490 | "files": [ 491 | "helpers.php" 492 | ] 493 | }, 494 | "notification-url": "https://packagist.org/downloads/", 495 | "license": [ 496 | "MIT" 497 | ], 498 | "authors": [ 499 | { 500 | "name": "Taylor Otwell", 501 | "email": "taylorotwell@gmail.com" 502 | } 503 | ], 504 | "description": "The Illuminate Support package.", 505 | "homepage": "http://laravel.com", 506 | "time": "2015-12-22 16:42:39" 507 | }, 508 | { 509 | "name": "nesbot/carbon", 510 | "version": "1.21.0", 511 | "source": { 512 | "type": "git", 513 | "url": "https://github.com/briannesbitt/Carbon.git", 514 | "reference": "7b08ec6f75791e130012f206e3f7b0e76e18e3d7" 515 | }, 516 | "dist": { 517 | "type": "zip", 518 | "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/7b08ec6f75791e130012f206e3f7b0e76e18e3d7", 519 | "reference": "7b08ec6f75791e130012f206e3f7b0e76e18e3d7", 520 | "shasum": "" 521 | }, 522 | "require": { 523 | "php": ">=5.3.0", 524 | "symfony/translation": "~2.6|~3.0" 525 | }, 526 | "require-dev": { 527 | "phpunit/phpunit": "~4.0|~5.0" 528 | }, 529 | "type": "library", 530 | "autoload": { 531 | "psr-4": { 532 | "Carbon\\": "src/Carbon/" 533 | } 534 | }, 535 | "notification-url": "https://packagist.org/downloads/", 536 | "license": [ 537 | "MIT" 538 | ], 539 | "authors": [ 540 | { 541 | "name": "Brian Nesbitt", 542 | "email": "brian@nesbot.com", 543 | "homepage": "http://nesbot.com" 544 | } 545 | ], 546 | "description": "A simple API extension for DateTime.", 547 | "homepage": "http://carbon.nesbot.com", 548 | "keywords": [ 549 | "date", 550 | "datetime", 551 | "time" 552 | ], 553 | "time": "2015-11-04 20:07:17" 554 | }, 555 | { 556 | "name": "psr/log", 557 | "version": "dev-master", 558 | "source": { 559 | "type": "git", 560 | "url": "https://github.com/php-fig/log.git", 561 | "reference": "9e45edca52cc9c954680072c93e621f8b71fab26" 562 | }, 563 | "dist": { 564 | "type": "zip", 565 | "url": "https://api.github.com/repos/php-fig/log/zipball/9e45edca52cc9c954680072c93e621f8b71fab26", 566 | "reference": "9e45edca52cc9c954680072c93e621f8b71fab26", 567 | "shasum": "" 568 | }, 569 | "require": { 570 | "php": ">=5.3.0" 571 | }, 572 | "type": "library", 573 | "extra": { 574 | "branch-alias": { 575 | "dev-master": "1.0.x-dev" 576 | } 577 | }, 578 | "autoload": { 579 | "psr-4": { 580 | "Psr\\Log\\": "Psr/Log/" 581 | } 582 | }, 583 | "notification-url": "https://packagist.org/downloads/", 584 | "license": [ 585 | "MIT" 586 | ], 587 | "authors": [ 588 | { 589 | "name": "PHP-FIG", 590 | "homepage": "http://www.php-fig.org/" 591 | } 592 | ], 593 | "description": "Common interface for logging libraries", 594 | "keywords": [ 595 | "log", 596 | "psr", 597 | "psr-3" 598 | ], 599 | "time": "2015-06-02 13:48:41" 600 | }, 601 | { 602 | "name": "symfony/debug", 603 | "version": "3.0.x-dev", 604 | "source": { 605 | "type": "git", 606 | "url": "https://github.com/symfony/debug.git", 607 | "reference": "4282ff2e8a736d07068f98a2450d84d1bb9e0748" 608 | }, 609 | "dist": { 610 | "type": "zip", 611 | "url": "https://api.github.com/repos/symfony/debug/zipball/2191d01d7708c57d131ce67de81355cdaa791ff3", 612 | "reference": "4282ff2e8a736d07068f98a2450d84d1bb9e0748", 613 | "shasum": "" 614 | }, 615 | "require": { 616 | "php": ">=5.5.9", 617 | "psr/log": "~1.0" 618 | }, 619 | "conflict": { 620 | "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" 621 | }, 622 | "require-dev": { 623 | "symfony/class-loader": "~2.8|~3.0", 624 | "symfony/http-kernel": "~2.8|~3.0" 625 | }, 626 | "type": "library", 627 | "extra": { 628 | "branch-alias": { 629 | "dev-master": "3.0-dev" 630 | } 631 | }, 632 | "autoload": { 633 | "psr-4": { 634 | "Symfony\\Component\\Debug\\": "" 635 | }, 636 | "exclude-from-classmap": [ 637 | "/Tests/" 638 | ] 639 | }, 640 | "notification-url": "https://packagist.org/downloads/", 641 | "license": [ 642 | "MIT" 643 | ], 644 | "authors": [ 645 | { 646 | "name": "Fabien Potencier", 647 | "email": "fabien@symfony.com" 648 | }, 649 | { 650 | "name": "Symfony Community", 651 | "homepage": "https://symfony.com/contributors" 652 | } 653 | ], 654 | "description": "Symfony Debug Component", 655 | "homepage": "https://symfony.com", 656 | "time": "2015-12-09 00:47:17" 657 | }, 658 | { 659 | "name": "symfony/event-dispatcher", 660 | "version": "dev-master", 661 | "source": { 662 | "type": "git", 663 | "url": "https://github.com/symfony/event-dispatcher.git", 664 | "reference": "ae978c6a5289e42111b2fadd38fb0b53f772cd66" 665 | }, 666 | "dist": { 667 | "type": "zip", 668 | "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/ae978c6a5289e42111b2fadd38fb0b53f772cd66", 669 | "reference": "ae978c6a5289e42111b2fadd38fb0b53f772cd66", 670 | "shasum": "" 671 | }, 672 | "require": { 673 | "php": ">=5.5.9" 674 | }, 675 | "require-dev": { 676 | "psr/log": "~1.0", 677 | "symfony/config": "~2.8|~3.0", 678 | "symfony/dependency-injection": "~2.8|~3.0", 679 | "symfony/expression-language": "~2.8|~3.0", 680 | "symfony/stopwatch": "~2.8|~3.0" 681 | }, 682 | "suggest": { 683 | "symfony/dependency-injection": "", 684 | "symfony/http-kernel": "" 685 | }, 686 | "type": "library", 687 | "extra": { 688 | "branch-alias": { 689 | "dev-master": "3.1-dev" 690 | } 691 | }, 692 | "autoload": { 693 | "psr-4": { 694 | "Symfony\\Component\\EventDispatcher\\": "" 695 | }, 696 | "exclude-from-classmap": [ 697 | "/Tests/" 698 | ] 699 | }, 700 | "notification-url": "https://packagist.org/downloads/", 701 | "license": [ 702 | "MIT" 703 | ], 704 | "authors": [ 705 | { 706 | "name": "Fabien Potencier", 707 | "email": "fabien@symfony.com" 708 | }, 709 | { 710 | "name": "Symfony Community", 711 | "homepage": "https://symfony.com/contributors" 712 | } 713 | ], 714 | "description": "Symfony EventDispatcher Component", 715 | "homepage": "https://symfony.com", 716 | "time": "2015-11-30 21:39:17" 717 | }, 718 | { 719 | "name": "symfony/finder", 720 | "version": "3.0.x-dev", 721 | "source": { 722 | "type": "git", 723 | "url": "https://github.com/symfony/finder.git", 724 | "reference": "8617895eb798b6bdb338321ce19453dc113e5675" 725 | }, 726 | "dist": { 727 | "type": "zip", 728 | "url": "https://api.github.com/repos/symfony/finder/zipball/8617895eb798b6bdb338321ce19453dc113e5675", 729 | "reference": "8617895eb798b6bdb338321ce19453dc113e5675", 730 | "shasum": "" 731 | }, 732 | "require": { 733 | "php": ">=5.5.9" 734 | }, 735 | "type": "library", 736 | "extra": { 737 | "branch-alias": { 738 | "dev-master": "3.0-dev" 739 | } 740 | }, 741 | "autoload": { 742 | "psr-4": { 743 | "Symfony\\Component\\Finder\\": "" 744 | }, 745 | "exclude-from-classmap": [ 746 | "/Tests/" 747 | ] 748 | }, 749 | "notification-url": "https://packagist.org/downloads/", 750 | "license": [ 751 | "MIT" 752 | ], 753 | "authors": [ 754 | { 755 | "name": "Fabien Potencier", 756 | "email": "fabien@symfony.com" 757 | }, 758 | { 759 | "name": "Symfony Community", 760 | "homepage": "https://symfony.com/contributors" 761 | } 762 | ], 763 | "description": "Symfony Finder Component", 764 | "homepage": "https://symfony.com", 765 | "time": "2015-12-05 11:13:14" 766 | }, 767 | { 768 | "name": "symfony/http-foundation", 769 | "version": "3.0.x-dev", 770 | "source": { 771 | "type": "git", 772 | "url": "https://github.com/symfony/http-foundation.git", 773 | "reference": "939c8c28a5b1e4ab7317bc30c1f9aa881c4b06b5" 774 | }, 775 | "dist": { 776 | "type": "zip", 777 | "url": "https://api.github.com/repos/symfony/http-foundation/zipball/937540ad69d3e578535d80ce48dbdfba2e1d7b93", 778 | "reference": "939c8c28a5b1e4ab7317bc30c1f9aa881c4b06b5", 779 | "shasum": "" 780 | }, 781 | "require": { 782 | "php": ">=5.5.9" 783 | }, 784 | "require-dev": { 785 | "symfony/expression-language": "~2.8|~3.0" 786 | }, 787 | "type": "library", 788 | "extra": { 789 | "branch-alias": { 790 | "dev-master": "3.0-dev" 791 | } 792 | }, 793 | "autoload": { 794 | "psr-4": { 795 | "Symfony\\Component\\HttpFoundation\\": "" 796 | }, 797 | "exclude-from-classmap": [ 798 | "/Tests/" 799 | ] 800 | }, 801 | "notification-url": "https://packagist.org/downloads/", 802 | "license": [ 803 | "MIT" 804 | ], 805 | "authors": [ 806 | { 807 | "name": "Fabien Potencier", 808 | "email": "fabien@symfony.com" 809 | }, 810 | { 811 | "name": "Symfony Community", 812 | "homepage": "https://symfony.com/contributors" 813 | } 814 | ], 815 | "description": "Symfony HttpFoundation Component", 816 | "homepage": "https://symfony.com", 817 | "time": "2015-12-18 15:43:53" 818 | }, 819 | { 820 | "name": "symfony/http-kernel", 821 | "version": "3.0.x-dev", 822 | "source": { 823 | "type": "git", 824 | "url": "https://github.com/symfony/http-kernel.git", 825 | "reference": "90a46805ab8cafc9e22327a54a9a8126ef71dc57" 826 | }, 827 | "dist": { 828 | "type": "zip", 829 | "url": "https://api.github.com/repos/symfony/http-kernel/zipball/ea1b85512d2d6c95f83a443ba45769586884a17a", 830 | "reference": "90a46805ab8cafc9e22327a54a9a8126ef71dc57", 831 | "shasum": "" 832 | }, 833 | "require": { 834 | "php": ">=5.5.9", 835 | "psr/log": "~1.0", 836 | "symfony/debug": "~2.8|~3.0", 837 | "symfony/event-dispatcher": "~2.8|~3.0", 838 | "symfony/http-foundation": "~2.8|~3.0" 839 | }, 840 | "conflict": { 841 | "symfony/config": "<2.8" 842 | }, 843 | "require-dev": { 844 | "symfony/browser-kit": "~2.8|~3.0", 845 | "symfony/class-loader": "~2.8|~3.0", 846 | "symfony/config": "~2.8|~3.0", 847 | "symfony/console": "~2.8|~3.0", 848 | "symfony/css-selector": "~2.8|~3.0", 849 | "symfony/dependency-injection": "~2.8|~3.0", 850 | "symfony/dom-crawler": "~2.8|~3.0", 851 | "symfony/expression-language": "~2.8|~3.0", 852 | "symfony/finder": "~2.8|~3.0", 853 | "symfony/process": "~2.8|~3.0", 854 | "symfony/routing": "~2.8|~3.0", 855 | "symfony/stopwatch": "~2.8|~3.0", 856 | "symfony/templating": "~2.8|~3.0", 857 | "symfony/translation": "~2.8|~3.0", 858 | "symfony/var-dumper": "~2.8|~3.0" 859 | }, 860 | "suggest": { 861 | "symfony/browser-kit": "", 862 | "symfony/class-loader": "", 863 | "symfony/config": "", 864 | "symfony/console": "", 865 | "symfony/dependency-injection": "", 866 | "symfony/finder": "", 867 | "symfony/var-dumper": "" 868 | }, 869 | "type": "library", 870 | "extra": { 871 | "branch-alias": { 872 | "dev-master": "3.0-dev" 873 | } 874 | }, 875 | "autoload": { 876 | "psr-4": { 877 | "Symfony\\Component\\HttpKernel\\": "" 878 | }, 879 | "exclude-from-classmap": [ 880 | "/Tests/" 881 | ] 882 | }, 883 | "notification-url": "https://packagist.org/downloads/", 884 | "license": [ 885 | "MIT" 886 | ], 887 | "authors": [ 888 | { 889 | "name": "Fabien Potencier", 890 | "email": "fabien@symfony.com" 891 | }, 892 | { 893 | "name": "Symfony Community", 894 | "homepage": "https://symfony.com/contributors" 895 | } 896 | ], 897 | "description": "Symfony HttpKernel Component", 898 | "homepage": "https://symfony.com", 899 | "time": "2015-12-18 16:22:11" 900 | }, 901 | { 902 | "name": "symfony/polyfill-mbstring", 903 | "version": "dev-master", 904 | "source": { 905 | "type": "git", 906 | "url": "https://github.com/symfony/polyfill-mbstring.git", 907 | "reference": "49ff736bd5d41f45240cec77b44967d76e0c3d25" 908 | }, 909 | "dist": { 910 | "type": "zip", 911 | "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/49ff736bd5d41f45240cec77b44967d76e0c3d25", 912 | "reference": "49ff736bd5d41f45240cec77b44967d76e0c3d25", 913 | "shasum": "" 914 | }, 915 | "require": { 916 | "php": ">=5.3.3" 917 | }, 918 | "suggest": { 919 | "ext-mbstring": "For best performance" 920 | }, 921 | "type": "library", 922 | "extra": { 923 | "branch-alias": { 924 | "dev-master": "1.0-dev" 925 | } 926 | }, 927 | "autoload": { 928 | "psr-4": { 929 | "Symfony\\Polyfill\\Mbstring\\": "" 930 | }, 931 | "files": [ 932 | "bootstrap.php" 933 | ] 934 | }, 935 | "notification-url": "https://packagist.org/downloads/", 936 | "license": [ 937 | "MIT" 938 | ], 939 | "authors": [ 940 | { 941 | "name": "Nicolas Grekas", 942 | "email": "p@tchwork.com" 943 | }, 944 | { 945 | "name": "Symfony Community", 946 | "homepage": "https://symfony.com/contributors" 947 | } 948 | ], 949 | "description": "Symfony polyfill for the Mbstring extension", 950 | "homepage": "https://symfony.com", 951 | "keywords": [ 952 | "compatibility", 953 | "mbstring", 954 | "polyfill", 955 | "portable", 956 | "shim" 957 | ], 958 | "time": "2015-11-20 09:19:13" 959 | }, 960 | { 961 | "name": "symfony/routing", 962 | "version": "3.0.x-dev", 963 | "source": { 964 | "type": "git", 965 | "url": "https://github.com/symfony/routing.git", 966 | "reference": "3b1bac52f42cb0f54df1a2dbabd55a1d214e2a59" 967 | }, 968 | "dist": { 969 | "type": "zip", 970 | "url": "https://api.github.com/repos/symfony/routing/zipball/13b1cca9e354fce3b282a411c45cbea0a21a12f8", 971 | "reference": "3b1bac52f42cb0f54df1a2dbabd55a1d214e2a59", 972 | "shasum": "" 973 | }, 974 | "require": { 975 | "php": ">=5.5.9" 976 | }, 977 | "conflict": { 978 | "symfony/config": "<2.8" 979 | }, 980 | "require-dev": { 981 | "doctrine/annotations": "~1.0", 982 | "doctrine/common": "~2.2", 983 | "psr/log": "~1.0", 984 | "symfony/config": "~2.8|~3.0", 985 | "symfony/expression-language": "~2.8|~3.0", 986 | "symfony/http-foundation": "~2.8|~3.0", 987 | "symfony/yaml": "~2.8|~3.0" 988 | }, 989 | "suggest": { 990 | "doctrine/annotations": "For using the annotation loader", 991 | "symfony/config": "For using the all-in-one router or any loader", 992 | "symfony/dependency-injection": "For loading routes from a service", 993 | "symfony/expression-language": "For using expression matching", 994 | "symfony/yaml": "For using the YAML loader" 995 | }, 996 | "type": "library", 997 | "extra": { 998 | "branch-alias": { 999 | "dev-master": "3.0-dev" 1000 | } 1001 | }, 1002 | "autoload": { 1003 | "psr-4": { 1004 | "Symfony\\Component\\Routing\\": "" 1005 | }, 1006 | "exclude-from-classmap": [ 1007 | "/Tests/" 1008 | ] 1009 | }, 1010 | "notification-url": "https://packagist.org/downloads/", 1011 | "license": [ 1012 | "MIT" 1013 | ], 1014 | "authors": [ 1015 | { 1016 | "name": "Fabien Potencier", 1017 | "email": "fabien@symfony.com" 1018 | }, 1019 | { 1020 | "name": "Symfony Community", 1021 | "homepage": "https://symfony.com/contributors" 1022 | } 1023 | ], 1024 | "description": "Symfony Routing Component", 1025 | "homepage": "https://symfony.com", 1026 | "keywords": [ 1027 | "router", 1028 | "routing", 1029 | "uri", 1030 | "url" 1031 | ], 1032 | "time": "2015-12-23 08:00:11" 1033 | }, 1034 | { 1035 | "name": "symfony/translation", 1036 | "version": "dev-master", 1037 | "source": { 1038 | "type": "git", 1039 | "url": "https://github.com/symfony/translation.git", 1040 | "reference": "2bda1138cccb5ad1295cd09de37dc189ff05f368" 1041 | }, 1042 | "dist": { 1043 | "type": "zip", 1044 | "url": "https://api.github.com/repos/symfony/translation/zipball/2bda1138cccb5ad1295cd09de37dc189ff05f368", 1045 | "reference": "2bda1138cccb5ad1295cd09de37dc189ff05f368", 1046 | "shasum": "" 1047 | }, 1048 | "require": { 1049 | "php": ">=5.5.9", 1050 | "symfony/polyfill-mbstring": "~1.0" 1051 | }, 1052 | "conflict": { 1053 | "symfony/config": "<2.8" 1054 | }, 1055 | "require-dev": { 1056 | "psr/log": "~1.0", 1057 | "symfony/config": "~2.8|~3.0", 1058 | "symfony/intl": "~2.8|~3.0", 1059 | "symfony/yaml": "~2.8|~3.0" 1060 | }, 1061 | "suggest": { 1062 | "psr/log": "To use logging capability in translator", 1063 | "symfony/config": "", 1064 | "symfony/yaml": "" 1065 | }, 1066 | "type": "library", 1067 | "extra": { 1068 | "branch-alias": { 1069 | "dev-master": "3.1-dev" 1070 | } 1071 | }, 1072 | "autoload": { 1073 | "psr-4": { 1074 | "Symfony\\Component\\Translation\\": "" 1075 | }, 1076 | "exclude-from-classmap": [ 1077 | "/Tests/" 1078 | ] 1079 | }, 1080 | "notification-url": "https://packagist.org/downloads/", 1081 | "license": [ 1082 | "MIT" 1083 | ], 1084 | "authors": [ 1085 | { 1086 | "name": "Fabien Potencier", 1087 | "email": "fabien@symfony.com" 1088 | }, 1089 | { 1090 | "name": "Symfony Community", 1091 | "homepage": "https://symfony.com/contributors" 1092 | } 1093 | ], 1094 | "description": "Symfony Translation Component", 1095 | "homepage": "https://symfony.com", 1096 | "time": "2015-12-05 17:45:25" 1097 | } 1098 | ], 1099 | "packages-dev": [], 1100 | "aliases": [], 1101 | "minimum-stability": "dev", 1102 | "stability-flags": { 1103 | "illuminate/config": 20 1104 | }, 1105 | "prefer-stable": false, 1106 | "prefer-lowest": false, 1107 | "platform": [], 1108 | "platform-dev": [] 1109 | } 1110 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | tests/ 15 | 16 | 17 | 18 | 19 | src/ 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/Facades/Tenant.php: -------------------------------------------------------------------------------- 1 | tenantManager = $tenantManager; 20 | } 21 | /** 22 | * Handle an incoming request. 23 | * 24 | * @param \Illuminate\Http\Request $request 25 | * @param \Closure $next 26 | * @return mixed 27 | */ 28 | public function handle($request, Closure $next) 29 | { 30 | if($tenantName = $this->tenantManager->getCurrentTenant()) { 31 | if ($this->tenantManager->reconnectDatabaseUsing($tenantName)) { 32 | return $next($request); 33 | } 34 | } 35 | 36 | abort(404, "Site Not Found"); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Providers/TenantServiceProvider.php: -------------------------------------------------------------------------------- 1 | publishConfig(); 20 | $this->registerSingletons(); 21 | $this->registerUrlGenerator(); 22 | } 23 | 24 | /** 25 | * Publica arquivo de configuração para a pasta de configurações do usuário 26 | * 27 | * Publish config files to config folder 28 | * 29 | * @return void 30 | */ 31 | protected function publishConfig() 32 | { 33 | $configPath = __DIR__ . '/../config/tenant.php'; 34 | $this->publishes([ 35 | $configPath => config_path('tenant.php') 36 | ], 'config'); 37 | } 38 | 39 | /** 40 | * Register singletons to container 41 | * 42 | * @return void 43 | */ 44 | protected function registerSingletons() 45 | { 46 | $this->app->singleton('tenant.manager', function(){ 47 | return new TenantManager( 48 | app('config'), app('db'), app('router') 49 | ); 50 | }); 51 | } 52 | 53 | /** 54 | * Override the UrlGenerator 55 | * 56 | * @return void 57 | */ 58 | protected function registerUrlGenerator() 59 | { 60 | $this->app->singleton('url', function($app) { 61 | $routes = $app['router']->getRoutes(); 62 | $app->instance('routes', $routes); 63 | $url = new UrlGenerator( 64 | $routes, $app->rebinding('request', $this->requestRebinder()), $app->make('tenant.manager') 65 | ); 66 | $url->setSessionResolver(function () { 67 | return $this->app['session']; 68 | }); 69 | return $url; 70 | }); 71 | } 72 | } -------------------------------------------------------------------------------- /src/Routing/UrlGenerator.php: -------------------------------------------------------------------------------- 1 | tenantManager = $tenantManager; 29 | } 30 | 31 | /** 32 | * Get the URL to a named route. 33 | * 34 | * @param string $name 35 | * @param mixed $parameters 36 | * @param bool $absolute 37 | * @return string 38 | * 39 | * @throws \InvalidArgumentException 40 | */ 41 | public function route($name, $parameters = [], $absolute = true) 42 | { 43 | if (! is_null($route = $this->routes->getByName($name))) { 44 | 45 | $actions = $route->getAction(); 46 | if(config('tenant.autobind') 47 | && isset($actions['domain']) 48 | && $actions['domain'] == $this->tenantManager->getFullDomain()) { 49 | $parameters = $this->mergeSubDomainParameters($parameters); 50 | } 51 | 52 | return $this->toRoute($route, $parameters, $absolute); 53 | } 54 | 55 | throw new InvalidArgumentException("Route [{$name}] not defined."); 56 | } 57 | 58 | /** 59 | * Merge user parameters with subdomain parameter 60 | * 61 | * @param array|string $parameters 62 | * @return array array of parameters 63 | */ 64 | protected function mergeSubDomainParameters($parameters = []) 65 | { 66 | if(!is_array($parameters)) { 67 | $parameters = [$parameters]; 68 | } 69 | 70 | if(isset($parameters[config('tenant.subdomain')])){ 71 | $subDomain = $parameters[config('tenant.subdomain')]; 72 | return array_replace($parameters, [config('tenant.subdomain') => $subDomain]); 73 | } 74 | 75 | if ($subDomain = $this->getSubDomainParameter()) { 76 | $parameters = array_replace($parameters, [config('tenant.subdomain') => $subDomain]); 77 | } 78 | return $parameters; 79 | } 80 | 81 | /** 82 | * Get the subdomain parameter value 83 | * 84 | * @return string|null subdomain parameter value 85 | */ 86 | private function getSubDomainParameter() 87 | { 88 | if(\Route::current() && ($param = \Route::input(config('tenant.subdomain'))) ) { 89 | return $param; 90 | } 91 | return $this->extractSubdomainFromUrl(); 92 | } 93 | 94 | /** 95 | * Extract the subdomain from url 96 | * @return string subdomain parameter value 97 | */ 98 | private function extractSubdomainFromUrl() 99 | { 100 | if (\Request::getHost() != $this->tenantManager->getDomain()) { 101 | return str_ireplace( $this->tenantManager->getDomain(), "", \Request::getHost() ); 102 | } 103 | return false; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/TenantManager.php: -------------------------------------------------------------------------------- 1 | config = $config; 38 | $this->databaseManager = $databaseManager; 39 | $this->router = $router; 40 | } 41 | 42 | /** 43 | * Get the domain configuration 44 | * 45 | * @return string like 'domain.app' 46 | */ 47 | public function getDomain() 48 | { 49 | return $this->config->get('tenant.host'); 50 | } 51 | 52 | /** 53 | * Get the full domain with subDomain configuration 54 | * 55 | * @return string like '{account}.domain.app' 56 | */ 57 | public function getFullDomain() 58 | { 59 | return "{" . $this->config->get("tenant.subdomain") . "}." 60 | . $this->config->get("tenant.host"); 61 | } 62 | 63 | /** 64 | * Get the database config 65 | * 66 | * @param $subDomain 67 | * @return array|false database configuration 68 | */ 69 | public function getDatabaseConfig($subDomain) 70 | { 71 | $file = realpath($this->getDatabaseConfigFileName($subDomain)); 72 | return $file ? require $file : false; 73 | } 74 | 75 | /** 76 | * Make the configuration database config file 77 | * @param $subDomain String name 78 | * @param $config array database configuration 79 | * @return boolean file creation success 80 | */ 81 | public function makeDatabaseConfigFile($subDomain, array $config) 82 | { 83 | $filename = $this->getDatabaseConfigFileName($subDomain); 84 | $content = "getArrayAsString($config) . ";"; 86 | return (bool) file_put_contents($filename, $content); 87 | } 88 | 89 | /** 90 | * Drop the configuration database config file 91 | * @param $subDomain string subDomain name 92 | * @return boolean 93 | */ 94 | public function dropDatabaseConfigFile($subDomain) 95 | { 96 | $filename = $this->getDatabaseConfigFileName($subDomain); 97 | if (file_exists($filename)) { 98 | return (bool) unlink($filename); 99 | } 100 | return false; 101 | } 102 | 103 | /** 104 | * Get the full database config file name 105 | * 106 | * @param $subDomain string subDomain name 107 | * @return string filename 108 | */ 109 | public function getDatabaseConfigFileName($subDomain) 110 | { 111 | $prefix = $this->getDatabaseConfigPrefix($subDomain); 112 | $suffix = $this->getDatabaseConfigSuffix($subDomain); 113 | return $this->config->get('tenant.database_path') .'/'. $prefix . $subDomain . $suffix . '.php'; 114 | } 115 | 116 | /** 117 | * Get the prefix of database configuration 118 | * 119 | * @param string $subDomain 120 | * @return string 121 | */ 122 | public function getDatabaseConfigPrefix($subDomain) 123 | { 124 | return is_callable($this->config->get('tenant.database_prefix')) 125 | ? call_user_func_array($this->config->get('tenant.database_prefix'),[$subDomain]) 126 | : $this->config->get('tenant.database_prefix'); 127 | } 128 | 129 | /** 130 | * Get the sufix of database configuration 131 | * 132 | * @param string $subDomain 133 | * @return string 134 | */ 135 | public function getDatabaseConfigSuffix($subDomain) 136 | { 137 | return is_callable($this->config->get('tenant.database_suffix')) 138 | ? call_user_func_array($this->config->get('tenant.database_suffix'),[$subDomain]) 139 | : $this->config->get('tenant.database_suffix'); 140 | } 141 | 142 | /** 143 | * Get Current Tenant SubDomain 144 | * @return null|string 145 | */ 146 | public function getCurrentTenant() 147 | { 148 | if ($this->config->get('tenant.name')) { 149 | return $this->config->get('tenant.name'); 150 | } 151 | 152 | return $this->getTenantFromRequest( request() ); 153 | } 154 | 155 | /** 156 | * Get Tenant from request 157 | * @param Request $request 158 | * @return null|object|string 159 | */ 160 | public function getTenantFromRequest(Request $request) 161 | { 162 | $route = $this->router->getRoutes()->match($request); 163 | 164 | if($route) { 165 | if($subDomain = $route->parameter( $this->config->get('tenant.subdomain') )){ 166 | $this->setCurrentTenant($subDomain); 167 | return $subDomain; 168 | } 169 | } 170 | 171 | return null; 172 | } 173 | 174 | /** 175 | * Set current tenant in config 176 | * @param $tenantName 177 | */ 178 | public function setCurrentTenant($tenantName) 179 | { 180 | $this->config->set("tenant.name", $tenantName); 181 | } 182 | 183 | /** 184 | * Reconnect Database Using Tenant Configs 185 | * @param $tenantName 186 | * @return bool 187 | */ 188 | public function reconnectDatabaseUsing($tenantName) 189 | { 190 | if ($config = $this->getDatabaseConfig($tenantName)) { 191 | $this->config->set("tenant.name", $tenantName); 192 | $this->config->set("database.connections.tenant", $config); 193 | 194 | $this->databaseManager->setDefaultConnection('tenant'); 195 | $this->databaseManager->reconnect('tenant'); 196 | return true; 197 | } 198 | return false; 199 | } 200 | 201 | /** 202 | * Transform array in string 203 | * @param $data array 204 | * @return string 205 | */ 206 | private function getArrayAsString(array $data) 207 | { 208 | $output = "[\n\r"; 209 | array_walk_recursive($data, function($value, $key) use (&$output) { 210 | $output.= "\t'$key' => '$value',\n\r"; 211 | }); 212 | return $output . "]"; 213 | } 214 | } -------------------------------------------------------------------------------- /src/config/tenant.php: -------------------------------------------------------------------------------- 1 | '', 10 | 11 | /** 12 | * Subdomain argument 13 | * 'subdomain' => 'argument' 14 | * produces {argument}.host.com 15 | */ 16 | 'subdomain' => env('TENANT_SUBDOMAIN_ARGUMENT', '_account_'), 17 | 18 | /** 19 | * Domain name 20 | */ 21 | 'host' => env('APP_HOST', 'example.com'), 22 | 23 | /** 24 | * Set TRUE to change default database config in middleware 25 | */ 26 | 'database_change'=> true, 27 | 28 | /** 29 | * Path to database configuration files 30 | * Only used if `database_change` is true 31 | */ 32 | 'database_path' => config_path('tenants'), 33 | 34 | /** 35 | * Database configuration prefix 36 | * Only used if `database_change` is true 37 | * allow closure 'database_prefix' => function($subdomain) { return md5($subdomain); } 38 | */ 39 | 'database_prefix' => 'database_', 40 | 41 | /** 42 | * Database configuration suffix 43 | * Only used if `database_change` is true 44 | * allow closure 'database_sufix' => function($subdomain) { return md5($subdomain); } 45 | */ 46 | 'database_suffix' => '.php', 47 | 48 | /** 49 | * Automatic subdomain route bind, like this: 50 | * 51 | * Defined Route: 52 | * Route::get('user/{id}', ['as' => 'me', 'domain' => '{subdomain}.my.app'] .... 53 | * 54 | * just call route('me', '123') 55 | * no more -> route('me', ['my-subdomain', '123']) 56 | */ 57 | 'autobind' => true, 58 | 59 | ]; -------------------------------------------------------------------------------- /tests/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dlimars/laravel-tenant-subdomain/dfcad810a39cc99de3eec9b8fd239862a77123ca/tests/.gitkeep -------------------------------------------------------------------------------- /tests/TenantManagerTest.php: -------------------------------------------------------------------------------- 1 | '_account_', 9 | 'host' => 'example.app', 10 | 'database_change' => true, 11 | 'database_path' => __DIR__ . '/stubs', 12 | 'database_prefix' => 'testing_', 13 | 'database_suffix' => '_stub' 14 | ]; 15 | 16 | public function tearDown() 17 | { 18 | $this->resetTempFolder(); 19 | } 20 | 21 | public function testGetFullDomain() 22 | { 23 | $tenant = $this->newTenantManager($this->config); 24 | $argument = $tenant->getFullDomain(); 25 | $this->assertEquals('{_account_}.example.app', $argument); 26 | } 27 | 28 | public function testGetDatabaseConfigFileName() 29 | { 30 | $config = $this->config; 31 | $config['database_path'] = 'foo'; 32 | $tenant = $this->newTenantManager($config); 33 | $filename = $tenant->getDatabaseConfigFileName('test'); 34 | $this->assertEquals('foo/testing_test_stub.php', $filename); 35 | } 36 | 37 | public function testGetDatabaseConfigIfFileExists() 38 | { 39 | $config = $this->config; 40 | $config['database_prefix'] = 'configuration_'; 41 | $config['database_path'] = realpath(__DIR__ . '/stubs'); 42 | $tenant = $this->newTenantManager($config); 43 | $configuration = $tenant->getDatabaseConfig('test'); 44 | $this->assertInternalType('array', $configuration); 45 | } 46 | 47 | public function testMakeDatabaseConfigFile() 48 | { 49 | $config = $this->config; 50 | $config['database_path'] = realpath(__DIR__ . '/temp'); 51 | $config['database_prefix'] = 'bar_'; 52 | $config['database_suffix'] = '_baz'; 53 | $tenant = $this->newTenantManager($config); 54 | $response = $tenant->makeDatabaseConfigFile('foo', ['bar' => 'baz'] ); 55 | $this->assertTrue( $response ); 56 | $this->assertFileExists( __DIR__ . '/temp/bar_foo_baz.php' ); 57 | $fileCompare = " 'baz',\n\r" 60 | . "];"; 61 | $fileContents = file_get_contents(__DIR__ . '/temp/bar_foo_baz.php'); 62 | $this->assertEquals($fileCompare, $fileContents); 63 | } 64 | 65 | public function testDropDatabaseConfigFile() 66 | { 67 | $config = $this->config; 68 | $config['database_path'] = realpath(__DIR__ . '/temp'); 69 | $config['database_prefix'] = 'foo_'; 70 | $config['database_suffix'] = '_baz'; 71 | $tenant = $this->newTenantManager($config); 72 | file_put_contents($config['database_path'] . "/foo_bar_baz.php", "foo_bar"); 73 | $this->assertTrue($tenant->dropDatabaseConfigFile('bar')); 74 | } 75 | 76 | 77 | private function newTenantManager($config) 78 | { 79 | $configMock = $this->getConfigMock($config); 80 | return new TenantManager($configMock); 81 | } 82 | 83 | private function getConfigMock(array $config) 84 | { 85 | $configMock = $this->getMock('Illuminate\Config\Repository'); 86 | 87 | $configMap = []; 88 | 89 | foreach ($config as $key => $value) { 90 | $configMap[] = ['tenant.' . $key, null, $value]; 91 | } 92 | 93 | $configMock->expects($this->any()) 94 | ->method('get') 95 | ->will($this->returnValueMap($configMap)); 96 | 97 | return $configMock; 98 | } 99 | 100 | private function resetTempFolder() 101 | { 102 | $files = glob(__DIR__ . '/temp/*.php'); 103 | foreach ($files as $file) { 104 | if (is_file($file)) { 105 | unlink($file); 106 | } 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /tests/stubs/configuration_test_stub.php: -------------------------------------------------------------------------------- 1 | 'mysql', 5 | 'host' => "test", 6 | 'database' => "test", 7 | 'username' => "test", 8 | 'password' => "test", 9 | 'charset' => 'utf8', 10 | 'collation' => 'utf8_unicode_ci', 11 | 'prefix' => '', 12 | 'strict' => false 13 | ]; -------------------------------------------------------------------------------- /tests/temp/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dlimars/laravel-tenant-subdomain/dfcad810a39cc99de3eec9b8fd239862a77123ca/tests/temp/.gitkeep --------------------------------------------------------------------------------