TYPO3 xClass and Dependency Injection - dependency-injection

I want to override an Extbase Controller, e.g. news in TYPO3 v10.4. Therefore I register an XClass in my ext_locaconf.php
$GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][\GeorgRinger\News\Controller\NewsController::class] = [
'className' => \Clickstorm\CsEvents\Controller\EventController::class
];
Afterwards I want to inject my new Repository
/**
* eventDateRepository
*
* #var EventDateRepository
*/
protected $eventDateRepository = null;
/**
* Inject a eventDateRepository
*
* #param EventDateRepository $eventDateRepository
*/
public function injectEventDateRepository(EventDateRepository $eventDateRepository)
{
$this->eventDateRepository = $eventDateRepository;
}
But my eventDateRepository is null. Also when I use a constructor instead.

Even though I don't know the reason for such a behaviour, a sustainable workaround is to manually instantiate your dependencies in the initializeAction method.
In my case, this is what I wrote:
/**
* #var \TYPO3\CMS\Extbase\Service\ImageService
*/
protected $imageService;
public function initializeAction()
{
parent::initializeAction();
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
$this->imageService = $objectManager->get(\TYPO3\CMS\Extbase\Service\ImageService::class);
}

Related

Discord oAuth2 login after authorization was given

I have a symfony4 application and I'm using knpuniversity/oauth2-client-bundle to authenticate a user against discords oAuth endpoint.
The user clicks on the 'Login via Discord' button, he sees the authorization page from discord, accepts it and is now logged in on my page.
So far so good.
Here is what is not working:
The user is on another computer, so no session on my page. He logs in to discord web client. After that he visits my page but he is not logged in.
When he clicks again on 'Login via Discord' he sees the authorization page again instead of directly being logged in on my page.
Maybe I get something wrong here but usually when I use an oAuth login with google, facebook or anything else just like here on stackoverflow I never see the authorization page again. I click on 'login with XY' and, as long as I'm logged in to my corresponding account, I will immediately be logged in on the other page as well.
<?php
namespace App\Security;
use App\Entity\User;
use App\Repository\UserRepository;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use KnpU\OAuth2ClientBundle\Client\OAuth2Client;
use KnpU\OAuth2ClientBundle\Security\Authenticator\SocialAuthenticator;
use League\OAuth2\Client\Token\AccessToken;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
class DiscordAuthenticator extends SocialAuthenticator
{
/**
* #var \KnpU\OAuth2ClientBundle\Client\ClientRegistry
*/
private $clientRegistry;
/**
* #var \App\Repository\UserRepository
*/
private $repository;
/**
* #var \Symfony\Component\Routing\RouterInterface
*/
private $router;
/**
* DiscordAuthenticator constructor.
*
* #param \KnpU\OAuth2ClientBundle\Client\ClientRegistry $clientRegistry
* #param \App\Repository\UserRepository $repository
* #param \Symfony\Component\Routing\RouterInterface $router
*/
public function __construct(
ClientRegistry $clientRegistry,
UserRepository $repository,
RouterInterface $router
) {
$this->clientRegistry = $clientRegistry;
$this->repository = $repository;
$this->router = $router;
}
/**
* #inheritDoc
*/
public function start(Request $request, AuthenticationException $authException = null)
{
return new RedirectResponse('/login/', Response::HTTP_TEMPORARY_REDIRECT);
}
/**
* #inheritDoc
*/
public function supports(Request $request): bool
{
return $request->attributes->get('_route') === 'discord_set_token';
}
/**
* #inheritDoc
*/
public function getCredentials(Request $request)
{
return $this->fetchAccessToken($this->getDiscordClient());
}
/**
* #inheritDoc
*/
public function getUser($credentials, UserProviderInterface $userProvider)
{
try {
/** #var \Wohali\OAuth2\Client\Provider\DiscordResourceOwner $discordUser */
$discordUser = $this->getDiscordClient()
->fetchUserFromToken($credentials);
$email = $discordUser->getEmail();
$existingUser = $this->repository->findOneBy(
[
'externalId' => $discordUser->getId(),
'externalIdSource' => 'discord',
]
);
if ($existingUser) {
return $existingUser;
}
$user = $this->repository->findOneBy(['email' => $email]);
if (!$user) {
$user = new User(
$discordUser->getId(),
'discord',
$discordUser->getUsername(),
$discordUser->getEmail()
);
$user->setExternalId($discordUser->getId());
$user->setExternalIdSource('discord');
$user->setToken($credentials->getToken());
$user->setRefreshToken($credentials->getRefreshToken());
$user->setTokenExpiresFromTimestamp($credentials->getExpires());
}
$this->repository->persist($user);
return $user;
} catch (\Throwable $e) {
throw new AuthenticationException($e->getMessage(), $e->getCode(), $e);
}
}
/**
* #inheritDoc
*/
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response
{
$message = strtr($exception->getMessageKey(), $exception->getMessageData());
return new Response($message, Response::HTTP_FORBIDDEN);
}
/**
* #inheritDoc
*/
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey): Response
{
$targetUrl = $this->router->generate('home');
return new RedirectResponse($targetUrl);
}
/**
* #return \KnpU\OAuth2ClientBundle\Client\OAuth2Client
*/
private function getDiscordClient(): OAuth2Client
{
return $this->clientRegistry
->getClient('discord');
}
}
<?php
namespace App\Security;
use App\Entity\User;
use App\Repository\UserRepository;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
class UserProvider implements UserProviderInterface
{
/**
* #var \App\Repository\UserRepository
*/
private $repository;
/**
* UserProvider constructor.
*
* #param \App\Repository\UserRepository $repository
*/
public function __construct(UserRepository $repository)
{
$this->repository = $repository;
}
/**
* #inheritDoc
*/
public function loadUserByUsername($username): UserInterface
{
$user = $this->repository->findOneBy(['email' => $username]);
if (!$user) {
throw new UsernameNotFoundException(sprintf('No User with username %s found', $username));
}
return $user;
}
/**
* #inheritDoc
*/
public function refreshUser(UserInterface $user): UserInterface
{
if (!$user instanceof User) {
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user)));
}
// TODO find out how to use the refresh token to get a new one
return $user;
}
/**
* #inheritDoc
*/
public function supportsClass($class): bool
{
return $class === User::class;
}
}
security:
providers:
user_provider:
entity:
class: App:User
property: username
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
logout:
path: /logout
target: /login
guard:
authenticators:
- App\Security\DiscordAuthenticator
access_control:
# - { path: ^/admin, roles: ROLE_ADMIN }
<?php
namespace App\Repository;
use App\Entity\User;
use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\EntityManager;
/**
* Class UserRepository
*/
class UserRepository
{
/**
* #var \Doctrine\ORM\EntityManager
*/
private $entityManager;
/**
* #var \Doctrine\ORM\EntityRepository
*/
private $repository;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
$this->repository = $entityManager->getRepository(User::class);
}
/**
* Finds an entity by its primary key / identifier.
*
* #param int $id
*
* #return \App\Entity\User|null
*/
public function find(int $id): ?User
{
return $this->repository->find($id);
}
/**
* Finds all entities in the repository.
*
* #return \App\Entity\User[]
*/
public function findAll(): iterable
{
return $this->repository->findAll();
}
/**
* Finds entities by a set of criteria.
*
* #param array $criteria
* #param array|null $orderBy
* #param int|null $limit
* #param int|null $offset
*
* #return \App\Entity\User[]
*/
public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null): iterable
{
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
}
/**
* Finds a single entity by a set of criteria.
*
* #param array $criteria
* #param array|null $orderBy
*
* #return \App\Entity\User|null
*/
public function findOneBy(array $criteria, array $orderBy = null): ?User
{
return $this->repository->findOneBy($criteria, $orderBy);
}
/**
* Counts entities by a set of criteria.
*
* #param array $criteria
*
* #return int
*/
public function count(array $criteria): int
{
return $this->repository->count($criteria);
}
/**
* Select all elements from a selectable that match the expression and
* return a new collection containing these elements.
*
* #param \Doctrine\Common\Collections\Criteria $criteria
*
* #return \App\Entity\User[]
*/
public function matching(Criteria $criteria): iterable
{
return $this->repository->matching($criteria);
}
/**
* #param \App\Entity\User $user
*
* #throws \Doctrine\ORM\ORMException
* #throws \Doctrine\ORM\OptimisticLockException
*/
public function persist(User $user): void
{
$this->entityManager->merge($user);
$this->entityManager->flush();
}
}
<?php
namespace App\Controller;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* #Route(path="discord/")
*/
class DiscordOAuthController extends AbstractController
{
/**
* #var \KnpU\OAuth2ClientBundle\Client\ClientRegistry
*/
private $clientRegistry;
/**
* DiscordOAuthController constructor.
*
* #param \KnpU\OAuth2ClientBundle\Client\ClientRegistry $clientRegistry
*/
public function __construct(ClientRegistry $clientRegistry)
{
$this->clientRegistry = $clientRegistry;
}
/**
* #Route(name="discord_redirect_authorization", path="redirect_authorization")
*
* #return \Symfony\Component\HttpFoundation\Response
*/
public function redirectAuthorizationAction(): Response
{
return $this->clientRegistry->getClient('discord')
->redirect(['identify', 'email', 'guilds']);
}
/**
* #Route(name="discord_set_token", path="set_token")
*
* #return void
*/
public function setTokenAction(): void
{
// empty as authenticator will handle the request
}
}
At the time I was working on this I didn't had much time and only briefly looked into the documentation.
Now I have had more time and put some time into reading the whole documentation of Discords oAuth.
I then found the query param prompt which when set to none will only once request the authorisation and otherwise will directly redirect to the redirect_uri

How to access properties of node over 2 relations with NEO4J-PHP-OGM

I cant get my head around how to access properties over 2 relationships with the neo4j-php-ogm library.
Say for example I have a "user" node, which connects to MANY "resource" nodes, which in return are each connected to a fixed number of predefined "MetaResource" node. The "resource" nodes have properties and the "MetaResource" nodes have the Meta-Properties of each resource type. How can I know access the properties of the "MetaResource" nodes starting from a "user" node? In Neo4j one such route looks like this:
(user)-[r:HAS_RESOURCE]->(resource)-[m:METARESOURCE]->(metaResource)
My example user entity:
/**
* #OGM\Node(label="User")
*/
class User
{
/**
* #OGM\GraphId()
* #var int
*/
private $id;
/**
* #OGM\Relationship(type="HAS_RESOURCE", direction="OUTGOING", targetEntity="\AppBundle\Entity\Resources", collection=true)
* #var ArrayCollection|\AppBundle\Entity\Resources[]
*/
protected $resources;
/**
* #return \Doctrine\Common\Collections\ArrayCollection|\AppBundle\Entity\Resources[]
*/
public function getResources()
{
return $this->resources;
}
/**
* #return \Doctrine\Common\Collections\ArrayCollection|\AppBundle\Entity\Resources[]
*/
public function getResource($name)
{
foreach($this->resources as $resource){
if($resource->getResourceType() == $name){
return $resource;
break;
}
}
}
/**
* #param AppBundle\Entity\Resources $resources
*/
public function addResource(Resources $resources)
{
if (!$this->resources->contains($resources)) {
$this->resources->add($resources);
}
}
}
My Example Resource Entity:
/**
* #OGM\Node(label="Resources")
*/
class Resources
{
/**
* #OGM\GraphId()
* #var int
*/
protected $id;
/**
* #OGM\Property(type="int")
* #var int
*/
protected $resourcecount;
/**
* #OGM\Relationship(type="METARESOURCE", direction="OUTGOING", targetEntity="\AppBundle\Entity\MetaResource", collection=false)
* #var MetaResource
*/
protected $metaResource;
/**
* #param \AppBundle\Entity\MetaResource $metaResource
*/
public function __construct(MetaResource $metaResource)
{
$this->metaResource = $metaResource;
}
public function getId()
{
return $this->id;
}
public function getResourceCount()
{
return $this->resourcecount;
}
public function setResourceCount($resourcecount)
{
$this->resourcecount = $resourcecount;
}
/**
* #return \AppBundle\Entity\MetaResource
*/
public function getMetaResource()
{
return $this->metaResource;
}
}
And my Example MetaResource Entity:
/**
* #OGM\Node(label="MetaResource")
*/
class MetaResource
{
/**
* #OGM\GraphId()
* #var int
*/
protected $id;
/**
* #OGM\Property(type="string")
* #var string
*/
protected $resourceType;
/**
* #OGM\Property(type="string")
* #var string
*/
protected $name_DE;
/**
* #OGM\Property(type="string")
* #var string
*/
protected $icon;
/**
* #OGM\Property(type="string")
* #var string
*/
protected $iconColour;
/**
* #OGM\Property(type="string")
* #var string
*/
protected $colour;
public function getResourceType()
{
return $this->resourceType;
}
public function getName_DE()
{
return $this->name_DE;
}
public function getIcon()
{
return $this->icon;
}
public function getIconColour()
{
return $this->iconColour;
}
public function getColour()
{
return $this->colour;
}
}
And finally the code from my controller, which sets up the relations:
$metaResource=$em->getRepository(MetaResource::class)->findOneBy('resourceType', 'wood');
$rWood = new Resources($metaResource);
$rWood->setResourceCount(20);
$em->persist($rWood);
$em->flush();
$user->addResource($rWood);
If I now have a look at the Neo4j webconsole, all relationships and nodes are correctly inserted.
Now, if I get the resources of a user with $user->getResources() I successfully get all the resource objects, but the "$metaResource" property is always NULL instead of the anticipated Object of my MetaResource entity. For example if I do:
foreach($user->getResources() as $resource){
var_dump($resource->getMetaResource());
}
Then it outputs only NULLs.
On the other hand, if I directly get a resource object (for example with $resource = $em->getRepository(Resources::class)->findOneById(123) ) and then try to get the connected MetaResource with $resource->getMetaResource() it works. What am I missing?
Cheers
I have made some progress regarding this use case. I'm using now a proxy generator that can handle this use case (this was a big missing part into the lib actually but takes time to implement).
So please test with the latest release 1.0.0-beta7.

PHPUnit: How do I mock an EntityManager without mocking the Metadata?

Working with PHPUnit and Doctrine I frequently end up writing very large methods for mocking Doctrines ClassMetadata, although in my opinion it does not need to be mocked, because it can be seen as stable. Still I need to mock the EntityManager because I don't want Doctrine to connect to a database.
So here's my question: How do I get my ClassMetadata via the EntityManager mock without needing a database connection? For all eventual database calls the EntityManager still needs to be a mock, I just don't want to write all my metadata down again.
I'm using the DoctrineModule for Zend 2 so it would be useful to be able to use my configuration to get the Metadata object, but I assume it's also ok to read the required sections manually.
Example:
public function testGetUniqueFields()
{
$this->prepareGetUniqueFields(); // about 50 lines of mocking ClassMetadata
$entity = 'UniqueWithoutAssociation';
$unique = $this->handler->getUniqueFields($entity);
$expected = ["uniqueColumn"];
$this->assertEquals($expected, $unique,
'getUniqueFields does not return the unique fields');
}
And the code of the actual class:
public function getUniqueFields($class)
{
$unique = array();
$metadata = $this->getClassMetadata($class);
$fields = $metadata->getFieldNames();
foreach ($fields as $field) {
if($metadata->isUniqueField($field) && !$metadata->isIdentifier($field)) {
$unique[] = $field;
}
}
return $unique;
}
The test works like expected, but every time I test another method or another behavior of the method, I need to prepare the mocks again or combine past definitions. Plus, the 50 lines I need for this code are the least I have in this test. Most of the test class is all about the ClassMetadata mock. It is a time consuming and - if you see ClassMetadata as a stable component - unnecessary work.
After spending many hours starring at the Doctrine source code, I found a solution.
Once again, this solution is only if you are working with Doctrines ClassMetadata object so often that it becomes unclean to mock every method call. In every other case you should still create a mock of ClassMetadata.
Still, since the composers minimum stability setting was set to stable, such components can be seen as stable so there is no absolute need to create a mock object.
ClassMetadata depends on several other Doctrine classes, which are all injected through the ubiquitous EntityManager:
Doctrine\ORM\Configuration to get the entity path
Doctrine\Common\Annotations\AnnotationReader and Doctrine\ORM\Mapping\Driver\AnnotationDriver injected through the Configuration object
Doctrine\DBAL\Connection to get the database platform in order to know about the identifier strategy. This object should be mocked so no database calls are possible
Doctrine\DBAL\Platforms\AbstractPlatform as mentioned
Doctrine\Common\EventManager to trigger some events
For single test methods or simple method calls I created a method returning an EntityManager mock object that is capable of returning a valid ClassMetadata object:
/**
* #return EntityManager|\PHPUnit_Framework_MockObject_MockObject
*/
public function getEmMock()
{
$dir = __DIR__."/Asset/Entity/";
$config = Setup::createAnnotationMetadataConfiguration(array($dir), true);
$eventManager = new \Doctrine\Common\EventManager();
$platform = new PostgreSqlPlatform();
$metadataFactory = new ClassMetadataFactory();
$config->setMetadataDriverImpl(new AnnotationDriver(new AnnotationReader()));
$connectionMock = $this->getMockBuilder('Doctrine\DBAL\Connection')
->disableOriginalConstructor()
->getMock();
$connectionMock->expects($this->any())
->method('getDatabasePlatform')
->will($this->returnValue($platform));
/** #var EntityManager|\PHPUnit_Framework_MockObject_MockObject $emMock */
$emMock = $this->getMockBuilder('Doctrine\ORM\EntityManager')
->disableOriginalConstructor()
->getMock();
$metadataFactory->setEntityManager($emMock);
$emMock->expects($this->any())
->method('getConfiguration')
->will($this->returnValue($config));
$emMock->expects($this->any())
->method('getConnection')
->will($this->returnValue($connectionMock));
$emMock->expects($this->any())
->method('getEventManager')
->will($this->returnValue($eventManager));
$emMock->expects($this->any())
->method('getClassMetadata')
->will($this->returnCallback(function($class) use ($metadataFactory){
return $metadataFactory->getMetadataFor($class);
}));
return $emMock;
}
Here you could even manipulate all objects by calling their getters created for the EntityManager mock. But that wouldn't be exactly clean and the method remains inflexible in some cases. Still a simple solution and you could e.g. add some parameters and put the method in a trait to reuse it.
For further needs, I created an abstract class that offers a maximum of flexibility and allows you to mock everything else or create some components in a whole other way.
It needs two configurations: The entity path and the platform object. You can manipulate or replace any object by setting it in the setUp method and then get the required EntityManager mock with getEmMock().
A little bit larger, but here it is:
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\ORM\Configuration;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Mapping\ClassMetadataFactory;
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
use Doctrine\ORM\Tools\Setup;
/**
* Class AbstractTestWithMetadata
* #author Marius Teller
*/
abstract class AbstractTestWithMetadata extends \PHPUnit_Framework_TestCase
{
const EXCEPTION_NO_ENTITY_PATHS_SET = "At least one entity path must be set";
const EXCEPTION_NO_PLATFORM_SET = "An instance of Doctrine\\DBAL\\Platforms\\AbstractPlatform must be set";
/**
* #var array
*/
protected $entityPaths = [];
/**
* #var AbstractPlatform
*/
protected $platform;
/**
* #var EntityManager
*/
protected $emMock;
/**
* #var Connection
*/
protected $connectionMock;
/**
* #var Configuration
*/
protected $configuration;
/**
* #var EventManager
*/
protected $eventManager;
/**
* #var ClassMetadataFactory
*/
protected $classMetadataFactory;
/**
* #return array
* #throws \Exception
*/
public function getEntityPaths()
{
if($this->entityPaths === []) {
throw new \Exception(self::EXCEPTION_NO_ENTITY_PATHS_SET);
}
return $this->entityPaths;
}
/**
* #param array $entityPaths
*/
public function setEntityPaths(array $entityPaths)
{
$this->entityPaths = $entityPaths;
}
/**
* add an entity path
* #param string $path
*/
public function addEntityPath($path)
{
$this->entityPaths[] = $path;
}
/**
* #return AbstractPlatform
* #throws \Exception
*/
public function getPlatform()
{
if(!isset($this->platform)) {
throw new \Exception(self::EXCEPTION_NO_PLATFORM_SET);
}
return $this->platform;
}
/**
* #param AbstractPlatform $platform
*/
public function setPlatform(AbstractPlatform $platform)
{
$this->platform = $platform;
}
/**
* #return EntityManager
*/
public function getEmMock()
{
if(!isset($this->emMock)) {
/** #var EntityManager|\PHPUnit_Framework_MockObject_MockObject $emMock */
$emMock = $this->getMockBuilder('Doctrine\ORM\EntityManager')
->disableOriginalConstructor()
->getMock();
$config = $this->getConfiguration();
$connectionMock = $this->getConnectionMock();
$eventManager = $this->getEventManager();
$classMetadataFactory = $this->getClassMetadataFactory();
$classMetadataFactory->setEntityManager($emMock);
$emMock->expects($this->any())
->method('getConfiguration')
->will($this->returnValue($config));
$emMock->expects($this->any())
->method('getConnection')
->will($this->returnValue($connectionMock));
$emMock->expects($this->any())
->method('getEventManager')
->will($this->returnValue($eventManager));
$emMock->expects($this->any())
->method('getClassMetadata')
->will($this->returnCallback(function($class) use ($classMetadataFactory){
return $classMetadataFactory->getMetadataFor($class);
}));
$this->setEmMock($emMock);
}
return $this->emMock;
}
/**
* #param EntityManager $emMock
*/
public function setEmMock($emMock)
{
$this->emMock = $emMock;
}
/**
* #return Connection
*/
public function getConnectionMock()
{
if(!isset($this->connectionMock)) {
$platform = $this->getPlatform();
/** #var Connection|\PHPUnit_Framework_MockObject_MockObject $connectionMock */
$connectionMock = $this->getMockBuilder('Doctrine\DBAL\Connection')
->disableOriginalConstructor()
->getMock();
$connectionMock->expects($this->any())
->method('getDatabasePlatform')
->will($this->returnValue($platform));
$this->setConnectionMock($connectionMock);
}
return $this->connectionMock;
}
/**
* #param Connection $connectionMock
*/
public function setConnectionMock($connectionMock)
{
$this->connectionMock = $connectionMock;
}
/**
* #return Configuration
*/
public function getConfiguration()
{
if(!isset($this->configuration)) {
$config = Setup::createAnnotationMetadataConfiguration($this->getEntityPaths(), true);
$config->setMetadataDriverImpl(new AnnotationDriver(new AnnotationReader()));
$this->setConfiguration($config);
}
return $this->configuration;
}
/**
* #param Configuration $configuration
*/
public function setConfiguration(Configuration $configuration)
{
$this->configuration = $configuration;
}
/**
* #return EventManager
*/
public function getEventManager()
{
if(!isset($this->eventManager)) {
$this->setEventManager(new EventManager());
}
return $this->eventManager;
}
/**
* #param EventManager $eventManager
*/
public function setEventManager($eventManager)
{
$this->eventManager = $eventManager;
}
/**
* #return ClassMetadataFactory
*/
public function getClassMetadataFactory()
{
if(!isset($this->classMetadataFactory)) {
$this->setClassMetadataFactory(new ClassMetadataFactory());
}
return $this->classMetadataFactory;
}
/**
* #param ClassMetadataFactory $classMetadataFactory
*/
public function setClassMetadataFactory(ClassMetadataFactory $classMetadataFactory)
{
$this->classMetadataFactory = $classMetadataFactory;
}
}
One more hint: You might have problems with annotations of other classes, e.g. Zend\Form\Annotation\Validator. Such annotations will throw an exception in Doctrines parser because this parser does not use auto loading and only checks for already loaded classes. So if you still want to use them, you just have to include them manually before parsing the classes annotations.

Doctrine2 - what DQL to use?

The first entity called BUNDLE
/**
* #var \Doctrine\Common\Collections\Collection of \Akademie\Course\Course
* #ManyToMany(targetEntity="Akademie\Course\Course", mappedBy="bundles")
*/
private $courses;
The second entity is called COURSE
/**
* #var \Doctrine\Common\Collections\Collection of \Akademie\Bundle\Bundle
* #ManyToMany(targetEntity="Akademie\Bundle\Bundle", inversedBy="courses")
*/
private $bundles;
/**
* #var \Doctrine\Common\Collections\Collection of \Akademie\Course\CourseDate
* #OneToMany(targetEntity="Akademie\Course\CourseDate", mappedBy="course")
*/
private $courseDates;
/**
* #var int
* #Column(type="boolean")
*/
private $hidden;
and the third is called COURSEDATE
/**
* #var \Akademie\Course\Course
* #ManyToOne(targetEntity="Akademie\Course\Course", inversedBy="courseDates")
* #JoinColumn(nullable=false)
*/
private $course;
/**
* #var \DateTime
* #Column(type="datetimetz", nullable=true)
*/
private $beginDate;
I have parameter course and I need to get all bundles, which contains this course. What's more, all other courses in that bundle has to have courseDate newer than current date and can't be hidden. Otherwise I don't want to get this bundle. I hope it is clear now...
In a repository (bundle of entity BUNDLE for example), you have to create a custom method :
public function myMethod($course){
$qb = $this->createQueryBuilder("b")
->join("b.course", "c")
->join("c.courseDates", "cd")
->Where("c = :course")
->andWhere(" cd.beginDate > :now ")
->andWhere("c.hidden = 1")// if 1 means not hidden
->setParameter(":course", $course)
->setParameter(":now", new \DateTime('now'));
And then, in your controller, you have to do something like that :
$bundles = $this->getDoctrine()->getManager()->getRepository('namespace of your BUNDLE entity')->myMethod($course);

doctrine2 ArrayCollection error

I am trying to follow the advice from the doctrine docs on this page - initialising a class member with the ArrayCollection. All works well for the example given in the docs. I'm trying to do it with an inherited class but get an error saying:
Class Doctrine\Common\Collections\ArrayCollection is not a valid entity or mapped super class
Inherited class:
/**
* #Entity
* #InheritanceType("JOINED")
* #DiscriminatorColumn(name="discr", type="string")
* #DiscriminatorMap({"user" = "App_User", "group" = "App_Group"})
*/
abstract class App_Acl_Role_Abstract implements Zend_Acl_Role_Interface {
/**
* #ManyToOne(targetEntity="App_Acl_Role_Abstract", inversedBy="children", cascade={"persist"})
*/
private $parents;
/**
* #OneToMany(targetEntity="App_Acl_Role_Abstract", mappedBy="parents", cascade={"persist"})
*/
private $children;
public function __construct()
{
$this->parents = new Doctrine\Common\Collections\ArrayCollection();
$this->children = new Doctrine\Common\Collections\ArrayCollection();
}
}
Inheriting class:
/**
* #Entity
* #Table(name="App_User")
*/
class App_User extends App_Acl_Role_Abstract
{
...
}
When I move the constructor to the inheriting class all works fine. But it would be much neater to have them in the inherited abstract class. Why does it not work? Is it possible?
My bad. I stuffed up the mapping. This is the mapping I'm now using:
/**
* #ManyToMany(targetEntity="App_Acl_Role_Abstract", cascade={"persist"})
* #JoinTable(name="role_parents",
* joinColumns={#JoinColumn(name="role_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="parent_id", referencedColumnName="id", unique=true)}
* )
*/
private $parents;
/**
* #ManyToMany(targetEntity="App_Acl_Role_Abstract", cascade={"persist"})
* #JoinTable(name="role_children",
* joinColumns={#JoinColumn(name="role_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="child_id", referencedColumnName="id", unique=true)}
* )
*/
private $children;
A role should be able to have many parents and many children

Resources