Skip to content

dev-quentin/sip2

Repository files navigation

SIP2 — PHP Library

PHP implementation of the 3M Standard Interchange Protocol v2.00 (SIP2), the de facto standard for communication between library self-service terminals (SC) and automated circulation systems (ACS).

The library handles parsing, serialization, checksums and message dispatching. TCP transport is intentionally out of scope: you keep full control of the socket layer (stream sockets, ReactPHP, Swoole...) and feed raw frames to the library.

Features

  • All 16 SIP2 requests and 15 responses (checkout, checkin, patron status, holds, renewals, fees...)
  • Strictly typed messages with named constructor arguments and readonly properties
  • Optional checksum and sequence number handling (AY/AZ fields), spec-compliant
  • Middleware pipeline for cross-cutting concerns (logging, validation...)
  • Dual mode: act as the ACS (server side) or the SC (client side) through a single facade
  • Zero runtime dependency, fully unit-tested

Requirements

  • PHP >= 8.3

Installation

The package is not published on Packagist yet. Clone the repository next to your project, then declare it as a path repository in your composer.json:

git clone https://github.com/dev-quentin/sip2.git
{
    "repositories": [
        {
            "type": "path",
            "url": "../sip2"
        }
    ],
    "require": {
        "sip2/sip2": "*"
    }
}
composer update sip2/sip2

Alternatively, skip the manual clone and let Composer fetch it straight from GitHub with a VCS repository:

{
    "repositories": [
        {
            "type": "vcs",
            "url": "https://github.com/dev-quentin/sip2.git"
        }
    ],
    "require": {
        "sip2/sip2": "dev-main"
    }
}

Usage

The only public entry point is SIP2\Protocol\Protocol. Everything else (drivers, parser, serializer) stays internal.

ACS mode (server side)

Register one handler per request type, then feed raw frames coming from your socket. The handler receives the parsed request and a pre-built response to mutate:

use SIP2\Enums\ProtocolMode;
use SIP2\Messages\Requests\CheckoutRequest;
use SIP2\Messages\Responses\CheckoutResponse;
use SIP2\Protocol\Protocol;

$protocol = Protocol::create(mode: ProtocolMode::ACS, checksums: true);

$protocol->on(CheckoutRequest::class, function (CheckoutRequest $req, CheckoutResponse $res): CheckoutResponse {
    $res->ok = true;
    $res->patronIdentifier = $req->patronIdentifier;
    $res->itemIdentifier = $req->itemIdentifier;

    return $res;
});

// $rawRequest comes from your socket; $rawResponse is ready to be written back.
$rawResponse = $protocol->handle($rawRequest);

SC mode (client side)

send() returns a fluent request: getFrame() gives you the wire-format string to transmit, resolve() parses the ACS reply and triggers your callbacks:

use SIP2\Enums\ProtocolMode;
use SIP2\Exceptions\SipException;
use SIP2\Messages\Requests\CheckoutRequest;
use SIP2\Messages\Responses\CheckoutResponse;
use SIP2\Protocol\Protocol;

$protocol = Protocol::create(mode: ProtocolMode::SC, checksums: true);

$request = $protocol->send(new CheckoutRequest(
    scRenewalPolicy: true,
    noBlock: false,
    institutionId: 'MAIN',
    patronIdentifier: 'PATRON-42',
    itemIdentifier: 'ITEM-1234',
    terminalPassword: 'secret',
));

fwrite($socket, $request->getFrame());
$rawReply = fread($socket, 4096);

$request
    ->then(fn (CheckoutResponse $res) => printf('Checked out: %s', $res->ok ? 'yes' : 'no'))
    ->catch(fn (SipException $e) => error_log($e->getMessage()))
    ->resolve($rawReply);

Middleware

Middlewares run on every message, in registration order:

use SIP2\Messages\MessageInterface;
use SIP2\Middleware\MiddlewareInterface;

final class LoggingMiddleware implements MiddlewareInterface
{
    public function process(MessageInterface $message, callable $next): MessageInterface
    {
        error_log(sprintf('SIP2 message %s', $message->getMessageId()->value));

        return $next($message);
    }
}

$protocol->pipe(new LoggingMiddleware());

Custom enum values

Known values are covered by backed enums (Language, MediaType, FeeType...), but every enum-typed constructor argument also accepts a raw string, so vendor-specific values do not require extending the library:

use SIP2\Enums\Language;
use SIP2\Messages\Requests\PatronStatusRequest;

new PatronStatusRequest(language: Language::French, ...);
new PatronStatusRequest(language: '099', ...);

Error Handling

All exceptions extend SIP2\Exceptions\SipException:

Exception Thrown when
WrongProtocolModeException calling an ACS method in SC mode, or vice versa
ChecksumException checksum mismatch on a received frame
ParseException malformed frame or unknown message identifier
TimeoutException no response received (raised by the consumer)

Development

vendor/bin/phpunit           # test suite
vendor/bin/phpstan analyse   # static analysis
vendor/bin/php-cs-fixer fix  # code style

License

This software is governed by the CeCILL v2.1 license under French law, compatible with the GNU GPL.

Copyright (c) 2026 dev-quentin

About

PHP library implementing the 3M Standard Interchange Protocol v2.00 (SIP2)

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Contributors

Languages