{"id":16817137,"url":"https://github.com/phanan/poddle","last_synced_at":"2025-03-22T03:31:22.442Z","repository":{"id":240225414,"uuid":"802031090","full_name":"phanan/poddle","owner":"phanan","description":"Parse podcast feeds with PHP following PSP-1 Podcast RSS Standard","archived":false,"fork":false,"pushed_at":"2024-10-31T09:12:20.000Z","size":170,"stargazers_count":13,"open_issues_count":0,"forks_count":4,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-11-10T09:07:22.538Z","etag":null,"topics":["feed","oop","podcasts","xml","xml-parser"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/phanan.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["phanan"]}},"created_at":"2024-05-17T11:39:26.000Z","updated_at":"2024-10-30T04:06:50.000Z","dependencies_parsed_at":"2024-05-31T08:38:06.090Z","dependency_job_id":"2e9037c2-8254-405e-a4dd-708c7a88fca8","html_url":"https://github.com/phanan/poddle","commit_stats":null,"previous_names":["phanan/podcast-feed-parser"],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phanan%2Fpoddle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phanan%2Fpoddle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phanan%2Fpoddle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phanan%2Fpoddle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/phanan","download_url":"https://codeload.github.com/phanan/poddle/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244085010,"owners_count":20395523,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["feed","oop","podcasts","xml","xml-parser"],"created_at":"2024-10-13T10:46:18.703Z","updated_at":"2025-03-22T03:31:22.435Z","avatar_url":"https://github.com/phanan.png","language":"PHP","readme":"# 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)\n\n![Poddle](./assets/banner.webp)\n\n\u003e Effortlessly parse podcast feeds in PHP following [PSP-1 Podcast RSS Standard](https://github.com/Podcast-Standards-Project/PSP-1-Podcast-RSS-Specification).\n\n## Requirements and Installation\n\nPoddle requires PHP 8.1 or higher. You can install the library via Composer by running the following command:\n\n```bash\ncomposer require phanan/poddle\n```\n\n## Usage\n\n### Parse from a URL\n\nTo parse a podcast feed from its URL, call the `fromUrl` method with the feed URL:\n\n```php\n$poddle = \\PhanAn\\Poddle::fromUrl('https://example.com/feed.xml');\n```\n\nThis method also accepts two additional parameters:\n\n* `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.\n* `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.\n\n### Parse from XML\n\nIf you already have the XML string, you can parse it using `Poddle::fromXml` instead:\n\n```php\n$poddle = \\PhanAn\\Poddle::fromXml(file_read_contents('feed.xml'));\n```\n\nUpon success, both `fromUrl` and `fromXml` methods return a `Poddle` object, which you can use to access the feed's channel and episodes.\n\n### Channel\n\nTo access the podcast channel, call `getChannel` on the `Poddle` object:\n\n```php\n/** @var \\PhanAn\\Poddle\\Values\\Channel $channel */\n$channel = $poddle-\u003egetChannel();\n```\n\nAll 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:\n\n```php\n$channel-\u003etitle; // string\n$channel-\u003elink; // ?string\n$channel-\u003edescription; // string\n$channel-\u003elanguage; // string\n$channel-\u003eimage; // string\n$channel-\u003ecategories; // \\PhanAn\\Poddle\\Values\\CategoryCollection\u003c\\PhanAn\\Poddle\\Values\\Category\u003e\n$channel-\u003eexplicit; // bool\n```\n\n\u003e [!NOTE]\n\u003e Although required by the standard, `link` isn't supplied by all feeds, including some bigger players like Spotify's [Megaphone](https://megaphone.spotify.com/).\n\u003e As such, `link` is nullable in Poddle.\n\nAll channel’s [recommended elements](https://github.com/Podcast-Standards-Project/PSP-1-Podcast-RSS-Specification#recommended-channel-elements) are available via the `metadata` property:\n\n```php\n$channel-\u003emetadata; // \\PhanAn\\Poddle\\Values\\ChannelMetadata\n$channel-\u003emetadata-\u003elocked; // bool\n$channel-\u003emetadata-\u003eguid; // ?string\n$channel-\u003emetadata-\u003eauthor; // ?string\n$channel-\u003emetadata-\u003ecopyright; // ?string\n$channel-\u003emetadata-\u003etxts; // \\PhanAn\\Poddle\\Values\\TxtCollection\u003c\\PhanAn\\Poddle\\Values\\Txt\u003e\n$channel-\u003emetadata-\u003efundings; // \\PhanAn\\Poddle\\Values\\FundingCollection\u003c\\PhanAn\\Poddle\\Values\\Funding\u003e\n$channel-\u003emetadata-\u003etype; // ?\\PhanAn\\Poddle\\Values\\PodcastType\n$channel-\u003emetadata-\u003ecomplete; // bool\n```\n\n### Episodes\n\nTo access the podcast episodes, call `getEpisodes` on the `Poddle` object:\n\n```php\n$episodes = $poddle-\u003egetEpisodes();\n```\n\nBy 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.\n\nThis 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:\n\n```php\n$episodes-\u003eeach(function (\\PhanAn\\Poddle\\Values\\Episode $episode) {\n    // Access episode properties\n});\n```\n\nAll 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:\n\n```php\n$episode-\u003etitle; // string\n$episode-\u003eenclosure; // \\PhanAn\\Poddle\\Values\\Enclosure\n$episode-\u003eguid; // \\PhanAn\\Poddle\\Values\\EpisodeGuid\n```\n\nAll episode's [recommended elements](https://github.com/Podcast-Standards-Project/PSP-1-Podcast-RSS-Specification#recommended-item-elements) are available via the `metadata` property:\n\n```php\n$episode-\u003emetadata; // \\PhanAn\\Poddle\\Values\\EpisodeMetadata\n$episode-\u003emetadata-\u003elink; // ?string\n$episode-\u003emetadata-\u003epubDate; // ?\\DateTime\n$episode-\u003emetadata-\u003edescription; // ?string\n$episode-\u003emetadata-\u003eduration; // ?int\n$episode-\u003emetadata-\u003eimage; // ?string\n$episode-\u003emetadata-\u003eexplicit; // ?bool\n$episode-\u003emetadata-\u003etranscripts; // \\PhanAn\\Poddle\\Values\\TranscriptCollection\u003c\\PhanAn\\Poddle\\Values\\Transcript\u003e\n$episode-\u003emetadata-\u003eepisode; // ?int\n$episode-\u003emetadata-\u003eseason; // ?int\n$episode-\u003emetadata-\u003etype; // ?\\PhanAn\\Poddle\\Values\\EpisodeType\n$episode-\u003emetadata-\u003eblock; // ?bool\n```\n\n### Other Elements and Values\n\nIf 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:\n\n```php\n$xmlReader = $poddle-\u003exmlReader;\n```\n\nThis 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:\n\n```php\n$poddle = \\PhanAn\\Poddle::fromUrl('https://example.com/feed.xml');\n$poddle-\u003exmlReader-\u003evalue('rss.channel.lastBuildDate')?-\u003esole(); // 'Thu, 02 May 2024 06:44:38 +0000'\n```\n\nFor more information on how to use `XmlReader`, refer to [Saloon\\XmlWrangler documentation](https://github.com/saloonphp/xml-wrangler).\n\nThe original feed content is available via the `xml` property on the `Poddle` object:\n\n```php\n$xml = $poddle-\u003exml; // string\n```\n\n## Serialization and Deserialization\n\nAll classes under the `PhanAn\\Poddle\\Values` namespace implement the [`\\Illuminate\\Contracts\\Support\\Arrayable`](https://laravel.com/api/11.x/Illuminate/Contracts/Support/Arrayable.html)\nand [`\\Illuminate\\Contracts\\Support\\Jsonable`](https://laravel.com/api/11.x/Illuminate/Contracts/Support/Jsonable.html) contracts, which provide two methods:\n\n```php\n/**\n  * Get the instance as an array. All nested objects are also converted to arrays.\n  */\npublic function toArray(): array;\n\n/**\n  * Convert the object to its JSON representation.\n  */\npublic function toJson($options = 0): string;\n```\n\nAdditionally, classes like `Channel` and `Episode` provide `fromArray` static methods to create instances from arrays.\nThese 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.\nFor instance, you can create an Eloquent [custom cast](https://laravel.com/docs/11.x/eloquent-mutators#custom-casts) in Laravel this way:\n\n```php\nuse Illuminate\\Contracts\\Database\\Eloquent\\CastsAttributes;\nuse PhanAn\\Poddle\\Values\\Channel;\n\nclass ChannelCast implements CastsAttributes\n{\n    public function get($model, string $key, $value, array $attributes): Channel\n    {\n        return Channel::fromArray(json_decode($value, true));\n    }\n\n    /** @param Channel $value */\n    public function set($model, string $key, $value, array $attributes)\n    {\n        return $value-\u003etoJson();\n    }\n}\n```\n\nThen, you can use the cast in your Eloquent model:\n\n```php\nuse Illuminate\\Database\\Eloquent\\Model;\n\nclass Podcast extends Model\n{\n    protected $casts = [\n        'channel' =\u003e ChannelCast::class,\n    ];\n}\n```\n\n## Possible Questions\n\n### Why does Poddle not include element or value X from the feed?\n\nPoddle follows the PSP-1 standard, which specifies the required and recommended elements for a podcast feed.\nIf 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.\n\n### How come `pubDate` is not a required element for episodes?\n\nThe PSP-1 standard does not require `pubDate` for episodes, but it is a recommended element.\nAs a result, `pubDate` is available as part of the episode's metadata as a nullable `\\DateTime` object.\nIt’s up to you to determine if the value always presents and design your system accordingly.\n\n### Why is the episode's GUID an object instead of a string?\n\nPer PSP-1 standard, an item’s `\u003cguid\u003e` element indeed contains a globally unique string value, but it can also have an attribute `isPermaLink` that indicates whether the GUID is a permalink.\nAs such, the item GUID in Poddle is represented as an object with two public properties: `value` (string) and `isPermaLink` (bool).\nThe object, however, implements the `__toString` method, so you can cast it to a string for convenience.\n\n### Where is an episode’s media URL?\n\nThe 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).\n\n### Why are the episodes returned as an `EpisodeCollection extends LazyCollection` object? What’s a lazy collection anyway?\n\nThe `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.\nSince 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,\nspeeding up the process and reducing memory consumption.\n\n### Can you support feature X/Y/Z?\n\nPoddle aims to be a lightweight and efficient podcast feed parser that follows the PSP-1 standard, not a full-blown RSS/Atom parser.\nThat said, if you have a feature request or suggestion, feel free to [open an issue](https://github.com/phanan/poddle/issues/new).\nBetter yet, you can fork the repository, implement the feature yourself, and submit a pull request.\n","funding_links":["https://github.com/sponsors/phanan"],"categories":["PHP"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphanan%2Fpoddle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fphanan%2Fpoddle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphanan%2Fpoddle/lists"}