Run multiple websites using the same Laravel installation while keeping tenant specific data separated for fully independent multi-domain setups, previously

Overview

Packagist build status codecov Packagist Codacy Badge Join our Discord server Mentioned in Awesome Laravel

The unobtrusive Laravel package that makes your app multi tenant. Serving multiple websites, each with one or more hostnames from the same codebase. But with clear separation of assets, database and the ability to override logic per tenant.

Suitable for marketing companies that like to re-use functionality for different clients or start-ups building the next software as a service.


Offers:

  • Integration with the awesome Laravel framework.
  • Event driven, extensible architecture.
  • Close - optional - integration into the web server.
  • The ability to add tenant specific configs, code, routes etc.

Database separation methods:

  • One system database and separated tenant databases (default).
  • Table prefixed in the system database.
  • Or .. manually, the way you want, by listening to an event.

Complete documentation covers more than just the installation and configuration.

Requirements, recommended environment

  • Laravel 8.0+.
  • PHP 7.3+
  • Apache or Nginx.
  • MySQL, MariaDB, or PostgreSQL.

Please read the full requirements in the documentation.

Installation

composer require hyn/multi-tenant

Automatic service registration

Using auto discovery, the tenancy package will be auto detected by Laravel automatically.

Manual service registration

In case you want to disable webserver integration or prefer manual integration, set the dont-discover in your application composer.json, like so:

{
    "extra": {
        "laravel": {
            "dont-discover": [
                "hyn/multi-tenant"
            ]
        }
    }
}

If you disable auto discovery you are able to configure the providers by yourself.

Register the service provider in your config/app.php:

    'providers' => [
        // [..]
        // Hyn multi tenancy.
        Hyn\Tenancy\Providers\TenancyProvider::class,
        // Hyn multi tenancy webserver integration.
        Hyn\Tenancy\Providers\WebserverProvider::class,
    ],

Deploy configuration

First publish the configuration and migration files so you can modify it to your needs:

php artisan vendor:publish --tag tenancy

Open the config/tenancy.php and config/webserver.php file and modify to your needs.

Make sure your system connection has been configured in database.php. In case you didn't override the system connection name the default connection is used.

Now run:

php artisan migrate --database=system

This will run the required system database migrations.


Backers

Thank you to all our backers! 🙏 [Become a backer]

Sponsors

Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [Become a sponsor]

Contributors


License and contributing

This package is offered under the MIT license. In case you're interested at contributing, make sure to read the contributing guidelines.

Testing

Run tests using:

vendor/bin/phpunit

If using MySQL, use:

LIMIT_UUID_LENGTH_32=1 vendor/bin/phpunit

Please be warned running tests will reset your current application completely, dropping tenant and system databases and removing the tenancy.json file inside the Laravel directory.

Changes

All changes are covered in the changelog.

Contact

Get in touch personally using;

Comments
  • Helper function for creating routes for Hostname

    Helper function for creating routes for Hostname

    I need a correct way to create routes for hostname

    e.g. I need to create a route for the password reset for hostname XYZ

    hostname_route($hostname, 'password.reset', $token);
    

    which will generate appropriate route for password reset for that particular hostname

    http(s)://xyz.localhost/password/reset/<token>
    

    A solution for above requirement is also helpful, Please share if you have any

    Note: tried changing the environment for that hostname but it won't work

    bug support 
    opened by RushabhJoshi 33
  • 5.4 breaks multiple hostnames for website

    5.4 breaks multiple hostnames for website

    Description

    First of, thanks for the amazing package ! I've been using for awhile and it saved me a lot of time !

    Migrating to Laravel 5.8 and consequently to multi-tenant 5.4, I've been experiencing an unexpected behavior with my hostnames, though.

    Basically I have 2 hostnames (one for internal use and one for external, using a NOIP domain) for the same Website.

    The internal hostname works just fine with a fake domain, registered in the etc/hosts file, but since the upgrade, I cannot access it from the external domain.

    Example:

    1. sub.domain.com
    2. sub.noip.com

    Access through the sub.domain.com on my local machine works fine, but trying to access it from the sub.noip.com, I get redirected to the sub.domain.com that does not exist, and can't access the system (which I could on 5.3).


    Actual behavior

    Can't access the Website by secondary Hostnames.

    Expected behavior

    Be able to access the Website by all its Hostnames.


    Information

    • hyn/multi-tenant version: 5.4
    • laravel version: 5.8

    Debugging

    I've been playing around with the source code and trying to check all the changes made from one version to the other and I think I've tracked down the problem to the Hyn\Tenancy\Listeners\URL\UpdateAppUrl class.

    More specifically, on line 40:

    $hostname = $event->hostname ?? $event->website->hostnames->first();
    

    So basically it's just grabbing the first hostname registered for the website, which I believe shouldn't be true, I guess we should first verify if the request's host matches any of the hostname's FQDN, so basically just do something like this:

    $hostname = $event->hostname
        ?? $event->website->hostnames->firstWhere('fqdn', optional(request())->getHost())
        ?? $event->website->hostnames->first();
    

    I'm not sure if the current behavior is actually the intended one or if the above solution would be ok (it seems to work alright), if so I could also open a PR.

    bug 
    opened by isecchin 32
  • Laravel Vapor SQS queue payload

    Laravel Vapor SQS queue payload "attempts" missing

    Description

    When a queued job throws an exception, Laravel Vapor tries to update a vapor-specific attempts key in the payload that seems to have been removed. After contacting the Laravel Vapor team for help, we were told that it is likely that someone has removed the custom keys that https://github.com/laravel/vapor-core adds to jobs. After doing some investigation, there isn't anything else that might have touched the payload accept this package, so I wanted to find some help if anyone might be able to figure out what might be happening.

    Laravel Vapor assigns the payload keys here: https://github.com/laravel/vapor-core/blob/9a486ab614e92c01cbd2d089b01cc0124c8eff0a/src/Queue/VaporQueue.php#L41

    This package modifies the payload here: https://github.com/tenancy/multi-tenant/blob/5.x/src/Providers/Tenants/QueueProvider.php#L29

    I don't really know how the createPayloadUsing or the createPayloadArray methods work, so I'm a bit stuck in figuring out where to look.


    Actual behavior

    When a job throws an exception on an app that's powered by Laravel Vapor, the job should recognise that it's failed, but the attempts key in the job payload is missing, possibly due to multi-tenant's queue provider.

    Expected behavior

    Additional keys added to a job payload should not be removed.


    Information

    • hyn/multi-tenant version: 5.5.0
    • laravel version: 6.2.0
    • database driver and version: MySQL 8.0
    • webserver software and version: Laravel Vapor
    • php version: 7.3? Not sure exact version Vapor is running, but minimum

    Error log

    vendor/laravel/vapor-core/src/Queue/VaporJob.php:21
    Undefined index: attempts
    

    I understand that there currently is no expectation for this package to work with Laravel Vapor, but would love to pick on the community's brain on how I may potentially resolve this.

    Thanks in advance!

    technical debt research integration 
    opened by jedimdan 29
  • 5.4 Upgrade - Need help

    5.4 Upgrade - Need help

    Good afternoon

    Following the upgrade guide, I added the key into config/tenancy.php file:

    'key' => env('TENANCY_KEY', env('APP_KEY')),
    

    Changed the following on my composer.json:

    "require": {
        ...
        "hyn/multi-tenant": "5.4.0",
        "laravel/framework": "5.8.*",
        ...
    },
    

    I'm correctly on php 7.2.1


    The main hostname example.com works fine. Whenever I try a tenant website sub.example.com, I get this error that I can't seem to debug.

    SQLSTATE[42S02]: Base table or view not found: 1146 Table 'd3442a527d7b4b08a8053755b87151f1.users' doesn't exist (SQL: select * from `users` where `id` = 1 limit 1)
    

    Looks like some middleware is trying to authenticate the user through it's own database.

    Information

    • hyn/multi-tenant version: 5.4.0
    • laravel version: v5.8.4
    • database driver and version: MySQL 5.7.24
    • webserver software and version: Apache/2.4.33 (Unix) (Via MAMP PRO 4.5)
    • php version: 7.2.1

    tenancy.php config

    <?php
    use Hyn\Tenancy\Database\Connection;
    
    return [
        'key' => env('TENANCY_KEY', env('APP_KEY')),
    
        'models' => [
            // Must implement \Hyn\Tenancy\Contracts\Hostname
            'hostname' => \App\Models\Hostname::class,
    
            // Must implement \Hyn\Tenancy\Contracts\Website
            'website' => \App\Models\Project::class
        ],
        'middleware' => [
            // The eager identification middleware.
            \Hyn\Tenancy\Middleware\EagerIdentification::class,
    
            // The hostname actions middleware (redirects, https, maintenance).
            \Hyn\Tenancy\Middleware\HostnameActions::class,
        ],
        'website' => [
            'disable-random-id' => false,
            'random-id-generator' => Hyn\Tenancy\Generators\Uuid\ShaGenerator::class,
            'uuid-limit-length-to-32' => env('LIMIT_UUID_LENGTH_32', false),
            'disk' => 'local-tenant',
            'auto-create-tenant-directory' => true,
            'auto-rename-tenant-directory' => true,
            'auto-delete-tenant-directory' => false,
            'cache' => 10,
        ],
        'hostname' => [
            'default' => env('TENANCY_DEFAULT_HOSTNAME'),
            'auto-identification' => env('TENANCY_AUTO_HOSTNAME_IDENTIFICATION', true),
            'early-identification' => env('TENANCY_EARLY_IDENTIFICATION', true),
            'abort-without-identified-hostname' => env('TENANCY_ABORT_WITHOUT_HOSTNAME', false),
            'cache' => 10,
            'update-app-url' => false,
        ],
        'db' => [
            'default' => env('TENANCY_DEFAULT_CONNECTION'),
            'system-connection-name' => env('TENANCY_SYSTEM_CONNECTION_NAME', Connection::DEFAULT_SYSTEM_NAME),
            'tenant-connection-name' => env('TENANCY_TENANT_CONNECTION_NAME', Connection::DEFAULT_TENANT_NAME),
            'tenant-division-mode' => env('TENANCY_DATABASE_DIVISION_MODE', 'database'),
            'password-generator' => Hyn\Tenancy\Generators\Database\DefaultPasswordGenerator::class,
            'tenant-migrations-path' => database_path('migrations/tenants'),
            'tenant-seed-class' => TenantDatabaseSeeder::class,
            'auto-create-tenant-database' => true,
            'auto-create-tenant-database-user' => true,
            'tenant-database-user-privileges' => null,
            'auto-rename-tenant-database' => true,
            'auto-delete-tenant-database' => env('TENANCY_DATABASE_AUTO_DELETE', false),
            'auto-delete-tenant-database-user' => env('TENANCY_DATABASE_AUTO_DELETE_USER', false),
            'force-tenant-connection-of-models' => [
               // App\AccountingGroupAccount::class
            ],
            'force-system-connection-of-models' => [
                //App\User::class
            ],
        ],
        'routes' => [
            'path' => base_path('routes/tenants.php'),
            'replace-global' => false,
        ],
        'folders' => [
            'config' => [
                'enabled' => true,
                'blacklist' => ['database', 'tenancy', 'webserver'],
            ],
            'routes' => [
                'enabled' => true,
                'prefix' => null,
            ],
            'trans' => [
                'enabled' => true,
                'override-global' => true,
                'namespace' => 'tenant',
            ],
            'vendor' => [
                'enabled' => true,
            ],
            'media' => [
                'enabled' => true,
            ],
            'views' => [
                'enabled' => true,
                'namespace' => null,
                'override-global' => true,
            ]
        ]
    ];
    

    webserver.php config

    <?php
    return [
        'apache2' => [
            'enabled' => true,
            'ports' => [
                'http' => 80,
                'https' => 443
            ],
            'generator' => \Hyn\Tenancy\Generators\Webserver\Vhost\ApacheGenerator::class,
            'view' => 'tenancy.generators::webserver.apache.vhost',
            'disk' => null,
    
            'paths' => [
                'vhost-files' => [
                    'storage/app/tenancy/webserver/apache2/'
                ],
                'actions' => [
                    'exists' => '/etc/init.d/apache2',
                    'test-config' => true,
                    'reload' => null
                ]
            ]
        ],
        'nginx' => [
            'enabled' => false,
            'php-sock' => 'unix:/var/run/php/php7.1-fpm.sock',
            'ports' => [
                'http' => 80,
                'https' => 443
            ],
            'generator' => \Hyn\Tenancy\Generators\Webserver\Vhost\NginxGenerator::class,
            'view' => 'tenancy.generators::webserver.nginx.vhost',
            'disk' => null,
    
            'paths' => [
                'vhost-files' => [
                    '/etc/nginx/sites-enabled/'
                ],
                'actions' => [
                    'exists' => '/etc/init.d/nginx',
                    'test-config' => '/etc/init.d/nginx configtest',
                    'reload' => '/etc/init.d/nginx reload'
                ]
            ]
        ]
    ];
    

    support 
    opened by xalunda 28
  • Database [system] not configured and migration issue

    Database [system] not configured and migration issue

    Description

    php artisan tenancy:install raises error Database [system] not configured.

    php artisan migrate --tenant=all raises The "--tenant" option does not exist.


    Information

    • hyn/multi-tenant version: 3.x
    • laravel version: 5.4
    • database driver and version: mysql
    • webserver software and version: apache
    • php version: 7.0

    bug 
    opened by xyrintech 28
  • Undefined index: driver when sending notification to new created tenant

    Undefined index: driver when sending notification to new created tenant

    I have come across an issue when trying to send a welcome email after I set up a new tenant in my system.

    I have put my controller and notification code below which will hopefully be straight forward to replicate for testing purposes.

    I am interested to see if anyone else is having this issue too and what the fix might be?

    Here is the error I am getting:

    screen shot 2018-02-05 at 22 08 34

    Stack Trace

    [2018-02-05 21:51:23] local.ERROR: Undefined index: driver {"userId":14,"email":"[email protected]","exception":"[object] (ErrorException(code: 0): Undefined index: driver at /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Database/DatabaseManager.php:115)
    [stacktrace]
    #0 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Database/DatabaseManager.php(115): Illuminate\\Foundation\\Bootstrap\\HandleExceptions->handleError(8, 'Undefined index...', '/Users/mike/Sit...', 115, Array)
    #1 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Database/DatabaseManager.php(74): Illuminate\\Database\\DatabaseManager->makeConnection('tenant')
    #2 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Auth/Passwords/PasswordBrokerManager.php(96): Illuminate\\Database\\DatabaseManager->connection('tenant')
    #3 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Auth/Passwords/PasswordBrokerManager.php(74): Illuminate\\Auth\\Passwords\\PasswordBrokerManager->createTokenRepository(Array)
    #4 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Auth/Passwords/PasswordBrokerManager.php(51): Illuminate\\Auth\\Passwords\\PasswordBrokerManager->resolve('users')
    #5 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php(221): Illuminate\\Auth\\Passwords\\PasswordBrokerManager->broker()
    #6 /Users/mike/Sites/openpci/app/Notifications/TenantCreated.php(23): Illuminate\\Support\\Facades\\Facade::__callStatic('broker', Array)
    #7 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Notifications/Channels/MailChannel.php(50): App\\Notifications\\TenantCreated->toMail(Object(App\\User))
    #8 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Notifications/NotificationSender.php(113): Illuminate\\Notifications\\Channels\\MailChannel->send(Object(App\\User), Object(App\\Notifications\\TenantCreated))
    #9 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Notifications/NotificationSender.php(89): Illuminate\\Notifications\\NotificationSender->sendToNotifiable(Object(App\\User), '986a1f45-4c49-4...', Object(App\\Notifications\\TenantCreated), 'mail')
    #10 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Notifications/NotificationSender.php(64): Illuminate\\Notifications\\NotificationSender->sendNow(Object(Illuminate\\Database\\Eloquent\\Collection), Object(App\\Notifications\\TenantCreated))
    #11 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Notifications/ChannelManager.php(35): Illuminate\\Notifications\\NotificationSender->send(Object(Illuminate\\Database\\Eloquent\\Collection), Object(App\\Notifications\\TenantCreated))
    #12 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Notifications/RoutesNotifications.php(18): Illuminate\\Notifications\\ChannelManager->send(Object(App\\User), Object(App\\Notifications\\TenantCreated))
    #13 /Users/mike/Sites/openpci/app/Http/Controllers/TenantsController.php(50): App\\User->notify(Object(App\\Notifications\\TenantCreated))
    #14 [internal function]: App\\Http\\Controllers\\TenantsController->store(Object(Illuminate\\Http\\Request))
    #15 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Routing/Controller.php(54): call_user_func_array(Array, Array)
    #16 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(45): Illuminate\\Routing\\Controller->callAction('store', Array)
    #17 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Routing/Route.php(212): Illuminate\\Routing\\ControllerDispatcher->dispatch(Object(Illuminate\\Routing\\Route), Object(App\\Http\\Controllers\\TenantsController), 'store')
    #18 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Routing/Route.php(169): Illuminate\\Routing\\Route->runController()
    #19 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Routing/Router.php(658): Illuminate\\Routing\\Route->run()
    #20 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(30): Illuminate\\Routing\\Router->Illuminate\\Routing\\{closure}(Object(Illuminate\\Http\\Request))
    #21 /Users/mike/Sites/openpci/vendor/spatie/laravel-permission/src/Middlewares/RoleMiddleware.php(25): Illuminate\\Routing\\Pipeline->Illuminate\\Routing\\{closure}(Object(Illuminate\\Http\\Request))
    #22 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(149): Spatie\\Permission\\Middlewares\\RoleMiddleware->handle(Object(Illuminate\\Http\\Request), Object(Closure), 'super-admin')
    #23 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(53): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
    #24 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php(41): Illuminate\\Routing\\Pipeline->Illuminate\\Routing\\{closure}(Object(Illuminate\\Http\\Request))
    #25 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(149): Illuminate\\Routing\\Middleware\\SubstituteBindings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
    #26 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(53): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
    #27 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Auth/Middleware/Authenticate.php(43): Illuminate\\Routing\\Pipeline->Illuminate\\Routing\\{closure}(Object(Illuminate\\Http\\Request))
    #28 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(149): Illuminate\\Auth\\Middleware\\Authenticate->handle(Object(Illuminate\\Http\\Request), Object(Closure))
    #29 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(53): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
    #30 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php(67): Illuminate\\Routing\\Pipeline->Illuminate\\Routing\\{closure}(Object(Illuminate\\Http\\Request))
    #31 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(149): Illuminate\\Foundation\\Http\\Middleware\\VerifyCsrfToken->handle(Object(Illuminate\\Http\\Request), Object(Closure))
    #32 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(53): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
    #33 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/View/Middleware/ShareErrorsFromSession.php(49): Illuminate\\Routing\\Pipeline->Illuminate\\Routing\\{closure}(Object(Illuminate\\Http\\Request))
    #34 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(149): Illuminate\\View\\Middleware\\ShareErrorsFromSession->handle(Object(Illuminate\\Http\\Request), Object(Closure))
    #35 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(53): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
    #36 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(63): Illuminate\\Routing\\Pipeline->Illuminate\\Routing\\{closure}(Object(Illuminate\\Http\\Request))
    #37 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(149): Illuminate\\Session\\Middleware\\StartSession->handle(Object(Illuminate\\Http\\Request), Object(Closure))
    #38 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(53): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
    #39 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php(37): Illuminate\\Routing\\Pipeline->Illuminate\\Routing\\{closure}(Object(Illuminate\\Http\\Request))
    #40 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(149): Illuminate\\Cookie\\Middleware\\AddQueuedCookiesToResponse->handle(Object(Illuminate\\Http\\Request), Object(Closure))
    #41 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(53): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
    #42 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php(59): Illuminate\\Routing\\Pipeline->Illuminate\\Routing\\{closure}(Object(Illuminate\\Http\\Request))
    #43 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(149): Illuminate\\Cookie\\Middleware\\EncryptCookies->handle(Object(Illuminate\\Http\\Request), Object(Closure))
    #44 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(53): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
    #45 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(102): Illuminate\\Routing\\Pipeline->Illuminate\\Routing\\{closure}(Object(Illuminate\\Http\\Request))
    #46 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Routing/Router.php(660): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
    #47 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Routing/Router.php(635): Illuminate\\Routing\\Router->runRouteWithinStack(Object(Illuminate\\Routing\\Route), Object(Illuminate\\Http\\Request))
    #48 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Routing/Router.php(601): Illuminate\\Routing\\Router->runRoute(Object(Illuminate\\Http\\Request), Object(Illuminate\\Routing\\Route))
    #49 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Routing/Router.php(590): Illuminate\\Routing\\Router->dispatchToRoute(Object(Illuminate\\Http\\Request))
    #50 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(176): Illuminate\\Routing\\Router->dispatch(Object(Illuminate\\Http\\Request))
    #51 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(30): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}(Object(Illuminate\\Http\\Request))
    #52 /Users/mike/Sites/openpci/vendor/fideloper/proxy/src/TrustProxies.php(56): Illuminate\\Routing\\Pipeline->Illuminate\\Routing\\{closure}(Object(Illuminate\\Http\\Request))
    #53 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(149): Fideloper\\Proxy\\TrustProxies->handle(Object(Illuminate\\Http\\Request), Object(Closure))
    #54 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(53): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
    #55 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(30): Illuminate\\Routing\\Pipeline->Illuminate\\Routing\\{closure}(Object(Illuminate\\Http\\Request))
    #56 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(149): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))
    #57 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(53): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
    #58 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(30): Illuminate\\Routing\\Pipeline->Illuminate\\Routing\\{closure}(Object(Illuminate\\Http\\Request))
    #59 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(149): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))
    #60 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(53): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
    #61 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ValidatePostSize.php(27): Illuminate\\Routing\\Pipeline->Illuminate\\Routing\\{closure}(Object(Illuminate\\Http\\Request))
    #62 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(149): Illuminate\\Foundation\\Http\\Middleware\\ValidatePostSize->handle(Object(Illuminate\\Http\\Request), Object(Closure))
    #63 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(53): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
    #64 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/CheckForMaintenanceMode.php(46): Illuminate\\Routing\\Pipeline->Illuminate\\Routing\\{closure}(Object(Illuminate\\Http\\Request))
    #65 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(149): Illuminate\\Foundation\\Http\\Middleware\\CheckForMaintenanceMode->handle(Object(Illuminate\\Http\\Request), Object(Closure))
    #66 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(53): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
    #67 /Users/mike/Sites/openpci/vendor/hyn/multi-tenant/src/Middleware/EagerIdentification.php(29): Illuminate\\Routing\\Pipeline->Illuminate\\Routing\\{closure}(Object(Illuminate\\Http\\Request))
    #68 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(149): Hyn\\Tenancy\\Middleware\\EagerIdentification->handle(Object(Illuminate\\Http\\Request), Object(Closure))
    #69 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(53): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
    #70 /Users/mike/Sites/openpci/vendor/hyn/multi-tenant/src/Middleware/HostnameActions.php(72): Illuminate\\Routing\\Pipeline->Illuminate\\Routing\\{closure}(Object(Illuminate\\Http\\Request))
    #71 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(149): Hyn\\Tenancy\\Middleware\\HostnameActions->handle(Object(Illuminate\\Http\\Request), Object(Closure))
    #72 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(53): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
    #73 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(102): Illuminate\\Routing\\Pipeline->Illuminate\\Routing\\{closure}(Object(Illuminate\\Http\\Request))
    #74 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(151): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
    #75 /Users/mike/Sites/openpci/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(116): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))
    #76 /Users/mike/Sites/openpci/public/index.php(55): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))
    #77 /Users/mike/.composer/vendor/laravel/valet/server.php(133): require('/Users/mike/Sit...')
    #78 {main}
    "} 
    

    Tenant Controller

    <?php
    
    namespace App\Http\Controllers;
    
    use App\Notifications\TenantCreated;
    use App\User;
    use Hyn\Tenancy\Environment;
    use Hyn\Tenancy\Contracts\Repositories\CustomerRepository;
    use Hyn\Tenancy\Contracts\Repositories\HostnameRepository;
    use Hyn\Tenancy\Contracts\Repositories\WebsiteRepository;
    use Hyn\Tenancy\Models\Customer;
    use Hyn\Tenancy\Models\Hostname;
    use Hyn\Tenancy\Models\Website;
    use Illuminate\Console\Command;
    use Illuminate\Http\Request;
    use Illuminate\Support\Facades\Hash;
    
    class TenantsController extends Controller
    {
        public function store(Request $request)
        {
            $tenantName = 'My Tenant Name';
            $slug = 'my-tenant-name';
            $name = 'First Tenant';
            $email = '[email protected]';
    
            if ($this->tenantExists($name, $email)) {
                 // REPORT ERROR
                 return;
            }
    
            $this->registerTenant($tenantName, $slug, $name, $email);
    
            $password = str_random();
            $admin = $this->addAdmin($name, $email, $password);
            $admin->notify(new TenantCreated());
    
            return redirect('/admin/tenants');
        }
    
        private function tenantExists($name, $email)
        {
            return Customer::where('name', $name)->orWhere('email', $email)->exists();
        }
    
        private function registerTenant($tenantName, $tenantSlug, $name, $email)
        {
            // create a customer
            $customer = new Customer;
            $customer->name = $name;
            $customer->email = $email;
            app(CustomerRepository::class)->create($customer);
    
            // associate the customer with a website
            $website = new Website;
            $website->customer()->associate($customer);
            $website->name = $tenantName;
            app(WebsiteRepository::class)->create($website);
    
            // associate the website with a hostname
            $hostname = new Hostname;
            $baseUrl = config('app.url_base');
            $hostname->fqdn = "{$tenantSlug}.{$baseUrl}";
            $hostname->customer()->associate($customer);
            app(HostnameRepository::class)->attach($hostname, $website);
    
            return $hostname;
        }
    
        private function addAdmin($name, $email, $password)
        {
            return User::create(['name' => $name, 'email' => $email, 'password' => Hash::make($password)]);
        }
    }
    
    

    Notification

    namespace App\Notifications;
    
    use Illuminate\Notifications\Messages\MailMessage;
    use Illuminate\Notifications\Notification;
    use Illuminate\Support\Facades\Password;
    
    class TenantCreated extends Notification
    {
        public function __construct()
        {
    
        }
    
        public function via()
        {
            return ['mail'];
        }
    
        public function toMail($notifiable)
        {
            $token = Password::broker()->createToken($notifiable);
    
            $resetUrl = "https://myurl.dev/password/reset/{$token}";
    
            $app = config('app.name');
    
            return (new MailMessage())
                ->subject("{$app} Invitation")
                ->greeting("Hello {$notifiable->name},")
                ->line("You have been invited to use {$app}!")
                ->line('To get started you need to set a password.')
                ->action('Set password', $resetUrl);
        }
    }
    
    research 
    opened by michaeldaly 27
  • unable to install/update hyn package 1.0

    unable to install/update hyn package 1.0

    on executing following command

    composer require hyn/multi-tenant

    I will receive following error :

    Updating dependencies (including require-dev)
      - Installing hyn-me/framework (0.4.0)
        Downloading: Failed
        Failed to download hyn-me/framework from dist: The "https://api.github.com/repos/hyn-me/framework/zipball/347784b8257099c661419d280f45662a2d3497c8" file could not be downloaded (
    HTTP/1.1 404 Not Found)
        Now trying to download from source
      - Installing hyn-me/framework (0.4.0)
        Cloning 347784b8257099c661419d280f45662a2d3497c8
    
    Installation failed, reverting ./composer.json to its original content.
    
    
      [RuntimeException]
      Failed to clone https://github.com/hyn-me/framework.git via https, ssh protocols, aborting.
      - https://github.com/hyn-me/framework.git
        Cloning into 'E:\Ampps\www\managetrainings4u\SourceCode\vendor\hyn-me\framework'...
        remote: Invalid username or password.
        fatal: Authentication failed for 'https://github.com/hyn-me/framework.git/'
      - [email protected]:hyn-me/framework.git
        Cloning into 'E:\Ampps\www\managetrainings4u\SourceCode\vendor\hyn-me\framework'...
        Host key verification failed.
        fatal: Could not read from remote repository.
    
        Please make sure you have the correct access rights
        and the repository exists.
    
    
    require [--dev] [--prefer-source] [--prefer-dist] [--no-progress] [--no-update] [--update-no-dev] [--update-with-dependencies] [--ignore-platform-reqs] [--sort-packages] [-o|--optimi
    ze-autoloader] [-a|--classmap-authoritative] [--] [<packages>]...
    
    opened by hisenbergguj 26
  • Driver [redis_tenancy] is not supported.

    Driver [redis_tenancy] is not supported.

    Hi,

    related to Make cache handling better for tenants #309 I get the issue of getting the error Driver [redis_tenancy] is not supported. Currently I setup my own CI/CD process with local docker (base on php:7.1.8-apache), gitlab and production server. This seems to be related to some autogenerated loading of composer or so as it seems nearly to be random when it works and when not.

    grafik

    • hyn/multi-tenant version: hyn/multi-tenant 5.3.1
    • laravel version: laravel/framework v5.6.39
    • webserver software and version: apache
    • php version: PHP 7.1.8 (cli) (built: Aug 4 2017 18:55:44) ( NTS )

    Composer:

        "extra": {
            "laravel": {
                "dont-discover": [
    				"hyn/multi-tenant",
    				"spatie/laravel-permission"
                ]
            },
    

    config/app

         /*
             * Laravel Framework Service Providers...
             */
            Illuminate\Auth\AuthServiceProvider::class,
            ...
    
    	// Needs to be before Hyn\Tenancy: https://github.com/hyn/multi-tenant/issues/309#issuecomment-401509408
    	App\Providers\AppServiceProvider::class,
    	App\Providers\CacheServiceProvider::class,		
    		
            /*
             * Package Service Providers...
             */	   
            Hyn\Tenancy\Providers\TenancyProvider::class,
            Hyn\Tenancy\Providers\WebserverProvider::class,
            Spatie\Permission\PermissionServiceProvider::class,
            ...
    
            /*
             * Application Service Providers...
             */
            App\Providers\HelperServiceProvider::class,        
            App\Providers\AuthServiceProvider::class,
            App\Providers\ComposerServiceProvider::class,
            // App\Providers\BroadcastServiceProvider::class,
            App\Providers\EventServiceProvider::class,
            App\Providers\RouteServiceProvider::class,	    
           ...
    

    config/cache.php

    return [
        'default' => env('CACHE_DRIVER', 'file'), // in env = redis
        'stores' => [
            ...
            'redis' => [
                'driver' => 'redis_tenancy', //'redis', 'redis_tenancy'
                'connection' => 'default',
            ],
        ],
        'prefix' => env(
            'CACHE_PREFIX',
            str_slug(env('APP_NAME', 'laravel'), '_').'_cache'
        ),
    ];   
    

    CacheServiceProvider:

    <?php
    
    namespace App\Providers;
    
    use Illuminate\Support\ServiceProvider;
    use Illuminate\Support\Facades\Cache;
    use Illuminate\Support\Facades\DB;
    use Illuminate\Cache\RedisStore;
    
    class CacheServiceProvider extends ServiceProvider
    {
        /**
         * Bootstrap services.
         *
         * @return void
         */
        public function boot()
        {	
    
            // https://github.com/hyn/multi-tenant/issues/309
    		Cache::extend('redis_tenancy', function ($app) {
           		if (PHP_SAPI === 'cli') {
    	            $uuid = $app['config']['driver'];
    	        } else {
    	            // ok, this is basically a hack to set the redis cache store
    	            // prefix to the UUID of the current website being called
    	            $fqdn = $_SERVER['SERVER_NAME'];
                
    	            $uuid = DB::table('hostnames')
    	                ->select('websites.uuid')
    	                ->join('websites', 'hostnames.website_id', '=', 'websites.id')
    	                ->where('fqdn', $fqdn)
    	                ->value('uuid');
    	        }
    
    	        return Cache::repository(new RedisStore(
    	            $app['redis'],
    	            $uuid,
    	            $app['config']['cache.stores.redis.connection']
    	        ));
    	    });	
        }
    
        /**
         * Register services.
         *
         * @return void
         */
        public function register()
        {
    	
        }
    }
    

    Any idea what is going wrong here? I think the boot of CacheServiceProvider is not loaded early enough, but how to change this?

    While writing this, I located this: https://stackoverflow.com/a/53263914/1597218 The order in the config/app is not used as expected. In my case the order is the following:

    1. Illuminate
    2. 3rd Party (which may will use Cache as well)
    3. Rest of app/config including CacheServiceProvider, Hyn & App\

    "According to the Application::registerConfiguredProviders it's hardcoded to have everything that starts with 'Illuminate' to go to the starting chunk, all others to the end, and Composer Packages go in the middle. "

    Even so I add all missing 3rd Party provider into the app/config manually, the Application::registerConfiguredProviders list the providers twice. So one option would be that this package add this provider to the default settings, as vendor files are loaded before app providers or alternativly adding a customIlluminate namespace.

    support 
    opened by Nowi5 24
  • Delete and recreate tenant with same uuid will throw error when creating db user in MySQL

    Delete and recreate tenant with same uuid will throw error when creating db user in MySQL

    Description

    I'm using my own uuid generator which is using my unique tenant name. This package helps to create db as well as the db user. However I faced a problem when I delete my tenant and recreate a tenant with same name, this error happened:

    SQLSTATE[HY000]: General error: 1396 Operation CREATE USER failed for 'tenant_name'@'%' (SQL: CREATE USER IF NOT EXISTS `tenant_name`@'%' IDENTIFIED BY 'password')
    

    Actual behavior

    From what I had research online, it seems like when I have the db user remained then CREATE USER IF NOT EXISTS 'tenant_name'@'%' IDENTIFIED BY 'password' will not worked. Then I checked the config, there's an option auto-delete-tenant-database-user to auto delete db user. However, it is not working for mysql perhaps? As I checked source code it only applied to MariaDB and PostgresSQL as you can see in the picture below.

    image

    Expected behavior

    Should drop db user before create tenant with same uuid where the uuid will be used to create db as well as the db user.

    Information

    • hyn/multi-tenant version: 5.2
    • laravel version: 5.6
    • database driver and version: mysql v5.7.10
    • webserver software and version: nginx
    • php version: 7.1

    tenancy.php config

    'models' => [
            /**
             * Specify different models to be used for the global, system database
             * connection. These are also used in their relationships. Models
             * used have to implement their respective contracts and
             * either extend the SystemModel or use the trait
             * UsesSystemConnection.
             */
    
            // Must implement \Hyn\Tenancy\Contracts\Hostname
            'hostname' => \Hyn\Tenancy\Models\Hostname::class,
    
            // Must implement \Hyn\Tenancy\Contracts\Website
            'website' => \Crmx\Tenant\Models\Tenant::class,
        ],
        'website' => [
            /**
             * Each website has a short random hash that identifies this entity
             * to the application. By default this id is randomized and fully
             * auto-generated. In case you want to force your own logic for
             * when you need to have a better overview of the complete
             * tenant folder structure, disable this and implement
             * your own id generation logic.
             */
            'disable-random-id' => true,
    
            /**
             * The random Id generator is responsible for creating the hash as mentioned
             * above. You can override what generator to use by modifying this value
             * in the configuration.
             *
             * @warn This won't work if disable-random-id is true.
             */
            'random-id-generator' => Hyn\Tenancy\Generators\Uuid\ShaGenerator::class,
    
            /**
             * Enable this flag in case you're using a driver that does not support
             * database username or database name with a length of more than 32 characters.
             *
             * This should be enabled for MySQL, but not for MariaDB and PostgreSQL.
             */
            'uuid-limit-length-to-32' => env('LIMIT_UUID_LENGTH_32', false),
    
            /**
             * Specify the disk you configured in the filesystems.php file where to store
             * the tenant specific files, including media, packages, routes and other
             * files for this particular website.
             *
             * @info If not set, will revert to the default filesystem.
             * @info If set to false will disable all tenant specific filesystem auto magic
             *       like the config, vendor overrides.
             */
            'disk' => null,
    
            /**
             * Automatically generate a tenant directory based on the random id of the
             * website. Uses the above disk to store files to override system-wide
             * files.
             *
             * @info set to false to disable.
             */
            'auto-create-tenant-directory' => true,
    
            /**
             * Automatically rename the tenant directory when the random id of the
             * website changes. This should not be too common, but in case it happens
             * we automatically want to move files accordingly.
             *
             * @info set to false to disable.
             */
            'auto-rename-tenant-directory' => true,
    
            /**
             * Automatically deletes the tenant specific directory and all files
             * contained within.
             *
             * @see
             * @info set to true to enable.
             */
            'auto-delete-tenant-directory' => false,
    
            /**
             * Time to cache websites in minutes. Set to false to disable.
             */
            'cache' => 10,
        ],
        'hostname' => [
            /**
             * If you want the multi tenant application to fall back to a default
             * hostname/website in case the requested hostname was not found
             * in the database, complete in detail the default hostname.
             *
             * @warn this must be a FQDN, these have no protocol or path!
             */
            'default' => env('TENANCY_DEFAULT_HOSTNAME', 'crm'),
            /**
             * The package is able to identify the requested hostname by itself,
             * disable to get full control (and responsibility) over hostname
             * identification. The hostname identification is needed to
             * set a specific website as currently active.
             *
             * @see src/Jobs/HostnameIdentification.php
             */
            'auto-identification' => env('TENANCY_AUTO_HOSTNAME_IDENTIFICATION', false),
    
            /**
             * In case you want to have the tenancy environment set up early,
             * enable this flag. This will run the tenant identification
             * inside a middleware. This will eager load tenancy.
             *
             * A good use case is when you have set "tenant" as the default
             * database connection.
             */
            'early-identification' => env('TENANCY_EARLY_IDENTIFICATION', true),
    
            /**
             * Abort application execution in case no hostname was identified. This will throw a
             * 404 not found in case the tenant hostname was not resolved.
             */
            'abort-without-identified-hostname' => false,
    
            /**
             * Time to cache hostnames in minutes. Set to false to disable.
             */
            'cache' => 10,
    
            /**
             * Automatically update the app.url configured inside Laravel to match
             * the tenant FQDN whenever a hostname/tenant was identified.
             *
             * This will resolve issues with password reset mails etc using the
             * correct domain.
             */
            'update-app-url' => true,
        ],
        'db' => [
            /**
             * The default connection to use; this overrules the Laravel database.default
             * configuration setting. In Laravel this is normally configured to 'mysql'.
             * You can set a environment variable to override the default database
             * connection to - for instance - the tenant connection 'tenant'.
             */
            'default' => env('TENANCY_DEFAULT_CONNECTION'),
            /**
             * Used to give names to the system and tenant database connections. By
             * default we configure 'system' and 'tenant'. The tenant connection
             * is set up automatically by this package.
             *
             * @see src/Database/Connection.php
             * @var system-connection-name The database connection name to use for the global/system database.
             * @var tenant-connection-name The database connection name to use for the tenant database.
             */
            'system-connection-name' => env('TENANCY_SYSTEM_CONNECTION_NAME', Connection::DEFAULT_SYSTEM_NAME),
            'tenant-connection-name' => env('TENANCY_TENANT_CONNECTION_NAME', Connection::DEFAULT_TENANT_NAME),
    
            /**
             * The tenant division mode specifies to what database websites will be
             * connecting. The default setup is to use a new database per tenant.
             * In case you prefer to use the same database with a table prefix,
             * set the mode to 'prefix'.
             *
             * @see src/Database/Connection.php
             */
            'tenant-division-mode' => env('TENANCY_DATABASE_DIVISION_MODE', 'database'),
    
            /**
             * The database password generator takes care of creating a valid hashed
             * string used for tenants to connect to the specific database. Do
             * note that this will only work in 'division modes' that set up
             * a connection to a separate database.
             */
            'password-generator' => Crmx\Tenant\Overrides\Hyn\MultiTenant\Generators\Database\DefaultPasswordGenerator::class,
    
            /**
             * The tenant migrations to be run during creation of a tenant. Specify a directory
             * to run the migrations from. If specified these migrations will be executed
             * whenever a new tenant is created.
             *
             * @info set to false to disable auto migrating.
             *
             * @warn this has to be an absolute path, feel free to use helper methods like
             * base_path() or database_path() to set this up.
             */
            'tenant-migrations-path' => database_path('migrations/tenant'),
    
            /**
             * The default Seeder class used on newly created databases and while
             * running artisan commands that fire seeding.
             *
             * @info requires tenant-migrations-path in order to seed newly created websites.
             *
             * @warn specify a valid fully qualified class name.
             */
            'tenant-seed-class' => false,
    //      eg an admin seeder under `app/Seeders/AdminSeeder.php`:
    //        'tenant-seed-class' => App\Seeders\AdminSeeder::class,
    
            'tenant-seed-classes' => [
            ],
    
            /**
             * Automatically generate a tenant database based on the random id of the
             * website.
             *
             * @info set to false to disable.
             */
            'auto-create-tenant-database' => true,
    
            /**
             * Automatically generate the user needed to access the database.
             *
             * @info Useful in case you use root or another predefined user to access the
             *       tenant database.
             * @info Only creates in case tenant databases are set to be created.
             *
             * @info set to false to disable.
             */
            'auto-create-tenant-database-user' => true,
    
            /**
             * Automatically rename the tenant database when the random id of the
             * website changes. This should not be too common, but in case it happens
             * we automatically want to move databases accordingly.
             *
             * @info set to false to disable.
             */
            'auto-rename-tenant-database' => true,
    
            /**
             * Automatically deletes the tenant specific database and all data
             * contained within.
             *
             * @info set to true to enable.
             */
            'auto-delete-tenant-database' => env('TENANCY_DATABASE_AUTO_DELETE', false),
    
            /**
             * Automatically delete the user needed to access the tenant database.
             *
             * @info Set to false to disable.
             * @info Only deletes in case tenant database is set to be deleted.
             */
            'auto-delete-tenant-database-user' => env('TENANCY_DATABASE_AUTO_DELETE_USER', true),
    
            /**
             * Define a list of classes that you wish to force onto the tenant or system connection.
             * The connection will be forced when the Model has booted.
             *
             * @info Useful for overriding the connection of third party packages.
             */
            'force-tenant-connection-of-models' => [
    //            App\User::class
            ],
            'force-system-connection-of-models' => [
    //            App\User::class
            ],
        ],
    
        /**
         * Global tenant specific routes.
         * Making it easier to distinguish between landing and tenant routing.
         *
         * @info only works with `tenancy.hostname.auto-identification` or identification happening
         *       before the application is booted (eg inside middleware or the register method of
         *       service providers).
         */
        'routes' => [
            /**
             * Routes file to load whenever a tenant was identified.
             *
             * @info Set to false or null to disable.
             */
            'path' => base_path('routes/tenants.php'),
    
            /**
             * Set to true to flush all global routes before setting the routes from the
             * tenants.php routes file.
             */
            'replace-global' => false,
        ],
    
        /**
         * Folders configuration specific per tenant.
         * The following section relates to configuration to files inside the tenancy/<uuid>
         * tenant directory.
         */
        'folders' => [
            'config' => [
                /**
                 * Merge configuration files from the config directory
                 * inside the tenant directory with the global configuration files.
                 */
                'enabled' => true,
    
                /**
                 * List of configuration files to ignore, preventing override of crucial
                 * application configurations.
                 */
                'blacklist' => ['database', 'tenancy', 'webserver'],
            ],
            'routes' => [
                /**
                 * Allows adding and overriding URL routes inside the tenant directory.
                 */
                'enabled' => true,
    
                /**
                 * Prefix all tenant routes.
                 */
                'prefix' => null,
            ],
            'trans' => [
                /**
                 * Allows reading translation files from a trans directory inside
                 * the tenant directory.
                 */
                'enabled' => true,
    
                /**
                 * Will override the global translations with the tenant translations.
                 * This is done by overriding the laravel default translator with the new path.
                 */
                'override-global' => true,
    
                /**
                 * In case you disabled global override, specify a namespace here to load the
                 * tenant translation files with.
                 */
                'namespace' => 'tenant',
            ],
            'vendor' => [
                /**
                 * Allows using a custom vendor (composer driven) folder inside
                 * the tenant directory.
                 */
                'enabled' => true,
            ],
            'media' => [
                /**
                 * Mounts the assets directory with (static) files for public use.
                 */
                'enabled' => true,
            ],
            'views' => [
                /**
                 * Adds the vendor directory of the tenant inside the application.
                 */
                'enabled' => true,
    
                /**
                 * Specify a namespace to use with which to load the views.
                 *
                 * @eg setting `tenant` will allow you to use `tenant::some.blade.php`
                 * @info set to null to add to the global namespace.
                 */
                'namespace' => null,
    
                /**
                 * If `namespace` is set to null (thus using the global namespace)
                 * make it override the global views. Disable to
                 */
                'override-global' => true,
            ]
    
    opened by WeiKian 23
  • routes/tenants.php is not loaded during tests, because there's no tenant, when app initializes

    routes/tenants.php is not loaded during tests, because there's no tenant, when app initializes

    Don't want this to get lost in discord.

    Currently routes/tenants.php is being loaded during boot of service providers and it checks for a tenant. My tests and tenant is being set up after that and since json request in tests do not actually create a new app, tenant routes are never defined.

    Would it be possible to have callable function to load routes during runtime?


    Information

    • hyn/multi-tenant version: 5.2
    • laravel version: 5.6
    enhancement 
    opened by Plytas 23
  • Make cache handling better for tenants

    Make cache handling better for tenants

    Currently the Laravel cache will use the same cache key for all tenants. This can and will cause issues in the long run. The config key for tenants should be automatically changed and the ability to disable overriding the cache key should be configurable inside tenancy.php

    documentation good first issue 
    opened by luceos 23
  • The command apachectl -t failed

    The command apachectl -t failed

    We are building a SaaS system where every customer will have access to our system. As soon as the system opens a new account, a subdomain will be created for the customer. Now to add this subdomain to the apache vhost I need to run the command "apachectl -t" and "apachectl graceful" on the user running the script. Apache can load the new vhost configuration. But the user running the script does not have permission to run the command "apachectl -t". All that the user needs is permission to run these two commands to load the configuration and register the subdomain.

    "The command \"apachectl -t\" failed.\n\nExit Code: 1(General error)\n\nWorking directory: /home/systemhubs/public_html\n\nOutput:\n================\n\n\nError Output:\n================\nhttpd: Could not open configuration file /etc/apache2/conf/httpd.conf: Permission denied\n" could you please let me know how I can solve this?

    opened by rehankausarDev 0
  • "PDOException: There is no active transaction" when creating a tenant (PHP 8).

    I'm currently working on upgrading my laravel app to run on PHP 8, and I got this error when trying to seed my local DB with some default tenants:

     PDOException 
    
      There is no active transaction
    
      at vendor/laravel/framework/src/Illuminate/Database/Concerns/ManagesTransactions.php:45
         41▕             }
         42▕ 
         43▕             try {
         44▕                 if ($this->transactions == 1) {
      ➜  45▕                     $this->getPdo()->commit();
         46▕                 }
         47▕ 
         48▕                 $this->transactions = max(0, $this->transactions - 1);
         49▕ 
    
          +34 vendor frames 
      35  app/Console/Commands/TenantCreate.php:63
          Hyn\Tenancy\Repositories\WebsiteRepository::create()
    
          +15 vendor frames 
      51  database/seeds/TenantSeeder.php:33
          Illuminate\Support\Facades\Facade::__callStatic()
    

    My command handle function hasn't changed since before the upgrade, and still seems to match what is in the documentation:

            $fqdn = $this->argument('fqdn');
            $hostname = new Hostname;
            $hostname->fqdn = $fqdn;
            $hostname = app(HostnameRepository::class)->create($hostname);
    
            $website = new Website;
            app(WebsiteRepository::class)->create($website);
    
            app(HostnameRepository::class)->attach($hostname, $website);
            $tenant->save();
    

    Here is the stack trace Laravel prints for me:

    [2022-12-21 15:52:44] local.ERROR: There is no active transaction {"exception":"[object] (PDOException(code: 0): There is no active transaction at /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Database/Concerns/ManagesTransactions.php:45)
    [stacktrace]
    #0 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Database/Concerns/ManagesTransactions.php(45): PDO->commit()
    #1 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php(402): Illuminate\\Database\\Connection->transaction()
    #2 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php(202): Illuminate\\Database\\Migrations\\Migrator->runMigration()
    #3 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php(167): Illuminate\\Database\\Migrations\\Migrator->runUp()
    #4 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php(112): Illuminate\\Database\\Migrations\\Migrator->runPending()
    #5 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/MigrateCommand.php(86): Illuminate\\Database\\Migrations\\Migrator->run()
    #6 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php(606): Illuminate\\Database\\Console\\Migrations\\MigrateCommand->Illuminate\\Database\\Console\\Migrations\\{closure}()
    #7 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/MigrateCommand.php(98): Illuminate\\Database\\Migrations\\Migrator->usingConnection()
    #8 /home/vagrant/code/bridge/portal/vendor/hyn/multi-tenant/src/Traits/AddWebsiteFilterOnCommand.php(34): Illuminate\\Database\\Console\\Migrations\\MigrateCommand->handle()
    #9 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Collections/Traits/EnumeratesValues.php(245): Hyn\\Tenancy\\Database\\Console\\Migrations\\MigrateCommand->Hyn\\Tenancy\\Traits\\{closure}()
    #10 /home/vagrant/code/bridge/portal/vendor/hyn/multi-tenant/src/Traits/AddWebsiteFilterOnCommand.php(39): Illuminate\\Support\\Collection->each()
    #11 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Database/Concerns/BuildsQueries.php(51): Hyn\\Tenancy\\Database\\Console\\Migrations\\MigrateCommand->Hyn\\Tenancy\\Traits\\{closure}()
    #12 /home/vagrant/code/bridge/portal/vendor/hyn/multi-tenant/src/Traits/AddWebsiteFilterOnCommand.php(40): Illuminate\\Database\\Eloquent\\Builder->chunk()
    #13 /home/vagrant/code/bridge/portal/vendor/hyn/multi-tenant/src/Traits/MutatesMigrationCommands.php(55): Hyn\\Tenancy\\Database\\Console\\Migrations\\MigrateCommand->processHandle()
    #14 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(36): Hyn\\Tenancy\\Database\\Console\\Migrations\\MigrateCommand->handle()
    #15 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Container/Util.php(40): Illuminate\\Container\\BoundMethod::Illuminate\\Container\\{closure}()
    #16 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(93): Illuminate\\Container\\Util::unwrapIfClosure()
    #17 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(37): Illuminate\\Container\\BoundMethod::callBoundMethod()
    #18 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Container/Container.php(653): Illuminate\\Container\\BoundMethod::call()
    #19 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Console/Command.php(136): Illuminate\\Container\\Container->call()
    #20 /home/vagrant/code/bridge/portal/vendor/symfony/console/Command/Command.php(298): Illuminate\\Console\\Command->execute()
    #21 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Console/Command.php(121): Symfony\\Component\\Console\\Command\\Command->run()
    #22 /home/vagrant/code/bridge/portal/vendor/symfony/console/Application.php(1040): Illuminate\\Console\\Command->run()
    #23 /home/vagrant/code/bridge/portal/vendor/symfony/console/Application.php(301): Symfony\\Component\\Console\\Application->doRunCommand()
    #24 /home/vagrant/code/bridge/portal/vendor/symfony/console/Application.php(171): Symfony\\Component\\Console\\Application->doRun()
    #25 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Console/Application.php(94): Symfony\\Component\\Console\\Application->run()
    #26 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Console/Application.php(186): Illuminate\\Console\\Application->run()
    #27 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php(263): Illuminate\\Console\\Application->call()
    #28 /home/vagrant/code/bridge/portal/vendor/hyn/multi-tenant/src/Database/Connection.php(266): Illuminate\\Foundation\\Console\\Kernel->call()
    #29 /home/vagrant/code/bridge/portal/vendor/hyn/multi-tenant/src/Listeners/Database/MigratesTenants.php(55): Hyn\\Tenancy\\Database\\Connection->migrate()
    #30 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php(404): Hyn\\Tenancy\\Listeners\\Database\\MigratesTenants->migrate()
    #31 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php(249): Illuminate\\Events\\Dispatcher->Illuminate\\Events\\{closure}()
    #32 /home/vagrant/code/bridge/portal/vendor/hyn/multi-tenant/src/Traits/DispatchesEvents.php(29): Illuminate\\Events\\Dispatcher->dispatch()
    #33 /home/vagrant/code/bridge/portal/vendor/hyn/multi-tenant/src/Repositories/WebsiteRepository.php(97): Hyn\\Tenancy\\Repositories\\WebsiteRepository->emitEvent()
    #34 /home/vagrant/code/bridge/portal/app/Console/Commands/TenantCreate.php(63): Hyn\\Tenancy\\Repositories\\WebsiteRepository->create()
    #35 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(36): App\\Console\\Commands\\TenantCreate->handle()
    #36 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Container/Util.php(40): Illuminate\\Container\\BoundMethod::Illuminate\\Container\\{closure}()
    #37 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(93): Illuminate\\Container\\Util::unwrapIfClosure()
    #38 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(37): Illuminate\\Container\\BoundMethod::callBoundMethod()
    #39 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Container/Container.php(653): Illuminate\\Container\\BoundMethod::call()
    #40 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Console/Command.php(136): Illuminate\\Container\\Container->call()
    #41 /home/vagrant/code/bridge/portal/vendor/symfony/console/Command/Command.php(298): Illuminate\\Console\\Command->execute()
    #42 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Console/Command.php(121): Symfony\\Component\\Console\\Command\\Command->run()
    #43 /home/vagrant/code/bridge/portal/vendor/symfony/console/Application.php(1040): Illuminate\\Console\\Command->run()
    #44 /home/vagrant/code/bridge/portal/vendor/symfony/console/Application.php(301): Symfony\\Component\\Console\\Application->doRunCommand()
    #45 /home/vagrant/code/bridge/portal/vendor/symfony/console/Application.php(171): Symfony\\Component\\Console\\Application->doRun()
    #46 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Console/Application.php(94): Symfony\\Component\\Console\\Application->run()
    #47 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Console/Application.php(186): Illuminate\\Console\\Application->run()
    #48 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php(263): Illuminate\\Console\\Application->call()
    #49 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php(261): Illuminate\\Foundation\\Console\\Kernel->call()
    #50 /home/vagrant/code/bridge/portal/database/seeds/TenantSeeder.php(33): Illuminate\\Support\\Facades\\Facade::__callStatic()
    #51 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(36): TenantSeeder->run()
    #52 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Container/Util.php(40): Illuminate\\Container\\BoundMethod::Illuminate\\Container\\{closure}()
    #53 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(93): Illuminate\\Container\\Util::unwrapIfClosure()
    #54 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(37): Illuminate\\Container\\BoundMethod::callBoundMethod()
    #55 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Container/Container.php(653): Illuminate\\Container\\BoundMethod::call()
    #56 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Database/Seeder.php(149): Illuminate\\Container\\Container->call()
    #57 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Database/Seeder.php(49): Illuminate\\Database\\Seeder->__invoke()
    #58 /home/vagrant/code/bridge/portal/database/seeds/DatabaseSeeder.php(16): Illuminate\\Database\\Seeder->call()
    #59 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(36): DatabaseSeeder->run()
    #60 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Container/Util.php(40): Illuminate\\Container\\BoundMethod::Illuminate\\Container\\{closure}()
    #61 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(93): Illuminate\\Container\\Util::unwrapIfClosure()
    #62 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(37): Illuminate\\Container\\BoundMethod::callBoundMethod()
    #63 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Container/Container.php(653): Illuminate\\Container\\BoundMethod::call()
    #64 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Database/Seeder.php(149): Illuminate\\Container\\Container->call()
    #65 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Database/Console/Seeds/SeedCommand.php(66): Illuminate\\Database\\Seeder->__invoke()
    #66 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php(157): Illuminate\\Database\\Console\\Seeds\\SeedCommand->Illuminate\\Database\\Console\\Seeds\\{closure}()
    #67 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Database/Console/Seeds/SeedCommand.php(67): Illuminate\\Database\\Eloquent\\Model::unguarded()
    #68 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(36): Illuminate\\Database\\Console\\Seeds\\SeedCommand->handle()
    #69 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Container/Util.php(40): Illuminate\\Container\\BoundMethod::Illuminate\\Container\\{closure}()
    #70 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(93): Illuminate\\Container\\Util::unwrapIfClosure()
    #71 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(37): Illuminate\\Container\\BoundMethod::callBoundMethod()
    #72 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Container/Container.php(653): Illuminate\\Container\\BoundMethod::call()
    #73 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Console/Command.php(136): Illuminate\\Container\\Container->call()
    #74 /home/vagrant/code/bridge/portal/vendor/symfony/console/Command/Command.php(298): Illuminate\\Console\\Command->execute()
    #75 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Console/Command.php(121): Symfony\\Component\\Console\\Command\\Command->run()
    #76 /home/vagrant/code/bridge/portal/vendor/symfony/console/Application.php(1040): Illuminate\\Console\\Command->run()
    #77 /home/vagrant/code/bridge/portal/vendor/symfony/console/Application.php(301): Symfony\\Component\\Console\\Application->doRunCommand()
    #78 /home/vagrant/code/bridge/portal/vendor/symfony/console/Application.php(171): Symfony\\Component\\Console\\Application->doRun()
    #79 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Console/Application.php(94): Symfony\\Component\\Console\\Application->run()
    #80 /home/vagrant/code/bridge/portal/vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php(129): Illuminate\\Console\\Application->run()
    #81 /home/vagrant/code/bridge/portal/artisan(37): Illuminate\\Foundation\\Console\\Kernel->handle()
    #82 {main}
    "} 
    
    opened by logan-waite 0
  • How can we separate databases by region or Instance ?

    How can we separate databases by region or Instance ?

    Description

    We need to separate database instances. For example, there are 3000 databases in Msql. We want to split them into 3 servers located on AWS according to fqdn. Now there is 1 database server and it started to cause problems. How can we do this?

    Thank you.


    Actual behavior

    ..

    Expected behavior

    ..


    Information

    • hyn/multi-tenant version: 5.6
    • laravel version: 6.x
    • database driver and version: 5.7
    • webserver software and version: Apache2
    • php version: 7.4

    tenancy.php config

    
    

    webserver.php config

    
    

    Error log

    
    
    opened by mzahirr 0
  • Question : can same tenant accessed by a multiple endpoint? by IPaddress or by FQDN

    Question : can same tenant accessed by a multiple endpoint? by IPaddress or by FQDN

    Description of your feature

    I have a website which is using this tenancy plugins, the portal can accessible both by user and by internal process, the internal process covered with VPN IP address, the user will be using FQDN, in this case I register VPN IP as a new tenant, then FQDN as a new tenant.


    Proposed behavior

    What I expect is the VPN IP tenant should have destination same as FQDN tenant, same storage (as an ALIAS) instead of working in different tenant

    Current behavior

    VPN IP tenant and FQDN tenant is two different thing which is not related each other


    Information

    • hyn/multi-tenant version: 5.6
    • laravel version: 8.1
    opened by reidsneo 0
  • queue retry uses system db connection instead of tenant

    queue retry uses system db connection instead of tenant

    Description

    Retrying failed jobs via "php artisan queue:retry all" results in system database connection being used instead of tenant databse connection.


    Actual behavior

    Illuminate\Database\QueryException

    SQLSTATE[42S02]: Base table or view not found: 1146 Table 'SYSTEM.messages' doesn't exist (SQL: select * from messages where messages.id = 1554856 limit 1)

    Expected behavior

    The tenant databse connection should be used. use Hyn\Tenancy\Traits\UsesTenantConnection is being ignored in the model.


    Information

    • hyn/multi-tenant version: 5.8.0
    • laravel version: 8
    • database driver and version: mariadb
    • php version: 8.2

    Error log

    php artisan queue:retry all

    Illuminate\Database\QueryException

    SQLSTATE[42S02]: Base table or view not found: 1146 Table 'sc4.messages' doesn't exist (SQL: select * from messages where messages.id = 1554856 limit 1)

    at vendor/laravel/framework/src/Illuminate/Database/Connection.php:712 708▕ // If an exception occurs when attempting to run a query, we'll format the error 709▕ // message to include the bindings with SQL, which will make this exception a 710▕ // lot more helpful to the developer instead of just the database's errors. 711▕ catch (Exception $e) { ➜ 712▕ throw new QueryException( 713▕ $query, $this->prepareBindings($bindings), $e 714▕ ); 715▕ } 716▕ }

    • A table was not found: You might have forgotten to run your migrations. You can run your migrations using php artisan migrate. https://laravel.com/docs/master/migrations#running-migrations

    1 [internal]:0 App\Jobs\SendMessage::__unserialize()

      +16 vendor frames 
    

    18 [internal]:0 App\Jobs\SendMessage::__unserialize()

    opened by jeremyj11 0
  • Memory leak when running phpunit

    Memory leak when running phpunit

    Hi,

    I'm using your package in a project and I started to have memory consumption increasing running phpunit. The more tests I have the more memory phpunit uses (have around 4500 tests and it reaches around 6 GB). Just to be sure I did a test that actually does not test anything, except doing a loop where I create the laravel application and then flush it.

    namespace Tests\Feature;
    
    use Tests\TestCase;
    
    class ExampleTest extends TestCase
    {
    
        public function test_leaks_memory_on_1000_iterations()
        {
            // Remove the existing application instance.
            $this->app->flush();
            $this->app = null;
    
            // Let's create a fully booted application instance 1,000 times.
            for($i = 1; $i <= 1000; ++$i) {
                $this->createApplication()->flush();
    
                // Each 50 times, report the MB used by the PHP process by dumping it.
                if ( $i % 50 === 0) {
                    dump('Using ' . ((int) (memory_get_usage(true) / (1024 * 1024))) . 'MB as ' . $i . ' iterations.');
                }
            }
    
    
            $this->app = $this->createApplication();
        }
    }
    
    
    
    

    ..


    Actual behavior

    Just to be sure I installed a fresh laravel application (see version details below) and run the test without laravel hyn. The results where this:

    memory-test-without-laravel-hyn You can see that there is a memory leak but not to much.

    With Laravel hyn 5.8 package installed here are the results: memory-test-with-laravel-hyn

    You can see that the memory leak increases. Then on my main project I started commenting / removing service providers, and the one that made a big difference was the Hyn\Tenancy\Providers\TenancyProvider.

    With the provider: memory-test-main-project-with-laravel-hyn

    Without the provider: memory-test-main-project-without-tenancy-provider You can see the difference, it is huge.

    I then started to look at the TenancyProvider, commenting and un-commenting the inner service provider and the problem is on the EventProvider. If I comment just this line see the difference:

    memory-test-main-project-without-event-provider

    Even on the standalone project there is a difference from 202MB to 86MB without the EventProvider. memory-test-without-event-provider

    .. Expected behavior

    The EventProvider should not have such a big memory leak when running PhpUnit tests.

    ..


    Information

    • hyn/multi-tenant version: 5.8
    • laravel version: 8.6.12
    • database driver and version: mysql 5.7
    • webserver software and version: apache 2.4
    • php version: 8.1

    tenancy.php config

    <?php
    
    /*
     * This file is part of the hyn/multi-tenant package.
     *
     * (c) Daniël Klabbers <[email protected]>
     *
     * For the full copyright and license information, please view the LICENSE
     * file that was distributed with this source code.
     *
     * @see https://tenancy.dev
     * @see https://github.com/hyn/multi-tenant
     */
    
    use Hyn\Tenancy\Database\Connection;
    
    return [
        /**
         * Random key used for tenant database user password
         */
        'key' => env('TENANCY_KEY', env('APP_KEY')),
    
        'models' => [
            /**
             * Specify different models to be used for the global, system database
             * connection. These are also used in their relationships. Models
             * used have to implement their respective contracts and
             * either extend the SystemModel or use the trait
             * UsesSystemConnection.
             */
    
            // Must implement \Hyn\Tenancy\Contracts\Hostname
            'hostname' => \Hyn\Tenancy\Models\Hostname::class,
    
            // Must implement \Hyn\Tenancy\Contracts\Website
            'website' => \Hyn\Tenancy\Models\Website::class
        ],
        /**
         * The package middleware. Removing a middleware here will disable it.
         * You can of course extend/replace them or add your own.
         */
        'middleware' => [
            // The eager identification middleware.
            \Hyn\Tenancy\Middleware\EagerIdentification::class,
    
            // The hostname actions middleware (redirects, https, maintenance).
            \Hyn\Tenancy\Middleware\HostnameActions::class,
        ],
        'website' => [
            /**
             * Each website has a short random hash that identifies this entity
             * to the application. By default this id is randomized and fully
             * auto-generated. In case you want to force your own logic for
             * when you need to have a better overview of the complete
             * tenant folder structure, disable this and implement
             * your own id generation logic.
             */
            'disable-random-id' => false,
    
            /**
             * The random Id generator is responsible for creating the hash as mentioned
             * above. You can override what generator to use by modifying this value
             * in the configuration.
             *
             * @warn This won't work if disable-random-id is true.
             */
            'random-id-generator' => Hyn\Tenancy\Generators\Uuid\ShaGenerator::class,
    
            /**
             * Enable this flag in case you're using a driver that does not support
             * database username or database name with a length of more than 32 characters.
             *
             * This should be enabled for MySQL, but not for MariaDB and PostgreSQL.
             */
            'uuid-limit-length-to-32' => env('LIMIT_UUID_LENGTH_32', false),
    
            /**
             * Specify the disk you configured in the filesystems.php file where to store
             * the tenant specific files, including media, packages, routes and other
             * files for this particular website.
             *
             * @info If not set, will revert to the default filesystem.
             * @info If set to false will disable all tenant specific filesystem auto magic
             *       like the config, vendor overrides.
             */
            'disk' => null,
    
            /**
             * Automatically generate a tenant directory based on the random id of the
             * website. Uses the above disk to store files to override system-wide
             * files.
             *
             * @info set to false to disable.
             */
            'auto-create-tenant-directory' => true,
    
            /**
             * Automatically rename the tenant directory when the random id of the
             * website changes. This should not be too common, but in case it happens
             * we automatically want to move files accordingly.
             *
             * @info set to false to disable.
             */
            'auto-rename-tenant-directory' => true,
    
            /**
             * Automatically deletes the tenant specific directory and all files
             * contained within.
             *
             * @see
             * @info set to true to enable.
             */
            'auto-delete-tenant-directory' => false,
    
            /**
             * Time to cache websites in seconds. Set to false to disable.
             */
            'cache' => 600,
        ],
        'hostname' => [
            /**
             * If you want the multi tenant application to fall back to a default
             * hostname/website in case the requested hostname was not found
             * in the database, complete in detail the default hostname.
             *
             * @warn this must be a FQDN, these have no protocol or path!
             */
            'default' => env('TENANCY_DEFAULT_HOSTNAME'),
            /**
             * The package is able to identify the requested hostname by itself,
             * disable to get full control (and responsibility) over hostname
             * identification. The hostname identification is needed to
             * set a specific website as currently active.
             *
             * @see src/Jobs/HostnameIdentification.php
             */
            'auto-identification' => env('TENANCY_AUTO_HOSTNAME_IDENTIFICATION', true),
    
            /**
             * In case you want to have the tenancy environment set up early,
             * enable this flag. This will run the tenant identification
             * inside a middleware. This will eager load tenancy.
             *
             * A good use case is when you have set "tenant" as the default
             * database connection.
             */
            'early-identification' => env('TENANCY_EARLY_IDENTIFICATION', true),
    
            /**
             * Abort application execution in case no hostname was identified. This will throw a
             * 404 not found in case the tenant hostname was not resolved.
             */
            'abort-without-identified-hostname' => env('TENANCY_ABORT_WITHOUT_HOSTNAME', false),
    
            /**
             * Time to cache hostnames in seconds. Set to false to disable.
             */
            'cache' => 600,
    
            /**
             * Automatically update the app.url configured inside Laravel to match
             * the tenant FQDN whenever a hostname/tenant was identified.
             *
             * This will resolve issues with password reset mails etc using the
             * correct domain.
             */
            'update-app-url' => false,
        ],
        'db' => [
            /**
             * The default connection to use; this overrules the Laravel database.default
             * configuration setting. In Laravel this is normally configured to 'mysql'.
             * You can set a environment variable to override the default database
             * connection to - for instance - the tenant connection 'tenant'.
             */
            'default' => env('TENANCY_DEFAULT_CONNECTION'),
            /**
             * Used to give names to the system and tenant database connections. By
             * default we configure 'system' and 'tenant'. The tenant connection
             * is set up automatically by this package.
             *
             * @see src/Database/Connection.php
             * @var system-connection-name The database connection name to use for the global/system database.
             * @var tenant-connection-name The database connection name to use for the tenant database.
             */
            'system-connection-name' => env('TENANCY_SYSTEM_CONNECTION_NAME', Connection::DEFAULT_SYSTEM_NAME),
            'tenant-connection-name' => env('TENANCY_TENANT_CONNECTION_NAME', Connection::DEFAULT_TENANT_NAME),
    
            /**
             * The tenant division mode specifies to what database websites will be
             * connecting. The default setup is to use a new database per tenant.
             * If using PostgreSQL, a new schema per tenant in the same database can
             * be setup, by optionally setting division mode to 'schema'.
             * In case you prefer to use the same database with a table prefix,
             * set the mode to 'prefix'.
             * To implement a custom division mode, set this to 'bypass'.
             *
             * @see src/Database/Connection.php
             */
            'tenant-division-mode' => env('TENANCY_DATABASE_DIVISION_MODE', 'database'),
    
            /**
             * The database password generator takes care of creating a valid hashed
             * string used for tenants to connect to the specific database. Do
             * note that this will only work in 'division modes' that set up
             * a connection to a separate database.
             */
            'password-generator' => Hyn\Tenancy\Generators\Database\DefaultPasswordGenerator::class,
    
            /**
             * The tenant migrations to be run during creation of a tenant. Specify a directory
             * to run the migrations from. If specified these migrations will be executed
             * whenever a new tenant is created.
             *
             * @info set to false to disable auto migrating.
             *
             * @warn this has to be an absolute path, feel free to use helper methods like
             * base_path() or database_path() to set this up.
             */
            'tenant-migrations-path' => database_path('migrations/tenant'),
    
            /**
             * The default Seeder class used on newly created databases and while
             * running artisan commands that fire seeding.
             *
             * @info requires tenant-migrations-path in order to seed newly created websites.
             * @info seeds stored in `database/seeds/tenants` need to be configured in your composer.json classmap.
             *
             * @warn specify a valid fully qualified class name.
             */
            'tenant-seed-class' => false,
    //      eg an admin seeder under `app/Seeders/AdminSeeder.php`:
    //        'tenant-seed-class' => App\Seeders\AdminSeeder::class,
    
            /**
             * Automatically generate a tenant database based on the random id of the
             * website.
             *
             * @info set to false to disable.
             */
            'auto-create-tenant-database' => true,
    
            /**
             * Automatically generate the user needed to access the database.
             *
             * @info Useful in case you use root or another predefined user to access the
             *       tenant database.
             * @info Only creates in case tenant databases are set to be created.
             *
             * @info set to false to disable.
             */
            'auto-create-tenant-database-user' => true,
    
            /**
             * Set of database privileges to give to the tenant database user.
             *
             * @info Useful in case your database restricts the privileges you
             *       can set (for example AWS RDS).
             * @info These privileges are only used in case tenant database users
             *       are set to be created.
             *
             * @info null by default means "ALL PRIVILEGES". Override with a list
             *       of privileges as a string, e.g. 'SELECT, UPDATE'.
             */
            'tenant-database-user-privileges' => null,
    
            /**
             * Automatically rename the tenant database when the random id of the
             * website changes. This should not be too common, but in case it happens
             * we automatically want to move databases accordingly.
             *
             * @info set to false to disable.
             */
            'auto-rename-tenant-database' => true,
    
            /**
             * Automatically deletes the tenant specific database and all data
             * contained within.
             *
             * @info set to true to enable.
             */
            'auto-delete-tenant-database' => env('TENANCY_DATABASE_AUTO_DELETE', false),
    
            /**
             * Automatically delete the user needed to access the tenant database.
             *
             * @info Set to false to disable.
             * @info Only deletes in case tenant database is set to be deleted.
             */
            'auto-delete-tenant-database-user' => env('TENANCY_DATABASE_AUTO_DELETE_USER', false),
    
            /**
             * Define a list of classes that you wish to force onto the tenant or system connection.
             * The connection will be forced when the Model has booted.
             *
             * @info Useful for overriding the connection of third party packages.
             */
            'force-tenant-connection-of-models' => [
    //            App\User::class
            ],
            'force-system-connection-of-models' => [
    //            App\User::class
            ],
        ],
    
        /**
         * Global tenant specific routes.
         * Making it easier to distinguish between landing and tenant routing.
         *
         * @info only works with `tenancy.hostname.auto-identification` or identification happening
         *       before the application is booted (eg inside middleware or the register method of
         *       service providers).
         */
        'routes' => [
            /**
             * Routes file to load whenever a tenant was identified.
             *
             * @info Set to false or null to disable.
             */
            'path' => base_path('routes/tenants.php'),
    
            /**
             * Set to true to flush all global routes before setting the routes from the
             * tenants.php routes file.
             */
            'replace-global' => false,
        ],
    
        /**
         * Folders configuration specific per tenant.
         * The following section relates to configuration to files inside the tenancy/<uuid>
         * tenant directory.
         */
        'folders' => [
            'config' => [
                /**
                 * Merge configuration files from the config directory
                 * inside the tenant directory with the global configuration files.
                 */
                'enabled' => true,
    
                /**
                 * List of configuration files to ignore, preventing override of crucial
                 * application configurations.
                 */
                'blacklist' => ['database', 'tenancy', 'webserver'],
            ],
            'routes' => [
                /**
                 * Allows adding and overriding URL routes inside the tenant directory.
                 */
                'enabled' => true,
    
                /**
                 * Prefix all tenant routes.
                 */
                'prefix' => null,
            ],
            'trans' => [
                /**
                 * Allows reading translation files from a trans directory inside
                 * the tenant directory.
                 */
                'enabled' => true,
    
                /**
                 * Will override the global translations with the tenant translations.
                 * This is done by overriding the laravel default translator with the new path.
                 */
                'override-global' => true,
    
                /**
                 * In case you disabled global override, specify a namespace here to load the
                 * tenant translation files with.
                 */
                'namespace' => 'tenant',
            ],
            'vendor' => [
                /**
                 * Allows using a custom vendor (composer driven) folder inside
                 * the tenant directory.
                 */
                'enabled' => true,
            ],
            'media' => [
                /**
                 * Mounts the assets directory with (static) files for public use.
                 */
                'enabled' => true,
            ],
            'views' => [
                /**
                 * Enables reading views from tenant directories.
                 */
                'enabled' => true,
    
                /**
                 * Specify a namespace to use with which to load the views.
                 *
                 * @eg setting `tenant` will allow you to use `tenant::some.blade.php`
                 * @info set to null to add to the global namespace.
                 */
                'namespace' => null,
    
                /**
                 * If `namespace` is set to null (thus using the global namespace)
                 * make it override the global views. Disable by setting to false.
                 */
                'override-global' => true,
            ]
        ]
    ];
    
    
    

    webserver.php config

    
    <?php
    
    /*
     * This file is part of the hyn/multi-tenant package.
     *
     * (c) Daniël Klabbers <[email protected]>
     *
     * For the full copyright and license information, please view the LICENSE
     * file that was distributed with this source code.
     *
     * @see https://tenancy.dev
     * @see https://github.com/hyn/multi-tenant
     */
    
    return [
    
        /**
         * Apache2 is one of the most widely adopted webserver packages available.
         *
         * @see http://httpd.apache.org/docs/
         * @see https://www.digitalocean.com/community/tutorials/how-to-install-linux-apache-mysql-php-lamp-stack-on-ubuntu
         */
        'apache2' => [
            /**
             * Whether the integration with Apache2 is currently active.
             */
            'enabled' => false,
    
            /**
             * Define the ports of your Apache service.
             */
            'ports' => [
                /**
                 * HTTP, non-SSL port.
                 *
                 * @default 80
                 */
                'http' => 80,
                /**
                 * HTTPS, SSL port.
                 *
                 * @default 443
                 */
                'https' => 443
            ],
    
            /**
             * The generator taking care of hooking into the Apache services and files.
             */
            'generator' => \Hyn\Tenancy\Generators\Webserver\Vhost\ApacheGenerator::class,
    
            /**
             * The view that holds the vhost configuration template.
             */
            'view' => 'tenancy.generators::webserver.apache.vhost',
    
            /**
             * Specify the disk you configured in the filesystems.php file where to store
             * the tenant vhost configuration files.
             *
             * @info If not set, will revert to the default filesystem.
             */
            'disk' => null,
    
            'paths' => [
    
                /**
                 * Location where vhost configuration files can be found.
                 */
                'vhost-files' => [
                    '/etc/apache2/sites-enabled/'
                ],
    
                /**
                 * Actions to run to work with the Apache2 service.
                 */
                'actions' => [
                    /**
                     * Action that asserts Apache2 is installed.
                     */
                    'exists' => '/etc/init.d/apache2',
                    /**
                     * Action to run to test the apache configuration.
                     *
                     * @set to a boolean to force the response of the test command.
                     * @info true succeeds, false fails
                     */
                    'test-config' => 'apache2ctl -t',
                    /**
                     * Action to run to reload the apache service.
                     *
                     * @info set to null to disable reloading.
                     */
                    'reload' => 'apache2ctl graceful'
                ]
            ]
        ],
    
        /**
         * Nginx webserver support.
         *
         * @see http://nginx.org
         */
        'nginx' => [
            /**
             * Whether the integration with nginx is currently active.
             */
            'enabled' => false,
    
            /**
             * The php sock to be used.
             */
            'php-sock' => 'unix:/var/run/php/php7.3-fpm.sock',
    
            /**
             * Define the ports of your nginx service.
             */
            'ports' => [
                /**
                 * HTTP, non-SSL port.
                 *
                 * @default 80
                 */
                'http' => 80,
                /**
                 * HTTPS, SSL port.
                 *
                 * @default 443
                 */
                'https' => 443
            ],
    
            /**
             * The generator taking care of hooking into the nginx services and files.
             */
            'generator' => \Hyn\Tenancy\Generators\Webserver\Vhost\NginxGenerator::class,
    
            /**
             * The view that holds the vhost configuration template.
             */
            'view' => 'tenancy.generators::webserver.nginx.vhost',
    
            /**
             * Specify the disk you configured in the filesystems.php file where to store
             * the tenant vhost configuration files.
             *
             * @info If not set, will revert to the default filesystem.
             */
            'disk' => null,
    
            'paths' => [
    
                /**
                 * Location where vhost configuration files can be found.
                 */
                'vhost-files' => [
                    '/etc/nginx/sites-enabled/'
                ],
    
                /**
                 * Actions to run to work with the Nginx service.
                 */
                'actions' => [
                    /**
                     * Action that asserts nginx is installed.
                     */
                    'exists' => '/etc/init.d/nginx',
                    /**
                     * Action to run to test the nginx configuration.
                     *
                     * @info set to a boolean to force the response of the test command.
                     *  true succeeds, false fails
                     */
                    'test-config' => '/etc/init.d/nginx configtest',
                    /**
                     * Action to run to reload the nginx service.
                     *
                     * @info set to null to disable reloading.
                     */
                    'reload' => '/etc/init.d/nginx reload'
                ]
            ]
        ]
    ];
    
    
    

    Thank you

    opened by pfbernardo 2
Releases(5.8.0)
Owner
Tenancy
The SaaS multi-tenant toolkit for @laravel.
Tenancy
Run multiple websites using the same Laravel installation while keeping tenant specific data separated for fully independent multi-domain setups.

Tenancy for Laravel Enabling awesome Software as a Service with the Laravel framework. This is the successor of hyn/multi-tenant. Feel free to show su

Tenancy 1.1k Dec 30, 2022
A Laravel package that allows you to use multiple ".env" files in a precedent manner. Use ".env" files per domain (multi-tentant)!

Laravel Multi ENVs Use multiple .envs files and have a chain of precedence for the environment variables in these different .envs files. Use the .env

Allyson Silva 48 Dec 29, 2022
Tiny hands is a Laravel multi-tenant boilerplate with SPA and i18n.

About Tiny Hands Tiny hands is a Laravel multi-tenant boilerplate with SPA and i18n using the following technology stack: Backend Laravel 8.0 API with

Bertrand Kintanar 12 Jun 23, 2022
Easily integrate single-database multi tenant features into your Laravel application

Laravel Tenant Aware Easily integrate single-database multi tenant features into your Laravel application. Installation You can install the package vi

H-FARM Innovation 9 Dec 21, 2022
Taskpm - Run multi tasks by PHP multi process

php-pkg-template Run multi tasks by PHP multi process Install composer composer require phppkg/taskpm Usage github: use the template for quick create

PHPPkg 2 Dec 20, 2021
A Laravel extension for using a laravel application on a multi domain setting

Laravel Multi Domain An extension for using Laravel in a multi domain setting Description This package allows a single Laravel installation to work wi

null 658 Jan 6, 2023
Simple project to send bulk comma-separated emails using laravel and messenger module from quick admin panel generator.

About Laravel Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experie

Novath Thomas 1 Dec 1, 2021
Livewire component that provides you with a modal that supports multiple child modals while maintaining state.

About LivewireUI Modal LivewireUI Modal is a Livewire component that provides you with a modal that supports multiple child modals while maintaining s

Livewire UI 806 Jan 6, 2023
Livewire component that provides you with a modal that supports multiple child modals while maintaining state.

About Wire Elements Modal Wire Elements Modal is a Livewire component that provides you with a modal that supports multiple child modals while maintai

Wire Elements 806 Jan 6, 2023
An example of multi-domain/subdomain app in Laravel.

?? UPDATE A better example with online demo: https://github.com/laravel-101/multi-domain-laravel-app Multi-Domain Laravel App An example of multi-doma

DigitalWheat 204 Dec 27, 2022
This package enables you to create and run a fully functioning WebSocket server in your Laravel app.

This package enables you to create and run a fully functioning WebSocket server in your Laravel app. It can optionally receive messages broadcast over ZeroMQ.

Asked.io 181 Oct 6, 2022
Keeping Your Laravel Forms Awake.

Caffeine for Laravel Supporting This Package This is an MIT-licensed open source project with its ongoing development made possible by the support of

GeneaLabs, LLC 839 Dec 22, 2022
LaravelFly is a safe solution to speeds up new or old Laravel 5.5+ projects, with preloading and coroutine, while without data pollution or memory leak

Would you like php 7.4 Preloading? Would you like php coroutine? Today you can use them with Laravel because of Swoole. With LaravalFly, Laravel will

null 456 Dec 21, 2022
Chrome extension to generate Laravel integration tests while using your app.

Laravel TestTools Check out the introduction post about the chrome extension. Installation git clone [email protected]:mpociot/laravel-testtools.git # i

Marcel Pociot 473 Nov 1, 2022
Barcode generator in PHP that is easy to use, non-bloated and framework independent.

PHP Barcode Generator This is an easy to use, non-bloated, framework independent, barcode generator in PHP. It creates SVG, PNG, JPG and HTML images,

Picqer 1.4k Jan 6, 2023
Easy alignment of multiple selections and multi-line selections

Sublime Alignment A simple key-binding for aligning multi-line and multiple selections in Sublime Text 2. Please see http://wbond.net/sublime_packages

Will Bond 516 Dec 28, 2022
Hashtopolis is a multi-platform client-server tool for distributing hashcat tasks to multiple computers.

Hashtopolis is a multi-platform client-server tool for distributing hashcat tasks to multiple computers. The main goals for Hashtopolis's development are portability, robustness, multi-user support, and multiple groups management.

Hashtopolis 1.1k Jan 4, 2023
This Laravel package merges staudenmeir/eloquent-param-limit-fix and staudenmeir/laravel-adjacency-list to allow them being used in the same model.

This Laravel package merges staudenmeir/eloquent-param-limit-fix and staudenmeir/laravel-adjacency-list to allow them being used in the same model.

Jonas Staudenmeir 5 Jan 6, 2023
Renamify is a package for Laravel used to rename a file before uploaded to prevent replacing exists file which has the same name to this new uploaded file.

Renamify Laravel package for renaming file before uploaded on server. Renamify is a package for Laravel used to rename a file before uploaded to preve

MB'DUSENGE Callixte 2 Oct 11, 2022