PHPackages                             phanan/poddle - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Parsing &amp; Serialization](/categories/parsing)
4. /
5. phanan/poddle

ActiveLibrary[Parsing &amp; Serialization](/categories/parsing)

phanan/poddle
=============

Parse podcast feeds with PHP following PSP-1 Podcast RSS Standard

v1.2.4(1mo ago)1732.7k↑1172.6%6[1 issues](https://github.com/phanan/poddle/issues)1MITPHPPHP &gt;=8.1CI passing

Since May 18Pushed 1mo ago1 watchersCompare

[ Source](https://github.com/phanan/poddle)[ Packagist](https://packagist.org/packages/phanan/poddle)[ GitHub Sponsors](https://github.com/phanan)[ RSS](/packages/phanan-poddle/feed)WikiDiscussions main Synced 2d ago

READMEChangelogDependencies (24)Versions (15)Used By (1)

Poddle – PHP Podcast Feed Parser [![Unit Tests](https://github.com/phanan/poddle/actions/workflows/unit.yml/badge.svg)](https://github.com/phanan/poddle/actions/workflows/unit.yml)
====================================================================================================================================================================================

[](#poddle--php-podcast-feed-parser-)

[![Poddle](./assets/banner.webp)](./assets/banner.webp)

> Effortlessly parse podcast feeds in PHP following [PSP-1 Podcast RSS Standard](https://github.com/Podcast-Standards-Project/PSP-1-Podcast-RSS-Specification).

Requirements and Installation
-----------------------------

[](#requirements-and-installation)

Poddle requires PHP 8.1 or higher. You can install the library via Composer by running the following command:

```
composer require phanan/poddle
```

Usage
-----

[](#usage)

### Parse from a URL

[](#parse-from-a-url)

To parse a podcast feed from its URL, call the `fromUrl` method with the feed URL:

```
$poddle = \PhanAn\Poddle::fromUrl('https://example.com/feed.xml');
```

This method also accepts two additional parameters:

- `timeoutInSeconds`: The number of seconds to wait while trying to connect. Defaults to 30. Note that the `max_execution_time` value in your PHP configuration may still limit the maximum timeout value.
- `client`: A PSR-7-compliant client to make the request. If not provided, Poddle will use a default client. This parameter may come in handy during testing or if you need to heavily customize the request.

### Parse from XML

[](#parse-from-xml)

If you already have the XML string, you can parse it using `Poddle::fromXml` instead:

```
$poddle = \PhanAn\Poddle::fromXml(file_read_contents('feed.xml'));
```

Upon success, both `fromUrl` and `fromXml` methods return a `Poddle` object, which you can use to access the feed's channel and episodes.

### Channel

[](#channel)

To access the podcast channel, call `getChannel` on the `Poddle` object:

```
/** @var \PhanAn\Poddle\Values\Channel $channel */
$channel = $poddle->getChannel();
```

All channel's [required elements](https://github.com/Podcast-Standards-Project/PSP-1-Podcast-RSS-Specification#required-channel-elements) per the PSP-1 standard are available as properties on the `Channel` object:

```
$channel->url; // string
$channel->atomLink; // string. Alias of $channel->url
$channel->title; // string
$channel->link; // string
$channel->description; // string
$channel->language; // string
$channel->image; // string
$channel->categories; // \PhanAn\Poddle\Values\CategoryCollection
$channel->explicit; // bool
```

All channel’s [recommended elements](https://github.com/Podcast-Standards-Project/PSP-1-Podcast-RSS-Specification#recommended-channel-elements) are available via the `metadata` property:

```
$channel->metadata; // \PhanAn\Poddle\Values\ChannelMetadata
$channel->metadata->locked; // bool
$channel->metadata->guid; // ?string
$channel->metadata->author; // ?string
$channel->metadata->copyright; // ?string
$channel->metadata->txts; // \PhanAn\Poddle\Values\TxtCollection
$channel->metadata->fundings; // \PhanAn\Poddle\Values\FundingCollection
$channel->metadata->type; // ?\PhanAn\Poddle\Values\PodcastType
$channel->metadata->complete; // bool
```

### Episodes

[](#episodes)

To access the podcast episodes, call `getEpisodes` on the `Poddle` object:

```
$episodes = $poddle->getEpisodes();
```

By default, `getEpisodes` will throw an error if any of the episodes is malformed. If you want a more forgiving behavior, pass `true` into the call to silently ignore the invalid episodes.

This method returns a [lazy collection](https://laravel.com/docs/11.x/collections#lazy-collections) of `\PhanAn\Poddle\Values\Episode` objects. You can iterate over the collection to access each episode:

```
$episodes->each(function (\PhanAn\Poddle\Values\Episode $episode) {
    // Access episode properties
});
```

All episode's [required elements](https://github.com/Podcast-Standards-Project/PSP-1-Podcast-RSS-Specification#required-item-elements) per the PSP-1 standard are available as properties on the `Episode` object:

```
$episode->title; // string
$episode->enclosure; // \PhanAn\Poddle\Values\Enclosure
$episode->guid; // \PhanAn\Poddle\Values\EpisodeGuid
```

All episode's [recommended elements](https://github.com/Podcast-Standards-Project/PSP-1-Podcast-RSS-Specification#recommended-item-elements) are available via the `metadata` property:

```
$episode->metadata; // \PhanAn\Poddle\Values\EpisodeMetadata
$episode->metadata->link; // ?string
$episode->metadata->pubDate; // ?\DateTime
$episode->metadata->description; // ?string
$episode->metadata->duration; // ?int
$episode->metadata->image; // ?string
$episode->metadata->explicit; // ?bool
$episode->metadata->transcripts; // \PhanAn\Poddle\Values\TranscriptCollection
$episode->metadata->episode; // ?int
$episode->metadata->season; // ?int
$episode->metadata->type; // ?\PhanAn\Poddle\Values\EpisodeType
$episode->metadata->block; // ?bool
```

### Other Elements and Values

[](#other-elements-and-values)

If you need to access other elements or values not covered by the PSP-1 standard, you can make use of the `$xmlReader` property on the `Poddle` object:

```
$xmlReader = $poddle->xmlReader;
```

This property is an instance of `Saloon\XmlWrangler\XmlReader` and allows you to navigate the XML document directly. For example, to access the feed's `lastBuildDate` value:

```
$poddle = \PhanAn\Poddle::fromUrl('https://example.com/feed.xml');
$poddle->xmlReader->value('rss.channel.lastBuildDate')?->sole(); // 'Thu, 02 May 2024 06:44:38 +0000'
```

For more information on how to use `XmlReader`, refer to [Saloon\\XmlWrangler documentation](https://github.com/saloonphp/xml-wrangler).

The original feed content is available via the `xml` property on the `Poddle` object:

```
$xml = $poddle->xml; // string
```

Serialization and Deserialization
---------------------------------

[](#serialization-and-deserialization)

All classes under the `PhanAn\Poddle\Values` namespace implement the [`\Illuminate\Contracts\Support\Arrayable`](https://laravel.com/api/11.x/Illuminate/Contracts/Support/Arrayable.html)and [`\Illuminate\Contracts\Support\Jsonable`](https://laravel.com/api/11.x/Illuminate/Contracts/Support/Jsonable.html) contracts, which provide two methods:

```
/**
  * Get the instance as an array. All nested objects are also converted to arrays.
  */
public function toArray(): array;

/**
  * Convert the object to its JSON representation.
  */
public function toJson($options = 0): string;
```

Additionally, classes like `Channel` and `Episode` provide `fromArray` static methods to create instances from arrays. These methods allow you to easily serialize and deserialize the objects, making it straightforward to store and retrieve the data in a database or JSON file. For instance, you can create an Eloquent [custom cast](https://laravel.com/docs/11.x/eloquent-mutators#custom-casts) in Laravel this way:

```
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use PhanAn\Poddle\Values\Channel;

class ChannelCast implements CastsAttributes
{
    public function get($model, string $key, $value, array $attributes): Channel
    {
        return Channel::fromArray(json_decode($value, true));
    }

    /** @param Channel $value */
    public function set($model, string $key, $value, array $attributes)
    {
        return $value->toJson();
    }
}
```

Then, you can use the cast in your Eloquent model:

```
use Illuminate\Database\Eloquent\Model;

class Podcast extends Model
{
    protected $casts = [
        'channel' => ChannelCast::class,
    ];
}
```

Possible Questions
------------------

[](#possible-questions)

### Why does Poddle not include element or value X from the feed?

[](#why-does-poddle-not-include-element-or-value-x-from-the-feed)

Poddle follows the PSP-1 standard, which specifies the required and recommended elements for a podcast feed. If an element or value is not part of the standard, it is not included in Poddle. However, you can still access any element or value using the `xmlReader` property as described above.

### How come `pubDate` is not a required element for episodes?

[](#how-come-pubdate-is-not-a-required-element-for-episodes)

The PSP-1 standard does not require `pubDate` for episodes, but it is a recommended element. As a result, `pubDate` is available as part of the episode's metadata as a nullable `\DateTime` object. It’s up to you to determine if the value always presents and design your system accordingly.

### Why is the episode's GUID an object instead of a string?

[](#why-is-the-episodes-guid-an-object-instead-of-a-string)

Per PSP-1 standard, an item’s `` element indeed contains a globally unique string value, but it can also have an attribute `isPermaLink` that indicates whether the GUID is a permalink. As such, the item GUID in Poddle is represented as an object with two public properties: `value` (string) and `isPermaLink` (bool). The object, however, implements the `__toString` method, so you can cast it to a string for convenience.

### Where is an episode’s media URL?

[](#where-is-an-episodes-media-url)

The media URL for an episode is available as part of the episode's [`enclosure` property](https://github.com/Podcast-Standards-Project/PSP-1-Podcast-RSS-Specification#item-enclosure).

### Why are the episodes returned as an `EpisodeCollection extends LazyCollection` object? What’s a lazy collection anyway?

[](#why-are-the-episodes-returned-as-an-episodecollection-extends-lazycollection-object-whats-a-lazy-collection-anyway)

The `LazyCollection` class leverages [PHP's generators](https://www.php.net/manual/en/language.generators.overview.php) to allow you to work with very large datasets while keeping memory usage low. Since a podcast feed can potentially contain a large number of episodes, returning a `LazyCollection` allows you to iterate over the episodes without loading them all into memory at once, speeding up the process and reducing memory consumption.

### Can you support feature X/Y/Z?

[](#can-you-support-feature-xyz)

Poddle aims to be a lightweight and efficient podcast feed parser that follows the PSP-1 standard, not a full-blown RSS/Atom parser. That said, if you have a feature request or suggestion, feel free to [open an issue](https://github.com/phanan/poddle/issues/new). Better yet, you can fork the repository, implement the feature yourself, and submit a pull request.

###  Health Score

55

—

FairBetter than 97% of packages

Maintenance91

Actively maintained with recent releases

Popularity40

Moderate usage in the ecosystem

Community24

Small or concentrated contributor base

Maturity57

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 77.8% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~61 days

Recently: every ~142 days

Total

13

Last Release

40d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/cf42e22d8fba4d5475bd6dae13887c897bbdaa99765b209010c09b7fc1f3aef7?d=identicon)[phanan](/maintainers/phanan)

---

Top Contributors

[![phanan](https://avatars.githubusercontent.com/u/8056274?v=4)](https://github.com/phanan "phanan (21 commits)")[![panVag](https://avatars.githubusercontent.com/u/68543979?v=4)](https://github.com/panVag "panVag (2 commits)")[![bencarrr](https://avatars.githubusercontent.com/u/8208631?v=4)](https://github.com/bencarrr "bencarrr (1 commits)")[![eruraindil](https://avatars.githubusercontent.com/u/2336581?v=4)](https://github.com/eruraindil "eruraindil (1 commits)")[![veekthoven](https://avatars.githubusercontent.com/u/32249717?v=4)](https://github.com/veekthoven "veekthoven (1 commits)")[![vool](https://avatars.githubusercontent.com/u/441840?v=4)](https://github.com/vool "vool (1 commits)")

---

Tags

feedooppodcastsxmlxml-parserxmlparserfeedrsspodcastpsp-1

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/phanan-poddle/health.svg)

```
[![Health](https://phpackages.com/badges/phanan-poddle/health.svg)](https://phpackages.com/packages/phanan-poddle)
```

###  Alternatives

[craftcms/cms

Craft CMS

3.6k3.6M3.1k](/packages/craftcms-cms)[illuminate/http

The Illuminate Http package.

11937.9M6.9k](/packages/illuminate-http)[illuminate/auth

The Illuminate Auth package.

10528.2M1.2k](/packages/illuminate-auth)[illuminate/routing

The Illuminate Routing package.

1419.2M3.0k](/packages/illuminate-routing)[psalm/plugin-laravel

Psalm plugin for Laravel

3355.3M346](/packages/psalm-plugin-laravel)[aedart/athenaeum

Athenaeum is a mono repository; a collection of various PHP packages

245.2k](/packages/aedart-athenaeum)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
