1.4.4 2017-05-25 06:08 UTC


Quality Assurance

PHP 5.5 PHP 5.6 PHP 7 Build Status Test Coverage Code Climate

See the Test Contract


Provides a simple double entry accounting system, that can be used as a component in a larger application.


  • Chart of Accounts (see here for a reasonable explanation)

    • You can define your own chart structures
  • Account types

    • DR
      • ASSET
        • BANK
        • CUSTOMER
      • EXPENSE
    • CR
      • INCOME
        • EQUITY
        • SUPPLIER
  • Ability to save and retrieve a Chart through a simple interface

  • Organisation concept that can have multiple Chart of Accounts

  • Fantasy currencies are catered for

  • Extensible Journal system, allowing recording of account transactions

The library is released under the GNU GPL V3 or later license

Commercial licenses are available


Whilst full blown accounting systems are available, requiring a massive integration effort, some applications simply need to be able keep some form of internal account. This library is the direct descendant of something I wrote for a client many years ago to keep account of game points earned on a web site. Using the double entry accounting paradigm allowed the site owner to keep track of who had gathered points, and in which ways, whilst at the same time seeing what this meant to their business as game points translated into real world value for the customer by way of discounts and prizes.


The current library support Organisations, Charts of Account, Journals and Control Accounts.


  • Accounting
    • closing accounts
  • Reporting
    • balance sheet
    • trial balance
    • profit and loss

If you want more, either suggest it, or better still, fork it and provide a pull request.

Check out ZF4 Packages for more packages


Coding Basics

Creating a new chart of accounts

Creating it manually
use SAccounts\Chart;
use SAccounts\Organisation;
use Chippyash\Currency\Factory as Currency;
use Chippyash\Type\Number\IntType;
use Chippyash\Type\String\StringType;

$org = new Organisation(new IntType(1), new StringType('Foo'), Currency::create('gbp'));
$chart = new Chart(new StringType('Foo Chart'), $org);
Using the Accountant

The Accountant is a useful 'person'! But as usual they come at a cost: they need a fileClerk to do some of the work for them, and you have to give them that as payment. A fileClerk implements the AccountStorageInterface. A simple example that allows saving of Charts as serialized PHP file is provided to get you started, but of course you can create your own.

use SAccounts\Accountant;
use SAccounts\Storage\Account\Serialized;

$fileClerk = new Serialized(new StringType('/path/To/My/Account/Store'));
$accountant = new Accountant($fileClerk);

To create a chart via the accountant you still need to tell it what organisation the new chart is for, and also which COA template you want to use. A simple 'personal accounts' template is provided, which is an XML file. You can create and supply your own.

use SAccounts\ChartDefinition;
use SAccounts\Organisation;
use Chippyash\Type\String\StringType;
use Chippyash\Type\Number\IntType;
use Chippyash\Currency\Factory as Currency;

$def = new ChartDefinition(new StringType('/path/to/definitions/personal.xml'));
$org = new Organisation(new IntType(1), new StringType('Foo'), Currency::create('gbp'));
$chart = $accountant->createChart(new StringType('Name of Chart'), $org, $def);

Adding accounts to the chart

Once you have a chart you may want to add new accounts to it. If you have created one manually from scratch it will not have a root account, so you need to add that first. All accounts are identified by a 'nominal code' or id. This is of type Nominal (based on chippyash\Type\String\DigitType) and is a numeric string. You can use any nominal code structure you like, but make sure you give yourself enough room to add the accounts you want. Take a look at the definitions/personal.xml for some insight.

Accounts also need a type for the account. These are defined in the AccountType enum class and are simply created by calling the class constant as a method. See the link in the thanks section for more information about Enums.

  • add a root account
use SAccounts\Nominal;
use SAccounts\AccountType;

ac1 = new Account($chart, new Nominal('2000'), AccountType::ASSET(), new StringType('Asset'));
  • add a child account
ac2 = new Account($chart, new Nominal('2100'), AccountType::BANK(), new StringType('Bank'));
$chart->addAccount($ac, $ac1->getId())

Saving the chart


Fetching the chart

$chart = $accountant->fetchChart(new StringType('Name of Chart'));

Making entries into accounts

You can make debit and credit entries to any account. Obviously, to maintain double entry accounting rules, you'll generally make one of each for any transaction.

You don't need to keep track of accounts, simply get them from the chart using their id.

Whilst it is not enforced, you are advised to use the same currency that you used for your organisation when creating amounts to debit and credit.

//can be used in most situations
$amount = Currency::create($chart->getOrg()->getCurrencyCode(), 12.26);

//use this method if locale issues are important or you are using a fantasy currency
$amount = clone $chart->getOrg()->getCurrency();
//or use set() if you know your currency precision

$chart->getAccount(new Nominal('1000'))->debit($amount);
$chart->getAccount(new Nominal('2000'))->credit($amount);

Getting account values

All account values are expressed as chippyash\Currency\Currency objects.

  • debit and credit amounts
$debitAsInt = $chart->getAccount(new Nominal('1000'))->getDebit()->get();
$debitAsFloat = $chart->getAccount(new Nominal('1000'))->getDebit()->getAsFloat()
echo $chart->getAccount(new Nominal('1000'))->getDebit()->display();
$creditAsInt = $chart->getAccount(new Nominal('1000'))->getCredit()->get();
$creditAsFloat = $chart->getAccount(new Nominal('1000'))->getCredit()->getAsFloat()
echo $chart->getAccount(new Nominal('1000'))->getCredit()->display();
  • account balance

For all account types (excluding DUMMY and REAL) get the account balance:

$balanceAsInt = $chart->getAccount(new Nominal('1000'))->getBalance()->get();
$balanceAsFloat = $chart->getAccount(new Nominal('1000'))->getBalance()->getAsFloat();
echo $chart->getAccount(new Nominal('1000'))->getBalance()->display();

The balance respects the conventions of DR and CR accounts.:

  • DR balance = dr-cr
  • CR balance = cr-dr

Using Journals

Whilst an Account records the value state at any given point in time, and a Chart holds the state of a collection (tree) of accounts, a Journal is responsible for recording the transaction history that led to the current state of the Account.

You may use the library without using Journalling at all, but most systems will want a transaction history. The Accountant can make use of an optional 'Journalist' that implements the JournalStorageInterface to create, save and amend both a Journal and the transactions that it records.

You must first supply a Journalist in the form of a JournalStorageInterface. An example is provided, Accounts\Storage\Journal\Xml which stores the Journal and its transactions into an XML file. You can provide your own to store against any other storage mechanism that you want to use.

use SAccounts\Storage\Journal\Xml as Journalist;

$accountant->setJournalist(new Journalist(new StringType('/path/to/my/journal/store/folder')));

Creating a Journal

use Chippyash\Currency\Factory as Currency;

$journal = $accountant->createJournal(new StringType('My Journal'), Currency::create('gbp'));

Under most circumstances, you'll associate an Organisation, and a Chart with a Journal, so it makes sense to use the same Currency:

$journal = $accountant->createJournal(new StringType('My Journal'), $chart->getOrg()->getCurrency());

Fetching a Journal that you already made

use SAccounts\Storage\Journal\Xml as Journalist;

$accountant->setJournalist(new Journalist(new StringType('/path/to/my/journal/store/folder')));
$journal = $accountant->fetchJournal();

You can also store a journal via the accountant if you amend its definition

Creating transactions in the journal

You can either manage the link between the Journal and the Chart yourself by calling their appropriate store mechanisms (see the code, tests and diagrams for that,) or more simply, ask the accountant to do it for you. In either case, you first of all need a Transaction. Transactions are provided by way of the SAccounts\Transaction\SplitTransaction and SAccounts\Transaction\SimpleTransaction. SimpleTransaction is provided as helper for creating and writing out transactions that consist of a pair of balanced debit and credit amounts.

use SAccounts\Transaction\SimpleTransaction;
use SAccounts\Nominal;

$drAc = new Nominal('0000');
$crAc = new Nominal('1000');
$amount = Currency::create($chart->getOrg()->getCurrencyCode(), 12.26);
$txn = new  SimpleTransaction($drAc, $crAc, $amount);

You can set an optional 4th parameter when creating a SimpleTransaction:

$txn = new  SimpleTransaction($drAc, $crAc, $amount, new StringType('This is a note'));

By default the date and time for the transaction is set to now(). You can set an optional 5th parameter when creating a SimpleTransaction and supply a DateTime object of your own choosing.

$txn = new  SimpleTransaction($drAc, $crAc, $amount, new StringType(''), new \DateTime('2015-12-03T12:14:30Z));

To record a transaction and update the chart of accounts you can now use the Accountant again:

$txn = $accountant->writeTransaction($txn, $chart, $journal);
$accountant->writeTransaction($txn, $chart, $journal);

The Transaction will now have its transaction id set, which you can recover via:

$txnId = $txn->getId() //returns IntType

You don't need to save the Journal, as it is inherently transactional, but don't forget to save your Chart once you have finished writing transactions!

The full power of the transaction is provided by the SplitTransaction. And remember, that when you read transactions back from the journal they will be in SplitTransaction format. A split transaction allows you to have, say, one debit entry and three credit entries. As long as the total debit entry amounts equal the total credit entry amounts, you have a balanced transaction, i.e. valid double entry transaction.

With power comes a little more complexity, as you'd expect!

use SAccounts\Transaction\SimpleTransaction;
use SAccounts\Transaction\Entry;
use SAccounts\Nominal;
use Chippyash\Type\String\StringType;
use Chippyash\Currency\Factory as Currency;

$txn = new SplitTransaction() // date == now(), note == ''
$txn = new SplitTransaction(new DateTime());
$txn = new SplitTransaction(null, new StringType('foo'));
$txn = new SplitTransaction(new DateTime(), new StringType('foo'));

//the following is analogous to a SimpleTransaction
$note = new StringType('foo bar');
$dt = new \DateTime();
$amount = Currency::create('gbp', 12.26);
$txn = (new SplitTransaction($dt, $note))
    ->addEntry(new Entry(new Nominal('0000'), $amount, AccountType::DR()))
    ->addEntry(new Entry(new Nominal('1000'), $amount, AccountType::CR()));

When creating an Entry, you need to tell it:

  • which account to use
  • how much
  • whether to debit or credit

To create true split transaction, lets use the following example:

  • bank account: 3001
  • vat account: 6007
  • items account: 9056
  • total transaction £120.00, £100 for the item, £20 as VAT
$txn = (new SplitTransaction($dt, $note))
    ->addEntry(new Entry(new Nominal('3001'), Currency::create('gbp', 120), AccountType::DR()))
    ->addEntry(new Entry(new Nominal('6007'), Currency::create('gbp', 20), AccountType::CR()))
    ->addEntry(new Entry(new Nominal('9056'), Currency::create('gbp', 100), AccountType::CR()));

On the whole it is a really bad idea to create an unbalanced transaction and you can check this with checkBalance() which returns true if the transaction is balanced else false.

You can also do a simple check to see if the transaction conforms to being simple using the isSimple() method:

$drAc = $txn->getDrAc();
if ($txn->isSimple()) {
    $actualDrAc = $drAc[0];
} else {
    //you have an array of debit accounts, so process them

getCrAc() gets the credit accounts on a transaction.

The getEntries() method of a SplitTransaction returns a SAccounts\Transaction\Entries collection of entries.

Control Accounts

In the sense of an accounting definition of Control Accounts, the Simple Accounts Control Account can certainly be used to point to an adjustment account in your Chart. However, in practical use, Control Accounts have a more broadly defined use; that of pointing to specific accounts within the Chart. So in this sense, the Simple Accounts Control Account is simply a pointer to another account.

In programming terms, we set up a Collection (Control\Links) of Control Accounts (Control\Link). Let's use an example:

You have a system in which you want generic cash transactions to go to specific accounts:

  • cash to/from the 'bank' account
  • purchases to the 'sundries' account
  • sales to the 'cash sales' account

The problem is, that as your COA (or business) grows, the actual account in the COA that you want to use may change. By dereferencing the actual account with a Control Account, your main code can remain the same, yet allowing you to reconfigure at will.

use SAccounts\Control;

$linkArray = [
    new Control\Link(new StringType('bank'), new Nominal('1000')),
    new Control\Link(new StringType('sundries'), new Nominal('2000')),
    new Control\Link(new StringType('cash sales'), new Nominal('3000')),
$ctrlAcs = (new Control\Links($linkArray));

$txn = (new SplitTransaction($dt, $note))
    ->addEntry($ctrlAcs->getLinkId(new StringType('bank')), Currency::create('gbp', 120), AccountType::DR()))
    ->addEntry($ctrlAcs->getLinkId(new StringType('cash sales')), Currency::create('gbp', 120), AccountType::CR()));

$txn2 = (new SplitTransaction($dt, $note))
    ->addEntry($ctrlAcs->getLinkId(new StringType('bank')), Currency::create('gbp', 90), AccountType::CR()))
    ->addEntry($ctrlAcs->getLinkId(new StringType('sundries')), Currency::create('gbp', 90), AccountType::DR()));

It is really as simple as that. I've not included a storage mechanism for Control Accounts on the basis, that it is likely that you'll dependency inject them into your application, however there is an XML XSD in the definitions folder, with an example XML file in the docs directory. In practice you may find yourself using a number of Control Account Collections in an application.

Class diagrams

UML Diagram UML Diagram UML Diagram

Changing the library

  1. fork it
  2. write the test
  3. amend it
  4. do a pull request

Found a bug you can't figure out?

  1. fork it
  2. write the test
  3. do a pull request

NB. Make sure you rebase to HEAD before your pull request

Or - raise an issue ticket.


The library is hosted at Github. It is available at


Install Composer

For production

    "chippyash/simple-accounts": "~1"

For development

Clone this repo, and then run Composer in local repo root to pull in dependencies

    git clone Accounts
    cd Accounts
    composer update

To run the tests:

    cd Accounts
    vendor/bin/phpunit -c test/phpunit.xml test/


Back in the day, when the first Simple Accounts was written, I had to write a lot of support code myself. In this version I have been able to take advantage of the work of others. As well as the normal suspects of PHPUnit and vfsStream for writing the test code, I'd like to highlight some others:

  • PHP Enum : a neat implementation of enums for PHP
  • Tree : A simple tree component that supports the visitor pattern allowing for easy extension


This software library is released under the GNU GPL V3 or later license

This software library is Copyright (c) 2015-2016, Ashley Kitson, UK

A commercial license is available for this software library, please contact the author. It is normally free to deserving causes, but gets you around the limitation of the GPL license, which does not allow unrestricted inclusion of this code in commercial works.


V1.0.0 Original release

V1.1.0 Journals added


  • replaced chippyash\Accounts namespace with SAccounts
  • Transaction deprecated, use SimpleTransaction (Transaction proxies to SimpleTransaction and will be removed in the future)
  • SplitTransactions introduced, use these for preference
  • BC break with XML Journal file format to accommodate split transactions

V1.3.0 Added Control Accounts

V1.4.0 Update dependencies

V1.4.1 Add link to packages

V1.4.2 Verify PHP 7 compatibility

V1.4.3 Code cleanup

V1.4.4 Dependency update