Installation:
composer require bookeenweb/opds-parser-bundle
Ensure your project uses Symfony 2.3–3.4 (last release was 2018).
Enable Bundle:
Register in config/bundles.php (Symfony 4+) or app/AppKernel.php (Symfony 2/3):
return [
// ...
bookeen\OpdsParserBundle\OpdsParserBundle::class => ['all' => true],
];
First Use Case: Parse an OPDS feed (e.g., from a library API) into a structured array:
use bookeen\OpdsParserBundle\Parser\OpdsParser;
$parser = new OpdsParser();
$opdsXml = file_get_contents('https://example.com/opds/feed');
$feedData = $parser->parse($opdsXml);
Key Output: Returns an associative array with parsed entries, navigation links, and metadata (e.g., title, author, links, id).
Parsing Feeds:
OpdsParser to convert raw OPDS XML into PHP arrays.$entries = $feedData['entries'] ?? [];
foreach ($entries as $entry) {
echo $entry['title'] . " by " . $entry['author'][0] . "\n";
}
Handling Navigation:
function fetchAllBooks($url, OpdsParser $parser) {
$data = $parser->parse(file_get_contents($url));
$books = $data['entries'] ?? [];
foreach ($data['links'] ?? [] as $link) {
if ($link['rel'] === 'http://opds-spec.org/acquisition/collection') {
$books = array_merge($books, fetchAllBooks($link['href'], $parser));
}
}
return $books;
}
Integration with Symfony:
config/services.yaml:
services:
bookeen.opds_parser:
class: bookeen\OpdsParserBundle\Parser\OpdsParser
use bookeen\OpdsParserBundle\Parser\OpdsParser;
class BookController extends AbstractController {
public function index(OpdsParser $parser) {
$feed = $parser->parse(file_get_contents('https://library.opds'));
return $this->render('books/index.html.twig', ['books' => $feed['entries']]);
}
}
Caching:
$cacheKey = md5($url);
$feedData = $cache->get($cacheKey, function() use ($parser, $url) {
return $parser->parse(file_get_contents($url));
});
Deprecated Symfony Version:
XML Namespaces:
xmlns:dcterms). The parser may not handle all edge cases. Validate XML structure first:
$dom = new DOMDocument();
if (!$dom->loadXML($opdsXml)) {
throw new \RuntimeException("Invalid OPDS XML");
}
Missing Error Handling:
try {
$data = $parser->parse($xml);
} catch (\Exception $e) {
// Log or retry with fallback
}
Date Handling:
published) may return as strings. Convert to DateTime:
$entry['published'] = new \DateTime($entry['published']);
Extend the Parser:
OpdsParser to add custom logic (e.g., normalize author names):
class CustomOpdsParser extends OpdsParser {
protected function parseAuthor($node) {
$authors = parent::parseAuthor($node);
return array_map('trim', $authors);
}
}
Debugging:
file_put_contents('debug.opds', $opdsXml);
SimpleXMLElement to inspect nodes:
$xml = simplexml_load_string($opdsXml);
print_r($xml->xpath('//entry'));
Performance:
SimpleXMLIterator instead of loading full DOM:
$iterator = new SimpleXMLIterator($opdsXml);
foreach ($iterator as $entry) {
// Process incrementally
}
Testing:
$this->mockHttpClient()
->with('https://library.opds')
->respondWith(file_get_contents('tests/fixtures/feed.opds'));
How can I help you explore Laravel packages today?