PHP code example of saifulferoz / multi-tenancy-bundle

1. Go to this page and download the library: Download saifulferoz/multi-tenancy-bundle library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

saifulferoz / multi-tenancy-bundle example snippets


// config/bundles.php
return [
    Feroz\MultiTenancyBundle\FerozMultiTenancyBundle::class => ['all' => true],
];

use Feroz\MultiTenancyBundle\Doctrine\Attribute\TenantShared;
use Feroz\MultiTenancyBundle\Tenant\TenantInterface;

#[ORM\Entity]
#[TenantShared]                       // omain label

    #[ORM\Column]
    public bool $active = true;

    public function getId(): string|int { return $this->id; }
    public function getIdentifier(): string { return $this->identifier; }
    public function isActive(): bool { return $this->active; }
}

use Feroz\MultiTenancyBundle\Doctrine\Attribute\TenantAware;
use Feroz\MultiTenancyBundle\Doctrine\Attribute\TenantShared;
use Feroz\MultiTenancyBundle\Doctrine\TenantOwnedTrait;
use Feroz\MultiTenancyBundle\Security\TenantOwnedInterface;

#[ORM\Entity]
#[TenantAware]                                  // scoped to one tenant
class Invoice implements TenantOwnedInterface
{
    use TenantOwnedTrait;                       // column + accessor
}

#[ORM\Entity]
#[TenantShared]                                 // global reference data
class Currency {}

public function __construct(private TenantContextInterface $tenants) {}

public function index(): Response
{
    $tenant = $this->tenants->getTenant();   // throws if none resolved
}

$this->tenants->runFor($tenant, function () {
    // reads and writes are scoped to $tenant here
});

$em->getFilters()->enable('tenant')->setParameter('tenant_id', 1);
$em->find(Doc::class, 1);                     // tenant 1's row, cached

$em->getFilters()->enable('tenant')->setParameter('tenant_id', 2);
$em->find(Doc::class, 1);                     // still tenant 1's row

use Feroz\MultiTenancyBundle\Security\TenantMembershipInterface;
use Feroz\MultiTenancyBundle\Security\TenantMembershipProviderInterface;

final class MembershipProvider implements TenantMembershipProviderInterface
{
    public function findMembership(
        string $userIdentifier,
        TenantInterface $tenant,
    ): ?TenantMembershipInterface {
        // null means "not a member here", which reads as "not authenticated
        // here" — that is the intended meaning, not an error.
        return $this->repository->findOneBy([
            'user' => $userIdentifier,
            'tenant' => $tenant->getId(),
        ]);
    }
}

interface TenantMembershipInterface
{
    public function getUserIdentifier(): string;
    public function getTenantId(): string|int;

    /** @return list<string> tenant-local role names */
    public function getRoles(): array;

    /** @return list<string> effective permissions, already flattened */
    public function getPermissions(): array;

    public function isActive(): bool;
}

$this->denyAccessUnlessGranted('post.publish', $post);   // yes
$this->denyAccessUnlessGranted('ROLE_EDITOR');           // no

use Feroz\MultiTenancyBundle\Security\TenantOwnedInterface;

#[ORM\Entity]
#[TenantAware]
class Post implements TenantOwnedInterface
{
    #[ORM\Column(name: 'tenant_id')]
    public ?int $tenantId = null;

    public function getTenantId(): string|int|null
    {
        return $this->tenantId;
    }
}

use Feroz\MultiTenancyBundle\Security\Api\TenantIssuerAware;

class Tenant implements TenantInterface, TenantIssuerAware
{
    #[ORM\Column(nullable: true)]
    public ?string $issuer = null;   // https://kc.example.com/realms/acme

    public function getIssuer(): ?string { return $this->issuer; }
}

$bus->dispatch(new Envelope($message, [new TenantStamp('acme')]));

final class PathPrefixTenantResolver implements TenantResolverInterface
{
    public function __construct(private TenantRegistryInterface $registry) {}

    public function supports(ResolutionContext $context): bool
    {
        // No Request outside HTTP — console commands and message handlers
        // share this interface, so never assume one exists.
        return $context->isHttp();
    }

    public function resolve(ResolutionContext $context): ?TenantInterface
    {
        if (!preg_match('#^/t/([a-z0-9-]+)#', $context->request->getPathInfo(), $m)) {
            return null;   // "not applicable" — try the next resolver
        }

        return $this->registry->findByIdentifier($m[1]);
    }
}