---
title: "Breaking: The partner controller is final"
manual: "Academic Partners"
version: "main"
permalink: "https://docs.typo3.org/permalink/fgtclb/academic-partners:breaking-1791043406@main"
source: "Changelog/3.0/Breaking-PartnerControllerIsFinal.rst"
rendered: "2026-10-26T19:20:19+00:00"
---

# Breaking: The partner controller is final {#breaking-1791043406}

## Description {#description}

`\FGTCLB\AcademicPartners\Controller\PartnerController` is `final`.
It serves the partner list (`List`), the partner map (`Map`), the partnerships
list (`PartnershipsList`) and the partnerships teaser (`PartnershipsTeaser`).

Its collaborators are private constructor arguments now. The methods
`injectFilterRedirectExtensionService()` and
`injectFilterTypeResolver()` are removed, and
`redirectFilterSubmission()` and `pluginControllerActionContext()`
are private. All of them existed only so that a subclass could keep calling the
old constructor and the helpers of the shipped actions.

Every plugin controller of the academic extensions is final in 3.0. A plugin is
extended through its events, not through a subclass of its controller.

## Impact {#impact}

A class that extends `PartnerController` stops loading with a fatal
error, `Class ... cannot extend final class
FGTCLB\AcademicPartners\Controller\PartnerController`. That happens as soon as
anything loads the subclass, a plugin registered with it renders, or the
container is built with it.

An XCLASS of the controller fails the same way. The upgrade check
`academic:upgrade:check` of **EXT:academic_base** reports it as
an error. A `configurePlugin()` call that points one of the four plugins
at a subclass is not reported.

The plugins, their templates and their settings are unchanged. The behaviour
is the same on TYPO3 v13 and v14.

## Affected Installations {#affected-installations}

Installations with a class that extends `PartnerController`, registered
for a plugin through `ExtensionUtility::configurePlugin()`, as an XCLASS,
or in the service container.

## Migration {#migration}

Remove the subclass, and the `configurePlugin()` call or the XCLASS
registration that points at it, so the shipped controller serves the plugin
again. Move each override to its replacement:

-   Changing the filter or the sorting before the query: a listener of
    `\FGTCLB\AcademicPartners\Event\ModifyPartnerDemandEvent`, which
    adjusts or replaces the demand. See [Feature: Change the partner list and map by event](https://docs.typo3.org/permalink/fgtclb/academic-partners:feature-list-plugin-events@main). The
    demand accepts only the sorting options of this extension and cannot
    express a sorting by another field, `tstamp` for instance. A listener
    of the list event can hand back a result of its own query sorted that way.
-   A replaced or reduced result, other filter categories, or additional view
    variables of the list and the map: a listener of
    `\FGTCLB\AcademicPartners\Event\ModifyPartnerListEvent`.
-   Additional view variables of the partnerships list and teaser: a listener
    of the plugin view event of **academic_base**, see
    [Feature: The plugins dispatch the plugin view event](https://docs.typo3.org/permalink/fgtclb/academic-partners:feature-plugin-view-event@main).
-   Pagination of the partner list: the **Pagination** tab of the
    content element, see [Feature: The partner list can be paginated](https://docs.typo3.org/permalink/fgtclb/academic-partners:feature-1790272109@main).
-   Filter selections that can be bookmarked: the list and the map answer a
    filter submission with a redirect to a GET URL, see
    [Feature: Partner filter selections have a URL](https://docs.typo3.org/permalink/fgtclb/academic-partners:feature-1790226100@main).

A subclass that adds a view variable:

**Before: EXT:my_extension/Classes/Controller/PartnerController.php**

```php
final class PartnerController extends \FGTCLB\AcademicPartners\Controller\PartnerController
{
    public function listAction(?array $demand = null): ResponseInterface
    {
        $this->view->assign('countries', $this->countryRepository->findAll());
        return parent::listAction($demand);
    }
}
```

does the same as a listener:

**After: EXT:my_extension/Classes/EventListener/AddCountriesToPartnerList.php**

```php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\EventListener;

use FGTCLB\AcademicPartners\Event\ModifyPartnerListEvent;
use MyVendor\MyExtension\Domain\Repository\CountryRepository;
use TYPO3\CMS\Core\Attribute\AsEventListener;

final readonly class AddCountriesToPartnerList
{
    public function __construct(
        private CountryRepository $countryRepository,
    ) {}

    #[AsEventListener(identifier: 'my-extension/add-countries-to-partner-list')]
    public function __invoke(ModifyPartnerListEvent $event): void
    {
        $event->getView()->assign('countries', $this->countryRepository->findAll());
    }
}
```

The listener acts on the list and the map alike. It reads
`$event->getPluginControllerActionContext()->getPluginName()` where it
means only one of them.
