Skip to content

Instantly share code, notes, and snippets.

@bwaidelich
Last active March 2, 2016 11:14
Show Gist options
  • Save bwaidelich/5056310 to your computer and use it in GitHub Desktop.
Save bwaidelich/5056310 to your computer and use it in GitHub Desktop.
A simple example showing how doctrine behaviours (in this chase (nested) tree and soft-delete) can be used within TYPO3 Flow.
<?php
namespace Your\Package\Domain\Repository;
use TYPO3\Flow\Annotations as Flow;
/**
* @Flow\Scope("singleton")
*/
abstract class AbstractTreeRepository extends \Gedmo\Tree\Entity\Repository\NestedTreeRepository implements \TYPO3\Flow\Persistence\RepositoryInterface {
/**
* @var \TYPO3\Flow\Persistence\PersistenceManagerInterface
*/
protected $persistenceManager;
/**
* @var \Doctrine\ORM\EntityManager
*/
protected $entityManager;
/**
* Warning: if you think you want to set this,
* look at RepositoryInterface::ENTITY_CLASSNAME first!
*
* @var string
*/
protected $objectType;
/**
* @var array
*/
protected $defaultOrderings = array();
/**
* Initializes a new Repository.
*
* @param \Doctrine\Common\Persistence\ObjectManager $entityManager The EntityManager to use.
* @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $classMetadata The class descriptor.
*/
public function __construct(\Doctrine\Common\Persistence\ObjectManager $entityManager, \Doctrine\Common\Persistence\Mapping\ClassMetadata $classMetadata = NULL) {
if ($classMetadata === NULL) {
if (static::ENTITY_CLASSNAME === NULL) {
$this->objectType = str_replace(array('\\Repository\\', 'Repository'), array('\\Model\\', ''), get_class($this));
} else {
$this->objectType = static::ENTITY_CLASSNAME;
}
$classMetadata = $entityManager->getClassMetadata($this->objectType);
}
parent::__construct($entityManager, $classMetadata);
$this->entityManager = $this->_em;
}
/**
* Injects the persistence manager
*
* @param \TYPO3\Flow\Persistence\PersistenceManagerInterface $persistenceManager
* @return void
*/
public function injectPersistenceManager(\TYPO3\Flow\Persistence\PersistenceManagerInterface $persistenceManager) {
$this->persistenceManager = $persistenceManager;
}
/**
* Returns the classname of the entities this repository is managing.
*
* @return string
* @api
*/
public function getEntityClassName() {
return $this->objectType;
}
/**
* Adds an object to this repository.
*
* @param object $object The object to add
* @return void
* @api
*/
public function add($object) {
$this->entityManager->persist($object);
}
/**
* Removes an object from this repository.
*
* @param object $object The object to remove
* @return void
* @api
*/
public function remove($object) {
$this->entityManager->remove($object);
}
/**
* Finds all entities in the repository.
*
* @return \TYPO3\Flow\Persistence\QueryResultInterface The query result
* @api
*/
public function findAll() {
return $this->createQuery()->execute();
}
/**
* Finds an object matching the given identifier.
*
* @param mixed $identifier The identifier of the object to find
* @return object The matching object if found, otherwise NULL
* @api
*/
public function findByIdentifier($identifier) {
return $this->entityManager->find($this->objectType, $identifier);
}
/**
* Returns a query for objects of this repository
*
* @return \TYPO3\Flow\Persistence\Doctrine\Query
* @api
*/
public function createQuery() {
$query = new \TYPO3\Flow\Persistence\Doctrine\Query($this->objectType);
if ($this->defaultOrderings) {
$query->setOrderings($this->defaultOrderings);
}
return $query;
}
/**
* Counts all objects of this repository
*
* @return integer
* @api
*/
public function countAll() {
return $this->createQuery()->count();
}
/**
* Removes all objects of this repository as if remove() was called for
* all of them.
*
* @return void
* @api
* @todo maybe use DQL here, would be much more performant
*/
public function removeAll() {
foreach ($this->findAll() AS $object) {
$this->remove($object);
}
}
/**
* Sets the property names to order results by. Expected like this:
* array(
* 'foo' => \TYPO3\Flow\Persistence\QueryInterface::ORDER_ASCENDING,
* 'bar' => \TYPO3\Flow\Persistence\QueryInterface::ORDER_DESCENDING
* )
*
* @param array $defaultOrderings The property names to order by by default
* @return void
* @api
*/
public function setDefaultOrderings(array $defaultOrderings) {
$this->defaultOrderings = $defaultOrderings;
}
/**
* Schedules a modified object for persistence.
*
* @param object $object The modified object
* @return void
* @throws \TYPO3\Flow\Persistence\Exception\IllegalObjectTypeException
* @api
*/
public function update($object) {
if (!($object instanceof $this->objectType)) {
throw new \TYPO3\Flow\Persistence\Exception\IllegalObjectTypeException('The modified object given to update() was not of the type (' . $this->objectType . ') this repository manages.', 1249479625);
}
$this->persistenceManager->update($object);
}
}
?>
<?php
namespace Your\Package\Domain\Model;
use TYPO3\Flow\Annotations as Flow;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
/**
* A Category (Nested tree structure)
*
* @Flow\Entity
* @Gedmo\Tree(type="nested")
*/
class Category {
/**
* @var string
*/
protected $title;
/**
* @Gedmo\TreeLeft
* @var integer
*/
protected $treeLeft;
/**
* @Gedmo\TreeLevel
* @var integer
*/
protected $treeLevel;
/**
* @Gedmo\TreeRight
* @var integer
*/
protected $treeRight;
/**
* @Gedmo\TreeRoot
* @ORM\Column(nullable=true)
* @var integer
*/
protected $treeRoot;
/**
* @Gedmo\TreeParent
* @ORM\ManyToOne(inversedBy="treechildren")
* @ORM\Column(nullable=true)
* @var \Your\Package\Domain\Model\Category
*/
protected $treeParent;
/**
* @ORM\OneToMany(mappedBy="treeparent")
* @ORM\OrderBy({"left" = "ASC"})
* @var \Doctrine\Common\Collections\ArrayCollection<\Your\Package\Domain\Model\Category>
*/
protected $treeChildren;
/**
* @param string $title
*/
public function setTitle($title) {
$this->title = $title;
}
/**
* @return string
*/
public function getTitle() {
return $this->title;
}
/**
* @param \Your\Package\Domain\Model\Category $treeParent
*/
public function setTreeParent(\Your\Package\Category $treeParent) {
$this->treeParent = $treeParent;
}
/**
* @return \Your\Package\Domain\Model\Category
*/
public function getTreeParent() {
return $this->treeParent;
}
}
?>
<?php
namespace Your\Package\Controller;
use TYPO3\Flow\Annotations as Flow;
use Your\Package\Domain\Model\Category;
/**
* Category controller
*/
class CategoryController extends \TYPO3\Flow\Mvc\Controller\ActionController {
/**
* @var \Your\Package\Domain\Repository\CategoryRepository
* @Flow\Inject
*/
protected $categoryRepository;
/**
* @return void
*/
public function indexAction() {
$this->view->assign('categories', $this->categoryRepository->childrenHierarchy());
}
/**
* @return void
*/
public function addAction() {
$food = new Category();
$food->setTitle('Food');
$this->categoryRepository->add($food);
$fruits = new Category();
$fruits->setTitle('Fruits');
$fruits->setTreeParent($food);
$this->categoryRepository->add($fruits);
$vegetables = new Category();
$vegetables->setTitle('Vegetables');
$vegetables->setTreeParent($food);
$this->categoryRepository->add($vegetables);
$carrots = new Category();
$carrots->setTitle('Carrots');
$carrots->setTreeParent($vegetables);
$this->categoryRepository->add($carrots);
$this->redirect('index');
}
}
?>
<?php
namespace Your\Package\Domain\Repository;
use TYPO3\Flow\Annotations as Flow;
/**
* @Flow\Scope("singleton")
*/
class CategoryRepository extends \Your\Package\Domain\Repository\AbstractTreeRepository {
}
?>
{
...
"require": {
"typo3/flow": "2.0.*",
"gedmo/doctrine-extensions": "dev-master"
},
...
}
<?php
namespace Your\Package\Aop;
use TYPO3\Flow\Annotations as Flow;
/**
* "Hooks into" creation of doctrine EntityManager in order to register Behaviours
* from the "gedmo/doctrine-extensions" package.
* Note: Currently only "soft-delete" and "tree" behaviours are supported
*
* @Flow\Aspect
*/
class EntityManagerFactoryAspect {
/**
* @var \TYPO3\Flow\Reflection\ReflectionService
* @Flow\Inject
*/
protected $reflectionService;
/**
* @Flow\Around("method(TYPO3\Flow\Persistence\Doctrine\EntityManagerFactory->create())")
* @param \TYPO3\Flow\Aop\JoinPointInterface $joinPoint The current join point
* @return \Doctrine\ORM\EntityManager
*/
public function registerDoctrineBehaviors(\TYPO3\Flow\Aop\JoinPointInterface $joinPoint) {
/** @var $entityManager \Doctrine\ORM\EntityManager */
$entityManager = $joinPoint->getAdviceChain()->proceed($joinPoint);
/** @var $eventManager \Doctrine\Common\EventManager */
$eventManager = $entityManager->getEventManager();
$classNamesAnnotatedAsDeletable = $this->reflectionService->getClassNamesByAnnotation('Gedmo\Mapping\Annotation\SoftDeleteable');
$classNamesAnnotatedAsTree = $this->reflectionService->getClassNamesByAnnotation('Gedmo\Mapping\Annotation\Tree');
if (!is_array($classNamesAnnotatedAsDeletable) && !is_array($classNamesAnnotatedAsTree)) {
return $entityManager;
}
foreach ($classNamesAnnotatedAsDeletable as $index => $className) {
if (!$this->reflectionService->isClassAnnotatedWith($className, 'TYPO3\Flow\Annotations\Entity')) {
unset($classNamesAnnotatedAsDeletable[$index]);
}
}
foreach ($classNamesAnnotatedAsTree as $index => $className) {
if (!$this->reflectionService->isClassAnnotatedWith($className, 'TYPO3\Flow\Annotations\Entity')) {
unset($classNamesAnnotatedAsTree[$index]);
}
}
if ($classNamesAnnotatedAsDeletable !== array()) {
$doctrineConfiguration = $entityManager->getConfiguration();
$doctrineConfiguration->addFilter('soft-deletable', 'Gedmo\SoftDeleteable\Filter\SoftDeleteableFilter');
$entityManager->getFilters()->enable('soft-deletable');
$softDeletableListener = new \Gedmo\SoftDeleteable\SoftDeleteableListener();
$eventManager->addEventSubscriber($softDeletableListener);
}
if ($classNamesAnnotatedAsTree !== array()) {
$treeListener = new \Gedmo\Tree\TreeListener();
$eventManager->addEventSubscriber($treeListener);
}
return $entityManager;
}
}
?>
<!DOCTYPE html>
<html>
<body>
<h1>Categories</h1>
<ul class="t3-content-navigation">
<f:render section="categoryTree" arguments="{categories: categories}" />
</ul>
<f:section name="categoryTree">
<f:for each="{categories}" as="category">
<li>
{category.title} ({category.treeLevel})
<f:if condition="{category.__children}">
<ul>
<f:render section="categoryTree" arguments="{categories: category.__children}" />
</ul>
</f:if>
</li>
</f:for>
</f:section>
<f:link.action action="add">Add new tree</f:link.action>
</body>
</html>
TYPO3:
Flow:
# disable reflection for non psr-0 compliant 3rd party packages
object:
excludeClasses:
'gedmo.doctrineextensions' : ['Gedmo\\.*']
<?php
namespace Your\Package\Domain\Model;
use TYPO3\Flow\Annotations as Flow;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
/**
* An example entity with soft-delete behaviour
*
* @Flow\Entity
* @Gedmo\SoftDeleteable(fieldName="deletedAt")
*/
class SoftDeletableEntity {
/**
* @var string
*/
protected $title;
/**
* DateTime when this entity was deleted (used for "Soft-Delete behaviour")
*
* @var \DateTime
* @ORM\Column(nullable=true)
*/
protected $deletedAt;
/**
* @return \DateTime
*/
public function getDeletedAt() {
return $this->deletedAt;
}
/**
* @param \DateTime $deletedAt
* @return void
*/
public function setDeletedAt(\DateTime $deletedAt) {
$this->deletedAt = $deletedAt;
}
/**
* @return string The Document's title
*/
public function getTitle() {
return $this->title;
}
/**
* Sets this Document's title
*
* @param string $title The Document's title
* @return void
*/
public function setTitle($title) {
$this->title = $title;
}
}
?>
@bwaidelich
Copy link
Author

@svparijs sorry, only saw your comment now.. If this is still a problem please flush the caches (if that gives the same exception use ./flow typo3.flow:cache:flush --force
Those annotations should be ignored with the provided Settings.yaml

@bwaidelich
Copy link
Author

FYI: With the latest version of Flow (and a change that is currently still under review) this will get very easy: https://gist.github.com/bwaidelich/9617211

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment