1. Go to this page and download the library: Download n5s/rangelog library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
n5s / rangelog example snippets
declare(strict_types=1);
n5s\Rangelog\Domain\Package;
use n5s\Rangelog\Fetcher\HttpFetcher;
use n5s\Rangelog\Parser\ParserRegistry;
use n5s\Rangelog\Renderer\MarkdownRenderer;
use n5s\Rangelog\SourceProvider\GitHubFileResolver;
use n5s\Rangelog\SourceProvider\GitHubReleasesResolver;
use n5s\Rangelog\SourceProvider\GitLabFileResolver;
use n5s\Rangelog\SourceProvider\GitLabReleasesResolver;
use n5s\Rangelog\SourceProvider\MarkdownUrlResolver;
use n5s\Rangelog\SourceProvider\SourceProviderChain;
use n5s\Rangelog\SourceProvider\WordPressOrgResolver;
use Nyholm\Psr7\Factory\Psr17Factory;
use Symfony\Component\HttpClient\Psr18Client; // or any PSR-18 client
$factory = new Psr17Factory(); // PSR-17 — request and stream factory.
$client = new Psr18Client(); // PSR-18 — any implementation; no Authorization headers here.
$fetcher = new HttpFetcher($client, $factory); // Wraps the PSR-18 client; caching is optional (see § Caching).
$chain = new SourceProviderChain([ // Manual composition (BYO) — order = first wins.
new WordPressOrgResolver($fetcher), // wp.org SVN readme.txt — matches wordpress.org/plugins/* URLs.
new GitHubReleasesResolver($fetcher), // Paginated Releases API (up to 1000 entries).
new GitHubFileResolver($fetcher), // CHANGELOG.md / CHANGELOG / HISTORY.md / CHANGES.md fallback.
new GitLabReleasesResolver($fetcher), // GitLab Releases API; optional $host for self-hosted.
new GitLabFileResolver($fetcher), // GitLab Repository Files API; optional $host.
new MarkdownUrlResolver($fetcher), // Generic fallback: fetches sourceUrl as-is and parses markdown.
]);
$parsers = ParserRegistry::defaults(); // Convenience factory: parsers keyed by SourceTypes::*.
$renderer = new MarkdownRenderer(); // Produces GFM with empty fallback + partial admonition.
$libClient = new Rangelog($chain, $fetcher, $parsers, $renderer);
$pkg = new Package('symfony/console', 'https://github.com/symfony/console');
$changelog = $libClient->changelog($pkg, '6.4.0', '7.1.0');
echo $libClient->render($changelog);
use n5s\Rangelog\Auth\HostScopedCredentialProvider;
use n5s\Rangelog\Cache\Psr6CacheAdapter;
use n5s\Rangelog\Fetcher\AuthorizingFetcher;
use n5s\Rangelog\Fetcher\CachingFetcher;
use n5s\Rangelog\Fetcher\FetcherInterface;
use n5s\Rangelog\Fetcher\FetcherStack;
use n5s\Rangelog\Fetcher\HttpFetcher;
use n5s\Rangelog\Fetcher\NoRedirectClient;
use n5s\Rangelog\Renderer\MarkdownRenderer;
use n5s\Rangelog\Renderer\SanitizingMarkdownRenderer;
use n5s\Rangelog\Renderer\TruncatingRenderer;
use n5s\Rangelog\SourceProvider\GitHubFileResolver;
use n5s\Rangelog\SourceProvider\GitHubReleasesResolver;
use n5s\Rangelog\SourceProvider\GitLabFileResolver;
use n5s\Rangelog\SourceProvider\GitLabReleasesResolver;
use n5s\Rangelog\SourceProvider\MarkdownUrlResolver;
use n5s\Rangelog\SourceProvider\SourceProviderChain;
use n5s\Rangelog\SourceProvider\WordPressOrgResolver;
use Nyholm\Psr7\Factory\Psr17Factory;
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Component\HttpClient\Psr18Client;
// 1. PSR-18 client with redirects disabled at the client level too.
$rawClient = new Psr18Client(HttpClient::create(['max_redirects' => 0]));
$client = new NoRedirectClient($rawClient);
$factory = new Psr17Factory();
// 2. Credentials, scoped to known hosts. Unknown hosts get [] (no auth leak).
// ::standard() pre-wires github.com (both api + raw hosts) and gitlab.com.
// For self-hosted GHE / GitLab, drop back to the constructor with an
// explicit map.
$credentials = HostScopedCredentialProvider::standard(
githubToken: $githubPat,
gitlabToken: $gitlabPat,
);
// 3. Fetcher stack: HTTP at the base, auth then caching layered on top.
// Effective wiring is Caching(Authorizing(Http)) — cache hits skip auth entirely.
$fetcher = new FetcherStack(
base: new HttpFetcher($client, $factory),
decorators: [
fn (FetcherInterface $i) => new AuthorizingFetcher($i, $credentials),
fn (FetcherInterface $i) => new CachingFetcher($i, new Psr6CacheAdapter($yourPsr6Pool)),
],
);
// 4. Resolver chain. Constrain MarkdownUrlResolver to known hosts so
// attacker-controlled package metadata can't steer the library into
// fetching arbitrary URLs.
$chain = new SourceProviderChain([
new WordPressOrgResolver($fetcher),
new GitHubReleasesResolver($fetcher),
new GitHubFileResolver($fetcher),
new GitLabReleasesResolver($fetcher),
new GitLabFileResolver($fetcher),
new MarkdownUrlResolver($fetcher, allowedHosts: [
'raw.githubusercontent.com',
'gitlab.com',
]),
]);
// 5. Renderer stack. Sanitize upstream content, then cap output size.
$renderer = new TruncatingRenderer(
inner: new SanitizingMarkdownRenderer(
inner: new MarkdownRenderer(),
stripMentions: true,
linkRewriter: fn (string $url) => 'https://my-redirect.internal/?to=' . urlencode($url),
),
maxBytes: 60_000,
);
$pkg = new Package('react', 'https://github.com/facebook/react');
$changelog = $libClient->changelog($pkg, '18.0.0', '18.2.0');
$pkg = new Package('serde', 'https://github.com/serde-rs/serde');
$changelog = $libClient->changelog($pkg, '1.0.180', '1.0.200');
use n5s\Rangelog\Renderer\MarkdownRenderer;
use n5s\Rangelog\Renderer\TruncatingRenderer;
$renderer = new TruncatingRenderer(
inner: new MarkdownRenderer(),
maxBytes: 60_000, // GitHub PR body is ~65 KB, leave headroom.
);
$markdown = $renderer->render($changelog);
use n5s\Rangelog\Renderer\MarkdownRenderer;
use n5s\Rangelog\Renderer\SanitizingMarkdownRenderer;
$renderer = new SanitizingMarkdownRenderer(
inner: new MarkdownRenderer(),
stripMentions: true, // @user / @org/team → user / org/team
linkRewriter: fn (string $url) => 'https://my-redirect.test/?to=' . urlencode($url),
);
use n5s\Rangelog\Fetcher\CachingFetcher;
use n5s\Rangelog\Fetcher\HttpFetcher;
use Psr\SimpleCache\CacheInterface;
/** @var CacheInterface $cache Any PSR-16 implementation. */
$fetcher = new CachingFetcher(
inner: new HttpFetcher($client, $factory),
cache: $cache,
);
use n5s\Rangelog\Cache\Psr6CacheAdapter;
use n5s\Rangelog\Fetcher\CachingFetcher;
use n5s\Rangelog\Fetcher\HttpFetcher;
use Psr\Cache\CacheItemPoolInterface;
/** @var CacheItemPoolInterface $pool Any PSR-6 pool. */
$fetcher = new CachingFetcher(
inner: new HttpFetcher($client, $factory),
cache: new Psr6CacheAdapter($pool),
);
use n5s\Rangelog\Auth\HostScopedCredentialProvider;
use n5s\Rangelog\Fetcher\AuthorizingFetcher;
use n5s\Rangelog\Fetcher\CachingFetcher;
use n5s\Rangelog\Fetcher\FetcherInterface;
use n5s\Rangelog\Fetcher\FetcherStack;
use n5s\Rangelog\Fetcher\HttpFetcher;
$credentials = HostScopedCredentialProvider::standard(
githubToken: $githubPat,
gitlabToken: $gitlabPat,
);
$fetcher = new FetcherStack(
base: new HttpFetcher($psr18, $psr17Factory),
decorators: [
fn (FetcherInterface $i) => new AuthorizingFetcher($i, $credentials), // innermost
fn (FetcherInterface $i) => new CachingFetcher($i, $cache), // outermost; auth runs on cache miss only
],
);
// Effective wiring: Caching(Authorizing(Http))
use n5s\Rangelog\Auth\BearerTokenProvider;
use n5s\Rangelog\Auth\GitLabTokenProvider;
$credentials = new HostScopedCredentialProvider([
'github.example.corp' => new BearerTokenProvider($ghePat),
'gitlab.internal' => new GitLabTokenProvider($glPat),
]);
use n5s\Rangelog\Domain\Package;
use n5s\Rangelog\Exception\ChangelogException;
use n5s\Rangelog\Exception\ChangelogNotFoundException;
use n5s\Rangelog\Exception\FetchException;
use n5s\Rangelog\Exception\ParseException;
use n5s\Rangelog\Exception\RateLimitedException;
use n5s\Rangelog\Exception\UnsupportedPackageException;
try {
$package = new Package('vendor/package', 'https://github.com/vendor/package');
$changelog = $libClient->changelog($package, '1.0.0', '2.0.0');
} catch (RateLimitedException $e) {
sleep($e->retryAfter ?? 60); // Back off and retry later.
} catch (ChangelogNotFoundException $e) {
// No source could be located; fall back to package metadata, log, etc.
} catch (UnsupportedPackageException $e) {
// Known-unresolvable shape; do not retry against another resolver.
} catch (FetchException $e) {
// Network / HTTP failure; inspect $e->statusCode + $e->bodyExcerpt.
} catch (ParseException $e) {
// Upstream body could not be parsed in the expected format.
} catch (ChangelogException $e) {
// Safety net — should be unreachable given the per-type catches above.
}
bash
# Curl + nyholm/psr7
composer
Loading please wait ...
Before you can download the PHP files, the dependencies should be resolved. This can take some minutes. Please be patient.