PHPackages                             markhj/php-tendency - 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. markhj/php-tendency

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

markhj/php-tendency
===================

A tool to make complex randomization scenarios readable and modular.

v0.1.2(1y ago)012MITPHPPHP ^8.3

Since Nov 9Pushed 1y ago1 watchersCompare

[ Source](https://github.com/markhj/php-tendency)[ Packagist](https://packagist.org/packages/markhj/php-tendency)[ RSS](/packages/markhj-php-tendency/feed)WikiDiscussions master Synced 1mo ago

READMEChangelogDependencies (2)Versions (4)Used By (0)

[![PHP Tendency banner](https://camo.githubusercontent.com/c6ab40beb70aedfdc17f870d5365f4a16a75795b825027bdb337a407972b1f92/68747470733a2f2f7265732e636c6f7564696e6172792e636f6d2f6472667a74766664682f696d6167652f75706c6f61642f76313733313136303435382f6f70656e676c2e69742e636f6d2f47697468756225323073706c61736865732f7068702d74656e64656e63795f71626c3436712e6a7067)](https://camo.githubusercontent.com/c6ab40beb70aedfdc17f870d5365f4a16a75795b825027bdb337a407972b1f92/68747470733a2f2f7265732e636c6f7564696e6172792e636f6d2f6472667a74766664682f696d6167652f75706c6f61642f76313733313136303435382f6f70656e676c2e69742e636f6d2f47697468756225323073706c61736865732f7068702d74656e64656e63795f71626c3436712e6a7067)

[![GitHub Tag](https://camo.githubusercontent.com/fc63aaf6206f2c77270347ad1cceb0f2b4774bee3132a80e2ffa2759673e062a/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f7461672f6d61726b686a2f7068702d74656e64656e63793f6c6162656c3d76657273696f6e)](https://camo.githubusercontent.com/fc63aaf6206f2c77270347ad1cceb0f2b4774bee3132a80e2ffa2759673e062a/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f7461672f6d61726b686a2f7068702d74656e64656e63793f6c6162656c3d76657273696f6e)[![Codacy Badge](https://camo.githubusercontent.com/8bfae693b67028c50dc57e91aa22cfb4fb53a5c9cb83830e4790dd9fca6d841a/68747470733a2f2f6170702e636f646163792e636f6d2f70726f6a6563742f62616467652f47726164652f3335613835643365663431643437326161326636336236356264383266323938)](https://app.codacy.com/gh/markhj/php-tendency)[![License: MIT](https://camo.githubusercontent.com/47d79c7886f2fff2cbfc4c68b3e2898581e6f4db0ae86a3aa7a1fb203be9b449/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d79656c6c6f772e7376673f6c6162656c3d6c6963656e7365)](https://camo.githubusercontent.com/47d79c7886f2fff2cbfc4c68b3e2898581e6f4db0ae86a3aa7a1fb203be9b449/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d79656c6c6f772e7376673f6c6162656c3d6c6963656e7365)

**PHP Tendency** is a random value generator. But it's not the one you'd pick to find lottery numbers, rather it's the one you'd choose to calculate complex scenarios, where tens or hundreds of factors impact the likelihood of an outcome.

💫 Sounds cool, tell me more
---------------------------

[](#-sounds-cool-tell-me-more)

**PHP Tendency** had its beginning as a "spin-off" from a political simulation game, in which the choices of people, countries and companies needed a degree of randomness, but never *total* randomness.

The risk that a person commits a crime *isn't* 50/50. It's determined by personality, criminal history, how the person was raised, life circumstances, social circles, and so forth.

Implementing such complex determination in code isn't easy, and without the proper tooling, it's outright cumbersome: Hard to test, hard to predict, and hard to keep in check.

This is the exact problem *PHP Tendency* set out to solve. Let's imagine a function which calculates the risk of someone committing a crime. For a prototype, most would probably do something like:

```
class Person
{
    public function shouldCommitCrime(): bool
    {
        $risk = 0;
        if ($this->personality->integrity < 0.3) {
            $risk += 0.1;
        }
        if ($this->criminalHistory->count() > 0) {
            $risk += 0.15;
        }
        // ... And hundreds more conditions
        return $risk > 0.5;
    }
}
```

But... What if that same thing could look like this?

```
public function shouldCommitCrime(): bool
{
    return $this->randomizer()
        ->hasLow(Personality::Integrity)
        ->hasCriminalRecord()
        // And so forth
        ->compute()
        ->result;
}
```

Much better, right? This is easier to test, easier to maintain, and easier to read.

"*But where do all these methods like `hasLow` and `hasCriminalRecord`come from?*" What a great question! They come from extensions. Think of them as custom modules you write specifically for *your* project. We'll get back to that later in this guide. But they basically provide reusability, modularity, improved overview, and testability.

🍃 Features
----------

[](#-features)

- **Extensible and modular**: Create separate classes which are injected into a randomizer per an on-demand basis.
- **Biased outcomes**: Make certain outcomes more likely than others using a weight-based randomization (mean + standard deviation).
- **Several types**: Int, float or bool? Doesn't matter. You can also build your own custom randomizer which takes strings, arrays, or specific classes. The sky is the limit.

📌 Prerequisites
---------------

[](#-prerequisites)

- PHP 8.3 or higher

📦 Installation
--------------

[](#-installation)

The library is easily installed with Composer:

```
composer require markhj/php-tendency
```

🚀 Getting started
-----------------

[](#-getting-started)

Let's have a look at the fundamental usage of *PHP Tendency*. Take note that the really fun stuff comes in the **Extensions** chapter.

There are a set of base classes called "***randomizers***". Three are bundled out-of-the-box with *PHP Tendency*, but you can easily build more on your own, simply by extending `RandomBase`.

### Boolean

[](#boolean)

To retrieve a random boolean value:

```
use Markhj\PhpTendency\RandomBool;
```

```
(new RandomBool())->compute();
```

### Integer

[](#integer)

Retrieve a random integer between a min and max.

```
use Markhj\PhpTendency\RandomInt;
```

```
(new RandomInt(15, 35))->compute();
```

### Float

[](#float)

Retrieve a random floating-point value between a min and max.

```
use Markhj\PhpTendency\RandomInt;
```

```
(new RandomInt(-25.0, 25.0))->compute();
```

### Bias

[](#bias)

All of these classes sport a `changeMean` method, which moves the bias. The bias expresses the most likely outcome, which by default is `0.5` (50%, the middle).

And yes, this also means that if you want a random value between 0 and 100, getting a number around 50 is more likely than numbers close the bounds. Again: This isn't the choice for a lottery number, where you'd want an un-biased pick.

However, the trick is that you can move the bias, so if you move it to `0.3` (30%), then a number around 30 is most likely.

You can sway the mean using these functions:

```
(new RandomBool())->changeMean(-0.25);  // More likely to be false
(new RandomBool())->changeMean(0.25);   // More like to be true
(new RandomInt(0, 100))->changeMean(0.25);  // More like to land around 75
```

Keep in mind that `changeMean` isn't a *setter*, it increments or decreases.

> The mean value starts at `0.5`, and would be between 0 and 1 for most use-cases. But it's perfectly fine to fall outside of this bound.

### The `RandomizedResult` class

[](#the-randomizedresult-class)

Randomizers return an instance of `RandomizedResult`. This object provides some information on top of the computed random value. These values are mainly useful for testing, but should you have some reason to use them... Well, they are there.

PropertyTypeDescription`mean``float`The final mean value used after extensions have manipulated it.`computed``float`The final computed random value (between `0.0` and `1.0`).`result``mixed`The actual result, typically a number between X and Y, boolean, or something else.### Standard deviation

[](#standard-deviation)

You can modify the standard deviation on all of the above classes. The standard deviation is an expression of "how far" a random value *typically* falls from the mean.

Example:

```
(new RandomFloat(10.0, 25.0, 0.25))->compute();
```

Here, the standard deviation is `0.25` which corresponds to 25% from the mean value.

Learn more: [Standard deviation on Wikipedia](https://en.wikipedia.org/wiki/Standard_deviation)

💡 Extensions
------------

[](#-extensions)

Okay, so what we've seen so far is pretty dull. But it's necessary to know it, to get to the fun part -- which we have finally reached.

If randomizers represent the heart of *PHP Tendency*, then extensions represent the brain, liver, kidney and spleen. *PHP Tendency* makes only limited sense without them.

### The idea

[](#the-idea)

The ultimate goal of an extension is to manipulate the mean value contained in the randomizer (.e.g `RandomBool` or `RandomFloat`).

The mean value always starts at `0.5` (perfectly between `0.0`and `1.0`), which means the random value gravitates towards the middle of whatever you're looking to randomize.

> It's perfectly fine if the mean moves below zero or above one.

If we go back to trying to determine if a person should commit a crime or not, then having a criminal history should sway the mean towards 1, increasing the likelihood of that outcome.

The ultimate purpose of extensions is to sway the mean in negative or positive direction, creating bias towards a specific outcome.

### Creating an extension

[](#creating-an-extension)

Creating a basic extension is as easy as:

```
class SimpleExtension implements Extension
{
    #[Expose]
    public function myFunc(Extendable $random, float $change): Extendable
    {
        return $random->changeMean($change);
    }
}
```

There are a few things to note down:

- The extension class must implement the `Extension` interface.
- Every method that must be accessible through the randomizer, must have the `#[Expose]` attribute.
- The first argument of an exposed method must be the randomizer (`Extendable`). The randomizer instance which will be injected, when the method is called.
- Exposed methods must return `Extendable` (i.e. the randomizer).

> You can explore `ExtensionTest` for a real-life example.

### Usage

[](#usage)

When you want to use your extension, it must first get injected into the randomizer. Once that's done, you have access to its exposed methods.

Here, we extend a `RandomFloat` with the amazing extension we just built.

```
$randomizer = new RandomFloat(50.0, 75.0);
$randomizer->extend(new SimpleExtension());
```

Now, you have access to the `myFunc` method:

```
$randomizer->myFunc(0.3);
```

What happens in this particular example, is that the randomizer's mean value (which starts at `0.5`) is increased by `0.3`, to `0.8`.

That in effect means that `RandomFloat` with min/max at 50 to 75, is more likely to produce a number around 70, because we have shifted the bias.

Notice a lot of "maybe" and "probably". This is because we use biased randomization with standard distribution. No outcome is completely guaranteed, which is exactly what we want: *A tendency*.

A person with a criminal history may have a *tendency* to commit crime, but they don't *always* do it.

> **Tip!** Extensions can themselves take parameters. In our crime determination example, we would for instance provide information about the person as constructor arguments.

### What about the criminal?

[](#what-about-the-criminal)

Don't worry, we haven't forgotten our hypothetical criminal.

Let's imagine we have an extension class like this:

```
class PersonTendency implements Extension
{
    public function __construct(
        private Person $person,
    ) {
    }

    #[Expose]
    public function hasCriminalRecord(Extendable $randomizer): Extendable
    {
        $records = getCriminalRecordFromDatabase($this->person);

        // Increase the likelihood 10% (+0.1) per record
        $randomizer->changeMean(count($records) / 10);

        // You could even look at the severity of the records, and other stuff

        return $randomizer;
    }
}
```

And where you use this in your actual logic, it would now look like:

```
$shouldCommitCrime = (new RandomBool())
    ->extend(new PersonTendency($somePerson))
    ->hasCriminalRecord()
    ->compute();
```

The idea is now that you add tens or hundreds of factors, where each factor sways the mean in a direction.

> Keep in mind: It's perfectly acceptable that the mean falls below `0.0` (0%) or above `1.0` (100%).

💝 Working on the project
------------------------

[](#-working-on-the-project)

This information is good to know if you want to fork the repository, or even contribute to the original.

### Testing

[](#testing)

You can run the test suite using:

```
composer test
```

### Linting

[](#linting)

Linting is carried out with **Laravel Pint**. Point your IDE to use the file `pint.json`.

###  Health Score

26

—

LowBetter than 43% of packages

Maintenance38

Infrequent updates — may be unmaintained

Popularity5

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity46

Maturing project, gaining track record

 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.

###  Release Activity

Cadence

Every ~0 days

Total

3

Last Release

555d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/06b8bda6e13d68f552e84c5ae6c4c9f60db7127ef99676d4d153f35a282a5119?d=identicon)[markhj](/maintainers/markhj)

---

Top Contributors

[![markhj](https://avatars.githubusercontent.com/u/5261387?v=4)](https://github.com/markhj "markhj (6 commits)")

###  Code Quality

TestsPHPUnit

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/markhj-php-tendency/health.svg)

```
[![Health](https://phpackages.com/badges/markhj-php-tendency/health.svg)](https://phpackages.com/packages/markhj-php-tendency)
```

###  Alternatives

[symfony/expression-language

Provides an engine that can compile and evaluate expressions

2.8k208.2M1.4k](/packages/symfony-expression-language)[ergebnis/composer-normalize

Provides a composer plugin for normalizing composer.json.

1.1k37.3M2.1k](/packages/ergebnis-composer-normalize)[sanmai/pipeline

General-purpose collections pipeline

7424.5M21](/packages/sanmai-pipeline)

PHPackages © 2026

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