<?php declare(strict_types=1); namespace phpDocumentor\Reflection\Php\Factory; use phpDocumentor\Reflection\DocBlock; use phpDocumentor\Reflection\DocBlockFactoryInterface; use phpDocumentor\Reflection\Fqsen; use phpDocumentor\Reflection\Php\Class_ as ClassElement; use phpDocumentor\Reflection\Php\Interface_ as InterfaceElement; use phpDocumentor\Reflection\Php\Project; use phpDocumentor\Reflection\Php\ProjectFactoryStrategy; use phpDocumentor\Reflection\Php\Property as PropertyElement; use phpDocumentor\Reflection\Php\StrategyContainer; use phpDocumentor\Reflection\Php\Visibility; use PhpParser\Comment\Doc; use PhpParser\Node\Expr\Variable; use PhpParser\Node\Identifier; use PhpParser\Node\Param; use PhpParser\Node\Scalar\String_; use PhpParser\Node\Stmt\Class_ as ClassNode; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\PrettyPrinter\Standard; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\DataProvider; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use stdClass; use function current; #[CoversClass(ConstructorPromotion::class)] final class ConstructorPromotionTest extends TestCase { use ProphecyTrait; private ObjectProphecy $strategy; private ObjectProphecy $docblockFactory; protected function setUp(): void { $this->strategy = $this->prophesize(ProjectFactoryStrategy::class); $this->docblockFactory = $this->prophesize(DocBlockFactoryInterface::class); $printer = $this->prophesize(Standard::class); $printer->prettyPrintExpr(Argument::any())->willReturn('myType'); $this->fixture = new ConstructorPromotion( $this->strategy->reveal(), $this->docblockFactory->reveal(), $printer->reveal(), ); } #[DataProvider('objectProvider')] public function testMatches(ContextStack $context, object $object, bool $expected): void { self::assertEquals($expected, $this->fixture->matches($context, $object)); } /** @return mixed[][] */ public static function objectProvider(): array { $context = new ContextStack(new Project('test')); return [ 'emptyContext' => [ $context, new stdClass(), false, ], 'invalid stack type' => [ $context->push(new InterfaceElement(new Fqsen('\MyInterface'))), new ClassMethod('foo'), false, ], 'with class but not constructor' => [ $context->push(new ClassElement(new Fqsen('\MyInterface'))), new ClassMethod('foo'), false, ], 'with class but and is constructor' => [ $context->push(new ClassElement(new Fqsen('\MyInterface'))), new ClassMethod('__construct'), true, ], ]; } #[DataProvider('visibilityProvider')] public function testCreateWithProperty(int $flags, string $visibility, bool $readOnly = false): void { $methodNode = new ClassMethod('__construct'); $methodNode->params = [ new Param( new Variable('myArgument'), new String_('MyDefault'), new Identifier('string'), false, false, [ 'comments' => [ new Doc('text'), ], ], $flags, ), ]; $docBlock = new DocBlock('Test'); $class = new ClassElement(new Fqsen('\MyClass')); $context = self::createContext()->push($class); $this->docblockFactory->create('text', null)->willReturn($docBlock); $this->strategy->create($context, $methodNode, Argument::type(StrategyContainer::class)) ->shouldBeCalled(); $this->fixture->create( $context, $methodNode, $this->prophesize(StrategyContainer::class)->reveal(), ); $property = current($class->getProperties()); self::assertInstanceOf(PropertyElement::class, $property); self::assertEquals($visibility, $property->getVisibility()); self::assertSame($docBlock, $property->getDocBlock()); self::assertSame('myType', $property->getDefault()); self::assertEquals('\MyClass::$myArgument', $property->getFqsen()); self::assertSame($readOnly, $property->isReadOnly()); } /** @return mixed[][] */ public static function visibilityProvider(): array { return [ [ ClassNode::MODIFIER_PUBLIC, Visibility::PUBLIC_, ], [ ClassNode::MODIFIER_PROTECTED, Visibility::PROTECTED_, ], [ ClassNode::MODIFIER_PRIVATE, Visibility::PRIVATE_, ], [ ClassNode::MODIFIER_PRIVATE | ClassNode::MODIFIER_READONLY, Visibility::PRIVATE_, true, ], ]; } }