Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Phppdu Laravel Package

jackkum/phppdu

jackkum/phppdu is a PHP library for GSM SMS PDU encoding and decoding, supporting 7-bit, 8-bit, and UCS-2 payloads, multipart messages, user data headers, timestamps, and sender/recipient fields for SMS gateways and modem integrations.

Deep Wiki
Context7

Getting Started

jackkum/phppdu is a small, single-purpose encoder: you give it an SMSC number, a destination and a message body, and it returns the hex PDU string for a GSM 03.40 SMS-SUBMIT. It does not talk to modems, gateways or queues — it only builds the string. That is the whole value proposition, and it is worth keeping the package in that box.

composer require jackkum/phppdu

Read the source before you read anything else. With 10 stars and a 2015 release there is no meaningful documentation, and you will spend more time guessing at method names than reading ~300 lines of encoding logic. In priority order:

  1. The class that builds the hex (usually src/.../PDU.php) — find the method that returns the final string and the setters that feed it.
  2. Whether it handles the 7-bit septet packing itself or delegates to bin2hex/pack.
  3. Whether concatenation (UDH) exists at all. If there is no part/reference/udh concept, you will be splitting messages yourself.
  4. composer.json — the PHP constraint. A 2015 package is very likely PHP 5.x-era and will need a fork or a patch to survive PHP 8.

Minimal first use case — one ASCII message, one recipient, dump the hex:

use Jackkum\PHPPDU\PDU; // confirm the real namespace from src/ before trusting this

$pdu = new PDU();
$pdu->setSmsc('79112223344');        // international, digits only, NO leading '+'
$pdu->setDestination('79112223344');
$pdu->setMessage('Hello');

$hex = $pdu->getPdu();               // e.g. 0011000B91791122233440000A C8329BFD06...

Before wiring it into anything, paste that hex into any online PDU decoder (or SmsMessage.createFromPdu on Android) and confirm the SMSC, destination and text round-trip. Getting this check in on day one saves you from debugging a modem driver when the real bug is an encoding one.

The realistic first production use case is not "send SMS from a web request". It is a queued job that hands the PDU to a GSM modem over a serial port, or posts it to a provider's raw-PDU endpoint. Build it that way from the start.

Implementation Patterns

Treat it as an encoder behind your own interface. The package is a leaf dependency with a high chance of abandonment. Wrap it:

namespace App\Services\Sms;

interface PduEncoder
{
    /** @return string hex-encoded SMS-SUBMIT PDU */
    public function encode(string $destination, string $message, ?string $smsc = null): string;
}

JackkumPduEncoder implements it and is the only class in your codebase that mentions the vendor namespace. When you have to fork the package (you probably will), you swap one binding in a service provider and nothing else moves. Register it in AppServiceProvider::register() and pull the SMSC from config('sms.smsc').

Serial modem flow. The PDU is only half the conversation. The AT sequence is:

AT+CMGF=0            → text mode off, PDU mode
AT+CMGS=<length>     → length = (strlen($hex) / 2) - 1 - (smsc_len_bytes)
<hex><Ctrl-Z>        → 0x1A terminates

The length calculation trips people up: it is the TPDU length in octets, excluding the SMSC length octet and the SMSC itself. When the SMSC is 00 (use SIM default) that subtraction is simpler. Run this inside a queued job with php-serial or dio, never in a request — serial writes block and time out.

Multipart / concatenated SMS. Long messages need a UDH: 05 00 03 <ref> <total> <seq> prepended to the payload, with a shared reference byte per message. If the package does not do this, do it yourself in the adapter:

  • Detect length over the limit for the chosen encoding.
  • Split on a septet boundary for GSM-7 (153 chars per part with UDH) and on a byte boundary for UCS2 (67 UCS-2 characters per part), being careful not to split a surrogate pair.
  • Re-encode each part with UDL = payload septets + 7 (the UDH occupies 7 septets after bit-fill for 7-bit data) or UDL = 6 + 2 * chars for UCS2.

Charset selection is a routing decision, not a detail. GSM 03.38 covers ASCII plus a handful of accented characters; Cyrillic, Greek, Arabic, emoji and most CJK force UCS2, which doubles the byte cost and halves the per-part limit (70 chars, 67 with UDH). Encoders in this era often default to UCS2 unconditionally. Force GSM-7 when the text allows it — it is a straight 2x cost saving on Cyrillic-heavy traffic and it keeps you single-part far more often.

$isGsm7 = preg_match('/^[A-Za-z0-9 @£$¥èéùìòÇØøÅåΔ_ΦΓΛΩΠΨΣΘΞÆæßÉ!"#¤%&\'()*+,\-.\/]*$/', $text);

Golden-vector tests. You cannot unit-test "did it send". You can unit-test "does it produce the same hex as the reference implementation". Store fixtures from a known-good source (an Android device, sms-pdu, or a modem that demonstrably works) and assert byte equality:

public function test_gsm7_ascii(): void
{
    $this->assertSame(
        '0011000B91791122233440000AC8329BFD06',
        app(PduEncoder::class)->encode('79112223344', 'hello')
    );
}

Cover at minimum: plain ASCII, a message exactly at 160 chars, 161 chars (spill to two parts), one UCS2 message, one message with an extension character (€, [, ], {, }), and an empty SMSC.

Persist what you generated. Store the PDU hex, the message reference (TP-MR) and the destination alongside your outbound message row. When a modem silently rejects a message, the hex is the only evidence you will have, and regenerating it later tells you nothing about what was actually on the wire.

Gotchas and Tips

PHP version is the first blocker. A package last released in September 2015 almost certainly targets PHP 5.4–5.6. Expect each(), loose == comparisons on octets, possibly mcrypt, a PSR-0 autoloader, and PHPUnit 4. On PHP 8 you will hit deprecations or hard errors. Plan for a fork from the start: copy the encoding class into app/Support/Pdu/, add declare(strict_types=1), and keep the public method names so your adapter stays unchanged. 300 lines is cheaper to vendor than to fight.

Strip the + from every number. SMSC and destination are given in international format with no + and no separators, and the encoder adds the 91 type-of-address. A leading + produces a TON/NPI byte of 0x81 (unknown) instead of 0x91 (international) and the network will drop it or misroute it. Validate with /^\d{6,15}$/ at the boundary.

SMSC 00 means "use the SIM's default". Many libraries require an SMSC setter to be called. If yours does, passing an empty string should yield 00 — otherwise you get an SMSC length octet of 00 followed by garbage. This is the single most common reason a valid-looking PDU is rejected.

7-bit payload counting is septets, not characters. UDL is the septet count for GSM-7 and the octet count for 8-bit/UCS2. Extension characters in GSM 03.38 (€, {, }, [, ], |, ~, ^, form feed) are encoded as an escape sequence and consume two septets while looking like one character. A 160-character message containing a € is 161 septets and silently needs two parts. Count septets, not strlen.

Empty bodies encode to UDL 0 and some networks reject them. Guard before you build.

Unicode beyond the BMP. Emoji are surrogate pairs in UCS2 and consume 4 bytes for one visible character. Some gateways expect UTF-16BE, some expect raw UCS-2 — verify with the actual provider, and never split a 4-byte sequence across parts.

Validity period and delivery reports are separate fields. The first octet (TP-VPF, TP-SRR, TP-RD) is where these live; if the library does not expose setters, you will have to patch the first octet or fork. Delivery report bit (TP-SRR bit 5, 0x20) is worth setting in production — without it you have no signal that sends are landing.

Message reference (TP-MR) must advance. Some providers deduplicate on it. If the library hardcodes 0x00, roll your own counter and patch the octet after encoding, or fork.

Debug by diffing against a reference, not by reading hex. When a message fails, decode both your PDU and a known-good one from the same phone in the same decoder and diff field by field — SMSC length, first octet, TON/NPI, DCS, UDL. Nine times out of ten the diff is the DCS byte or the UDL.

Extension points are thin. There is almost certainly no plugin surface for a custom encoder. The realistic extension point is the class itself: fork it, keep the constructor and getter signatures, and treat your fork as the maintained version. Put a README note in the fork explaining why it exists so the next developer does not try to reinstall the original.

Do not rely on Packagist availability long-term. Vendor the fork into your repo or a private VCS entry, pin the commit, and add a test that asserts the PDU for one known message so a silent upstream change or a Composer resolution flip shows up as a red test rather than a production incident.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels