Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<?php
declare(strict_types=1);

namespace Neos\Flow\Persistence\Doctrine\Mapping\Driver;

/*
* This file is part of the Neos.Flow package.
*
* (c) Contributors of the Neos Project - www.neos.io
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/

use Doctrine\Common\Annotations\Reader;
use Neos\Flow\ObjectManagement\Proxy\Compiler;
use Neos\Flow\Reflection\ReflectionService;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;

class FlowAnotationReader implements Reader
{
private ReflectionService $reflectionService;

public function __construct(ReflectionService $reflectionService)
{
$this->reflectionService = $reflectionService;
}

/**
* Gets the annotations applied to a class.
*
* @param ReflectionClass $class The ReflectionClass of the class from which the class annotations should be read.
* @return array<object> An array of Annotations.
*/
public function getClassAnnotations(ReflectionClass $class)
{
$className = $this->getUnproxiedClassName($class->getName());
$indexedAnnotations = [];
foreach ($this->reflectionService->getClassAnnotations($className) as $annotation) {
$indexedAnnotations[get_class($annotation)] = $annotation;
}
return $indexedAnnotations;
}

/**
* Gets a class annotation.
*
* @param ReflectionClass $class The ReflectionClass of the class from which the class annotations should be read.
* @param class-string<T> $annotationName The name of the annotation.
* @return T|null The Annotation or NULL, if the requested annotation does not exist.
* @template T
*/
public function getClassAnnotation(ReflectionClass $class, $annotationName)
{
$className = $this->getUnproxiedClassName($class->getName());
return $this->reflectionService->getClassAnnotation($className, $annotationName);
}

/**
* Gets the annotations applied to a method.
*
* @param ReflectionMethod $method The ReflectionMethod of the method from which the annotations should be read.
* @return array<object> An array of Annotations.
*/
public function getMethodAnnotations(ReflectionMethod $method)
{
$className = $this->getUnproxiedClassName($method->class);
$indexedAnnotations = [];
foreach ($this->reflectionService->getMethodAnnotations($className, $method->getName()) as $annotation) {
$indexedAnnotations[get_class($annotation)] = $annotation;
}
return $indexedAnnotations;
}

/**
* Gets a method annotation.
*
* @param ReflectionMethod $method The ReflectionMethod to read the annotations from.
* @param class-string<T> $annotationName The name of the annotation.
* @return T|null The Annotation or NULL, if the requested annotation does not exist.
* @template T
*/
public function getMethodAnnotation(ReflectionMethod $method, $annotationName)
{
$className = $this->getUnproxiedClassName($method->class);
return $this->reflectionService->getMethodAnnotation($className, $method->getName(), $annotationName);
}

/**
* Gets the annotations applied to a property.
*
* @param ReflectionProperty $property The ReflectionProperty of the property from which the annotations should be read.
* @return array<object> An array of Annotations.
*/
public function getPropertyAnnotations(ReflectionProperty $property)
{
$className = $this->getUnproxiedClassName($property->class);
$indexedAnnotations = [];
foreach ($this->reflectionService->getPropertyAnnotations($className, $property->getName()) as $annotation) {
$indexedAnnotations[get_class($annotation)] = $annotation;
}
return $indexedAnnotations;
}

/**
* Gets a property annotation.
*
* @param ReflectionProperty $property The ReflectionProperty to read the annotations from.
* @param class-string<T> $annotationName The name of the annotation.
* @return T|null The Annotation or NULL, if the requested annotation does not exist.
* @template T
*/
public function getPropertyAnnotation(ReflectionProperty $property, $annotationName)
{
$className = $this->getUnproxiedClassName($property->class);
return $this->reflectionService->getPropertyAnnotation($className, $property->getName(), $annotationName);
}

/**
* Returns the classname after stripping a potentially present Compiler::ORIGINAL_CLASSNAME_SUFFIX.
*
* @param string $className
* @return string
*/
protected function getUnproxiedClassName($className)
{
return preg_replace('/' . Compiler::ORIGINAL_CLASSNAME_SUFFIX . '$/', '', $className);
}
}
15 changes: 10 additions & 5 deletions Neos.Flow/Classes/Reflection/ReflectionService.php
Original file line number Diff line number Diff line change
Expand Up @@ -1568,16 +1568,21 @@ protected function evaluateClassPropertyAnnotationsForSchema(ClassSchema $classS
return false;
}

if (!$this->isPropertyTaggedWith($className, $propertyName, 'var')) {
return false;
}

$varTagValues = $this->getPropertyTagValues($className, $propertyName, 'var');
if (count($varTagValues) > 1) {
throw new InvalidPropertyTypeException('More than one @var annotation given for "' . $className . '::$' . $propertyName . '"', 1367334366);
}
// We need var tags for typed arrays or collections as those cannot be
// expressed natively so they have to take precedence
if (count($varTagValues) === 0) {
$declaredType = $this->getPropertyType($className, $propertyName);
} else {
$declaredType = strtok(trim(current($varTagValues), " \n\t"), " \n\t");
}

$declaredType = strtok(trim(current($varTagValues), " \n\t"), " \n\t");
if (!$declaredType) {
return false;
}

if ($this->isPropertyAnnotatedWith($className, $propertyName, ORM\Id::class)) {
$skipArtificialIdentity = true;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<?php
declare(strict_types=1);

namespace Neos\Flow\Tests\Functional\Persistence\Aspect;

/*
Expand All @@ -21,7 +23,7 @@
class PersistenceMagicAspectTest extends FunctionalTestCase
{
/**
* @var boolean
* @var bool
*/
protected static $testablePersistenceEnabled = true;

Expand All @@ -32,23 +34,23 @@ protected function setUp(): void
{
parent::setUp();
if (!$this->persistenceManager instanceof PersistenceManager) {
$this->markTestSkipped('Doctrine persistence is not enabled');
static::markTestSkipped('Doctrine persistence is not enabled');
}
}

/**
* @test
*/
public function aspectIntroducesUuidIdentifierToEntities()
public function aspectIntroducesUuidIdentifierToEntities(): void
{
$entity = new Fixtures\AnnotatedIdentitiesEntity();
$this->assertStringMatchesFormat('%x%x%x%x%x%x%x%x-%x%x%x%x-%x%x%x%x-%x%x%x%x-%x%x%x%x%x%x%x%x', $this->persistenceManager->getIdentifierByObject($entity));
static::assertStringMatchesFormat('%x%x%x%x%x%x%x%x-%x%x%x%x-%x%x%x%x-%x%x%x%x-%x%x%x%x%x%x%x%x', $this->persistenceManager->getIdentifierByObject($entity));
}

/**
* @test
*/
public function aspectDoesNotIntroduceUuidIdentifierToEntitiesWithCustomIdProperties()
public function aspectDoesNotIntroduceUuidIdentifierToEntitiesWithCustomIdProperties(): void
{
$entity = new Fixtures\AnnotatedIdEntity();
self::assertNull($this->persistenceManager->getIdentifierByObject($entity));
Expand All @@ -57,37 +59,38 @@ public function aspectDoesNotIntroduceUuidIdentifierToEntitiesWithCustomIdProper
/**
* @test
*/
public function aspectFlagsClonedEntities()
public function aspectFlagsClonedEntities(): void
{
$entity = new Fixtures\AnnotatedIdEntity();
$clonedEntity = clone $entity;
self::assertObjectNotHasAttribute('Flow_Persistence_clone', $entity);
$this->assertObjectHasAttribute('Flow_Persistence_clone', $clonedEntity);
self::assertObjectNotHasProperty('Flow_Persistence_clone', $entity);
static::assertObjectHasProperty('Flow_Persistence_clone', $clonedEntity);
/** @noinspection PhpUndefinedFieldInspection */
self::assertTrue($clonedEntity->Flow_Persistence_clone);
}

/**
* @test
*/
public function valueHashIsGeneratedForValueObjects()
public function valueHashIsGeneratedForValueObjects(): void
{
$valueObject = new Fixtures\TestValueObject('value');

$this->assertObjectHasAttribute('Persistence_Object_Identifier', $valueObject);
static::assertObjectHasProperty('Persistence_Object_Identifier', $valueObject);
self::assertNotEmpty($this->persistenceManager->getIdentifierByObject($valueObject));
}

/**
* @test
* @dataProvider sameValueObjectDataProvider
*/
public function valueObjectsWithTheSamePropertyValuesAreEqual($closure)
public function valueObjectsWithTheSamePropertyValuesAreEqual(\Closure $closure): void
{
[$valueObject1, $valueObject2] = $closure();
self::assertEquals($this->persistenceManager->getIdentifierByObject($valueObject1), $this->persistenceManager->getIdentifierByObject($valueObject2));
}

public function sameValueObjectDataProvider()
public function sameValueObjectDataProvider(): array
{
// These need to be provided as closures so that the construction happens inside the test and not outside of the test environment.
return [
Expand All @@ -101,13 +104,13 @@ public function sameValueObjectDataProvider()
* @test
* @dataProvider differentValueObjectDataProvider
*/
public function valueObjectWithDifferentPropertyValuesAreNotEqual($closure)
public function valueObjectWithDifferentPropertyValuesAreNotEqual(\Closure $closure): void
{
[$valueObject1, $valueObject2] = $closure();
self::assertNotEquals($this->persistenceManager->getIdentifierByObject($valueObject1), $this->persistenceManager->getIdentifierByObject($valueObject2));
}

public function differentValueObjectDataProvider()
public function differentValueObjectDataProvider(): array
{
// These need to be provided as closures so that the construction happens inside the test and not outside of the test environment.
return [
Expand All @@ -120,7 +123,7 @@ public function differentValueObjectDataProvider()
/**
* @test
*/
public function valueHashMustBeUniqueForEachClassIndependentOfPropertiesOrValues()
public function valueHashMustBeUniqueForEachClassIndependentOfPropertiesOrValues(): void
{
$valueObject1 = new Fixtures\TestValueObjectWithConstructorLogic('value1', 'value2');
$valueObject2 = new Fixtures\TestValueObjectWithConstructorLogicAndInversedPropertyOrder('value2', 'value1');
Expand All @@ -131,7 +134,7 @@ public function valueHashMustBeUniqueForEachClassIndependentOfPropertiesOrValues
/**
* @test
*/
public function transientPropertiesAreDisregardedForValueHashGeneration()
public function transientPropertiesAreDisregardedForValueHashGeneration(): void
{
$valueObject1 = new Fixtures\TestValueObjectWithTransientProperties('value1', 'thisDoesntRegardPersistenceWhatSoEver');
$valueObject2 = new Fixtures\TestValueObjectWithTransientProperties('value1', 'reallyThisPropertyIsTransient');
Expand All @@ -142,7 +145,7 @@ public function transientPropertiesAreDisregardedForValueHashGeneration()
/**
* @test
*/
public function dateTimeIsDifferentDependingOnTheTimeZone()
public function dateTimeIsDifferentDependingOnTheTimeZone(): void
{
$valueObject1 = new Fixtures\TestValueObjectWithDateTimeProperty(new \DateTime('01.01.2013 00:00', new \DateTimeZone('GMT')));
$valueObject2 = new Fixtures\TestValueObjectWithDateTimeProperty(new \DateTime('01.01.2013 00:00', new \DateTimeZone('CEST')));
Expand All @@ -155,7 +158,7 @@ public function dateTimeIsDifferentDependingOnTheTimeZone()
/**
* @test
*/
public function subValueObjectsAreIncludedInTheValueHash()
public function subValueObjectsAreIncludedInTheValueHash(): void
{
$subValueObject1 = new Fixtures\TestValueObject('value');
$subValueObject2 = new Fixtures\TestValueObject('value');
Expand Down
18 changes: 10 additions & 8 deletions Neos.Flow/Tests/Functional/Persistence/Doctrine/AggregateTest.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<?php
declare(strict_types=1);

namespace Neos\Flow\Tests\Functional\Persistence\Doctrine;

/*
Expand All @@ -21,7 +23,7 @@
class AggregateTest extends FunctionalTestCase
{
/**
* @var boolean
* @var bool
*/
protected static $testablePersistenceEnabled = true;

Expand All @@ -42,7 +44,7 @@ protected function setUp(): void
{
parent::setUp();
if (!$this->persistenceManager instanceof PersistenceManager) {
$this->markTestSkipped('Doctrine persistence is not enabled');
static::markTestSkipped('Doctrine persistence is not enabled');
}
$this->postRepository = $this->objectManager->get(Fixtures\PostRepository::class);
$this->commentRepository = $this->objectManager->get(Fixtures\CommentRepository::class);
Expand All @@ -51,7 +53,7 @@ protected function setUp(): void
/**
* @test
*/
public function entitiesWithinAggregateAreRemovedAutomaticallyWithItsRootEntity()
public function entitiesWithinAggregateAreRemovedAutomaticallyWithItsRootEntity(): void
{
$image = new Fixtures\Image();
$post = new Fixtures\Post();
Expand All @@ -74,7 +76,7 @@ public function entitiesWithinAggregateAreRemovedAutomaticallyWithItsRootEntity(
/**
* @test
*/
public function entitiesWithOwnRepositoryAreNotRemovedIfRelatedRootEntityIsRemoved()
public function entitiesWithOwnRepositoryAreNotRemovedIfRelatedRootEntityIsRemoved(): void
{
$comment = new Fixtures\Comment();
$this->commentRepository->add($comment);
Expand Down Expand Up @@ -102,7 +104,7 @@ public function entitiesWithOwnRepositoryAreNotRemovedIfRelatedRootEntityIsRemov
*
* @test
*/
public function valueObjectsAreNotCascadeRemovedWhenARelatedEntityIsDeleted()
public function valueObjectsAreNotCascadeRemovedWhenARelatedEntityIsDeleted(): void
{
$post1 = new Fixtures\Post();
$post1->setAuthor(new Fixtures\TestValueObject('Some Name'));
Expand All @@ -124,7 +126,7 @@ public function valueObjectsAreNotCascadeRemovedWhenARelatedEntityIsDeleted()
/**
* @test
*/
public function unidirectionalOneToManyRelationsAreMapped()
public function unidirectionalOneToManyRelationsAreMapped(): void
{
$tag1 = new Fixtures\Tag('Tag1');
$tag2 = new Fixtures\Tag('Tag2');
Expand All @@ -148,14 +150,14 @@ public function unidirectionalOneToManyRelationsAreMapped()
$this->persistenceManager->clearState();

$retrievedTag2 = $this->persistenceManager->getObjectByIdentifier($tag2identifier, Fixtures\Tag::class);
self::assertTrue($retrievedTag2 === null, 'Tag not deleted');
self::assertNull($retrievedTag2, 'Tag not deleted');

$post = $this->postRepository->find($postIdentifier);
$this->postRepository->remove($post);
$this->persistenceManager->persistAll();
$this->persistenceManager->clearState();

$retrievedTag1 = $this->persistenceManager->getObjectByIdentifier($tag1identifier, Fixtures\Tag::class);
self::assertTrue($retrievedTag1 === null, 'Tag not orphan removed');
self::assertNull($retrievedTag1, 'Tag not orphan removed');
}
}
Loading