PHPackages                             somework/offset-page - 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. [Database &amp; ORM](/categories/database)
4. /
5. somework/offset-page

ActiveLibrary[Database &amp; ORM](/categories/database)

somework/offset-page
====================

Convert page-based APIs to offset-based pagination. Transform any paginated data source into seamless offset-based access for databases, REST APIs, and external services.

3.0.0(5mo ago)05.6k↓45%[1 PRs](https://github.com/somework/offset-page/pulls)MITPHPPHP ^8.2CI passing

Since Jun 4Pushed 2mo ago1 watchersCompare

[ Source](https://github.com/somework/offset-page)[ Packagist](https://packagist.org/packages/somework/offset-page)[ Docs](https://github.com/somework/offset-page)[ RSS](/packages/somework-offset-page/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (2)Dependencies (4)Versions (15)Used By (0)

Offset Page
===========

[](#offset-page)

[![CI](https://camo.githubusercontent.com/346663e38084d7a0f5047e1933150cf41bcdc1d1e945b8870a1e3f17c63fb5b9/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f736f6d65776f726b2f6f66667365742d706167652f63692e796d6c3f6272616e63683d6d6173746572266c6162656c3d4349)](https://github.com/somework/offset-page/actions/workflows/ci.yml?query=branch%3Amaster)[![Latest Stable Version](https://camo.githubusercontent.com/fc28b7a737ede3e39c9fef32d4c16e2c4d2dfab6e3168b80c149df6e7167b892/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f736f6d65776f726b2f6f66667365742d706167652e737667)](https://packagist.org/packages/somework/offset-page)[![License](https://camo.githubusercontent.com/7013272bd27ece47364536a221edb554cd69683b68a46fc0ee96881174c4214c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75652e737667)](LICENSE)[![PHP Version](https://camo.githubusercontent.com/1697aa707d4b31d02e105f265b2212c622c438469483dedd2698c78ca3f483ac/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f736f6d65776f726b2f6f66667365742d706167652e737667)](https://packagist.org/packages/somework/offset-page)

Transform page-based APIs into offset-based pagination with zero hassle
=======================================================================

[](#transform-page-based-apis-into-offset-based-pagination-with-zero-hassle)

Convert any page-based data source (APIs, databases, external services) into seamless offset-based pagination. Perfect for when your app needs "give me items 50-99" but your data source only speaks "give me page 3 with 25 items each".

✨ **Framework-agnostic** • 🚀 **High performance** • 🛡️ **Type-safe** • 🧪 **Well tested**

Why This Package?
-----------------

[](#why-this-package)

**The Problem**: Your application uses offset-based pagination ("show items 100-199"), but your database or API only supports page-based pagination ("give me page 5 with 20 items").

**Manual Solution**: Write complex math to convert offsets to pages, handle edge cases, manage memory efficiently, and deal with different data source behaviors.

**This Package**: Handles all the complexity automatically. Just provide a callback that fetches pages, and get seamless offset-based access.

### Why Choose This Over Manual Implementation?

[](#why-choose-this-over-manual-implementation)

- ✅ **Zero Boilerplate** - One callback function vs dozens of lines of pagination math
- ✅ **Memory Efficient** - Lazy loading prevents loading unnecessary data
- ✅ **Type Safe** - Full PHP 8.2+ type safety with generics
- ✅ **Well Tested** - Comprehensive test suite covering edge cases
- ✅ **Framework Agnostic** - Works with any PHP project (Laravel, Symfony, plain PHP, etc.)
- ✅ **Production Ready** - Used in real applications with battle-tested logic

### Why Choose This Over Framework-Specific Solutions?

[](#why-choose-this-over-framework-specific-solutions)

Unlike Laravel's `paginate()` or Symfony's pagination components that are tied to specific frameworks and ORMs, this package:

- Works with **any data source** (SQL, NoSQL, REST APIs, GraphQL, external services)
- Has **zero dependencies** on frameworks or ORMs
- Provides **consistent behavior** across different projects and teams
- Is **future-proof** - not tied to any framework's roadmap

Installation
------------

[](#installation)

```
composer require somework/offset-page
```

Quickstart
----------

[](#quickstart)

**Get started in 30 seconds:**

```
use SomeWork\OffsetPage\OffsetAdapter;

// Your page-based API or database function
function fetchPage(int $page, int $pageSize): array {
    $offset = ($page - 1) * $pageSize;
    // Your database query or API call here
    return fetchFromDatabase($offset, $pageSize);
}

// Create adapter with a callback
$adapter = OffsetAdapter::fromCallback(function (int $page, int $pageSize) {
    $data = fetchPage($page, $pageSize);
    foreach ($data as $item) {
        yield $item;
    }
});

// Get items 50-99 (that's offset 50, limit 50)
$items = $adapter->fetchAll(50, 50);

// That's it! Your page-based source now works with offset-based requests.
```

How It Works
------------

[](#how-it-works)

The adapter automatically converts your offset-based requests into page-based requests:

```
// You want: "Give me items 50-99"
$items = $adapter->fetchAll(50, 50);

// The adapter translates this into:
// Page 3 (items 51-75), Page 4 (items 76-100)
// Then returns exactly items 50-99 from the results
```

Usage Patterns
--------------

[](#usage-patterns)

### Database with LIMIT/OFFSET

[](#database-with-limitoffset)

```
$adapter = OffsetAdapter::fromCallback(function (int $page, int $pageSize) {
    $offset = ($page - 1) * $pageSize;

    $stmt = $pdo->prepare("SELECT * FROM users LIMIT ? OFFSET ?");
    $stmt->execute([$pageSize, $offset]);

    foreach ($stmt->fetchAll() as $user) {
        yield $user;
    }
});

$users = $adapter->fetchAll(100, 25); // Users 100-124
```

### REST API with Page Parameters

[](#rest-api-with-page-parameters)

```
$adapter = OffsetAdapter::fromCallback(function (int $page, int $pageSize) {
    $response = $httpClient->get("/api/products?page={$page}&size={$pageSize}");
    $data = json_decode($response->getBody(), true);

    foreach ($data['products'] as $product) {
        yield $product;
    }
});

$products = $adapter->fetchAll(50, 20); // Products 50-69
```

### Custom Source Implementation

[](#custom-source-implementation)

For complex scenarios, implement `SourceInterface`:

```
use SomeWork\OffsetPage\SourceInterface;

class DatabaseSource implements SourceInterface
{
    public function __construct(private PDO $pdo) {}

    public function execute(int $page, int $pageSize): \Generator
    {
        $offset = ($page - 1) * $pageSize;
        $stmt = $this->pdo->prepare("SELECT * FROM items LIMIT ? OFFSET ?");
        $stmt->execute([$pageSize, $offset]);

        foreach ($stmt->fetchAll() as $item) {
            yield $item;
        }
    }
}

$adapter = new OffsetAdapter(new DatabaseSource($pdo));
$items = $adapter->fetchAll(1000, 100);
```

Advanced Usage
--------------

[](#advanced-usage)

### Error Handling

[](#error-handling)

The library provides specific exceptions for different error types:

```
use SomeWork\OffsetPage\Exception\InvalidPaginationArgumentException;
use SomeWork\OffsetPage\Exception\PaginationExceptionInterface;

try {
    $result = $adapter->fetchAll(-1, 50); // Invalid!
} catch (InvalidPaginationArgumentException $e) {
    echo "Invalid parameters: " . $e->getMessage();
} catch (PaginationExceptionInterface $e) {
    echo "Pagination error: " . $e->getMessage();
}
```

### Streaming Results

[](#streaming-results)

For memory-efficient processing of large result sets:

```
$result = $adapter->execute(1000, 500);

while (null !== ($item = $result->fetch())) {
    processItem($item); // Process one at a time
}
```

### Getting Result Metadata

[](#getting-result-metadata)

```
$result = $adapter->execute(50, 25);
$items = $result->fetchAll();
$count = $result->getFetchedCount(); // Number of items actually returned
```

Upgrading
---------

[](#upgrading)

See [UPGRADE.md](UPGRADE.md) for migration guides between major versions, including breaking changes and upgrade paths.

Development
-----------

[](#development)

### Available Scripts

[](#available-scripts)

This project includes several composer scripts for development and quality assurance:

```
composer test          # Run PHPUnit tests
composer stan          # Run PHPStan static analysis
composer cs-check      # Check code style with PHP-CS-Fixer
composer cs-fix         # Fix code style issues with PHP-CS-Fixer
composer quality        # Run static analysis and code style checks
```

### Testing

[](#testing)

The library includes comprehensive tests covering:

- Unit tests for all core classes
- Integration tests for real-world scenarios
- Property-based tests for edge cases
- Memory usage and performance tests
- Exception handling scenarios

Author
------

[](#author)

[Igor Pinchuk](https://github.com/somework) -

License
-------

[](#license)

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

###  Health Score

53

—

FairBetter than 97% of packages

Maintenance79

Regular maintenance activity

Popularity22

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity83

Battle-tested with a long release history

 Bus Factor1

Top contributor holds 80% 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 ~282 days

Recently: every ~776 days

Total

12

Last Release

164d ago

Major Versions

1.1.2 → 2.0.02025-12-04

2.0.0 → 3.0.02025-12-05

PHP version history (2 changes)1.0.0PHP &gt;=5.5

2.0.0PHP ^8.2

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/19801428?v=4)[Igor Pinchuk](/maintainers/somework)[@somework](https://github.com/somework)

---

Top Contributors

[![somework](https://avatars.githubusercontent.com/u/19801428?v=4)](https://github.com/somework "somework (8 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (2 commits)")

---

Tags

apidatabasepaginationconverteradapterframework agnosticphp8offsetpage-sizeoffset-paginationpage-basedpagination-adapterlimit-offsetdata-source

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/somework-offset-page/health.svg)

```
[![Health](https://phpackages.com/badges/somework-offset-page/health.svg)](https://phpackages.com/packages/somework-offset-page)
```

###  Alternatives

[tommyknocker/pdo-database-class

Framework-agnostic PHP database library with unified API for MySQL, MariaDB, PostgreSQL, SQLite, MSSQL, and Oracle. Query Builder, caching, sharding, window functions, CTEs, JSON, migrations, ActiveRecord, CLI tools, AI-powered analysis. Zero external dependencies.

845.7k](/packages/tommyknocker-pdo-database-class)[tbolier/php-rethink-ql

A clean and solid RethinkDB driver for PHP.

5211.7k](/packages/tbolier-php-rethink-ql)

PHPackages © 2026

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