beecubu / php-foundation-core
Main package of PHP Object Foundation Framework.
Requires
- php: >=7.1.0
- ext-json: *
- ext-openssl: *
Requires (Dev)
- phpunit/phpunit: ^8
This package is auto-updated.
Last update: 2026-06-24 21:38:23 UTC
README
Core package for PHP Object Foundation. This is the base used by the rest of the framework libraries. It provides:
- A strict property system (
Objectum) with typed properties and validation. - Serialization and JSON export/import (
Serializable) with custom hooks. - Enum utilities (
Enum), property definitions (Property), and serialization helpers. - Common tools: date/time utilities and formatters.
Requirements
- PHP 7.1+
ext-opensslext-json
Installation
composer require beecubu/php-foundation-core
Key Concepts
Objectum (Typed Properties)
Define properties with types and access rules:
<?php
use Beecubu\Foundation\Core\Objectum;
use Beecubu\Foundation\Core\Property;
class User extends Objectum
{
protected function properties(): void
{
$this->properties += [
'name' => [Property::READ_WRITE, Property::IS_STRING],
'age' => [Property::READ_WRITE, Property::IS_INTEGER],
'active' => [Property::READ_WRITE, Property::IS_BOOLEAN],
];
}
}
$u = new User();
$u->name = 'Ada';
$u->active = 'yes';
Enums
use Beecubu\Foundation\Core\Enum;
class Status extends Enum
{
public const Draft = 'draft';
public const Sent = 'sent';
}
Use enums in properties with validation:
use Beecubu\Foundation\Core\Property;
$this->properties += [
'status' => [Property::READ_WRITE, Property::IS_ENUM, Status::class],
];
Serializable
Serializable extends Objectum and adds JSON/RawData serialization with custom hooks and property filtering.
<?php
use Beecubu\Foundation\Core\Serializable;
use Beecubu\Foundation\Core\Property;
class Article extends Serializable
{
protected function properties(): void
{
parent::properties();
$this->properties += [
'title' => [Property::READ_WRITE, Property::IS_STRING],
'content' => [Property::READ_WRITE, Property::IS_STRING],
];
}
}
$a = new Article();
$a->title = 'Hello';
$json = $a->json(); // stdClass
$raw = $a->rawData(); // stdClass
$copy = Article::instanceWithJson($json);
Encrypted Strings
If a property is Property::IS_STRING_ENCRYPTED, values are stored encrypted in raw data.
In JSON, the framework exports them as an EncryptedStringProperty object with kind, encoding, and value.
$this->properties += [
'secret' => [Property::READ_WRITE, Property::IS_STRING_ENCRYPTED],
];
Example JSON shape:
$json = $article->json();
$json->secret->kind; // encrypted-string
$json->secret->encoding; // base64
$json->secret->value; // base64 payload
When importing from JSON:
- If
secretis astring, it is treated as plain text and encrypted. - If
secretis an object, it is treated as anEncryptedStringProperty.
Object Auto-Initialization
By default, IS_OBJECT properties are auto-created when first accessed (if no value is set).
You can disable this globally:
Beecubu\Foundation\Core\Objectum::$CreateObjectPropertiesOnGet = false;
IS_OBJECT and IS_OBJECT_NULLABLE can also accept more than one class by passing an array of class names as the third property definition value:
$this->properties += [
'target' => [Property::READ_WRITE, Property::IS_OBJECT_NULLABLE, [User::class, Team::class]],
];
When importing serialized data for a property with multiple possible classes, include _class in the raw/json object so the framework can resolve which concrete class must be created.
On-Demand References
SerializableProperty::CREATE_ON_DEMAND_BY_ID allows lazy-loading related objects by id.
Important: the referenced class must implement protected static function instanceByRawDataId($id) or lazy loading will fail.
This method is called by the serialization layer when a property is accessed and only an id is available. The default implementation throws an exception, so each on-demand class must provide its own factory.
use Beecubu\Foundation\Core\SerializableProperty;
$this->properties += [
'author' => [Property::READ_WRITE, Property::IS_OBJECT_NULLABLE, Author::class, SerializableProperty::CREATE_ON_DEMAND_BY_ID],
];
Example implementation:
class Author extends Serializable
{
// properties()...
protected static function instanceByRawDataId($id)
{
// Fetch from persistence layer by id and return an Author instance.
// Example: return AuthorRepository::findById($id);
}
}
Serializable Snapshots
SerializableProperty::IS_SNAPSHOT stores a read-only partial copy of a related Serializable object instead of storing the live object or only its id. This is useful for historical data where the owner must keep a small immutable picture of another entity.
The source class declares which fields are part of the snapshot:
class Status extends Serializable
{
protected function properties(): void
{
parent::properties();
$this->properties += [
'title' => [Property::READ_WRITE, Property::IS_STRING],
'color' => [Property::READ_WRITE, Property::IS_STRING],
'description' => [Property::READ_WRITE, Property::IS_STRING],
];
}
protected function snapshotProperties(): void
{
$this->snapshotProperties = ['title', 'color'];
}
}
The owner marks the property as a snapshot:
use Beecubu\Foundation\Core\SerializableProperty;
$this->properties += [
'status' => [Property::READ_WRITE, Property::IS_OBJECT_NULLABLE, Status::class, SerializableProperty::IS_SNAPSHOT],
];
IS_SNAPSHOT is only supported for IS_OBJECT, IS_OBJECT_NULLABLE, and typed IS_ARRAY object properties. Using it with another property type throws SerializableUnsupportedSnapshotPropertyTypeException.
Assigning a Status creates a SerializableSnapshot. The snapshot only exposes the configured properties and all of them are read-only:
$notification->status = $status;
$notification->status->isSnapshot(); // true
$notification->status->isSnapshotOf(Status::class); // true
$notification->status->title; // copied from $status
If an existing SerializableSnapshot is assigned directly, the framework validates that its source class is accepted by the property and that its scope matches the property scope when one is defined.
Snapshots can also be scoped when the same source class needs different partial views:
class Status extends Serializable
{
protected function snapshotProperties(): void
{
$this->snapshotProperties = [
'notification' => ['title', 'color'],
'badge' => ['color'],
];
}
}
$this->properties += [
'status' => [Property::READ_WRITE, Property::IS_OBJECT_NULLABLE, Status::class, SerializableProperty::IS_SNAPSHOT, 'notification'],
];
For dynamic cases, override snapshotPropertiesForScope(?string $scope = null): array and return the property names for that scope.
In RawData mode, snapshots always include _class and include _scope when a scope is defined. These metadata fields are used to reconstruct the virtual snapshot and to validate consistency. If a property defines a scope and imported RawData contains a different _scope, the framework throws SerializableSnapshotScopeMismatchException.
IS_SNAPSHOT also supports IS_OBJECT / IS_OBJECT_NULLABLE properties with multiple accepted classes:
$this->properties += [
'state' => [Property::READ_WRITE, Property::IS_OBJECT_NULLABLE, [OpenState::class, ClosedState::class], SerializableProperty::IS_SNAPSHOT],
];
It can also be used with typed arrays of objects:
$this->properties += [
'states' => [Property::READ_WRITE, Property::IS_ARRAY, State::class, SerializableProperty::IS_SNAPSHOT],
];
SerializableProperty::CREATE_SNAPSHOT_BY_ID behaves like IS_SNAPSHOT, but also accepts reference data as input. When an id or reference payload is assigned or imported, the framework normalizes it through TargetClass::parseDataBeforeInsertInIvarsOnDemand($data), calls TargetClass::instanceByRawDataId($id) immediately, and stores the resulting snapshot.
$this->properties += [
'status' => [Property::READ_WRITE, Property::IS_OBJECT_NULLABLE, Status::class, SerializableProperty::CREATE_SNAPSHOT_BY_ID],
'states' => [Property::READ_WRITE, Property::IS_ARRAY, State::class, SerializableProperty::CREATE_SNAPSHOT_BY_ID],
];
Important: the target class must implement protected static function instanceByRawDataId($id). If the incoming data is not a primitive id, use parseDataBeforeInsertInIvarsOnDemand() in the target class to extract the id exactly as with CREATE_ON_DEMAND_BY_ID. The property value is still a SerializableSnapshot (or an array of snapshots), never a lazy reference.
Tools
Helpers
use Beecubu\Foundation\Core\Tools\Helpers;
Helpers::empty($value); // Better empty() for framework objects
Formatters
use Beecubu\Foundation\Core\Tools\Formatters\NumberFormatter;
use Beecubu\Foundation\Core\Tools\Formatters\DateFormatter;
use Beecubu\Foundation\Core\Tools\Formatters\StringFormatter;
use Beecubu\Foundation\Core\Tools\Formatters\PluralsFormatter;
NumberFormatter::numberToString(1234.56, 'en'); // "1,234.56"
DateFormatter::dateToString(new DateTime(), 'en');
StringFormatter::utf82iso('àéí');
PluralsFormatter::plural('day', 2, 'en'); // "days"
DateTime Helper
use Beecubu\Foundation\Core\Tools\DateTime;
$dt = new DateTime('@1717352123'); // respects php.ini timezone
Custom Properties (Serialization Hooks)
Serializable lets you override how specific properties are imported/exported by defining custom flags in customProperties().
The flags are defined in Beecubu\Foundation\Core\CustomProperty and control:
- When the hook runs: import, export, or both
- Which mode: JSON, RawData, or both
Typical pattern:
use Beecubu\Foundation\Core\CustomProperty;
class Report extends Serializable
{
protected function properties(): void
{
parent::properties();
$this->properties += [
'status' => [Property::READ_WRITE, Property::IS_STRING],
'secret' => [Property::READ_WRITE, Property::IS_STRING],
];
}
protected function customProperties(): void
{
$this->customProperties += [
// Only transform when exporting to JSON
'status' => CustomProperty::ON_EXPORT | CustomProperty::ON_JSON_MODE,
// Only transform when importing from RawData
'secret' => CustomProperty::ON_IMPORT | CustomProperty::ON_RAW_MODE,
];
}
protected function exportCustomProperty(string $property, $value, bool $rawDataMode, ?array $fields = null, bool $excluding = true, bool $ignoreEmptyValues = true)
{
if ($property === 'status') return strtoupper($value);
return $value;
}
protected function importCustomProperty(string $property, $value, bool $replace = true, bool $rawDataMode = false, bool $writableOnly = false)
{
if ($property === 'secret') return trim($value);
return $value;
}
}
Useful flag presets:
CustomProperty::ON_IMPORT_AND_EXPORT_IN_ALL_MODESCustomProperty::ON_IMPORT | CustomProperty::ON_JSON_MODECustomProperty::ON_EXPORT | CustomProperty::ON_RAW_MODE
Property Types Reference
Common property types:
Property::IS_STRINGProperty::IS_STRING_ENCRYPTEDProperty::IS_INTEGERProperty::IS_FLOATProperty::IS_BOOLEANProperty::IS_DATEProperty::IS_ENUMProperty::IS_ENUM_NULLABLEProperty::IS_ARRAYProperty::IS_ARRAY_ENUMProperty::IS_OBJECTProperty::IS_OBJECT_NULLABLEProperty::IS_DICTIONARYProperty::IS_BINARY
Access types:
Property::READ_ONLYProperty::READ_WRITEProperty::WRITE_ONLY