Commit 64c8a784 authored by Alexander Schnitzler's avatar Alexander Schnitzler Committed by Benjamin Franzke
Browse files

[TASK] Deprecate @Extbase\Inject

Since core dependency injection is in place and is about to
replace the extbase dependency injection, marking properties
with the @Extbase\Inject annotation to invoke property injection is
deprecated and must be replaced by one of the following di
methods:

- constructor injection: works both with core and extbase di
  and is well suited to make extensions compatible for multiple
  TYPO3 versions.

- setter injection: Basically the same like constructor injection.
  Both the core and extbase di can handle setter injection and
  both are supported in different TYPO3 versions.

- (core) property injection: This kind of injection can be used
  but it requires the configuration of services via a Services.yaml
  in the Configuration folder of an extension.

The recommended way is constructor injection. Not only is it the
most compatible version of di, it also brings the advantage of
clearly showing dependencies of a class. Also, it quickly shows
if dependencies stack up which indicates that the service should
be refactored.

Releases: master
Resolves: #92386
Change-Id: I61afbb6bb15b136c200849c6c8f2cd6211d4c306
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/65835

Tested-by: default avatarTYPO3com <noreply@typo3.com>
Tested-by: Georg Ringer's avatarGeorg Ringer <georg.ringer@gmail.com>
Tested-by: Benjamin Franzke's avatarBenjamin Franzke <bfr@qbus.de>
Reviewed-by: Georg Ringer's avatarGeorg Ringer <georg.ringer@gmail.com>
Reviewed-by: Benjamin Franzke's avatarBenjamin Franzke <bfr@qbus.de>
parent 154aacdd
......@@ -267,6 +267,7 @@
"typo3/sysext/core/Tests/Unit/Core/Fixtures/test_extension/",
"typo3/sysext/core/Tests/Functional/Fixtures/",
"typo3/sysext/extbase/Tests/Unit/Object/Container/Fixtures/",
"typo3/sysext/extbase/Tests/UnitDeprecated/Object/Container/Fixtures/",
"typo3/sysext/extbase/Tests/Functional/Fixtures/",
"typo3/sysext/fluid/Tests/Functional/Fixtures/Extensions/fluid_test/Classes/"
],
......
.. include:: ../../Includes.txt
==============================================================
Deprecation: #92386 - Extbase property injection is deprecated
==============================================================
See :issue:`92386`
Description
===========
Since core dependency injection is in place and is about to replace the extbase dependency injection completely, using property injection via the :php:`@Extbase\Inject` is deprecated.
Impact
======
Classes that use extbase property injection will experience non injected services for properties that have a :php:`@Extbase\Inject` annotation.
Affected Installations
======================
All installations that use extbase property injection via annotation :php:`@Extbase\Inject`.
Migration
=========
Extbase property injection can be replaced by one of the following methods:
- constructor injection: works both with core and extbase dependency injection and is well suited to make extensions compatible for multiple TYPO3 versions.
- setter injection: Basically the same like constructor injection. Both the core and extbase di can handle setter injection and both are supported in different TYPO3 versions.
- (core) property injection: This kind of injection can be used but it requires the configuration of services via a `Services.yaml` in the Configuration folder of an extension.
Given the following example for a :php:`@Extbase\Inject` annotation based injection:
.. code-block:: php
/**
* @var MyService
* @Extbase\Inject
*/
protected $myService;
This service injection can be changed to constructor injection by adding the
service as constructor argument and removing the :php:`@Extbase\Inject` annoation:
.. code-block:: php
/**
@var MyService
*/
protected $MyService;
public function __construct(MyService $MyService) {
$this->myService = $myService;
}
Please consult the dependency-injection_ documentation for more information.
.. _dependency-injection https://docs.typo3.org/m/typo3/reference-coreapi/master/en-us/ApiOverview/DependencyInjection/Index.html
.. index:: PHP-API, FullyScanned, ext:extbase
......@@ -17,7 +17,6 @@ namespace OliverHader\IrreTutorial\Controller;
use OliverHader\IrreTutorial\Service\QueueService;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Annotation as Extbase;
use TYPO3\CMS\Extbase\Http\ForwardResponse;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use TYPO3\CMS\Extbase\Mvc\RequestInterface;
......@@ -31,11 +30,15 @@ use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
abstract class AbstractController extends ActionController
{
/**
* @Extbase\Inject
* @var \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapFactory
*/
public $dataMapFactory;
public function __construct(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapFactory $dataMapFactory)
{
$this->dataMapFactory = $dataMapFactory;
}
/**
* @param \TYPO3\CMS\Extbase\Mvc\RequestInterface $request
* @return \Psr\Http\Message\ResponseInterface
......
......@@ -25,10 +25,18 @@ use TYPO3\CMS\Extbase\Mvc\View\JsonView;
class ContentController extends AbstractController
{
/**
* @Extbase\Inject
* @var \OliverHader\IrreTutorial\Domain\Repository\ContentRepository
*/
public $contentRepository;
private $contentRepository;
public function __construct(
\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapFactory $dataMapFactory,
\OliverHader\IrreTutorial\Domain\Repository\ContentRepository $contentRepository
) {
parent::__construct($dataMapFactory);
$this->contentRepository = $contentRepository;
}
/**
* @var string
......
......@@ -15,7 +15,6 @@
namespace OliverHader\IrreTutorial\Controller;
use TYPO3\CMS\Extbase\Annotation as Extbase;
use TYPO3\CMS\Extbase\Http\ForwardResponse;
use TYPO3\CMS\Extbase\Mvc\Controller\ControllerInterface;
use TYPO3\CMS\Extbase\Mvc\Exception\InvalidControllerException;
......@@ -28,10 +27,18 @@ use TYPO3\CMS\Extbase\Mvc\View\JsonView;
class QueueController extends AbstractController
{
/**
* @Extbase\Inject
* @var \OliverHader\IrreTutorial\Domain\Repository\ContentRepository
*/
public $contentRepository;
private $contentRepository;
public function __construct(
\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapFactory $dataMapFactory,
\OliverHader\IrreTutorial\Domain\Repository\ContentRepository $contentRepository
) {
parent::__construct($dataMapFactory);
$this->contentRepository = $contentRepository;
}
/**
* @var string
......
services:
_defaults:
autowire: true
autoconfigure: true
public: false
OliverHader\IrreTutorial\:
resource: '../Classes/*'
exclude: '../Classes/Domain/Model'
......@@ -20,6 +20,7 @@ namespace TYPO3\CMS\Extbase\Annotation;
/**
* @Annotation
* @Target({"PROPERTY"})
* @deprecated since v11, will be removed in v12
*/
class Inject
{
......
......@@ -249,6 +249,10 @@ class ClassSchema
&& ($annotationReader->getPropertyAnnotation($reflectionProperty, Inject::class) instanceof Inject);
if ($isInjectProperty) {
trigger_error(
sprintf('Property "%s" of class "%s" uses Extbase\' property injection which is deprecated.', $propertyName, $reflectionClass->getName()),
E_USER_DEPRECATED
);
$propertyCharacteristicsBit += PropertyCharacteristics::ANNOTATED_INJECT;
$classHasInjectProperties = true;
}
......
......@@ -17,7 +17,6 @@ namespace ExtbaseTeam\BlogExample\Controller;
use ExtbaseTeam\BlogExample\Domain\Model\Blog;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Annotation as Extbase;
use TYPO3\CMS\Extbase\Annotation\IgnoreValidation;
use TYPO3\CMS\Extbase\Http\ForwardResponse;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
......@@ -32,10 +31,9 @@ use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
class BlogController extends ActionController
{
/**
* @Extbase\Inject
* @var \ExtbaseTeam\BlogExample\Domain\Repository\BlogRepository
*/
public $blogRepository;
private $blogRepository;
/**
* @var string
......@@ -43,10 +41,17 @@ class BlogController extends ActionController
protected $defaultViewObjectName = JsonView::class;
/**
* @Extbase\Inject
* @var \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapFactory
*/
public $dataMapFactory;
private $dataMapFactory;
public function __construct(
\ExtbaseTeam\BlogExample\Domain\Repository\BlogRepository $blogRepository,
\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapFactory $dataMapFactory
) {
$this->blogRepository = $blogRepository;
$this->dataMapFactory = $dataMapFactory;
}
public function listAction(): ResponseInterface
{
......
......@@ -18,7 +18,6 @@ declare(strict_types=1);
namespace ExtbaseTeam\BlogExample\Controller;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Annotation as Extbase;
use TYPO3\CMS\Extbase\Domain\Model\FileReference;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use TYPO3\CMS\Extbase\Mvc\RequestInterface;
......@@ -32,10 +31,9 @@ use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
class ContentController extends ActionController
{
/**
* @Extbase\Inject
* @var \ExtbaseTeam\BlogExample\Domain\Repository\TtContentRepository
*/
public $contentRepository;
private $contentRepository;
/**
* @var string
......@@ -43,10 +41,17 @@ class ContentController extends ActionController
protected $defaultViewObjectName = JsonView::class;
/**
* @Extbase\Inject
* @var \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapFactory
*/
public $dataMapFactory;
private $dataMapFactory;
public function __construct(
\ExtbaseTeam\BlogExample\Domain\Repository\TtContentRepository $contentRepository,
\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapFactory $dataMapFactory
) {
$this->contentRepository = $contentRepository;
$this->dataMapFactory = $dataMapFactory;
}
/**
* @return array
......
services:
_defaults:
autowire: true
autoconfigure: true
public: false
ExtbaseTeam\BlogExample\:
resource: '../Classes/*'
exclude: '../Classes/Domain/{Model,Validator}'
......@@ -25,12 +25,10 @@ use TYPO3\CMS\Extbase\Object\Exception\CannotBuildObjectException;
use TYPO3\CMS\Extbase\Reflection\Exception\UnknownClassException;
use TYPO3\CMS\Extbase\Reflection\ReflectionService;
use TYPO3\CMS\Extbase\Tests\Unit\Object\Container\Fixtures\ArgumentTestClass;
use TYPO3\CMS\Extbase\Tests\Unit\Object\Container\Fixtures\ArgumentTestClassForPublicPropertyInjection;
use TYPO3\CMS\Extbase\Tests\Unit\Object\Container\Fixtures\MandatoryConstructorArgument;
use TYPO3\CMS\Extbase\Tests\Unit\Object\Container\Fixtures\MandatoryConstructorArgumentTwo;
use TYPO3\CMS\Extbase\Tests\Unit\Object\Container\Fixtures\NamespacedClass;
use TYPO3\CMS\Extbase\Tests\Unit\Object\Container\Fixtures\OptionalConstructorArgument;
use TYPO3\CMS\Extbase\Tests\Unit\Object\Container\Fixtures\PublicPropertyInjectClass;
use TYPO3\CMS\Extbase\Tests\Unit\Object\Container\Fixtures\SimpleTypeConstructorArgument;
use TYPO3\CMS\Extbase\Tests\Unit\Object\Container\Fixtures\TwoConstructorArgumentsBothOptional;
use TYPO3\CMS\Extbase\Tests\Unit\Object\Container\Fixtures\TwoConstructorArgumentsFirstOptional;
......@@ -958,13 +956,4 @@ class ContainerTest extends UnitTestCase
$object->argumentTestClassTwo
);
}
/**
* @test
*/
public function getInstanceInjectsPublicProperties()
{
$object = $this->subject->getInstance(PublicPropertyInjectClass::class);
self::assertInstanceOf(ArgumentTestClassForPublicPropertyInjection::class, $object->foo);
}
}
......@@ -64,17 +64,6 @@ class PropertyTest extends UnitTestCase
self::assertTrue($property->isPrivate());
}
/**
* @test
*/
public function classSchemaDetectsInjectProperty(): void
{
$property = (new ClassSchema(DummyClassWithAllTypesOfProperties::class))
->getProperty('propertyWithInjectAnnotation');
self::assertTrue($property->isInjectProperty());
}
/**
* @test
*/
......
......@@ -21,7 +21,6 @@ use TYPO3\CMS\Extbase\Reflection\ReflectionService;
use TYPO3\CMS\Extbase\Tests\Unit\Reflection\Fixture\DummyClassWithAllTypesOfMethods;
use TYPO3\CMS\Extbase\Tests\Unit\Reflection\Fixture\DummyClassWithAllTypesOfProperties;
use TYPO3\CMS\Extbase\Tests\Unit\Reflection\Fixture\DummyClassWithConstructorAndConstructorArguments;
use TYPO3\CMS\Extbase\Tests\Unit\Reflection\Fixture\DummyClassWithInjectDoctrineAnnotation;
use TYPO3\CMS\Extbase\Tests\Unit\Reflection\Fixture\DummyControllerWithValidateAnnotationWithoutParam;
use TYPO3\CMS\Extbase\Tests\Unit\Reflection\Fixture\DummyControllerWithValidateAnnotationWithoutParamTypeHint;
use TYPO3\CMS\Extbase\Tests\Unit\Reflection\Fixture\DummyEntity;
......@@ -127,28 +126,6 @@ class ClassSchemaTest extends UnitTestCase
);
}
/**
* @test
*/
public function classSchemaDetectsInjectProperties()
{
$classSchema = new ClassSchema(DummyClassWithInjectDoctrineAnnotation::class);
self::assertTrue($classSchema->hasInjectProperties());
$injectProperties = $classSchema->getInjectProperties();
self::assertArrayHasKey('propertyWithFullQualifiedClassName', $injectProperties);
self::assertSame(DummyClassWithInjectDoctrineAnnotation::class, $injectProperties['propertyWithFullQualifiedClassName']->getType());
self::assertArrayHasKey('propertyWithRelativeClassName', $injectProperties);
self::assertSame(DummyClassWithInjectDoctrineAnnotation::class, $injectProperties['propertyWithRelativeClassName']->getType());
self::assertArrayHasKey('propertyWithImportedClassName', $injectProperties);
self::assertSame(self::class, $injectProperties['propertyWithImportedClassName']->getType());
self::assertArrayHasKey('propertyWithImportedAndAliasedClassName', $injectProperties);
self::assertSame(self::class, $injectProperties['propertyWithImportedAndAliasedClassName']->getType());
}
/**
* @test
*/
......
......@@ -54,7 +54,6 @@ class DummyClassWithAllTypesOfProperties extends AbstractEntity
public $propertyWithIgnoredTags;
/**
* @Extbase\Inject
* @var DummyClassWithAllTypesOfProperties
*/
public $propertyWithInjectAnnotation;
......
<?php
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Extbase\Tests\UnitDeprecated\Object\Container;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
use Psr\Log\LoggerInterface;
use TYPO3\CMS\Core\Log\Logger;
use TYPO3\CMS\Extbase\Object\Container\Container;
use TYPO3\CMS\Extbase\Reflection\ReflectionService;
use TYPO3\CMS\Extbase\Tests\UnitDeprecated\Object\Container\Fixtures\ArgumentTestClassForPublicPropertyInjection;
use TYPO3\CMS\Extbase\Tests\UnitDeprecated\Object\Container\Fixtures\PublicPropertyInjectClass;
use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
/**
* Test case
*/
class ContainerTest extends UnitTestCase
{
/**
* @var bool Reset singletons created by subject
*/
protected $resetSingletonInstances = true;
/**
* @var Container
*/
protected $subject;
/**
* @var LoggerInterface|\PHPUnit\Framework\MockObject\MockObject
*/
protected $logger;
protected function setUp(): void
{
parent::setUp();
$this->logger = $this->getMockBuilder(Logger::class)
->setMethods(['notice'])
->disableOriginalConstructor()
->getMock();
$reflectionService = new ReflectionService();
$notFoundException = new class() extends \Exception implements NotFoundExceptionInterface {
};
$psrContainer = $this->getMockBuilder(ContainerInterface::class)
->setMethods(['has', 'get'])
->getMock();
$psrContainer->expects(self::any())->method('has')->willReturn(false);
$psrContainer->expects(self::any())->method('get')->will(self::throwException($notFoundException));
$this->subject = $this->getMockBuilder(Container::class)
->setConstructorArgs([$psrContainer])
->setMethods(['getLogger', 'getReflectionService'])
->getMock();
$this->subject->setLogger($this->logger);
$this->subject->expects(self::any())->method('getReflectionService')->willReturn($reflectionService);
}
/**
* @test
*/
public function getInstanceInjectsPublicProperties()
{
$object = $this->subject->getInstance(PublicPropertyInjectClass::class);
self::assertInstanceOf(ArgumentTestClassForPublicPropertyInjection::class, $object->foo);
}
}
......@@ -15,7 +15,7 @@ declare(strict_types=1);
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Extbase\Tests\Unit\Object\Container\Fixtures;
namespace TYPO3\CMS\Extbase\Tests\UnitDeprecated\Object\Container\Fixtures;
use TYPO3\CMS\Extbase\Annotation as Extbase;
......@@ -23,7 +23,7 @@ class PublicPropertyInjectClass
{
/**
* @Extbase\Inject
* @var \TYPO3\CMS\Extbase\Tests\Unit\Object\Container\Fixtures\ArgumentTestClassForPublicPropertyInjection
* @var \TYPO3\CMS\Extbase\Tests\UnitDeprecated\Object\Container\Fixtures\ArgumentTestClassForPublicPropertyInjection
*/
public $foo;
}
......@@ -37,7 +37,7 @@ class ProtectedPropertyInjectClass
/**
* @Extbase\Inject
* @var \TYPO3\CMS\Extbase\Tests\Unit\Object\Container\Fixtures\ArgumentTestClassForPublicPropertyInjection
* @var \TYPO3\CMS\Extbase\Tests\UnitDeprecated\Object\Container\Fixtures\ArgumentTestClassForPublicPropertyInjection
*/
protected $foo;
......
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Extbase\Tests\UnitDeprecated\Reflection\ClassSchema;
use TYPO3\CMS\Extbase\Reflection\ClassSchema;
use TYPO3\CMS\Extbase\Tests\UnitDeprecated\Reflection\Fixture\DummyClassWithAllTypesOfProperties;
use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
/**
* Class TYPO3\CMS\Extbase\Tests\UnitDeprecated\Reflection\PropertyTest
*/
class PropertyTest extends UnitTestCase
{
/**
* @test
*/
public function classSchemaDetectsInjectProperty(): void
{
$property = (new ClassSchema(DummyClassWithAllTypesOfProperties::class))
->getProperty('propertyWithInjectAnnotation');
self::assertTrue($property->isInjectProperty());
}
}
<?php
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Extbase\Tests\UnitDeprecated\Reflection;
use TYPO3\CMS\Extbase\Reflection\ClassSchema;
use TYPO3\CMS\Extbase\Tests\UnitDeprecated\Reflection\Fixture\DummyClassWithInjectDoctrineAnnotation;
use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
/**
* Test case
*/
class ClassSchemaTest extends UnitTestCase
{
/**
* @test
*/
public function classSchemaDetectsInjectProperties()
{
$classSchema = new ClassSchema(DummyClassWithInjectDoctrineAnnotation::class);
self::assertTrue($classSchema->hasInjectProperties());
$injectProperties = $classSchema->getInjectProperties();
self::assertArrayHasKey('propertyWithFullQualifiedClassName', $injectProperties);
self::assertSame(DummyClassWithInjectDoctrineAnnotation::class, $injectProperties['propertyWithFullQualifiedClassName']->getType());
self::assertArrayHasKey('propertyWithRelativeClassName', $injectProperties);
self::assertSame(DummyClassWithInjectDoctrineAnnotation::class, $injectProperties['propertyWithRelativeClassName']->getType());
self::assertArrayHasKey('propertyWithImportedClassName', $injectProperties);
self::assertSame(self::class, $injectProperties['propertyWithImportedClassName']->getType());
self::assertArrayHasKey('propertyWithImportedAndAliasedClassName', $injectProperties);
self::assertSame(self::class, $injectProperties['propertyWithImportedAndAliasedClassName']->getType());
}
}
......@@ -13,20 +13,19 @@
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Extbase\Tests\Fixture;
namespace TYPO3\CMS\Extbase\Tests\UnitDeprecated\Reflection\Fixture;
use TYPO3\CMS\Extbase\Annotation as Extbase;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;