PHPackages                             prinx/ussd-lib - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. prinx/ussd-lib

Abandoned → [rejoice/rejoice](/?search=rejoice%2Frejoice)Library[Utility &amp; Helpers](/categories/utility)

prinx/ussd-lib
==============

Write easily your USSD applications.

121PHP

Since Mar 27Pushed 6y ago1 watchersCompare

[ Source](https://github.com/prinx/gh-ussd-lib)[ Packagist](https://packagist.org/packages/prinx/ussd-lib)[ RSS](/packages/prinx-ussd-lib/feed)WikiDiscussions master Synced 5d ago

READMEChangelogDependenciesVersions (1)Used By (0)

USSD LIB
========

[](#ussd-lib)

This library helps you write easily your USSD applications.

DISCLAIMER
----------

[](#disclaimer)

### 1.

[](#1)

This is a Work In Progress. The tutorial is been updated regularly.

### 2.

[](#2)

This library is made, tested and used to develop USSD applications in Ghana. It may or may not work in other country due to the fact that the authors don't know how USSD flows are handled by mobile operators in other countries (what we really want to know). Feel free to open an issue or contact the authors if you are having any issue. Feele free to contribute to the code or fork for any improvement.

INSTALLATION
------------

[](#installation)

`composer require prinx/ussd-lib:dev-master`

USAGE
-----

[](#usage)

The Library require a class that we call **Menu Manager**. The menu manager provides the menus, the menu functions, menus parameters, and database parameters to the library and run the library. The menu manager can be the controller attached to the route on which the USSD application will be available or a custom class that will be ran on the route on which the USSD application will be available.

Therefore, creating your USSD application will typically follow this schema:

- Database configuration;
- Menu construction;
- menu functions development;
- Menu parameters configuration

You create your app (the menu manager) in a class that will have as properties your database configuration, the menus, and the app parameters. The menu manager will have as methods, some functions that will handle the menus' logic.

Check `index.sample.php` for a sample application.

### The Menu manager class

[](#the-menu-manager-class)

Create a file called `ussd_menu_manager.php` (the name does not matter). The location of the file is up to you. Just remember you will import the file into your `index.php` or your controller (if you are using a framework). Inside the file cerate a class.

```
// use Prinx\UssdLib\Lib\UssdLib;

// The name of the class does not matter.
class USSDApp
{
  // The parameters of the application that will be passed to the USSD library
  protected $params = [];

  // A property to store the instance of the library so that you can call some
  protected $ussd;

  // Will contain the menus logic (the typical ussd flow)
  protected $menus = [];

  // Let's define the getters (or you can just make the properties public and skip the getters)

  public function params()
  {
      return $this->params;
  }

  public function menus()
  {
      return $this->menus;
  }
}
```

### Database configuration

[](#database-configuration)

Now let's define the database parameters. *Why do we need some database?*We need a database to store a user session. For Every user who dials the shortcode, a session is created. This session keep track of the user's answers to the various menus and return response to the specific user.

By providing the database parameters, the first time you run the library, a session table will be automatically created.

```
define('ENV', 'development');
use Prinx\UssdLib\Lib\UssdLib;

class USSDApp
{
  // PROPERTIES
  //...

  // GETTERS
  //...

  // DB PARAMS
  public function db_params()
  {
    // It's highly recommended to use a .env file to store the database credentials
    $config = [
        'username' => '',
        'password' => '',
    ];

    if (ENV !== 'production') {
        $config['hostname'] = '';
        $config['dbname'] = '';
    } else {
        $config['hostname'] = '';
        $config['password'] = '';
        $config['port'] = '';
        $config['dbname'] = '';
    }

    return $config;
  }
}
```

We highly recommend you use a .env file to store the database credentials. If you are not using a framework or your framework does not have a env parser, you can use the php `env` function of the library to get your env variable.

Let's create the .env file at the root of the project. Define the parameters in the `.env` file

```
DEV_DB_USER=root
DEV_DB_PASS=password
DEV_DB_HOST=db_ip_address
DEV_DB_PORT=3306
DEV_DB_NAME=ussd_session_db_name_or_your_app_db_name

PROD_DB_USER=root
PROD_DB_PASS=password
PROD_DB_HOST=db_ip_address
PROD_DB_PORT=3306
PROD_DB_NAME=ussd_session_db_name_or_your_app_db_name
```

The function belongs to the namespace `Prinx\Dotenv\env;`. You can learn more on the `env()` function in this [documentation](https://github.com/Prinx/dotenv/blob/master/README.md).

```
use function Prinx\Dotenv\env;

class USSDApp
{
  //...
  public function db_params()
  {
    // It's highly recommended to use a .env file to store the database credentials
    $config = [];

    if (ENV !== 'production') {
        $config['username'] = env('DEV_DB_USER');
        $config['password'] = env('DEV_DB_PASS');
        $config['hostname'] = env('DEV_DB_HOST');
        $config['port'] = env('DEV_DB_PORT');
        $config['dbname'] = env('DEV_DB_NAME');
    } else {
        $config['username'] = env('PROD_DB_USER');
        $config['password'] = env('PROD_DB_PASS');
        $config['hostname'] = env('PROD_DB_HOST');
        $config['port'] = env('PROD_DB_PORT');
        $config['dbname'] = env('PROD_DB_NAME');
    }

    return $config;
  }
}
```

### Menu construction

[](#menu-construction)

Let's concentrate now on the construction of the menu. This is the most important part of the application.

### Menu parameters configuration

[](#menu-parameters-configuration)

The parameters of the application will be specified either directly in the `params` property of the Menu manager class, either inside the constructor of the menu manager class.

```
class USSDApp
{
  //...
  public function __construct()
  {
    // The id parameter is required. It will be used to create the ussd session table. Therefore, only letters and underscore will be accepted.
    $this->params['id'] = 'first_ussd_app';
  }
}
```

The id is the only parameter that does not have a default value and therefore is required.

The other parameters (optional parameters):

```
class USSDApp
{
  //...

  public function __construct()
  {
    $this->params['id'] = 'first_ussd_app';

    // Optional parameters
    $this->params['environment'] = 'dev';

    $this->params['end_on_user_error'] = false;

    $this->params['always_start_new_session'] = false;
    $this->params['ask_user_before_reload_last_session'] = true;

    $this->params['always_send_sms'] = true;
    $this->params['sms_sender_name'] = '';
    $this->params['sms_endpoint'] = '';

    $this->params['back_action_thrower'] = '0';
    $this->params['back_action_display'] = 'Back';

    $this->params['splitted_menu_next_thrower'] = '99';
    $this->params['splitted_menu_display'] = 'More';

    $this->params['default_end_msg'] = 'Thank you.';
    $this->params['default_error_msg'] = 'Invalid Input.';
  }
}
```

Or

```
class USSDApp
{
  protected $params = [
    'id' => 'first_ussd_app',
    'environment' => 'dev',
    'end_on_user_error' => false,

    'always_start_new_session' => false,
    'ask_user_before_reload_last_session' => true,

    'always_send_sms' => true,
    'sms_sender_name' => '',
    'sms_endpoint' => '',

    'back_action_thrower' => '0',
    'back_action_display' => 'Back',
    'splitted_menu_next_thrower' => '99',
    'splitted_menu_display' => 'More',

    'default_end_msg' => 'Thank you.',
    'default_error_msg' => 'Invalid Input.',
  ];

  public function __construct()
  {
  }
}
```

#### `always_start_new_session`: boolean

[](#always_start_new_session-boolean)

If `true`, anytime the user dials the shortcode, the welcome menu is the menu that will be presented to her-him. If `false`, if ever the user session times out without the user been able to complete its request or the user her-himself cancells the session, the next time the user will dial the shortcode, (s)he will be brought to the menu on which (s)he left. **The default value is `true`**.

*Note*: If `always_start_new_session` is set to `false`, you can use the `ask_user_before_reload_last_session` parameter to control whether a prompt has to be sent to user to decide if (s)he wants to restart from where (s)he left or start from the welcome menu.

#### `ask_user_before_reload_last_session`: boolean

[](#ask_user_before_reload_last_session-boolean)

This parameter is true, the user will have a prompt to choose if he wants to continue from the stage where he left or start from the Welcome menu. **The default value is `false`**.

*Note*: `ask_user_before_reload_last_session` will have effect only if `always_start_new_session` is `false`.

#### `back_action_thrower`: string or int

[](#back_action_thrower-string-or-int)

The input that will take the user to the previous menu. **The default value is `0` (zero).**

#### `back_action_display`

[](#back_action_display)

The indication to the user that (s)he can go back. **The default value is `"Back"`**.

#### `splitted_menu_next_thrower`: string or int

[](#splitted_menu_next_thrower-string-or-int)

The input that will take the user to the following menu, if that particular menu is splitted. **The default value is `99`.**

#### `splitted_menu_display`

[](#splitted_menu_display)

The indication to the user that there is another page of the same menu. **The default value is `"More"`.**

#### `default_end_msg`

[](#default_end_msg)

Default goodbye message. It's used if no message is provided in your menu. **The default value is `"Goodbye"`.**

#### `default_error_msg`:

[](#default_error_msg)

Default error message, if the user input is invalid. It can be modify with the `set_error` method of the library. **The default value is `"Invalid input"`.**

#### `environment`:

[](#environment)

Must be "prod" or "dev". Remember to modify it to "production" when in production environment. A lot of checks are bypassed to make the application faster. **The default value is `"dev"`.**

#### `end_on_user_error`: boolean

[](#end_on_user_error-boolean)

If `true`, the session will be terminated, with an error message, when the user input an invalid response. The default error message is the value of `default_error_msg`.

#### `always_send_sms`: boolean

[](#always_send_sms-boolean)

If true, the last message displayed to the user will always be sent as sms too, provided the SMS API endpoint has been set. If not you can use your own function to send SMS. You can, at any point, use the `send_sms` method of the library to send SMS to the user (provided the SMS API endpoint has been set). **The default value is `false`.**

#### `sms_sender_name`

[](#sms_sender_name)

The name that will appear to the user as the SMS sender. **The default value is `""` (enpty string).**

#### `sms_endpoint`

[](#sms_endpoint)

An API endpoint to send SMS. You can decide not to use the SMS interface of the library and use your own. **The default value is `""` (empty string).**

### Running the library

[](#running-the-library)

In the `__construct` method:

```
class USSDApp
{
  //...

  public function __construct()
  {
    // Application parameters...
    // ...

    $this->ussd = new USSDLib();
    $this->ussd->run($this);
  }
}
```

### Running the application

[](#running-the-application)

In your `index.php` or inside your controller (if your are using a framework), place this code.

```
// Top of the file
require_once 'path/to/ussd/app.php';

// Place the following line inside the controller if your are using a framework.
// If not, just place it in the index.php.
$app = new USSDApp();
```

This is the minimum requirement for the ussp app to run. But often you will need to validate the response of the user, or to another stuff before or after the user sent a response, like calling an API to retrieve the balance of the user, retrieving some data from a database to display to the user, etc. Let's look at how we can do that.

### Menu functions development (Hooks)

[](#menu-functions-development-hooks)

#### `before_` functions

[](#before_-functions)

Three main purposes:

- feed the menu message;
- feed the menu actions
- run a specific code before the menu is shown to the user.

If you want to run a code before the menu message. The before\_ function allows you to run a code before a menu page is sent and displayed to the user. Therefore, it allows you to modify the menu message. To modify the menu message, you can either return a string, or an array of placeholders. If you return a string, the string will be what will be displayed to the user. If you return an array, the values of the array will replace the placeholders specified in your message.

So

#### `after_` functions

[](#after_-functions)

The ``after\_ functions are the functions that run after the user has sent a response to the application. Hence, you can validate the user response with the validate\_ function. You can do other stuff in the generic after\_ function.

##### Validate the user response

[](#validate-the-user-response)

The last user response is passed to the function by the library. This function must return a boolean: `true` if the validation passes, `false` if not. If it returns `false`, the same menu will be run again but will have at its top an error message. The error message is the `default_error_msg` parameter. You can change the error message for a specific menu with the `set_error` function of the library.

```
class USSDApp
{
    //...

    public function validate_get_birthdate($response)
    {
        $date = $this->create_date_from_format($response);

        if ($date === false) {
            $this->ussd->set_error('Invalid birthdate format.');
            return false;
        }

        $min = 0;
        $max = 150;
        $age = $this->calculate_age($response);

        if (!$this->is_valid_date($date) || !$this->age_within($min, $max, $age)) {
            $this->ussd->set_error('Invalid birthdate.');
            return false;
        }

        return true;
    }

    public function create_date_from_format($date, $format = '')
    {
        $format = $format !== '' ? $format : $this->default_date_format;

        return DateTime::createFromFormat($format, $date);
    }

    public function is_valid_date($date)
    {
        $year = $date->format('Y');
        $month = $date->format('m');
        $day = $date->format('d');

        return checkdate($month, $day, $year);
    }

    public function age_within(int $min, int $max, int $age)
    {
        return $min diff(new DateTime('now'))
            ->y;
    }
}
```

You will notice we also ceated some other helper methods (calculate\*age, age\_within, is\_valid\_date, create\_date\_from\_format). Those methods will not be used by the library. The library will only search for the hooks (methods prefixed by `before\_`,`validate\_`,`after\_`) will be used

### Changing the default error message

[](#changing-the-default-error-message)

Use it typically inside a validate function to define the error message that will be shown to the user if the response does not pass the validation. This function is not required. If you don't use it, the `default_error_msg` parameter will be used.

```
class USSDApp
{
  // ...
    public function validate_get_birthdate($response)
    {
        $date = $this->create_date_from_format($response);

        if ($date === false) {
            $this->ussd->set_error('Invalid birthdate format.');
            return false;
        }

        $min = 0;
        $max = 150;
        $age = $this->calculate_age($response);

        if (!$this->is_valid_date($date) || !$this->age_within($min, $max, $age)) {
            $this->ussd->set_error('Invalid birthdate.');
            return false;
        }

        return true;
    }
}
```

### Exiting the application

[](#exiting-the-application)

You can decide to exit the application at any point with this function. Typically use it if you want to quit the application when the user sent a wrong answer. You can pass the message to display to the user if not the `default_end_msg` parameter will be used.

```
class USSDApp
{
  // ...

    public function validate_get_birthdate($response)
    {
        $date = $this->create_date_from_format($response);

        if ($date === false) {
            $this->ussd->set_error('Invalid birthdate format.');
            return false;
        }

        $min = 0;
        $max = 150;
        $age = $this->calculate_age($response);

        if (!$this->is_valid_date($date) || !$this->age_within($min, $max, $age)) {
            $this->ussd->set_error('Invalid birthdate.');
            return false;
        }

        return true;
    }

    public function create_date_from_format($date, $format = '')
    {
        $format = $format !== '' ? $format : $this->default_date_format;

        return DateTime::createFromFormat($format, $date);
    }

    public function is_valid_date($date)
    {
        $year = $date->format('Y');
        $month = $date->format('m');
        $day = $date->format('d');

        return checkdate($month, $day, $year);
    }

    public function age_within(int $min, int $max, int $age)
    {
        return $min diff(new DateTime('now'))
            ->y;
    }

}
```

CODE
----

[](#code)

The .env file:

```
DEV_DB_USER=root
DEV_DB_PASS=password
DEV_DB_HOST=db_ip_address
DEV_DB_PORT=3306
DEV_DB_NAME=ussd_session_db_name_or_your_app_db_name

PROD_DB_USER=root
PROD_DB_PASS=password
PROD_DB_HOST=db_ip_address
PROD_DB_PORT=3306
PROD_DB_NAME=ussd_session_db_name_or_your_app_db_name
```

The USSD App code:

```
define('ENV', 'development');
require_once __DIR__ . '/vendor/prinx/ussd-lib/src/USSD.php';

use function Prinx\Dotenv\env;
use Prinx\USSD\USSD;

class USSDApp
{
    protected $default_date_format = 'd/m/Y';

    protected $app_params = [
        'id' => 'first_ussd_app',
        'environment' => 'dev',
        'end_on_user_error' => false,

        'always_start_new_session' => false,
        'ask_user_before_reload_last_session' => true,

        'always_send_sms' => false,
        'sms_sender_name' => 'BASICAPP',
        // 'sms_endpoint' => '',

        'back_action_thrower' => '0',
        'back_action_display' => 'Back',
        'splitted_menu_next_thrower' => '99',
        'splitted_menu_display' => 'More',

        'default_end_msg' => 'Thank you.',
        'default_error_msg' => 'Invalid Input.',
    ];

    protected $ussd;

    protected $menus = [
        'welcome' => [
            'message' => "Welcome.\nSelect an option",
            'actions' => [
                '1' => [
                    'display' => 'Am I working ?',
                    'next_menu' => 'verify_working',
                ],
                '2' => [
                    'display' => 'What is the date?',
                    'next_menu' => 'show_date',
                ],
                '3' => [
                    'display' => 'Caluculate age',
                    'next_menu' => 'get_birthdate',
                ],
                '4' => [
                    'display' => 'Say Goodbye',
                    'next_menu' => 'say_goodbye',
                ],
            ],
        ],

        'verify_working' => [
            'message' => "Of course, I'm working!"
        ],

        'show_date' => [
            'message' => 'Today is :date:!',
            'actions' => [
                '1' => [
                    'display' => 'Back',
                    'next_menu' => '__back',
                ],
                '0' => [
                    'display' => 'End',
                    'next_menu' => '__end',
                ],
            ],
        ],

        'get_birthdate' => [
            'message' => "Enter your birthdate (dd/mm/yyyy)\nOr enter 0 to go back:",
            'actions' => [
                '0' => [
                    'display' => 'Back',
                    'next_menu' => '__back',
                ],

                'default_next_menu' => 'show_age',
            ],
        ],

        'show_age' => [
            'message' => "You are :age: years old!",
            'actions' => [
                '0' => [
                    'display' => 'Back',
                    'next_menu' => '__back',
                ],
                '1' => [
                    'display' => 'Main menu',
                    'next_menu' => '__welcome',
                ],
                '2' => [
                    'display' => 'End',
                    'next_menu' => '__end',
                ],
            ],
        ],

        'say_goodbye' => [
            'message' => "Goodbye",
        ],
    ];

    public function __construct()
    {
        $this->ussd = new USSD();
        $this->ussd->run($this);
    }

    public function before_show_date()
    {
        return ['date' => date('D-m-Y')];
    }

    public function create_date_from_format($date, $format = '')
    {
        $format = $format !== '' ? $format : $this->default_date_format;

        return DateTime::createFromFormat($format, $date);
    }

    public function is_valid_date($date)
    {
        $year = $date->format('Y');
        $month = $date->format('m');
        $day = $date->format('d');

        return checkdate($month, $day, $year);
    }

    public function validate_get_birthdate($response)
    {
        $date = $this->create_date_from_format($response);

        if ($date === false) {
            $this->ussd->set_error('Invalid birthdate format.');
            return false;
        }

        $min = 0;
        $max = 150;
        $age = $this->calculate_age($response);

        if (!$this->is_valid_date($date) || !$this->age_within($min, $max, $age)) {
            $this->ussd->set_error('Invalid birthdate.');
            return false;
        }

        return true;
    }

    public function age_within(int $min, int $max, int $age)
    {
        return $min diff(new DateTime('now'))
            ->y;
    }

    public function before_show_age($user_previous_response)
    {
        $birthdate = $user_previous_response['get_birthdate'][0];
        $age = $this->calculate_age($birthdate);

        return ['age' => $age];
    }

    public function db_params()
    {
        $config = [];

        if (ENV !== 'production') {
            $config['username'] = env('DEV_DB_USER');
            $config['password'] = env('DEV_DB_PASS');
            $config['hostname'] = env('DEV_DB_HOST');
            $config['port'] = env('DEV_DB_PORT');
            $config['dbname'] = env('DEV_DB_NAME');
        } else {
            $config['username'] = env('PROD_DB_USER');
            $config['password'] = env('PROD_DB_PASS');
            $config['hostname'] = env('PROD_DB_HOST');
            $config['port'] = env('PROD_DB_PORT');
            $config['dbname'] = env('PROD_DB_NAME');
        }

        return $config;
    }

    public function app_params()
    {
        return $this->app_params;
    }

    public function menus()
    {
        return $this->menus;
    }
}

$app = new USSDApp();
```

###  Health Score

18

—

LowBetter than 8% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity5

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity34

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://www.gravatar.com/avatar/896d24b4882562faf6f1c8c25ea90e515037f5811f251a11fd80b68db61fc458?d=identicon)[Prinx](/maintainers/Prinx)

---

Top Contributors

[![prinx](https://avatars.githubusercontent.com/u/7337610?v=4)](https://github.com/prinx "prinx (33 commits)")

### Embed Badge

![Health badge](/badges/prinx-ussd-lib/health.svg)

```
[![Health](https://phpackages.com/badges/prinx-ussd-lib/health.svg)](https://phpackages.com/packages/prinx-ussd-lib)
```

###  Alternatives

[konekt/menu

Menus in Laravel 10 - 12

28103.9k1](/packages/konekt-menu)[slope-it/breadcrumb-bundle

A bundle for generating dynamic breadcrumbs in Symfony applications

1865.5k](/packages/slope-it-breadcrumb-bundle)[jxlwqq/quill

quill editor for laravel-admin

2427.1k](/packages/jxlwqq-quill)[cheesegrits/filament-phone-numbers

A Filament PHP plugin for normalizing phone numbers

2215.4k](/packages/cheesegrits-filament-phone-numbers)[johannschopplich/kirby-serp-preview

Kirby Panel plugin for search engine result page previews

292.4k1](/packages/johannschopplich-kirby-serp-preview)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
