Created
August 5, 2014 14:36
-
-
Save mneuhaus/aec764a422010cfdd20b to your computer and use it in GitHub Desktop.
Doctrine/Flow Tenancy Behavior
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
TYPO3: | |
Flow: | |
persistence: | |
doctrine: | |
filters: | |
'SoftDeleteableFilter': 'Famelo\Saas\Domain\Filter\TenancyFilter' | |
eventListeners: | |
- | |
events: ['onFlush'] | |
listener: 'Famelo\Saas\Domain\Listener\TenancyListen |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Famelo\Saas\Annotations; | |
/* * | |
* This script belongs to the TYPO3 Flow framework. * | |
* * | |
* It is free software; you can redistribute it and/or modify it under * | |
* the terms of the GNU Lesser General Public License, either version 3 * | |
* of the License, or (at your option) any later version. * | |
* * | |
* The TYPO3 project - inspiring people to share! * | |
* */ | |
/** | |
* @Annotation | |
* @Target("CLASS") | |
*/ | |
final class Tenancy { | |
/** | |
* @var string | |
*/ | |
public $tenantRole; | |
/** | |
* @var string | |
*/ | |
public $ownerRole; | |
/** | |
* @param array $values | |
*/ | |
public function __construct(array $values) { | |
if (isset($values['tenant'])) { | |
$this->tenantRole = $values['tenant']; | |
} | |
if (isset($values['owner'])) { | |
$this->ownerRole = $values['owner']; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Famelo\Saas\Domain\Filter; | |
use Doctrine\ORM\EntityManager; | |
use Doctrine\ORM\Mapping\ClassMetaData; | |
use Doctrine\ORM\Query\Filter\SQLFilter; | |
use Doctrine\ORM\Query\ParameterTypeInferer; | |
use Famelo\Saas\Annotations\Tenancy; | |
use Famelo\Saas\Domain\Model\SaasPartyInterface; | |
use TYPO3\Flow\Annotations as Flow; | |
use TYPO3\Flow\Persistence\PersistenceManagerInterface; | |
use TYPO3\Flow\Reflection\ReflectionService; | |
use TYPO3\Flow\Security\Context; | |
class TenancyFilter { | |
protected $listener; | |
protected $entityManager; | |
protected $disabled = array(); | |
/** | |
* @Flow\Inject | |
* @var Context | |
*/ | |
protected $securityContext; | |
/** | |
* @Flow\Inject | |
* @var ReflectionService | |
*/ | |
protected $reflectionService; | |
/** | |
* @Flow\Inject | |
* @var PersistenceManagerInterface | |
*/ | |
protected $persistenceManager; | |
/** | |
* @Flow\Inject(setting="Roles.OwnerRole", package="Famelo.Saas") | |
* @var string | |
*/ | |
protected $defaultOwnerRole; | |
/** | |
* @Flow\Inject(setting="Roles.TenantRole", package="Famelo.Saas") | |
* @var string | |
*/ | |
protected $defaultTenantRole; | |
/** | |
* The entity manager. | |
* @var EntityManager | |
*/ | |
private $em; | |
/** | |
* Parameters for the filter. | |
* @var array | |
*/ | |
private $parameters; | |
/** | |
* Constructs the SQLFilter object. | |
* | |
* @param EntityManager $em The EM | |
*/ | |
public function __construct(EntityManager $em) { | |
$this->em = $em; | |
} | |
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias) { | |
if (FLOW_SAPITYPE === 'CLI') { | |
return '1 = 1'; | |
} | |
$className = $targetEntity->getName(); | |
$annotation = $this->reflectionService->getClassAnnotation($className, 'Famelo\Saas\Annotations\Tenancy'); | |
if (!$annotation instanceof Tenancy) { | |
return '1 = 1'; | |
} | |
$ownerRole = $annotation->ownerRole !== NULL ? $annotation->ownerRole : $this->defaultOwnerRole; | |
if ($this->securityContext->hasRole($ownerRole)) { | |
return '1 = 1'; | |
} | |
$party = $this->securityContext->getParty(); | |
if ($party === NULL) { | |
return '1 = 0'; | |
} | |
$identifier = $this->persistenceManager->getIdentifierByObject($party); | |
return 'tenant = "' . $identifier . '"'; | |
} | |
public function disableForEntity($class) { | |
$this->disabled[$class] = true; | |
} | |
public function enableForEntity($class) { | |
$this->disabled[$class] = false; | |
} | |
protected function getListener() { | |
if ($this->listener === null) { | |
$em = $this->getEntityManager(); | |
$evm = $em->getEventManager(); | |
foreach ($evm->getListeners() as $listeners) { | |
foreach ($listeners as $listener) { | |
if ($listener instanceof TenancyFilter) { | |
$this->listener = $listener; | |
break 2; | |
} | |
} | |
} | |
if ($this->listener === null) { | |
throw new \RuntimeException('Listener "SoftDeleteableListener" was not added to the EventManager!'); | |
} | |
} | |
return $this->listener; | |
} | |
protected function getEntityManager() { | |
if ($this->entityManager === null) { | |
$refl = new \ReflectionProperty('Doctrine\ORM\Query\Filter\SQLFilter', 'em'); | |
$refl->setAccessible(true); | |
$this->entityManager = $refl->getValue($this); | |
} | |
return $this->entityManager; | |
} | |
/** | |
* Sets a parameter that can be used by the filter. | |
* | |
* @param string $name Name of the parameter. | |
* @param string $value Value of the parameter. | |
* @param string $type The parameter type. If specified, the given value will be run through | |
* the type conversion of this type. This is usually not needed for | |
* strings and numeric types. | |
* | |
* @return SQLFilter The current SQL filter. | |
*/ | |
public function setParameter($name, $value, $type = null) { | |
if (null === $type) { | |
$type = ParameterTypeInferer::inferType($value); | |
} | |
$this->parameters[$name] = array('value' => $value, 'type' => $type); | |
// Keep the parameters sorted for the hash | |
ksort($this->parameters); | |
// The filter collection of the EM is now dirty | |
$this->em->getFilters()->setFiltersStateDirty(); | |
return $this; | |
} | |
/** | |
* Gets a parameter to use in a query. | |
* | |
* The function is responsible for the right output escaping to use the | |
* value in a query. | |
* | |
* @param string $name Name of the parameter. | |
* | |
* @return string The SQL escaped parameter to use in a query. | |
*/ | |
public function getParameter($name) { | |
if (!isset($this->parameters[$name])) { | |
throw new \InvalidArgumentException("Parameter '" . $name . "' does not exist."); | |
} | |
return $this->em->getConnection()->quote($this->parameters[$name]['value'], $this->parameters[$name]['type']); | |
} | |
/** | |
* Returns as string representation of the SQLFilter parameters (the state). | |
* | |
* @return string String representation of the SQLFilter. | |
*/ | |
public function __toString() { | |
return serialize($this->parameters); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Famelo\Saas\Domain\Listener; | |
use Doctrine\Common\EventArgs; | |
use Doctrine\Common\EventSubscriber; | |
use Doctrine\Common\Persistence\Mapping\ClassMetadata; | |
use Doctrine\Common\Persistence\ObjectManager; | |
use Famelo\Saas\Annotations\Tenancy; | |
use TYPO3\Flow\Annotations as Flow; | |
use TYPO3\Flow\Reflection\ReflectionService; | |
use TYPO3\Flow\Security\Context; | |
class TenancyListener implements EventSubscriber { | |
/** | |
* @Flow\Inject | |
* @var Context | |
*/ | |
protected $securityContext; | |
/** | |
* @Flow\Inject | |
* @var ReflectionService | |
*/ | |
protected $reflectionService; | |
/** | |
* @Flow\Inject(setting="Roles.OwnerRole", package="Famelo.Saas") | |
* @var string | |
*/ | |
protected $defaultOwnerRole; | |
/** | |
* @Flow\Inject(setting="Roles.TenantRole", package="Famelo.Saas") | |
* @var string | |
*/ | |
protected $defaultTenantRole; | |
/** | |
* {@inheritdoc} | |
*/ | |
public function getSubscribedEvents() { | |
return array( | |
'onFlush' | |
); | |
} | |
/** | |
* If it's a SoftDeleteable object, update the "deletedAt" field | |
* and skip the removal of the object | |
* | |
* @param EventArgs $args | |
* @return void | |
*/ | |
public function onFlush(EventArgs $args) { | |
if (FLOW_SAPITYPE === 'CLI') { | |
return '1 = 1'; | |
} | |
$entityManager = $args->getEntityManager(); | |
$unitOfWork = $entityManager->getUnitOfWork(); | |
$party = $this->securityContext->getParty(); | |
foreach ($unitOfWork->getScheduledEntityInsertions() as $entity) { | |
$className = get_class($entity); | |
$classMetadata = $entityManager->getClassMetadata($className); | |
$annotation = $this->reflectionService->getClassAnnotation($className, 'Famelo\Saas\Annotations\Tenancy'); | |
if ($annotation instanceof Tenancy) { | |
$tenantRole = $annotation->tenantRole !== NULL ? $annotation->tenantRole : $this->defaultTenantRole; | |
if (!$this->securityContext->hasRole($tenantRole)) { | |
continue; | |
} | |
$entity->setTenant($party); | |
$unitOfWork->propertyChanged($entity, 'tenant', NULL, $party); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Famelo\Saas\Domain\Model; | |
use Doctrine\ORM\Mapping as ORM; | |
use TYPO3\Flow\Annotations as Flow; | |
trait TenancyTrait { | |
/** | |
* @var \Famelo\Saas\Domain\Model\SaasPartyInterface | |
* @ORM\ManyToOne | |
*/ | |
protected $tenant; | |
/** | |
* @param \Famelo\Saas\Domain\Model\SaasPartyInterface $tenant | |
*/ | |
public function setTenant($tenant) { | |
$this->tenant = $tenant; | |
} | |
/** | |
* @return \Famelo\Saas\Domain\Model\SaasPartyInterface | |
*/ | |
public function getTenant() { | |
return $this->tenant; | |
} | |
} | |
?> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Famelo\Saas\Domain\Model; | |
use Doctrine\ORM\Mapping as ORM; | |
use Famelo\Common\Annotations as Common; | |
use TYPO3\Flow\Annotations as Flow; | |
use Famelo\Saas\Annotations as Saas; | |
/** | |
* A transaction | |
* | |
* @Flow\Entity | |
* @Saas\Tenancy | |
*/ | |
class Transaction { | |
use TenancyTrait; | |
/** | |
* @var float | |
*/ | |
protected $amount; | |
/** | |
* Gets amount. | |
* | |
* @return float $amount | |
*/ | |
public function getAmount() { | |
return $this->amount; | |
} | |
/** | |
* Sets the amount. | |
* | |
* @param float $amount | |
*/ | |
public function setAmount($amount) { | |
$this->amount = $amount; | |
} | |
} | |
?> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment