Metadata¶
The Metadata class provides comprehensive management of Dublin Core metadata within an EPUB file. It uses a trait-based approach to organize metadata operations, with each trait handling a specific metadata field.
Overview¶
EPUB files use Dublin Core metadata elements (dc:) defined in the OPF (Open Packaging Format) file. The Metadata class provides get/set methods for all commonly used Dublin Core elements, along with persistence to save changes back to the OPF XML file.
Only elements inside the package's <metadata> element are read or changed. Both EPUB 2 and EPUB 3 packages are supported, including packages that use a prefix for the OPF namespace (<opf:package>) or that do not declare a dc prefix yet.
Key Methods¶
Constructor¶
public function __construct(SimpleXMLElement $opfXml, string $opfFilePath)
Initializes the Metadata object. The OPF XML element is typically obtained from parsing the EPUB's OPF file. The $opfFilePath is needed to save changes back to disk. Throws an InvalidEpubException if the package has no <metadata> element.
Saving Changes¶
public function save(): void
public function isModified(): bool
save() serializes the OPF XML back to disk. When metadata was changed since the last save, the modification date is updated (it is left alone when nothing changed): for EPUB 3 packages dcterms:modified is set to the current UTC time, and for EPUB 2 packages an existing dc:date with opf:event="modification" is set to the current UTC date (none is added). Throws an exception if the file cannot be written.
isModified() tells whether there are unsaved changes. EpubFile::save() calls Metadata::save() automatically when there are, so calling it yourself is optional.
public function getOpfFilePath(): string
Returns the file path of the OPF file for reference.
Metadata Fields¶
Single-valued getters return the first matching element (or ''); setters update it, or create it when missing. Values are escaped, so characters such as & and < are safe.
Title¶
public function getTitle(): string
public function setTitle(string $title): void
public function getTitles(): array
public function setTitles(array $titles): void
public function getSubtitle(): ?string
public function setSubtitle(?string $subtitle): void
The name given to the resource (dc:title). A book can have several titles; in EPUB 3 a title-type refinement says which is the main title and which are subtitles, collection titles, and so on.
getTitle()/setTitle()work on the main title: the one refined asmain, or else the first. Other titles are kept.getTitles()/setTitles()read and replace all titles in document order. Existing titles are reused in order, so theirtitle-typerefinements stay with their position; a title whose text changes loses itsfile-assort key.getSubtitle()returns the first title refined assubtitle, ornull.setSubtitle()replaces it (and drops any further subtitles), or adds a title refined assubtitleand refines the main title asmainwhen no title is refined yet;nullremoves every subtitle. Subtitles need EPUB 3 refinements, so on an EPUB 2 booksetSubtitle()throws anException(callupgradeToEpub3()first), as it does for an empty subtitle.- A book needs a title:
setTitles([])and empty values throw anException.
$metadata->setTitle('The Long Way');
$metadata->setSubtitle('A Novel');
Authors¶
public function getAuthors(): array<int, string>
public function setAuthors(array<int, string> $authors): void
public function getCreators(): array<int, Contributor>
public function setCreators(array<int, Contributor> $creators): void
public function addCreator(string $name, ?string $role = 'aut', ?string $fileAs = null): void
The authors are the creators of the resource (dc:creator) with the role aut or no role at all; creators with another role, such as illustrators (ill), are not authors. Multiple authors are supported.
setAuthors() only replaces the authors: other creators are kept. It reuses the existing authors in order, so their roles survive (EPUB 2 opf:role, EPUB 3 <meta refines="#id" property="role">). When an author's name changes, its sort key (opf:file-as / file-as refinement) is removed because it described the old name. Authors that are no longer needed are removed together with all their refinements.
getCreators() returns every creator as a PhpEpub\Contributor with name, role (a MARC relator code such as aut, ill or trl, or null) and fileAs (the sort key, or null). addCreator() adds one, writing the role and sort key as EPUB 3 refinements (scheme="marc:relators") or as EPUB 2 opf:role / opf:file-as attributes, depending on the package version.
setCreators() is the inverse of getCreators(): it replaces every creator by the given Contributor objects. Existing creators are reused in order, so their ids and other refinements survive; the role and sort key of each are set exactly as given (null removes them), in the form the package version expects. Creators beyond the list are removed with their refinements. It throws an Exception for an empty name or an invalid value, and then changes nothing.
$metadata->setCreators([
new Contributor('Jane Doe', 'aut', 'Doe, Jane'),
new Contributor('Ivan Illustrator', 'ill', null),
]);
Contributors¶
public function getContributors(): array<int, Contributor>
public function setContributors(array<int, string|Contributor> $contributors): void
public function addContributor(string $name, ?string $role = null, ?string $fileAs = null): void
People or organisations who contributed to the resource (dc:contributor), such as editors (edt) or translators (trl). These work like the creator methods. setContributors() reuses existing contributors in order. A plain name keeps the role of the contributor it reuses (its sort key is dropped when the name changes), while a Contributor object sets the role and sort key exactly (null removes them), so names and objects can be mixed. Empty names throw an Exception.
$metadata->addCreator('Ivan Illustrator', 'ill', 'Illustrator, Ivan');
$metadata->addContributor('Ed Editor', 'edt');
foreach ($metadata->getContributors() as $contributor) {
echo "{$contributor->name} ({$contributor->role})\n";
}
Description¶
public function getDescription(): string
public function setDescription(string $description): void
An account of the resource (dc:description).
Publisher¶
public function getPublisher(): string
public function setPublisher(string $publisher): void
The entity that made the resource available (dc:publisher).
Date¶
public function getDate(): string
public function setDate(string|DateTimeInterface $date): void
public function getModifiedDate(): ?string
public function getDateEvents(): array
Date of publication (dc:date). setDate() accepts W3CDTF, the format EPUB uses, and throws an Exception for anything else: YYYY, YYYY-MM, YYYY-MM-DD, or a date-time with a time zone (2020-07-31T10:20:30Z, 2020-07-31T10:20+02:00). A DateTimeInterface is written as a date-time with its own offset (2020-07-31T10:20:30+02:00).
getDate()/setDate()use the publication date: thedc:datewith the EPUB 2opf:event="publication", or else one without an event, or else the first. Dates of other events are kept.getModifiedDate()returns the EPUB 3dcterms:modifiedproperty, or else the EPUB 2dc:datewithopf:event="modification", ornull.save()keeps both up to date: it only touches the book-leveldcterms:modified(never a refinement of another element with that property), and adds it when the package only has refinements.getDateEvents()returns everydc:datekeyed by itsopf:event(""for a date without one), e.g.['publication' => '1999-01-01', 'modification' => '2020-05-05'].
Language¶
public function getLanguage(): string
public function setLanguage(string $language): void
public function getLanguages(): array<int, string>
public function setLanguages(array<int, string> $languages): void
The language of the resource (dc:language). Use BCP 47 language tags (e.g., "en", "pt-BR", "zh-Hant"). setLanguage() and setLanguages() throw an Exception for a value that is not a well-formed tag ("English" or "en_US"); only the syntax is checked, not whether the language is registered. getLanguage()/setLanguage() work on the main (first) language; getLanguages()/setLanguages() on all of them, for multilingual books. At least one language is required. A xml:lang on the package element that declared the previous main language follows the new one; save() does the same for the navigation document, together with its <title> when it equalled the previous book title.
Accessibility¶
public function getAccessModes(): array<int, string>
public function setAccessModes(array<int, string> $modes): void
public function getAccessModesSufficient(): array<int, string>
public function setAccessModesSufficient(array<int, string> $sets): void
public function getAccessibilityFeatures(): array<int, string>
public function setAccessibilityFeatures(array<int, string> $features): void
public function getAccessibilityHazards(): array<int, string>
public function setAccessibilityHazards(array<int, string> $hazards): void
public function getAccessibilitySummary(): ?string
public function setAccessibilitySummary(?string $summary): void
public function getConformsTo(): ?string
public function setConformsTo(?string $conformance): void
public function getCertifiedBy(): ?string
public function setCertifiedBy(?string $certifier): void
Accessibility metadata as described by EPUB Accessibility 1.1; the European Accessibility Act asks for it for e-books sold in the EU.
| Methods | Property | Example values |
|---|---|---|
getAccessModes() / setAccessModes() |
schema:accessMode |
textual, visual, auditory, tactile |
getAccessModesSufficient() / setAccessModesSufficient() |
schema:accessModeSufficient |
textual,visual (each value is a comma-separated set), textual |
getAccessibilityFeatures() / setAccessibilityFeatures() |
schema:accessibilityFeature |
alternativeText, tableOfContents, structuralNavigation, none |
getAccessibilityHazards() / setAccessibilityHazards() |
schema:accessibilityHazard |
none, unknown, noFlashingHazard, flashing |
getAccessibilitySummary() / setAccessibilitySummary() |
schema:accessibilitySummary |
free text |
getConformsTo() / setConformsTo() |
dcterms:conformsTo |
EPUB Accessibility 1.1 - WCAG 2.1 Level AA |
getCertifiedBy() / setCertifiedBy() |
a11y:certifiedBy |
the certifier's name |
The list methods read and replace every value in order; the single-value ones return null when absent, and null or "" removes the value. EPUB 3 packages store each as a <meta property="..."> element; EPUB 2 packages as <meta name="..." content="..."/>, which the specification allows. The values are not checked against the vocabularies. EpubFile::validate() warns when an EPUB 3 book has no access mode, feature, hazard or summary (see Validating).
$metadata->setAccessModes(['textual', 'visual']);
$metadata->setAccessModesSufficient(['textual']);
$metadata->setAccessibilityFeatures(['alternativeText', 'tableOfContents']);
$metadata->setAccessibilityHazards(['none']);
$metadata->setAccessibilitySummary('All images have text alternatives.');
$metadata->setConformsTo('EPUB Accessibility 1.1 - WCAG 2.1 Level AA');
Rendition (fixed layout)¶
public function getRenditionLayout(): ?string
public function setRenditionLayout(?string $layout): void
public function getRenditionOrientation(): ?string
public function setRenditionOrientation(?string $orientation): void
public function getRenditionSpread(): ?string
public function setRenditionSpread(?string $spread): void
public function getRenditionFlow(): ?string
public function setRenditionFlow(?string $flow): void
The EPUB Rendition properties rendition:layout (reflowable, pre-paginated), rendition:orientation (auto, landscape, portrait), rendition:spread (none, auto, landscape, portrait, both) and rendition:flow (auto, paginated, scrolled-continuous, scrolled-doc). The getters return null when the property is not set; null removes it. The setters throw an Exception for another value and for an EPUB 2 package, and declare the rendition prefix in the package's prefix attribute. Individual pages override them with Spine::setItemRendition(). See EPUB 3 Features.
Media Overlays¶
public function getMediaDuration(): ?string
public function setMediaDuration(?string $duration): void
public function getMediaDurationOf(string $overlayId): ?string
public function setMediaDurationOf(string $overlayId, ?string $duration): void
public function getMediaDurations(): array
public function getMediaActiveClass(): ?string
public function setMediaActiveClass(?string $class): void
public function getMediaPlaybackActiveClass(): ?string
public function setMediaPlaybackActiveClass(?string $class): void
public function getMediaNarrators(): array
public function setMediaNarrators(array $narrators): void
The media:duration of the whole book and, as a refinement of each SMIL manifest item, of each overlay; the media:active-class and media:playback-active-class CSS classes; and the media:narrator names. Durations must be SMIL clock values (0:32:29.5, 32:29, 1949.5s, 1500ms, 45min, 3h); the setters throw an Exception for an invalid value and for an EPUB 2 package, and setMediaDurationOf() also for an id that is not an application/smil+xml item. Attach an overlay to a content document with Manifest::setMediaOverlay(). See EPUB 3 Features.
Subject¶
public function getSubject(): string
public function setSubject(string $subject): void
public function getSubjects(): array<int, string>
public function setSubjects(array<int, string> $subjects): void
The topics of the resource (dc:subject). getSubject()/setSubject() work on the first subject; getSubjects()/setSubjects() work on all of them.
Identifiers¶
public function getIdentifiers(): array<int, string>
public function getUniqueIdentifier(): ?string
public function setIdentifiers(array<int, string> $identifiers): void
public function getTypedIdentifiers(): array<int, Identifier>
public function getIsbn(): ?string
public function setIsbn(string $isbn): void
Unambiguous references to the resource (dc:identifier), such as an ISBN or UUID.
The first value passed to setIdentifiers() is stored in the identifier that package@unique-identifier points to, so the package stays valid. At least one identifier is required. An identifier whose value changes loses its type information (opf:scheme / identifier-type refinement). getUniqueIdentifier() returns that identifier, or null when package@unique-identifier points to none.
Obfuscated fonts (listed in META-INF/encryption.xml) are keyed with the unique identifier, so when it changes, EpubFile::save() re-keys them: fonts obfuscated with the IDPF algorithm work with any identifier, while Adobe's older algorithm needs a urn:uuid: identifier, and saving throws an Exception naming the font otherwise.
getTypedIdentifiers() returns PhpEpub\Identifier objects (value, scheme). The scheme, in upper case, comes from the EPUB 2 opf:scheme attribute, else the EPUB 3 identifier-type refinement (ONIX codes 02 and 15 read as ISBN, 06 as DOI), else a urn:<scheme>: or doi: prefix, else a valid ISBN check digit; it is null when none of these applies. getIsbn() returns the first ISBN without prefix, hyphens or spaces (e.g. 9780306406157), or null.
foreach ($metadata->getTypedIdentifiers() as $identifier) {
echo $identifier->scheme ?? 'unknown', ': ', $identifier->value, "\n";
}
setIsbn() stores an ISBN-10 or ISBN-13 without prefix, hyphens or spaces, and throws an Exception when its check digit is wrong or an ISBN-13 does not start with 978 or 979. The first ISBN identifier gets the new value; a book without one gets a new dc:identifier. Either way the identifier is typed as an ISBN, with opf:scheme="ISBN" in EPUB 2 and an identifier-type refinement in EPUB 3 (ONIX code 15 for an ISBN-13, 02 for an ISBN-10), so stores and getIsbn() recognise it. The unique identifier is left as it is, unless it is the ISBN being replaced.
$metadata->setIsbn('978-0-306-40615-7');
echo $metadata->getIsbn(); // 9780306406157
Rights¶
public function getRights(): ?string
public function setRights(?string $rights): void
The rights statement (dc:rights), such as a copyright line or a licence. getRights() returns the first non-empty one, or null. setRights() replaces every dc:rights; null or '' removes them.
$metadata->setRights('© 2026 The Author. All rights reserved.');
Other Dublin Core Elements¶
public function getDublinCoreValues(string $element): array<int, string>
public function setDublinCoreValues(string $element, array<int, string> $values): void
Read and write any of the fifteen Dublin Core elements by name, including those without dedicated methods: rights (licence text), source, type, format, relation and coverage. setDublinCoreValues() replaces every element with that name ([] removes them all), reusing existing elements in order so the ids and refinements of unchanged values survive. Title and language still need at least one non-empty value, and identifiers are set with setIdentifiers(). Both throw an Exception for a name that is not a Dublin Core element.
$metadata->setDublinCoreValues('rights', ['CC BY 4.0']);
print_r($metadata->getDublinCoreValues('source'));
Series¶
public function getSeries(): ?string
public function getSeriesIndex(): ?string
public function setSeries(?string $name, int|float|string|null $index = null): void
Books record their series in two ways: EPUB 3 <meta property="belongs-to-collection"> refined with collection-type series (and group-position for the position), and Calibre's calibre:series and calibre:series_index metas, which EPUB 2 books and many reading systems use. getSeries() and getSeriesIndex() read the EPUB 3 series collection, or else the Calibre metas, and return null when there is none. The index is returned as written, e.g. "2" or "2.5".
setSeries() writes the Calibre metas and, for EPUB 3 packages, a series collection that replaces the previous one; other collections are kept. null removes the series (and its index); a call without an index removes the old index. It throws an Exception for an empty name or an index that is not a number.
$metadata->setSeries('The Expanse', 2);
echo $metadata->getSeries(), ' #', $metadata->getSeriesIndex(); // The Expanse #2
Other <meta> Elements¶
public function getMeta(string $name): ?string
public function setMeta(string $name, ?string $content): void
public function getProperty(string $property): ?string
public function setProperty(string $property, ?string $value): void
public function getMetaValues(string $name): array
public function setMetaValues(string $name, array $values): void
public function getPropertyValues(string $property): array
public function setPropertyValues(string $property, array $values): void
public function getVersion(): string
Access to metadata beyond the Dublin Core fields:
getMeta()/setMeta()read and write EPUB 2 style<meta name="…" content="…"/>elements, such ascalibre:series,calibre:series_indexorcover.getProperty()/setProperty()read and write EPUB 3<meta property="…">value</meta>elements that describe the whole book, such asbelongs-to-collectionorschema:accessMode. Refinements of other elements (refines="#id") are ignored.- Passing
nullas the value removes the element.getMeta()/getProperty()return the first match;setMeta()/setProperty()replace every match with the single new value, so no stale duplicates are left. getMetaValues()/setMetaValues()andgetPropertyValues()/setPropertyValues()read and write all elements with that name or property as a list (e.g. severaldcterms:subjectproperties); an empty list removes them all.getVersion()returns the package version, e.g.2.0or3.0.
$metadata->setMeta('calibre:title_sort', 'Expanse, The');
$metadata->setProperty('schema:accessMode', 'textual');
A prefixed property needs its prefix reserved (schema, dcterms, rendition, media, marc, onix, xsd, a11y) or declared in the package's prefix attribute, because EPUBCheck rejects an undeclared one. setProperty()/setPropertyValues() declare ibooks and prism themselves and throw an Exception for any other undeclared prefix; declare it first with declarePrefix(). Removing a property never needs a declaration.
public function declarePrefix(string $prefix, string $uri): void
$metadata->declarePrefix('foo', 'http://example.com/vocab/foo#');
$metadata->setProperty('foo:bar', 'value');
Usage Example¶
use PhpEpub\EpubFile;
$epubFile = EpubFile::open('/path/to/your.epub');
$metadata = $epubFile->getMetadata();
// Read metadata
echo "Title: " . $metadata->getTitle();
echo "Authors: " . implode(', ', $metadata->getAuthors());
echo "Language: " . $metadata->getLanguage();
// Update metadata
$metadata->setTitle('My New Title');
$metadata->setAuthors(['John Doe', 'Jane Smith']);
$metadata->setPublisher('My Publishing House');
$metadata->setSubjects(['Fiction', 'Adventure']);
$metadata->setLanguage('en');
// Save the complete EPUB (pending metadata changes are written first)
$epubFile->save();
Internal Implementation¶
The Metadata class uses PHP traits to organize code:
InteractsWithTitle- Title handlingInteractsWithDescription- Description handlingInteractsWithDate- Date handlingInteractsWithAuthors- Author and creator handlingInteractsWithContributors- Contributor handlingInteractsWithPublisher- Publisher handlingInteractsWithLanguage- Language handlingInteractsWithSubject- Subject handlingInteractsWithIdentifier- Identifier handlingInteractsWithSeries- Series handling (EPUB 3 collections and Calibre metas)InteractsWithAccessibility- Accessibility metadata
Each trait is a thin layer over shared protected helpers in Metadata (getDcValue(), getDcValues(), setDcValue(), setDcValues()), which look up elements inside <metadata> by namespace URI rather than by prefix.
Error Handling¶
- The constructor throws
InvalidEpubExceptionif the package has no<metadata>element. setIdentifiers([])throws anException, because a package needs at least one identifier.setLanguage()/setLanguages()throw anExceptionfor a malformed BCP 47 tag, andsetDate()for a date that is not W3CDTF.setCreators()andsetContributors()throw anExceptionfor an empty name.setTitle(),setLanguage()andsetIdentifiers()throw anExceptionfor an empty or whitespace-only value, because the OPF specification requires these elements to have content.- Every setter (including
setMeta()andsetProperty()) throws anExceptionfor a value that is not valid UTF-8 or contains a character XML cannot store (control characters other than tab, newline and carriage return). Convert legacy encodings first, e.g. withmb_convert_encoding($value, 'UTF-8', 'ISO-8859-1'). The package is left unchanged when a value is rejected, even if it was one of several in a list. save()throws anExceptionif the OPF file cannot be written.