Skip to content

Commit 2e4e5aa

Browse files
turegjorupclaude
andcommitted
feat(feed): remove deprecated feed types and make unknown-type handling consistent
Remove the feed types deprecated in 2.x — SparkleIOFeedType, EventDatabaseApiFeedType and KobaFeedType — for the 3.0.0 release. feed_source.feed_type stores the FQCN as a plain string (not an FK), so removing a class leaves orphaned rows that FeedService::getFeedType() can no longer resolve. Make that handling consistent across the API: - Reads degrade, never 500: FeedSourceProvider::toOutput() catches UnknownFeedTypeException and exposes no secrets, so feed sources (and feeds embedding them, item and collection) stay listable and removable. The feed data endpoint already returns an empty result. - Writes reject: creating/updating a feed source with an unknown feed type maps UnknownFeedTypeException to HTTP 422 (was an opaque 500) via exception_to_status. Tooling for cleaning up referencing rows: - DeprecatedFeedSourceFinder + app:feed:remove-deprecated-feed-sources (dry-run report; --force removes the feed sources, their feeds and the bound slides, cascading to PlaylistSlide), covering all tenants. - app:update prints a notice when deprecated feed sources remain. Also remove the backend/frontend references (admin feed-type selector, the v1 event-database config component and its translation), switch test fixtures to EventDatabaseApiV2FeedType, regenerate the PHPStan baseline, and document the upgrade step in UPGRADE.md / CHANGELOG.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 13ac191 commit 2e4e5aa

19 files changed

Lines changed: 529 additions & 1105 deletions

.claude/skills/add-feed-type/SKILL.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,11 @@ class MyFeedType implements FeedTypeInterface
8383
Authoritative signatures live in `src/Feed/FeedTypeInterface.php`. Canonical behaviour for each:
8484

8585
### 3.1 `getSupportedFeedOutputType(): string`
86+
8687
Return one of `FeedOutputModels::*`. **Frozen for the source's lifetime** — changing it on an existing `FeedSource` orphans all linked slides.
8788

8889
### 3.2 `getRequiredSecrets(): array`
90+
8991
Declare keys for `FeedSource.secrets`. `'exposeValue' => true` makes a key readable by the frontend.
9092

9193
```php
@@ -100,9 +102,11 @@ return [
100102
```
101103

102104
### 3.3 `getRequiredConfiguration(): array`
105+
103106
Keys that must exist in `Feed.configuration` before `getData()` runs. Documentation only — enforce in `getData()`.
104107

105108
### 3.4 `getSchema(): array`
109+
106110
JSON Schema (draft-04) validated by `FeedSourceProcessor` on POST/PUT. Start permissive.
107111

108112
```php
@@ -113,9 +117,11 @@ return [
113117
'required' => ['token'],
114118
];
115119
```
120+
116121
Gotcha: PUT with empty `secrets` payload skips validation (intentional — allows editing title without re-sending secrets).
117122

118123
### 3.5 `getAdminFormOptions(FeedSource $feedSource): array`
124+
119125
Admin form descriptor. Common inputs:
120126

121127
| `input` | Use for |
@@ -141,6 +147,7 @@ return [[
141147
`name` becomes the key under `Feed.configuration`. The second arg to `getFeedSourceConfigUrl()` is the `$name` your `getConfigOptions()` dispatches on.
142148

143149
### 3.6 `getConfigOptions(Request $request, FeedSource $feedSource, string $name): ?array`
150+
144151
Called by `FeedSourceConfigGetController` for dynamic dropdowns. Return `[{id, title, value}, …]` or `null` for unknown `$name`.
145152

146153
**Swallow exceptions here** — the admin form should degrade gracefully while an editor is still typing credentials. Log + return `null`/`[]`.
@@ -168,6 +175,7 @@ Rules (these are the most common bugs):
168175
1. **Validate required secrets/config first** — throw `\RuntimeException` with class-prefixed message if missing.
169176
2. **Let exceptions bubble.** `FeedService::getData()` wraps the call and caches an empty result for **30 s** on failure (`ERROR_CACHE_TTL_SECONDS`). Catching-and-returning-`[]` yourself caches empty with the **full feed TTL** (often hours) — silent breakage.
170177
3. **Log at the catch site, then rethrow:**
178+
171179
```php
172180
try { /* … */ }
173181
catch (\Throwable $t) {
@@ -179,6 +187,7 @@ Rules (these are the most common bugs):
179187
throw $t;
180188
}
181189
```
190+
182191
4. **Don't add per-call caching around the whole `getData()`**`FeedService` already caches per-feed and honours `configuration['cache_expire']` as a per-feed TTL override. Cache lower-level shared lookups separately if needed.
183192

184193
## 4. Output data contract
@@ -226,7 +235,8 @@ Declare in `config/packages/cache.yaml` and inject by name. **Don't** reuse `$fe
226235
| External system with its own SDK/client | `src/Feed/BrndFeedType.php` + `src/Feed/SourceType/Brnd/` |
227236
| Hydra/REST poster-event source | `src/Feed/EventDatabaseApiV2FeedType.php` + `src/Feed/EventDatabaseApiV2Helper.php` |
228237

229-
**Deprecated — do not use as templates:** `SparkleIOFeedType`, `EventDatabaseApiFeedType`, `KobaFeedType`.
238+
**Removed in 3.0.0:** `SparkleIOFeedType`, `EventDatabaseApiFeedType` (use `EventDatabaseApiV2FeedType`),
239+
and `KobaFeedType` were deprecated in 2.x and removed in 3.0.0 — do not reintroduce them.
230240

231241
## 7. Testing
232242

@@ -237,6 +247,7 @@ Add `tests/Feed/MyFeedTypeTest.php`. Pure unit tests (no kernel) are fine for tr
237247
`testGetDataErrorIsNotCachedWithNormalTtl` in the same file documents the error-caching contract — read it before tweaking exception handling.
238248

239249
Run:
250+
240251
```shell
241252
task test:api -- tests/Feed tests/Service/FeedServiceTest.php
242253
task code-analysis

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
44

55
## [Unreleased]
66

7+
- Removed the deprecated feed types `SparkleIOFeedType`, `EventDatabaseApiFeedType` and `KobaFeedType`.
8+
Made the unknown-feed-type handling consistent: **reads degrade, writes are rejected.** Feed sources
9+
(and feeds) that reference a removed type keep loading — item and collection reads return them with no
10+
exposed secrets instead of HTTP 500, and the feed data endpoint still returns an empty result — while
11+
creating or updating a feed source with an unknown feed type now returns HTTP 422 (was an opaque 500).
12+
Run the new `app:feed:remove-deprecated-feed-sources` command to review and `--force`-remove the
13+
inert feed sources together with their feeds and slides; `app:update` prints a notice when any exist.
14+
Migrate event database feeds to `EventDatabaseApiV2FeedType`. See `UPGRADE.md`.
715
- Added project-shared Claude Code configuration (hooks, skills, subagents, MCP servers, plugins) under `.claude/`
816
for consistent AI-assisted workflows on this codebase. Requires `npm install -g intelephense` once per developer
917
to activate the PHP language server (the `playwright` and `context7` plugins need no prerequisites).

UPGRADE.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,3 +268,24 @@ docker compose exec phpfpm bin/console app:screen-layouts:install
268268
- Use `--all` option for installing all available templates.
269269
- Use `--update` option for updating existing templates.
270270
- Use `--cleanupRegions` option for cleaning up regions that are no longer connected to a layout.
271+
272+
#### 8 - Clean up feed sources using removed feed types
273+
274+
The feed types `SparkleIOFeedType`, `EventDatabaseApiFeedType` and `KobaFeedType` were deprecated in
275+
2.x and **removed in 3.0**. Unknown feed types are now handled consistently: **reads degrade, writes are
276+
rejected.** Feed sources that still reference a removed type keep loading (item and collection reads
277+
return them with no exposed secrets, and feed data endpoints return empty) but can no longer fetch data;
278+
creating or updating a feed source with such a type returns HTTP 422. `app:update` prints a notice when
279+
any such feed sources exist.
280+
281+
Review them, then remove them together with their feeds and slides:
282+
283+
```shell
284+
# Report feed sources referencing a removed feed type (no changes made).
285+
docker compose exec phpfpm bin/console app:feed:remove-deprecated-feed-sources
286+
287+
# Remove them, their feeds and the slides bound to those feeds.
288+
docker compose exec phpfpm bin/console app:feed:remove-deprecated-feed-sources --force
289+
```
290+
291+
Event database feeds should be recreated using `EventDatabaseApiV2FeedType`.

assets/admin/components/feed-sources/feed-source-form.jsx

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import ContentBody from "../util/content-body/content-body";
99
import FormInput from "../util/forms/form-input";
1010
import CalendarApiFeedType from "./templates/calendar-api-feed-type";
1111
import NotifiedFeedType from "./templates/notified-feed-type";
12-
import EventDatabaseApiFeedType from "./templates/event-database-feed-type";
1312
import ColiboFeedType from "./templates/colibo-feed-type";
1413
import StickyFooter from "../util/sticky-footer";
1514
import EventDatabaseApiV2FeedType from "./templates/event-database-v2-feed-type";
@@ -133,14 +132,6 @@ function FeedSourceForm({
133132
feedSourceId={feedSource["@id"]}
134133
/>
135134
)}
136-
{feedSource?.feedType ===
137-
"App\\Feed\\EventDatabaseApiFeedType" && (
138-
<EventDatabaseApiFeedType
139-
handleInput={handleSecretInput}
140-
formStateObject={feedSource.secrets}
141-
mode={mode}
142-
/>
143-
)}
144135
{feedSource?.feedType ===
145136
"App\\Feed\\EventDatabaseApiV2FeedType" && (
146137
<EventDatabaseApiV2FeedType

assets/admin/components/feed-sources/feed-source-manager.jsx

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -79,14 +79,6 @@ function FeedSourceManager({
7979
recipients: [],
8080
},
8181
},
82-
{
83-
value: "App\\Feed\\EventDatabaseApiFeedType",
84-
title: t("dynamic-fields.event-database-api-feed-type.title"),
85-
key: "EventDatabaseApiFeedType",
86-
secretsDefault: {
87-
host: "",
88-
},
89-
},
9082
{
9183
value: "App\\Feed\\EventDatabaseApiV2FeedType",
9284
title: t("event-database-api-v2-feed-type.title"),

assets/admin/components/feed-sources/templates/event-database-feed-type.jsx

Lines changed: 0 additions & 25 deletions
This file was deleted.

assets/admin/translations/da/common.json

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -287,11 +287,6 @@
287287
"title": "BRND"
288288
},
289289
"dynamic-fields": {
290-
"event-database-api-feed-type": {
291-
"title": "Eventdatabase API",
292-
"host": "Eventdatabase API host",
293-
"redacted-value-input-placeholder": "Skjult værdi"
294-
},
295290
"notified-feed-type": {
296291
"title": "Notified",
297292
"token": "Notified API token",

config/packages/api_platform.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ api_platform:
6363
# Validation exception
6464
ApiPlatform\Validator\Exception\ValidationException: !php/const Symfony\Component\HttpFoundation\Response::HTTP_UNPROCESSABLE_ENTITY
6565

66+
# Writing a feed source with a feed type that cannot be resolved (e.g. a
67+
# type removed in 3.0.0) is a validation failure on feedType, not a 500.
68+
# Reads degrade instead of throwing (see FeedSourceProvider::toOutput).
69+
App\Exceptions\UnknownFeedTypeException: !php/const Symfony\Component\HttpFoundation\Response::HTTP_UNPROCESSABLE_ENTITY
70+
6671
# App exception mappings
6772
App\Exceptions\BadRequestException: 400
6873
App\Exceptions\ForbiddenException: 403

phpstan-baseline.neon

Lines changed: 0 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1608,42 +1608,6 @@ parameters:
16081608
count: 1
16091609
path: src/Feed/ColiboFeedType.php
16101610

1611-
-
1612-
message: '#^Method App\\Feed\\EventDatabaseApiFeedType\:\:getAdminFormOptions\(\) return type has no value type specified in iterable type array\.$#'
1613-
identifier: missingType.iterableValue
1614-
count: 1
1615-
path: src/Feed/EventDatabaseApiFeedType.php
1616-
1617-
-
1618-
message: '#^Method App\\Feed\\EventDatabaseApiFeedType\:\:getConfigOptions\(\) return type has no value type specified in iterable type array\.$#'
1619-
identifier: missingType.iterableValue
1620-
count: 1
1621-
path: src/Feed/EventDatabaseApiFeedType.php
1622-
1623-
-
1624-
message: '#^Method App\\Feed\\EventDatabaseApiFeedType\:\:getData\(\) return type has no value type specified in iterable type array\.$#'
1625-
identifier: missingType.iterableValue
1626-
count: 1
1627-
path: src/Feed/EventDatabaseApiFeedType.php
1628-
1629-
-
1630-
message: '#^Method App\\Feed\\EventDatabaseApiFeedType\:\:getRequiredConfiguration\(\) return type has no value type specified in iterable type array\.$#'
1631-
identifier: missingType.iterableValue
1632-
count: 1
1633-
path: src/Feed/EventDatabaseApiFeedType.php
1634-
1635-
-
1636-
message: '#^Method App\\Feed\\EventDatabaseApiFeedType\:\:getRequiredSecrets\(\) return type has no value type specified in iterable type array\.$#'
1637-
identifier: missingType.iterableValue
1638-
count: 1
1639-
path: src/Feed/EventDatabaseApiFeedType.php
1640-
1641-
-
1642-
message: '#^Method App\\Feed\\EventDatabaseApiFeedType\:\:getSchema\(\) return type has no value type specified in iterable type array\.$#'
1643-
identifier: missingType.iterableValue
1644-
count: 1
1645-
path: src/Feed/EventDatabaseApiFeedType.php
1646-
16471611
-
16481612
message: '#^Method App\\Feed\\EventDatabaseApiV2FeedType\:\:getAdminFormOptions\(\) return type has no value type specified in iterable type array\.$#'
16491613
identifier: missingType.iterableValue
@@ -1764,48 +1728,6 @@ parameters:
17641728
count: 1
17651729
path: src/Feed/FeedTypeInterface.php
17661730

1767-
-
1768-
message: '#^Method App\\Feed\\KobaFeedType\:\:getAdminFormOptions\(\) return type has no value type specified in iterable type array\.$#'
1769-
identifier: missingType.iterableValue
1770-
count: 1
1771-
path: src/Feed/KobaFeedType.php
1772-
1773-
-
1774-
message: '#^Method App\\Feed\\KobaFeedType\:\:getBookingsFromResource\(\) return type has no value type specified in iterable type array\.$#'
1775-
identifier: missingType.iterableValue
1776-
count: 1
1777-
path: src/Feed/KobaFeedType.php
1778-
1779-
-
1780-
message: '#^Method App\\Feed\\KobaFeedType\:\:getConfigOptions\(\) return type has no value type specified in iterable type array\.$#'
1781-
identifier: missingType.iterableValue
1782-
count: 1
1783-
path: src/Feed/KobaFeedType.php
1784-
1785-
-
1786-
message: '#^Method App\\Feed\\KobaFeedType\:\:getData\(\) return type has no value type specified in iterable type array\.$#'
1787-
identifier: missingType.iterableValue
1788-
count: 1
1789-
path: src/Feed/KobaFeedType.php
1790-
1791-
-
1792-
message: '#^Method App\\Feed\\KobaFeedType\:\:getRequiredConfiguration\(\) return type has no value type specified in iterable type array\.$#'
1793-
identifier: missingType.iterableValue
1794-
count: 1
1795-
path: src/Feed/KobaFeedType.php
1796-
1797-
-
1798-
message: '#^Method App\\Feed\\KobaFeedType\:\:getRequiredSecrets\(\) return type has no value type specified in iterable type array\.$#'
1799-
identifier: missingType.iterableValue
1800-
count: 1
1801-
path: src/Feed/KobaFeedType.php
1802-
1803-
-
1804-
message: '#^Method App\\Feed\\KobaFeedType\:\:getSchema\(\) return type has no value type specified in iterable type array\.$#'
1805-
identifier: missingType.iterableValue
1806-
count: 1
1807-
path: src/Feed/KobaFeedType.php
1808-
18091731
-
18101732
message: '#^Method App\\Feed\\NotifiedFeedType\:\:getAdminFormOptions\(\) return type has no value type specified in iterable type array\.$#'
18111733
identifier: missingType.iterableValue
@@ -1932,42 +1854,6 @@ parameters:
19321854
count: 1
19331855
path: src/Feed/SourceType/Colibo/ApiClient.php
19341856

1935-
-
1936-
message: '#^Method App\\Feed\\SparkleIOFeedType\:\:getAdminFormOptions\(\) return type has no value type specified in iterable type array\.$#'
1937-
identifier: missingType.iterableValue
1938-
count: 1
1939-
path: src/Feed/SparkleIOFeedType.php
1940-
1941-
-
1942-
message: '#^Method App\\Feed\\SparkleIOFeedType\:\:getConfigOptions\(\) return type has no value type specified in iterable type array\.$#'
1943-
identifier: missingType.iterableValue
1944-
count: 1
1945-
path: src/Feed/SparkleIOFeedType.php
1946-
1947-
-
1948-
message: '#^Method App\\Feed\\SparkleIOFeedType\:\:getData\(\) return type has no value type specified in iterable type array\.$#'
1949-
identifier: missingType.iterableValue
1950-
count: 1
1951-
path: src/Feed/SparkleIOFeedType.php
1952-
1953-
-
1954-
message: '#^Method App\\Feed\\SparkleIOFeedType\:\:getRequiredConfiguration\(\) return type has no value type specified in iterable type array\.$#'
1955-
identifier: missingType.iterableValue
1956-
count: 1
1957-
path: src/Feed/SparkleIOFeedType.php
1958-
1959-
-
1960-
message: '#^Method App\\Feed\\SparkleIOFeedType\:\:getRequiredSecrets\(\) return type has no value type specified in iterable type array\.$#'
1961-
identifier: missingType.iterableValue
1962-
count: 1
1963-
path: src/Feed/SparkleIOFeedType.php
1964-
1965-
-
1966-
message: '#^Method App\\Feed\\SparkleIOFeedType\:\:getSchema\(\) return type has no value type specified in iterable type array\.$#'
1967-
identifier: missingType.iterableValue
1968-
count: 1
1969-
path: src/Feed/SparkleIOFeedType.php
1970-
19711857
-
19721858
message: '#^Method App\\Filter\\CampaignFilter\:\:filterProperty\(\) has parameter \$value with no type specified\.$#'
19731859
identifier: missingType.parameter

0 commit comments

Comments
 (0)