Auto-gegenereerd via Reflection — blijft altijd in sync met src/.
AbstractSource
Framework\Media\AbstractSourceAbstract base voor alle media-bronnen. Subclasses
(`Source\Image`, `Source\Video`, `Source\YouTube`, `Source\Vimeo`) weten hoe
je hun URL embed't en welke HTML-attrs relevant zijn voor hun type. De main
`Renderer` orchestreert ze; per-type detail leeft binnen de subclass — geen
centrale type-match.
Constructor blijft compatibel: alle subclasses accepteren `url`, optioneel
`width` en `height`. URL-validatie is **lazy** — een `Source\YouTube` met
een onparsebare URL gooit niet bij constructie maar bij `videoId()` (of
geeft null terug). CMS-content is soms placeholder; we vallen niet om.
Naamgeving: project-conventie is `Abstract`-prefix voor base-classes
waarvan je niet zelf een instance maakt.
__construct(string $url, ?int $width = NULL, ?int $height = NULL)2 public methods
isEmpty(): boolrender(\Variant $variant, \RenderOptions $options): \ElRender deze bron als inner-element (`<img>`, `<video>`, `<iframe>`, …).
De main Renderer wrapt 'm vervolgens in z'n variant-container met
overlay/play-button/aspect-ratio. Variant geeft toegang tot
posterUrl/overlayUrl/hoverUrl en variant-specifieke options.
Breakpoints
Framework\Media\BreakpointsBekende variant-keys met hun default CSS media-query.
Een `Variant` heeft een optionele `when:` (eigen CSS media query string).
Is die null en is de variant-key bekend, dan wordt de default uit deze tabel
gebruikt. Onbekende keys zonder eigen `when:` matchen nooit — dat dwingt
callers om voor custom-keys (bv. `'compact'`) expliciet een query mee te
geven.
`desktop` is hier expliciet een variant zoals elke andere (geen impliciete
"altijd zichtbaar"-default). Dat voorkomt het 769–1024px gat waarin een
desktop-video per ongeluk op tablet zou kunnen lekken.
2 public methods
static knownKeys(): arraystatic resolve(string $key, ?string $when): ?stringGeeft de effectieve CSS media-query voor een variant.
Eigen `$when` wint; anders fallback op {@see self::DEFAULTS}; anders null.
Decoder
Framework\Media\DecoderMixed input → `Media`.
Eén plek waar alle alias-mapping uit legacy CMS-content gebeurt.
`Decoder::decode()` accepteert:
- `null` of `''` → lege Media (geen variants)
- `string` JSON → decoded en doorgereikt
- `string` URL → image (of video/youtube/vimeo via auto-detect),
onder `variants['desktop']`
- `array` / `object` → key-mapping (zie hieronder)
- `Media` → as-is (idempotent)
Geaccepteerde keys** (snake_case én camelCase, eerste match wint):
type 'image'|'video'|'youtube'|'vimeo'
src / image / image_src
image_mobile / imageMobile
video / youtube / vimeo
width / height
poster / video_poster / videoPoster
overlay / overlay_url
hover / hover_url
desktop, mobile, tablet, dark, print, ... genest object (variant-shorthand)
Variant-shorthand**: zodra de input minimaal één breakpoint-key bevat
(een key die in `Breakpoints::DEFAULTS` staat), worden die keys
recursief gedecoded en geplaatst onder `Media::variants[<key>]`.
Platte input** (zonder breakpoint-keys) wordt geplaatst onder
`variants['desktop']` — desktop is gewoon een eersterangs variant zoals
elke andere, geen impliciete fallback.
1 public method
static decode(?mixed $input): \MediaDisplayMode
Framework\Media\DisplayModeHoe de media past binnen z'n container.
- Fit: proportioneel binnen de container, hele beeld zichtbaar (`object-fit: contain`)
- Cover: vult de container, kan croppen (`object-fit: cover`)
- Resize: fysiek geresized naar exacte width/height (server-side url-transform; alleen
bruikbaar via een MediaTransformer — anders gedraagt 'ie zich als Cover)
3 public methods
static cases(): arraystatic from(string|int $value): staticstatic tryFrom(string|int $value): ?staticFit, Cover, ResizeMedia
Framework\Media\MediaEen set `Variant`s die samen "de media" vormen voor één content-item.
Een Media heeft N variants, elk gekoppeld aan een breakpoint-key:
`'desktop'`, `'mobile'`, `'tablet'`, `'dark'`, `'print'`, of een custom
key zoals `'compact'`. De Renderer toont per viewport/context één variant
via een scoped `<style>`-block dat z'n CSS media queries genereert uit
`Variant::$when` (of `Breakpoints::DEFAULTS` voor bekende keys).
Callers werken zelden direct met `Variant` — bijna alles loopt via
`Media::from()` (decoder) en `Media::render()` (decode + render in één call).
echo Media::render($cmsRow, width: 1600, aspectRatio: '16/9');
$media = Media::from($cmsRow);
echo $media->render(width: 800); // instance helper
echo $media->getVariant('mobile')?->source->url;
Manuele constructie blijft beschikbaar voor volledige controle:
use Framework\Media\Source\{Image, Video};
$media = new Media([
'desktop' => new Variant(new Video('/promo.mp4')),
'mobile' => new Variant(new Image('/hero-mobile.jpg')),
'dark' => new Variant(new Image('/hero-dark.jpg')),
]);
Readonly value-object — geen mutators in v1. Als je variants moet kunnen
uitbreiden na constructie, voegen we later `withVariant()` toe.
__construct(array $variants = array (
))7 public methods
static element(?mixed $input, ?int $width = NULL, ?int $height = NULL, \DisplayMode|string|null $mode = NULL, ?string $aspectRatio = NULL, \PosterMode|string $poster = \Framework\Media\PosterMode::Default, ?string $posterUrl = NULL, bool $showPlayButton = false, bool $autoplay = false, bool $loop = false, bool $controls = true, bool $muted = true, bool $lazy = true, ?string $fetchPriority = NULL, ?string $alt = NULL, ?string $cssClass = NULL): \ElZoals `render()` maar returnt het `El`-object zodat caller er nog op
kan doorbouwen.
static from(?mixed $input): selfDecode mixed input (string URL / JSON / array / object / Media) naar
een Media. Zie {@see Decoder::decode()} voor de geaccepteerde shapes.
getDefaultVariant(): ?\VariantEén variant terug die "altijd" zichtbaar zou moeten zijn — handig voor
fallback-rendering, og-image-extractie, alt-text, etc.
Strategie: 'desktop' wint, anders eerste niet-lege variant, anders null.
getVariant(string $key): ?\VarianthasVariant(string $key): boolisEmpty(): boolstatic render(?mixed $input, ?int $width = NULL, ?int $height = NULL, \DisplayMode|string|null $mode = NULL, ?string $aspectRatio = NULL, \PosterMode|string $poster = \Framework\Media\PosterMode::Default, ?string $posterUrl = NULL, bool $showPlayButton = false, bool $autoplay = false, bool $loop = false, bool $controls = true, bool $muted = true, bool $lazy = true, ?string $fetchPriority = NULL, ?string $alt = NULL, ?string $cssClass = NULL): stringConvenience: decode + render in één call. De named args mappen 1-op-1
op `RenderOptions`. Voor cross-type/per-variant overrides bouw je een
`Media` expliciet en roep je `$media->render(...)` aan.
echo Media::render($cmsRow,
width: 1600, height: 600, mode: 'cover', aspectRatio: '16/9',
autoplay: true, muted: true, poster: 'thumbnail',
);
PosterMode
Framework\Media\PosterModeHoe de Renderer een poster (placeholder) bepaalt voor een video-source.
- None: géén poster — `<video>` toont de eerste frame zelf
- Default: gebruik wat op `MediaItem::$poster` gezet is (anders fallback per type)
- Thumbnail: forceer auto-thumbnail (YouTube hqdefault.jpg, video → /original/→/thumbnail/.jpg)
- Custom: expliciet gezette URL (dat is wat al op `RenderOptions::$posterUrl` staat)
3 public methods
static cases(): arraystatic from(string|int $value): staticstatic tryFrom(string|int $value): ?staticNone, Default, Thumbnail, CustomRenderOptions
Framework\Media\RenderOptionsRender-instellingen — gewoon een readonly bag van display-opties.
Defaults zijn bewust *conservatief*: lazy-load aan, geen autoplay, controls
voor zelf-gehoste video aan, gemute uit. Dit volgt browser-defaults waar
mogelijk en blijft accessible (geen surprise-audio).
__construct(?int $width = NULL, ?int $height = NULL, ?\DisplayMode $mode = NULL, ?string $aspectRatio = NULL, \PosterMode $poster = \Framework\Media\PosterMode::Default, ?string $posterUrl = NULL, bool $showPlayButton = false, bool $autoplay = false, bool $loop = false, bool $controls = true, bool $muted = true, bool $lazy = true, ?string $fetchPriority = NULL, ?string $alt = NULL, ?string $cssClass = NULL)1 public method
with(?int $width = NULL, ?int $height = NULL, ?\DisplayMode $mode = NULL, ?string $aspectRatio = NULL, ?\PosterMode $poster = NULL, ?string $posterUrl = NULL, ?bool $showPlayButton = NULL, ?bool $autoplay = NULL, ?bool $loop = NULL, ?bool $controls = NULL, ?bool $muted = NULL, ?bool $lazy = NULL, ?string $fetchPriority = NULL, ?string $alt = NULL, ?string $cssClass = NULL): selfKleine fluent helpers — handig voor templates die er één optie aan willen
zetten zonder de hele constructor opnieuw te tikken.
Renderer
Framework\Media\Renderer`Media` + `RenderOptions` → `Framework\Html\El`-tree.
Per-type rendering wordt gedelegeerd naar `Source::render()`. De Renderer
doet alleen het orchestratie-werk:
- Per variant een wrapper-element met `data-variant="<key>"`,
eigen `aspect-ratio` (uit Variant.options of defaults), play-button,
overlay, hover.
- Alle variants samen in een `mm-media-stack`-container met daarin een
scoped `<style>`-block dat per breakpoint toggelt welke variant
`display: block` krijgt.
Single-variant short-circuit: als er maar één variant is, geen stack/style
— gewoon de variant-wrapper teruggeven.
Wrap-structuur — meerdere variants:
<div class="mm-media-stack" data-mm-id="m-7f3a" data-mm-component="media">
<style>...</style>
<div class="mm-media" data-variant="desktop" style="aspect-ratio:16/9">...</div>
<div class="mm-media" data-variant="mobile" style="aspect-ratio:1/1">...</div>
<div class="mm-media" data-variant="dark">...</div>
</div>
Wrap-structuur — single variant:
<div class="mm-media" data-mm-component="media" data-variant="desktop"
style="aspect-ratio:..."> ... </div>
__construct(?\Image $imageBuilder = NULL)1 public method
render(\Media $media, ?\RenderOptions $defaults = NULL): \ElImage
Framework\Media\Source\ImageImage bron — rendert een `<img>`-element. Ondersteunt `loading="lazy"`,
`fetchpriority`, `width`/`height`, `alt`. Geen poster (heeft geen video).
__construct(string $url, ?int $width = NULL, ?int $height = NULL)1 public method
render(\Variant $variant, \RenderOptions $options): \ElVideo
Framework\Media\Source\VideoSelf-hosted video — rendert een `<video>`-element met optionele autoplay,
loop, controls, muted, preload en poster.
Poster-resolutie:
- `PosterMode::None` → geen poster
- `PosterMode::Custom` → `RenderOptions::$posterUrl` (verplicht set)
- `PosterMode::Thumbnail` → forceer auto-poster via legacy-conventie
`/original/...mp4` → `/thumbnail/...mp4.jpg`
- `PosterMode::Default` → `Variant::$posterUrl` als die er is, anders auto
__construct(string $url, ?int $width = NULL, ?int $height = NULL)1 public method
render(\Variant $variant, \RenderOptions $options): \ElVimeo
Framework\Media\Source\VimeoVimeo embed — rendert een `<iframe>` met de embed-URL via `Url::vimeoEmbed()`.
Poster-resolutie:
- `PosterMode::None` → geen poster
- `PosterMode::Custom` → `$options->posterUrl`
- `PosterMode::Default` → `Variant::$posterUrl` (geen auto — Vimeo
vereist een API-call die niet in de renderer
hoort; caller of een PosterResolver-service
moet 'm vooraf invullen)
- `PosterMode::Thumbnail` → idem als Default (geen auto beschikbaar)
__construct(string $url, ?int $width = NULL, ?int $height = NULL)3 public methods
posterUrl(\Variant $variant, \RenderOptions $options): ?stringrender(\Variant $variant, \RenderOptions $options): \ElvideoId(): ?stringLazy-parse: Vimeo video-id of null als URL niet parsebaar is.
YouTube
Framework\Media\Source\YouTubeYouTube embed — rendert een `<iframe>` met de embed-URL via `Url::youTubeEmbed()`.
Honoreert autoplay/loop/muted/controls. Loop vereist `playlist={id}` (dat
regelt `Url::youTubeEmbed()`).
Poster-resolutie:
- `PosterMode::None` → geen poster
- `PosterMode::Custom` → `$options->posterUrl`
- `PosterMode::Thumbnail` → `Url::youTubePoster($url)` (hqdefault)
- `PosterMode::Default` → `Variant::$posterUrl` als die er is, anders
`Url::youTubePoster($url)`
Note: een `<iframe>` heeft géén native `poster=`-attribuut. De Renderer
tekent de poster zelf als overlay-image als 'ie wordt teruggegeven.
Daarom hangt de poster-resolutie hier op de Source en niet hard in de
iframe-element.
__construct(string $url, ?int $width = NULL, ?int $height = NULL)3 public methods
posterUrl(\Variant $variant, \RenderOptions $options): ?stringAuto-poster URL (hqdefault.jpg). Wordt door de Renderer als overlay-image
gebruikt voor het 'klik-om-te-spelen'-pattern.
render(\Variant $variant, \RenderOptions $options): \ElvideoId(): ?stringLazy-parse: geeft het YouTube video-id of null als de URL niet parsebaar is.
Wordt niet in de constructor gevalideerd zodat placeholder-URLs uit het
CMS geen exceptions veroorzaken.
Url
Framework\Media\UrlPure URL-helpers voor YouTube/Vimeo embedding + thumbnail-detectie.
Geen IO, geen statefull dependencies. Alle functies zijn deterministisch.
6 public methods
static videoThumbnail(string $videoUrl): stringAuto-thumbnail voor zelf-gehoste video's volgens legacy-conventie:
`/original/{path}.mp4` → `/thumbnail/{path}.mp4.jpg`. Werkt alleen als
de server zo'n endpoint heeft; geen garantie dat 't bestaat.
static vimeoEmbed(string $urlOrId, bool $autoplay = false, bool $loop = false, bool $muted = true): stringBouw een Vimeo `<iframe>` embed-URL.
static vimeoId(string $url): ?stringExtract de Vimeo video-ID uit een URL.
static youTubeEmbed(string $urlOrId, bool $controls = true, bool $autoplay = false, bool $loop = false, bool $muted = true): stringBouw een YouTube `<iframe>` embed-URL met opties.
static youTubeId(string $url): ?stringExtract de YouTube video-ID uit een URL.
Ondersteunt:
- youtu.be/{id}
- youtube.com/watch?v={id}
- youtube.com/embed/{id}
- youtube.com/v/{id}
- youtube.com/shorts/{id}
static youTubePoster(string $urlOrId, string $quality = 'hqdefault'): ?stringYouTube poster (default kwaliteit). `hqdefault` (480×360) is altijd
beschikbaar; `maxresdefault` (1280×720) niet voor elke video.
Variant
Framework\Media\VariantEén concrete media-variant zoals 'ie binnen een bepaald breakpoint
gerenderd wordt. Hangt typisch onder een `MediaItem` met een key (bv.
`'desktop'`, `'mobile'`, `'dark'`).
Een Variant bevat alles wat per breakpoint kan verschillen:
- `source` — URL + type (image/video/youtube/vimeo/raw) + dimensions
- `when` — CSS media query (`(max-width: 768px)`, `print`, …);
null = laat `Breakpoints` de default voor de key kiezen
- `options` — per-variant render-overrides (eigen aspect-ratio,
autoplay, controls, etc.)
- `posterUrl` — placeholder voor video-types
- `overlayUrl` — image die over de media heen ligt (badge/logo)
- `hoverUrl` — image die op hover wordt getoond (alleen images)
Geen recursie: een Variant bevat geen sub-variants. De volledige set zit op
`MediaItem.variants`. Callers maken zelden zelf een Variant aan — meestal
loopt alles via `MediaItem::from()` → `Decoder`.
__construct(\AbstractSource $source, ?string $when = NULL, ?\RenderOptions $options = NULL, ?string $posterUrl = NULL, ?string $overlayUrl = NULL, ?string $hoverUrl = NULL)1 public method
isEmpty(): bool