fds / multi-tenancy-bundle
这是一个多租户symfony扩展包,支持与任何symfony项目协同工作的多租户系统。
v1.0.x-dev
2024-08-22 10:10 UTC
Requires
- php: ^8.0|^8.1|^8.2|^8.3
- doctrine/annotations: ^1.0|^2.0
- doctrine/doctrine-bundle: ^2.7
- doctrine/doctrine-migrations-bundle: ^3.0
- symfony/config: ^6.0|^6.1|^6.2|^6.3|^7.0
- symfony/dependency-injection: ^6.0|^6.1|^6.2|^6.3|^7.0
- symfony/filesystem: ^6.0|^7.0
- symfony/http-kernel: ^6.0|^6.1|^6.2|^6.3|^7.0
- symfony/orm-pack: ^2.0
Requires (Dev)
- phpunit/phpunit: ^10.5
This package is auto-updated.
Last update: 2024-09-22 10:18:52 UTC
README
Packagist 扩展包
https://packagist.org.cn/packages/fds/multi-tenancy-bundle
Symfony 多租户扩展包提供了一种简单的方法,将多租户数据库无缝集成到您的Symfony应用程序中。通过单个实体管理器管理多个数据库,它简化了Doctrine的使用,并使得在运行时切换数据库成为可能。本包包含多种功能,包括根据事件简单地切换租户数据库。
请买我一杯咖啡🙂 ☕️: https://www.buymeacoffee.com/fouadsalkini
数字
功能
- 支持所有类型的数据库。
- 支持多个子域名(tenant1.example.com, tenant2.example.com,..)。
- 易于使用和处理
- 可扩展的包
- 不影响应用程序性能
- 能够通过派发单个事件在数据库之间切换
- 能够使用单个命令自动生成租户数据库
- 能够为每个租户数据库自动生成迁移
- 能够使用种子包将数据种子到特定的租户
- 能够使用messenger在后台运行进程
- 它使用默认的实体管理器连接。
要求
- PHP 8.1+
- Symfony 6+
- Doctrine 扩展包
- Doctrine 迁移扩展包
- Yaml
- Apache
- 虚拟主机
安装
composer require fds/multi-tenancy-bundle
使用
1. 环境要求
- 将
BASE_HOST
添加到您的 .env 文件中。例如:BASE_HOST=yourmaindomain.com
。
2. 添加 doctrine 连接包装器
- 打开您的
config/packages/doctrine.yaml
并添加wrapper_class
# config/packages/doctrine.yaml doctrine: dbal: wrapper_class: FDS\MultiTenancyBundle\DBAL\MultiDbConnectionWrapper
3. 租户实体
- 创建租户实体或使用您想要配置扩展包的任何实体:
- 在您的租户实体中使用
TenantConfigTrait
以实现完整的数据库属性要求。 -
// src/App/Entity/Tenant namespace App\Entity; use Doctrine\ORM\Mapping as ORM; use FDS\MultiTenancyBundle\Traits\TenantConfigTrait; class Tenant { use TenantConfigTrait; #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column] private ?int $id = null;
4. 更新 fds_multi_tenancy.yaml
- 将您的租户实体路径添加到
config/packages/fds_multi_tenancy.yaml
文件。# config/packages/fds_multi_tenancy.yaml fds_multi_tenancy: tenant_entity: App\Entity\Tenant # set your custom path for your Tenant entity created in step 2.
5. 租户实体仓库
- 您的
TenantRepository
应该实现TenantRepositoryInterface
接口。namespace App\Repository; use App\Entity\Tenant; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Persistence\ManagerRegistry; use FDS\MultiTenancyBundle\Model\TenantRepositoryInterface; /** * @extends ServiceEntityRepository<Tenant> */ class TenantRepository extends ServiceEntityRepository implements TenantRepositoryInterface{ // your custom functions here
- 在您的仓库中定义
findBySubdomain
和findByIdentifier
函数public function findBySubdomain($subdomain){ // subdomain is required here // you can add your specific filters here like "status","isActive",... return $this->findOneBy(["subdomain" => $subdomain]); } public function findByIdentifier($identifier){ // use your identifier (unique value) or whathever you want(email, username,id,...) return $this->findOneBy(["yourCustomIdentifier" => $identifier]); }
6. 创建第一个迁移
- 使用此命令创建数据库迁移
bin/console doctrine:migrations:diff // default migrations folder is '%kernel.project_dir%/migrations'
- 使用此命令更新数据库模式
bin/console doctrine:migrations:migrate
- 您可以通过编辑
doctrine_migrations.yaml
来更新您的迁移文件夹# config/packages/doctrine_migrations.yaml doctrine_migrations: migrations_paths: # namespace is arbitrary but should be different from App\Migrations # as migrations classes should NOT be autoloaded 'DoctrineMigrations\Main': '%kernel.project_dir%/migrations/main' enable_profiler: '%kernel.debug%'
7. 使用 evotodi/seed-bundle(可选)
- 遵循此文档以创建种子类:https://packagist.org.cn/packages/evotodi/seed-bundle
- 按照以下方式更新您的种子类,为您的租户数据库创建种子
namespace App\Seeds; /** * The load method is called when loading a seed */ public function load(InputInterface $input, OutputInterface $output): int { /** * Doctrine logging eats a lot of memory, this is a wrapper to disable logging */ $this->disableDoctrineLogging(); /** @var MultiDbConnectionWrapper $connection */ $connection = $this->em->getConnection(); $this->runSeeds(); // run your seeds for your main database; you should define the runSeeds() function before. $tenants = $this->getTenants(); // add a function to fetch tenants from your main database. if (!count($tenants)) { return 0; } // loading seeds for each tenant foreach ($tenants as $tenant) { try { $connection->changeDatabase($tenant->getDbName()); $this->runSeeds(); } catch (Exception $e) { // error handling here } } /** * Must return an exit code. * A value other than 0 or Command::SUCCESS is considered a failed seed load/unload. */ return 0; }
8. 创建租户实例和数据库
- 在您的租户实体中创建一条记录(email, name, subdomain, dbName)
$tenant = new Tenant(); $tenant->setEmail("email@example.com"); $tenant->setName("First tenant"); $tenant->setSubdomain("first"); $tenant->setDbName("tenant_1"); $em->persist($tenant); $em->flush();
- 使用此命令为特定租户创建数据库
php bin/console tenant:database:create
- 您将被提示输入租户标识符(username|id|email|..)
9. 将 RouterSubscriber 类添加到您的项目中(可选)
- 定义一个实现
EventSubscriberInterface
的类,以根据分配给特定租户的子域自动在数据库之间切换// src/EventSubscriber/RouterSubscriber.php namespace App\EventSubscriber; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\ControllerEvent; use FDS\MultiTenancyBundle\Service\TenantService; class RouterSubscriber implements EventSubscriberInterface { public function __construct( // inject the TenantService in your constructor private TenantService $tenantService ) { } public static function getSubscribedEvents() { return array( KernelEvents::CONTROLLER => array(array('onKernelController', 1)), ); } public function onKernelController(ControllerEvent $event) { $request = $event->getRequest(); // call the checkCurrentTenant function to detect the domain changes and switch to the tenant's specific database. $this->tenantService->checkCurrentTenant($request); } }
10. 手动在数据库之间切换(可选)
- 您可以通过调用此函数手动在数据库之间切换
// $em is the main entity manager $connection = $em->getConnection(); $connection->changeDatabase("your database name");
其他说明将很快添加。