PHPackages                             scrumble-nl/popo - 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. scrumble-nl/popo

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

scrumble-nl/popo
================

Plain old PHP object

2.0.0(7mo ago)511.3k↓35.7%2[1 PRs](https://github.com/scrumble-nl/popo/pulls)2MITPHPPHP &gt;=8.1

Since Feb 20Pushed 7mo agoCompare

[ Source](https://github.com/scrumble-nl/popo)[ Packagist](https://packagist.org/packages/scrumble-nl/popo)[ Docs](https://github.com/scrumble-nl/popo)[ RSS](/packages/scrumble-nl-popo/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (7)Dependencies (8)Versions (11)Used By (2)

Popo
====

[](#popo)

A Popo (Plain old PHP object) is our version of the Pojo (Plain old Java object). The goal of these objects is to replace all variables that would otherwise be key-value arrays with strictly typed PHP objects.

This package only contains a single class (BasePopo) which is intended to be extended by every Popo we create. The BasePopo class ensures that you can return Popo's from your back-end to your front-end, and that they will automatically be converted to JSON without you having to write code for this. Besides that, the BasePopo provides all Popo's with the `toArray()` function.

Installation
------------

[](#installation)

```
composer require scrumble-nl/popo
```

Usage
-----

[](#usage)

When creating a new Popo object simply extend BasePopo. Only public attributes will be included when converting to an array or when returning the Popo to your front-end, it is therefore required you mark visible attributes as public.

### Basic usage

[](#basic-usage)

```
class ProductPopo extends BasePopo
{
    /**
     * @var string
     */
    public string $name;

    /**
     * @var float
     */
    public float $price;

    /**
     * @param string $name
     * @param float  $price
     */
    public function __construct(string $name, float $price)
    {
        $this->name = $name;
        $this->price = $price;
    }
}
```

### Initialization

[](#initialization)

To instantiate the Popo, you could either use the constructor as you are already used to, or develop a reusable helper function in the Popo itself. This function should take into account where the input data is coming from, and in what format that data is.

For example, a typical API response will be decoded JSON, as an object or an array. The function to instantiate the Popo would then be as follows:

```
public static function fromApiResponse(array $apiResponse): ProductPopo
{
    return new self(
        $apiResponse['name'],
        (float) $apiResponse['price'],
    );
}
```

The Popo may then be instantiated from any place within your project by calling this function: `ProductPopo::fromApiResponse($array);`.

You are free to implement other methods, such as a `fromObject`, or `fromArray` whichever suits your case best.

### Strict typing and sub-Popo's

[](#strict-typing-and-sub-popos)

You may also have cases where your Popo's have their own sub-Popo's. For example, a `ProductPopo` may have a property `public Collection $categories`, which would then be an Collection of `CategoryPopo`'s.

To ensure optimal usage of strict typing when working with sub-Popo's in arrays or Collections, make sure you typehint these in your PHPDoc:

```
/**
* @var Collection
*/
public Collection $categories;
```

Testing
-------

[](#testing)

When writing tests for your application, you may find it useful to create instances of your Popo objects. One way to do this is by using factories.

A factory is a class that is responsible for creating instances of another class. In the context of Popo objects, a factory can be used to create instances of your Popo objects with random or specific values, which can be very useful for testing.

### Testing usage

[](#testing-usage)

First, create a factory class for your Popo object. The factory class should have a method that returns an instance of your Popo object with random or specific values. Here's an example factory class for the ProductPopo object.

```
use Tests\Popo\ProductPopo;
use Scrumble\Popo\PopoFactory;

class ProductPopoFactory extends PopoFactory
{
    /**
     * @var null|string
     */
    public ?string $popoClass = ProductPopo::class;

    /**
     * {@inheritDoc}
     */
    public function definition(): array
    {
        return [
            'name' => $this->faker->name,
            'price' => $this->faker->randomNumber(1, 10),
        ];
    }
}
```

Then, make sure you include the factory in your Popo's definition.

```
use Scrumble\Popo\HasPopoFactory;

class ProductPopo extends BasePopo
{
    use HasPopoFactory;

    /**
     * @var string
     */
    public string $name;

    /**
     * @var float
     */
    public float $price;

    /**
     * @param string $name
     * @param float  $price
     */
    public function __construct(string $name, float $price)
    {
        $this->name = $name;
        $this->price = $price;
    }

    /**
     * @return string
     */
    public static function popoFactory(): string
    {
        return ProductPopoFactory::class;
    }
}
```

### Factory create

[](#factory-create)

Let's say you have a service function `create` that takes in a ProductPopo object and creates a new product based on its properties. You can use Popo factories to easily create test data for this function:

```
/** @test */
public function can_create_product(): void
{
    $productPopo = ProductPopo::factory()->create();

    $product = $this->productService->create($productPopo);

    $this->assertDatabaseHas('products', [
        'name' => $productPopo->name,
        'price' => $productPopo->price,
    ]);
}
```

### Create multiple

[](#create-multiple)

Sometimes you may want to create multiple instances of a Popo with slightly different attributes in your unit tests. You can achieve this using the `count()` and `sequence()` methods provided by the package.

The `count(int $count)` method takes an integer as its argument and tells the factory how many instances to create. The `sequence(array|closure $sequense)` method takes an array of attribute values for each instance to create. You can also provide a closure to the `sequence()` method that returns an array of attribute values.

Here's an example of how you can use `count()` and `sequence()` to create multiple instances of a Popo with slightly different attributes:

```
ProductPopo::factory()
    ->count(3)
    ->sequence(
        ['name' => 'Apples'],
        ['name' => 'Cookies'],
        ['name' => 'Bread'],
    )
    ->create();
```

> Note that passing attributes to the `create()` method will override values passed in `sequence()`.

### Raw data

[](#raw-data)

The `raw(array $attributes = [])` function returns an array with the attributes that will be used to create an array with the values from the factory.

```
$productData = ProductPopo::factory()->raw();
```

### Overriding data

[](#overriding-data)

The `state()` function is a powerful feature of the Laravel factories that allows you to define a set of attributes that will override the default values of the factory. This can be useful when you need to create a Popo with a specific set of attributes that cannot be generated by the default factory.

Here's an example of how you can use the `state()` function to set the state of a Popo:

```
class ProductPopoFactory extends Factory
{
    ...

    public function published(): array
    {
        return $this->state(function (array $attributes) {
            return [
                'published' => true,
            ];
        });
    }
}
```

In this example, we have defined a factory for the `ProductPopo` class with a default set of attributes that includes a `published` attribute set to false. We have also defined a `published()` function that uses the `state()` function to set the `published` attribute to true.

Now, let's say we want to create a Popo that is published. We can do this by calling the published() function on the factory:

```
$popo = ProductPopo::factory()->published()->create();
```

You can override specific data by passing an array of attribute values to the `create()` function. The array should contain the attribute names as keys and the desired values as their corresponding values. The values in the array will override any default values and any values that have been set using the `state()` function.

```
$popo = ProductPopo::factory()->create([
    'name' => 'New product',
]);
```

In the above example, we are creating a new ProductPopo instance and overriding the default name value with our own value.

Similarly, you can also override specific data using the `raw()` function. By passing an array of attribute values to the `raw()` function, the values will override any default values and any values that have been set using the `state()` function.

```
$productData = ProductPopo::factory()->raw([
    'name' => 'New product',
]);
```

In the above example, we are creating a new array of ProductPopo instance attributes using the `raw()` function and overriding the default name value with our own value.

Laravel Data
------------

[](#laravel-data)

The Popo package uses [Laravel Data](https://spatie.be/docs/laravel-data) by Spatie under the hood, which adds a lot of functionality to the basic Popo concept. Make sure to check out their documentation for more information on what you can do with Popo's. The config file can be published using the following command:

```
php artisan vendor:publish --provider="Spatie\LaravelData\LaravelDataServiceProvider" --tag="data-config"
```

Typescript Type Generation
--------------------------

[](#typescript-type-generation)

This package also includes [Laravel TypeScript Transformer](https://spatie.be/docs/typescript-transformer) by Spatie to generate Typescript types from your Popo's. This makes it easy to keep your front-end and back-end in sync when it comes to data structures. To start using the TypeScript Transformer, you need to publish the config file using the following command:

```
php artisan vendor:publish --provider="Spatie\LaravelTypeScriptTransformer\TypeScriptTransformerServiceProvider" --tag="typescript-transformer-config"
```

This will publish a config file which you can customize to fit your needs. After publishing the config file, you can generate the TypeScript types by running the following command:

```
php artisan typescript:transform
```

Contributing
------------

[](#contributing)

If you would like to see additions/changes to this package you are always welcome to add some code or improve it.

Scrumble
--------

[](#scrumble)

This product has been originally developed by [Scrumble](https://www.scrumble.nl) for internal use. As we have been using lots of open source packages we wanted to give back to the community. We hope this helps you getting forward as much as other people helped us!

###  Health Score

45

—

FairBetter than 93% of packages

Maintenance65

Regular maintenance activity

Popularity31

Limited adoption so far

Community17

Small or concentrated contributor base

Maturity56

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 52% 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 ~100 days

Recently: every ~138 days

Total

7

Last Release

211d ago

Major Versions

1.3.1 → 2.0.02025-10-20

### Community

Maintainers

![](https://www.gravatar.com/avatar/a0d746a86e1d0d0b66e93e2ccdbab5b08931f5e5b93806ae183f3f3fc3c8ce57?d=identicon)[RicoClark](/maintainers/RicoClark)

---

Top Contributors

[![Luukdewaaier](https://avatars.githubusercontent.com/u/54863704?v=4)](https://github.com/Luukdewaaier "Luukdewaaier (13 commits)")[![RicoClark](https://avatars.githubusercontent.com/u/54905956?v=4)](https://github.com/RicoClark "RicoClark (4 commits)")[![stefanScrumble](https://avatars.githubusercontent.com/u/89080949?v=4)](https://github.com/stefanScrumble "stefanScrumble (4 commits)")[![christian2430](https://avatars.githubusercontent.com/u/228792502?v=4)](https://github.com/christian2430 "christian2430 (3 commits)")[![KevinVanH](https://avatars.githubusercontent.com/u/27767688?v=4)](https://github.com/KevinVanH "KevinVanH (1 commits)")

---

Tags

phplaraveldtopopo

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

### Embed Badge

![Health badge](/badges/scrumble-nl-popo/health.svg)

```
[![Health](https://phpackages.com/badges/scrumble-nl-popo/health.svg)](https://phpackages.com/packages/scrumble-nl-popo)
```

###  Alternatives

[gehrisandro/tailwind-merge-laravel

TailwindMerge for Laravel merges multiple Tailwind CSS classes by automatically resolving conflicts between them

341682.2k18](/packages/gehrisandro-tailwind-merge-laravel)[iteks/laravel-enum

A comprehensive Laravel package providing enhanced enum functionalities, including attribute handling, select array conversions, and fluent facade interactions for robust enum management in Laravel applications.

2516.7k](/packages/iteks-laravel-enum)[salmanzafar/laravel-geocode

A Laravel Library to find Lat and Long of a given Specific Address

153.9k](/packages/salmanzafar-laravel-geocode)

PHPackages © 2026

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