Skip to content

operation-hardcode/smpp-php

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

27 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Async SMPP v3.4 protocol implementation for PHP.

ci Codecov Software License

âť— The library will be experimental until the first major release.

See the specification for more information about protocol.

Contents

Installation

composer require operation-hardcode/smpp-php

Requirements

This library requires PHP 8.1 or later.

It is recommended to install the phpinnacle/ext-buffer extension to speed up the phpinnacle/buffer.

Features

  • BIND_RECEIVER
  • BIND_TRANSMITTER
  • BIND_TRANSCEIVER
  • ALERT_NOTIFICATION
  • CANCEL_SM
  • DATA_SM
  • DELIVER_SM
  • ENQUIRE_LINK
  • GENERIC_NACK
  • OUTBIND
  • QUERY_SM
  • REPLACE_SM
  • SUBMIT_SM
  • UNBIND
  • SUBMIT_MULTI

Usage

Receiver


<?php

declare(strict_types=1);

require_once __DIR__.'/../vendor/autoload.php';

use OperationHardcode\Smpp;
use OperationHardcode\Smpp\Interaction\Connector;
use OperationHardcode\Smpp\Protocol\PDU;
use OperationHardcode\Smpp\Transport\ConnectionContext;

Amp\Loop::run(function (): \Generator {
    $executor = Connector::connect()
        ->asReceiver(ConnectionContext::default(uri: 'smscsim.melroselabs.com:2775', systemId: '900238', password: 'c58775'));

    try {
        yield $executor->consume(function (PDU $pdu, Smpp\Interaction\SmppExecutor $executor): \Generator {
            var_dump($pdu);

            yield $executor->fin();
        });
    } catch (Smpp\Interaction\ConnectionWasNotEstablished) {
        yield $executor->fin();

        Amp\Loop::stop();
    }
});

Transmitter


<?php

declare(strict_types=1);

require_once __DIR__.'/../vendor/autoload.php';

use OperationHardcode\Smpp\Interaction\Connector;
use OperationHardcode\Smpp\Protocol\Command\SubmitSm;
use OperationHardcode\Smpp\Protocol\Destination;
use OperationHardcode\Smpp\Transport\ConnectionContext;

Amp\Loop::run(function (): \Generator {
    $transmitter = Connector::connect()
        ->asTransmitter(ConnectionContext::default(uri: 'smscsim.melroselabs.com:2775', systemId: '900238', password: 'c58775'));

    yield $transmitter->produce(new SubmitSm(new Destination('xxxx'), new Destination('xxxxx'), 'Hello, world'));
    yield $transmitter->fin();
});

Transceiver


<?php

declare(strict_types=1);

require_once __DIR__.'/../vendor/autoload.php';

use Amp\Log\ConsoleFormatter;
use Amp\Log\StreamHandler;
use Monolog\Logger;
use Monolog\Processor\MemoryPeakUsageProcessor;
use Monolog\Processor\MemoryUsageProcessor;
use Monolog\Processor\PsrLogMessageProcessor;
use OperationHardcode\Smpp;
use OperationHardcode\Smpp\Interaction\Connector;
use OperationHardcode\Smpp\Protocol\PDU;
use OperationHardcode\Smpp\Transport\ConnectionContext;
use Psr\Log\LoggerInterface;

function stdoutLogger(string $loggerName): LoggerInterface
{
    $handler = new StreamHandler(Amp\ByteStream\getStdout());
    $handler->setFormatter(new ConsoleFormatter());

    return new Logger($loggerName, [$handler], [new PsrLogMessageProcessor(), new MemoryUsageProcessor(), new MemoryPeakUsageProcessor()]);
}

Amp\Loop::run(function (): \Generator {
    $transceiver = Connector::connect()->asTransceiver(
        ConnectionContext::default(uri: 'smscsim.melroselabs.com:2775', systemId: '900238', password: 'c58775'),
        stdoutLogger('transceiver'),
    );

    try {
        yield $transceiver->consume(function (PDU $pdu, Smpp\Interaction\SmppExecutor $executor): \Generator {
            if ($pdu instanceof Smpp\Protocol\Command\Replyable) {
                yield $executor->produce($pdu->reply());
            }

            var_dump($pdu);

            return new Amp\Success();
        });
    } catch (\Throwable $e) {
        echo $e->getMessage() . \PHP_EOL;

        Amp\Loop::stop();
    }
});

Signals


<?php

declare(strict_types=1);

require_once __DIR__.'/../vendor/autoload.php';

use Amp;
use OperationHardcode\Smpp\Interaction\Connector;
use OperationHardcode\Smpp\Transport\ConnectionContext;
use OperationHardcode\Smpp;

Amp\Loop::run(function (): \Generator {
    $logger = stdoutLogger('transceiver');

    $transceiver = Connector::connect()
        ->asTransceiver(
            ConnectionContext::default(uri: 'smscsim.melroselabs.com:2775', systemId: '900238', password: 'c58775'),
            $logger
        );

    Amp\Loop::unreference(
        Amp\Loop::onSignal(\SIGINT, function () use ($transceiver): \Generator {
            yield $transceiver->fin();
        })
    );

    try {
        yield $transceiver->consume(function (PDU $pdu, SmppExecutor $executor): \Generator {
           if ($pdu instanceof Smpp\Protocol\Command\Replyable) {
                $reply = $pdu->reply();

                yield $executor->produce($reply);
            }

            return new Amp\Success();
        });
    } catch (\Throwable $e) {
        echo $e->getMessage() . \PHP_EOL;

        Amp\Loop::stop();
    }
});

Extensions


If you need more options when working with library, you can write an extension. The library provides 4 hooks that are called by the executor at different times of their work:

  • If you want to extend the behaviour on successful connection, implement the OperationHardcode\Smpp\Interaction\Extensions\AfterConnectionEstablishedExtension interface.
  • If you want to extend the behaviour on disconnection, implement the OperationHardcode\Smpp\Interaction\Extensions\AfterConnectionClosedExtension interface.
  • If you want to extend the behaviour on each PDU produced by executor, implement the OperationHardcode\Smpp\Interaction\Extensions\AfterPduProducedExtension interface.
  • Or vice versa, if you want to extend the behaviour on each PDU consumed by executor, implement the OperationHardcode\Smpp\Interaction\Extensions\AfterPduConsumedExtension interface.
<?php

declare(strict_types=1);

require_once __DIR__.'/../vendor/autoload.php';

use Amp;
use OperationHardcode\Smpp\Interaction\Connector;
use OperationHardcode\Smpp\Transport\ConnectionContext;
use OperationHardcode\Smpp;
use OperationHardcode\Smpp\Interaction\SmppExecutor;
use OperationHardcode\Smpp\Protocol\PDU;
use Psr\Log\LoggerInterface;
use OperationHardcode\Smpp\Interaction\Extensions\AfterConnectionEstablishedExtension;
use OperationHardcode\Smpp\Interaction\Extensions\AfterConnectionClosedExtension;
use OperationHardcode\Smpp\Interaction\Extensions\AfterPduConsumedExtension;
use OperationHardcode\Smpp\Interaction\Extensions\AfterPduProducedExtension;

final class Debug implements
    AfterConnectionEstablishedExtension,
    AfterConnectionClosedExtension,
    AfterPduConsumedExtension,
    AfterPduProducedExtension
{
    public function __construct(private LoggerInterface $logger)
    {
    }

    public function afterConnectionEstablished(SmppExecutor $smppExecutor): Amp\Promise
    {
        return Amp\call(function (): void {
           $this->logger->debug('Connection was established.');
        });
    }

    public function afterConnectionClosed(?\Throwable $e = null): Amp\Promise
    {
        return Amp\call(function () use ($e): void {
            $this->logger->debug('Connection was closed.');
        });
    }

    public function afterPduConsumed(PDU $pdu, SmppExecutor $smppExecutor): Amp\Promise
    {
        return Amp\call(function () use ($pdu): void {
            $this->logger->debug('The pdu "{pdu}" was consumed.', [
                'pdu' => get_class($pdu),
            ]);
        });
    }

    public function afterPduProduced(PDU $pdu, SmppExecutor $smppExecutor): Amp\Promise
    {
        return Amp\call(function () use ($pdu): void {
            $this->logger->debug('The pdu "{pdu}" was produced.', [
                'pdu' => get_class($pdu),
            ]);
        });
    }
}

Amp\Loop::run(function (): \Generator {
    $logger = stdoutLogger('transceiver');

    $transceiver = Connector::connect()
        ->asTransceiver(
            ConnectionContext::default(uri: 'smscsim.melroselabs.com:2775', systemId: '900238', password: 'c58775'),
            $logger
        )
        ->withExtensions([
            new Debug($logger),
        ]);

    Amp\Loop::unreference(
        Amp\Loop::onSignal(\SIGINT, function () use ($transceiver): \Generator {
            yield $transceiver->fin();
        })
    );

    try {
        yield $transceiver->consume(function (PDU $pdu, SmppExecutor $executor): \Generator {
           if ($pdu instanceof Smpp\Protocol\Command\Replyable) {
                $reply = $pdu->reply();

                yield $executor->produce($reply);
            }

            return new Amp\Success();
        });
    } catch (\Throwable $e) {
        echo $e->getMessage() . \PHP_EOL;

        Amp\Loop::stop();
    }
});

Heartbeat


The library provides the Heartbeat extension which periodically send the ENQUIRE_LINK command, required by SMPP specification. You can configure the interval and timeout within which you must receive a ENQUIRE_LINK_RESP with command status ESME_ROK.

<?php

declare(strict_types=1);

require_once __DIR__.'/../vendor/autoload.php';

use Amp;
use OperationHardcode\Smpp\Interaction\Connector;
use OperationHardcode\Smpp\Transport\ConnectionContext;
use OperationHardcode\Smpp\Interaction\SmppExecutor;
use OperationHardcode\Smpp\Interaction\Heartbeat\Heartbeat;
use OperationHardcode\Smpp\Time;

Amp\Loop::run(function (): \Generator {
    $logger = stdoutLogger('transceiver');

    $transceiver = Connector::connect()
        ->asTransceiver(
            ConnectionContext::default(uri: 'smscsim.melroselabs.com:2775', systemId: '900238', password: 'c58775'),
            $logger
        )
        ->withExtensions([
            new Heartbeat(
                Time::fromSeconds(10), // interval
                Time::fromSeconds(2), // timeout
                $logger,
            ),
        ]);

    Amp\Loop::unreference(
        Amp\Loop::onSignal(\SIGINT, function () use ($transceiver): \Generator {
            yield $transceiver->fin();
        })
    );

    try {
        yield $transceiver->consume(function (PDU $pdu, SmppExecutor $executor): \Generator {
           if ($pdu instanceof Smpp\Protocol\Command\Replyable) {
                $reply = $pdu->reply();

                yield $executor->produce($reply);
            }

            return new Amp\Success();
        });
    } catch (\Throwable $e) {
        echo $e->getMessage() . \PHP_EOL;

        Amp\Loop::stop();
    }
});

Testing

$ composer test

License

The MIT License (MIT). See License File for more information.