PHP code example of n5s / rangelog

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('symfony/console', 'https://github.com/symfony/console');
$changelog = $libClient->changelog($pkg, '6.4.0', '7.1.0');
echo $libClient->render($changelog);

$pkg = new Package('monolog/monolog', 'https://github.com/Seldaek/monolog');
$changelog = $libClient->changelog($pkg, '3.5.0', '3.7.0');
echo $libClient->render($changelog);

$pkg = new Package('akismet', 'https://wordpress.org/plugins/akismet/');
$changelog = $libClient->changelog($pkg, '5.0', '5.3');
echo $libClient->render($changelog);

$pkg = new Package('myorg/myrepo', 'https://gitlab.com/myorg/myrepo');
$changelog = $libClient->changelog($pkg, '1.0.0', '2.0.0');
echo $libClient->render($changelog);

$pkg = new Package('myorg/myrepo', 'https://gitlab.com/myorg/myrepo');
$changelog = $libClient->changelog($pkg, '1.0.0', '2.0.0');
echo $libClient->render($changelog);

$pkg = new Package('some-lib', 'https://raw.example.com/CHANGELOG.md');
$changelog = $libClient->changelog($pkg, '1.0.0', '2.0.0');
echo $libClient->render($changelog);

$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