Download the PHP package ianfoxdev/ledger without Composer

On this page you can find all versions of the php package ianfoxdev/ledger. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package ledger

ledger

php

Double-entry ledger for PHP that runs inside your application's database transaction: wallets, holds, refunds and prepaid credits that expire. Balances are derived from postings, nothing is updated or deleted, and verify() recalculates everything and names what does not add up.

Status: v0.1. Until 1.0 a minor version may change the API; such changes are marked BREAKING in the CHANGELOG. Stored transactions keep verifying: a change to what their hash covers would be breaking.

The usual wallet is a balance column and UPDATE accounts SET balance = balance + ?. Two requests read the same balance and both spend it. A wrong row gets fixed in place, and the history that would explain the balance is gone. A deposit is added to the user and taken from nowhere, so the books do not balance and nothing notices. Reconciliation finds it a month later.

Install

PHP 8.3 or later, PostgreSQL or MySQL 8. Create the tables from schema/mysql.sql. The only dependency is brick/math.

Example

The first argument of every write is an idempotency key. The same key with the same content returns the stored transaction; with different content it throws:

examples/saas runs a wallet and expiring credits end to end, including a retried capture, a refunded duplicate charge and credits that expire.

What it guarantees

Why it is built this way: docs/adr/0001-balances-are-derived-and-nothing-is-edited.md.

Holds

hold() moves the amount to an account of its own (user:42@hold:...); capture() takes part or all of it to an account with the same normal side, release() returns the rest. What remains on a hold is that account's balance, read under the lock, so a second capture of a settled hold fails even if it runs twice at once. held('user:42') is the total on open holds.

Credits that expire

Each grant is a lot on an account of its own, named after its expiry, so usage takes the lot that expires first. The customer's credits account is a liability: the platform owes the service. Used credits go to the lot's revenue account; what expires unused goes to its breakage account. For promotional credits, breakage can be the promotion expense itself, which undoes the part of the expense that was never used. An expired lot is never used, even before expire() has run.

Isolation and throughput

Run writes at READ COMMITTED. A transaction the ledger opens itself does that. When it joins yours, your level applies:

Every write to an account waits for the previous one. A revenue account that every purchase credits is locked by every purchase. On a laptop (M1 Pro, the database in Docker, each transfer its own transaction, 8 processes), bench/transfers.php gives:

1000 accounts 2 accounts
PostgreSQL 17 440 to 520 transfers/s 150 to 160 transfers/s
MySQL 8.4 (Percona) 560 to 690 transfers/s 235 to 255 transfers/s

These are not server numbers. The ratio is the point: spread writes scale, one hot account does not.

Checking the ledger

Exit code 0 when nothing is wrong, 1 with violations (each one names the account or transaction), 2 on a usage or connection error. --json for a machine-readable report. It reads the whole ledger: run it from a nightly job, not a request.

How it differs

This library is for the case in between: the ledger lives in your database, writes in your transaction, and can prove its balances.

More: docs/usage.md.

Not yet

Laravel and Symfony integration, multi-currency transactions with a rate, refunds of used credits, expiry across all customers in one call, snapshots for very long chains, export to recon, writing an outbox event in the same transaction. Open an issue if you need one of them first.

License

MIT


All versions of ledger with dependencies

PHP Build Version
Package Version
Requires php Version ^8.3
brick/math Version ^0.12 || ^0.13 || ^0.14 || ^0.15 || ^0.16 || ^0.17 || ^0.18 || ^0.19 || ^0.20 || ^1.0
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package ianfoxdev/ledger contains the following files

Loading the files please wait ...