POPO - "Plain Old Php Object" was inspired by "Plain Old Java Object" (POJO) concept.
POPO generator can also locate, load, validate, and combine schemas to create PHP source code files, representing Arrays / Data Structures / Data Transfer Objects / Doctrine ORM Entities / MongoDB ODM Documents.
POPO Schema can be defined and extended on few levels, and it can be defined in multiple files.
Simple schema in YAML format, describing properties and relations of POPO objects.
#example.popo.yml
$:
config:
namespace: App\Example\Readme
outputPath: tests/
Example:
Foo:
property: [
{name: title}
{name: bar, type: popo, default: Bar::class}
]
Bar:
property: [
{name: title}
]The same example can be split into multiple files. However, this time, it's the Bar that modifies the Foo definition.
#foo.popo.yml
Example:
Foo:
property: [
{name: title}
]#bar.popo.yml
Example:
Foo:
property: [
{name: bar, type: popo, default: Bar::class}
]
Bar:
property: [
{name: title}
]The generated code is the same, but the schema dependencies are inverted.
In both cases, Foo object uses Bar object as its dependency, and they are both defined under Example schema name.
use App\Example\Readme\Foo;
$data = [
'title' => 'A title',
'bar' => [
'title' => 'Bar lorem ipsum',
],
];
$foo = (new Foo)->fromArray($data);
echo $foo->getTitle();
echo $foo->requireBar()->getTitle();Output:
A title
Bar lorem ipsum
use App\Example\Readme\Foo;
$foo = (new Foo);
$foo->requireBar()->setTitle('new value');
print_r($foo->toArray());Output:
[
'title' => null,
'bar' => [
'title' => 'new value',
],
];
Run bin/popo generate -s tests/fixtures/popo-readme.yml or docker-popo generate -s tests/fixtures/popo-readme.yml to generate files from this example.
composer require popo/generator --devNote: The installation can be skipped when using docker, see Docker support section.
You can either use it as composer dependency or as docker command.
-
Define schema file, see tests/fixtures for examples.
-
Generate POPO files, run:
-
with composer
vendor/bin/popo generate -s <schema-path> -o <output-path>
-
with docker
docker-popo generate -s <schema-path> -o <output-path>
-
For example: bin/popo generate -s tests/fixtures/popo.yml or docker-popo generate -s tests/fixtures/popo.yml.
POPO Schema can be defined and extended on few levels- and it can be defined in multiple files.
The schema supports key mapping- inheritance- collections and encapsulation of other POPO objects.
$: # file-config, shared configuration for all POPO objects in current schema file
config:
namespace: string
outputPath: string
namespaceRoot: string|null # if set remaps namespace and outputPath
extend: string|null # which class POPO objects should extend from
implement: string|null # which interface POPO objects should implement
comment: string|null # Class docblock comment
phpComment: string|null # Generated PHP File docblock comment
use: array<string>|[] # Import block in generated PHP class
trait: array<string>|[] # Traits to be used with generated class
attribute: string|null # Class attributes as string
attributes: array<key, value>|[] # Class attributes as key value pairs
classPluginCollection: array<string>|[]
phpFilePluginCollection: array<string>|[]
namespacePluginCollection: array<string>|[]
propertyPluginCollection: array<string>|[]
mappingPolicyPluginCollection: array<string>|[]
default: array # default values
property: array # shared properties
SchemaName: # schema-config
$: # shared configuration for all POPO objects in SchemaName, in all schema files
config:
namespace: string
outputPath: string
namespaceRoot: string|null
extend: string|null
implement: string|null
comment: string|null
phpComment: string|null
use: array<string>|[]
trait: array<string>|[]
attribute: string|null,
attributes: array<key, value>|[]
classPluginCollection: array<string>|[]
phpFilePluginCollection: array<string>|[]
namespacePluginCollection: array<string>|[]
propertyPluginCollection: array<string>|[]
mappingPolicyPluginCollection: array<string>|[]
default: array
property: [{
name: string,
type:
type: string
default: string
supportedTypes: ['array','bool','float','int','string','mixed','const','popo', 'datetime'],
comment: string|null,
default: mixed,
itemType: string|null,
itemName: string|null,
extra: {timezone: ..., format: ...},
attribute: string|null,
attributes: array<key, value>|[]
mappingPolicy: ['none', 'lower', 'upper', 'camel-to-snake', 'snake-to-camel'],
mappingPolicyValue: string|null
}]
PopoName: # popo-config
config:
namespace: string
outputPath: string
namespaceRoot: string|null
extend: string|null
implement: string|null
comment: string|null
phpComment: string|null
use: array<string>|[]
trait: array<string>|[]
attribute: string|null,
attributes: array<key, value>|[]
classPluginCollection: array<string>|[]
phpFilePluginCollection: array<string>|[]
namespacePluginCollection: array<string>|[]
propertyPluginCollection: array<string>|[]
mappingPolicyPluginCollection: array<string>|[]
default: array
property: [{
name: string,
type:
type: string
default: string
supportedTypes: ['array','bool','float','int','string','mixed','const','popo', 'datetime'],
comment: string|null,
default: mixed,
itemType: string|null,
itemName: string|null,
extra: {timezone: ..., format: ...},
attribute: string|null,
attributes: array<key, value>|[]
mappingPolicy: ['none', 'lower', 'upper', 'camel-to-snake', 'snake-to-camel'],
mappingPolicyValue: string|null
}]Defines generated class namespace.
config:
namespace: App\Example
...Defines output directory.
config:
outputPath: src/
...Defines the begging of outputPath that should be removed.
For example to generated files under src/Example with App\Example namespace.
config:
namespace: App\Example
outputPath: src/
namespaceRoot: App\
...Which class should the generated class extend from. Must start with \ or contain ::class.
config:
extend: \App\Example\AbstractDto::class
...Which interface should the generated class implement. Must start with \ or contain ::class.
config:
implement: \App\Example\DtoInterface::class
...Class comment.
config:
comment: |
@Document(collection="events")
...Generated PHP file comment.
config:
phpComment: |
Auto generated.
@SuppressWarnings(PHPMD)
@phpcs:ignoreFile
...Import statements.
config:
use:
- Doctrine\ODM\MongoDB\Mapping\Annotations\Document
- Doctrine\ODM\MongoDB\Mapping\Annotations\Field
- Doctrine\ODM\MongoDB\Mapping\Annotations\Id
...Traits statements.
config:
trait:
- App\Example\MyTrait
...Class attributes value.
config:
attribute: |
#[Doctrine\ORM\Mapping\Entity(repositoryClass: LogEventRepository::class)]
...Attribute value as collection. Supported values:
namevalue:mixed
config:
attributes:
- name: Doctrine\ORM\Mapping\Entity
value: {repositoryClass: LogEventRepository::class}
...Additional plugins used to generate methods.
config:
classPluginCollection:
- \App\Plugin\ExampleMethodPopoPlugin::class
...Additional plugins used to generate namespace block.
config:
namespacePluginCollection:
- \App\Plugin\ExampleNamespacePopoPlugin::class
...Additional plugins used to generate properties.
config:
propertyPluginCollection:
- \App\Plugin\ExamplePropertyPopoPlugin::class
...Set of plugins used to map property names, e.g. fooId => FOO_ID.
config:
mappingPolicyPluginCollection:
- \App\Plugin\SpecialCaseMappingPopoPlugin::class
...The name of the property. The property related methods will be generated based on this value. For example getFooBar().
This is required parameter.
property:
- name: title
...Property data type, supported are:
arrayboolfloatintstringmixedconstpopodatetime
Default property type is string.
property:
- name: precision
type: float
...Docblock value for property and methods.
property:
- name: title
comment: Lorem ipsum
...Default value.
property:
- name: items
default: \App\ExampleInterface::TEST_BUZZ
...Used by datetime data type. Supported values:
formattimezone
property:
- name: created
type: datetime
extra:
timezone: Europe/Paris
format: D, d M y H:i:s O
...Used by array data type together with itemName element. Describes type of single array element.
property:
- name: products
type: array
itemType: Product::class
...Used by array data type. Describes name of single array element. For example: setProducts(array $products), addProduct(Product $item).
property:
- name: products
type: array
itemName: product
...Attribute value.
property:
- name: price
attribute: |
#[Doctrine\ORM\Mapping\Column(type: Types::INTEGER)]
...Attribute value as collection. Supported values:
namevalue:mixed
property:
- name: id
attributes:
- name: Doctrine\ORM\Mapping\Column
value: ['length: 255']
...Dynamically remaps property names, for example, fooId => FOO_ID. Supported values:
noneloweruppercamel-to-snakesnake-to-camel
property:
- name: fooId
mappingPolicy:
- camel-to-snake
- upper
...Statically remaps property names, for example, fooId => FOO_ID.
property:
- name: fooId
mappingPolicyValue: FOO_ID
...The popo-config values override schema-file-config values- and schema-file-config values overwrite schema-config values.
On top of that there is a global-config that is defined when using --schemaConfigFilename parameter.
The configuration was defined as a Schema property.
It will be used by all POPO objects in all files under given schema.
The configuration was defined as a SchemaFile property.
It will be used by all POPO objects in current file.
The configuration was defined as a POPO property.
It will be used by one specific POPO objects in current file.
See tests/fixtures for schema examples.
POPO can remap property keys names, for example change foo_id into fooId.
See Property Name Remapping doc.
New functionality can be provided on the command line, or via configuration.
See Plugins doc.
See Doctrine Support doc.
See Command Line Options doc.
See fixtures and tests for more usage examples.
Add popo scrip to composer and run composer popo in a project.
"scripts": {
"popo": [
"bin/popo generate -s <schema-path>"
]
},
"scripts-descriptions": {
"popo": "Generate POPO files"
}
With docker you can generate files without installing POPO as dependency in the project.
docker container run -it --rm oliwierptak/popo /app/bin/popo
You can either run the command directly, or create an alias, e.g.:
alias docker-popo='docker container run -it --rm oliwierptak/popo /app/bin/popo ${@}'
For example:
docker-popo generate -s tests/fixtures/popo.yml
docker-popo report -s tests/fixtures/popo.yml
See also: bin/docker-popo.
- POPO
v1.x- PHP 7.2+ - POPO
v2.x- PHP 7.2+ - POPO
v3.x- PHP 7.4+ - POPO
v4.x- PHP 7.4+ - POPO
v5.x- PHP 7.4+ - POPO
v6.x- PHP 8+
Schema example that produces generated PopoConfigurator class.
$:
config:
namespace: Popo
outputPath: src/
phpComment: |
@SuppressWarnings(PHPMD)
@phpcs:ignoreFile
Popo:
PopoConfigurator:
default:
phpFilePluginCollection:
- \Popo\Plugin\PhpFilePlugin\StrictTypesPhpFilePlugin::class
- \Popo\Plugin\PhpFilePlugin\CommentPhpFilePlugin::class
namespacePluginCollection:
- \Popo\Plugin\NamespacePlugin\UseStatementPlugin::class
classPluginCollection:
- \Popo\Plugin\ClassPlugin\ClassAttributePlugin::class
- \Popo\Plugin\ClassPlugin\ClassCommentPlugin::class
- \Popo\Plugin\ClassPlugin\ConstPropertyClassPlugin::class
- \Popo\Plugin\ClassPlugin\DateTimeMethodClassPlugin::class
- \Popo\Plugin\ClassPlugin\ExtendClassPlugin::class
- \Popo\Plugin\ClassPlugin\ImplementClassPlugin::class
- \Popo\Plugin\ClassPlugin\IsNewClassPlugin::class
- \Popo\Plugin\ClassPlugin\ListModifiedPropertiesClassPlugin::class
- \Popo\Plugin\ClassPlugin\MetadataClassPlugin::class
- \Popo\Plugin\ClassPlugin\ModifiedToArrayClassPlugin::class
- \Popo\Plugin\ClassPlugin\PopoMethodClassPlugin::class
- \Popo\Plugin\ClassPlugin\RequireAllClassPlugin::class
- \Popo\Plugin\ClassPlugin\UpdateMapClassPlugin::class
- \Popo\Plugin\ClassPlugin\FromArrayClassPlugin::class
- \Popo\Plugin\ClassPlugin\FromMappedArrayClassPlugin::class
- \Popo\Plugin\ClassPlugin\ToArrayClassPlugin::class
- \Popo\Plugin\ClassPlugin\ToMappedArrayClassPlugin::class
- \Popo\Plugin\ClassPlugin\MappingPolicyMethod\ToArrayLowercasePlugin::class
- \Popo\Plugin\ClassPlugin\MappingPolicyMethod\ToArrayUppercasePlugin::class
- \Popo\Plugin\ClassPlugin\MappingPolicyMethod\ToArraySnakeToCamelPlugin::class
- \Popo\Plugin\ClassPlugin\MappingPolicyMethod\ToArrayCamelToSnakePlugin::class
propertyPluginCollection:
- \Popo\Plugin\PropertyPlugin\AddItemPropertyMethodPlugin::class
- \Popo\Plugin\PropertyPlugin\DefinePropertyPlugin::class
- \Popo\Plugin\PropertyPlugin\GetPropertyMethodPlugin::class
- \Popo\Plugin\PropertyPlugin\HasPropertyMethodPlugin::class
- \Popo\Plugin\PropertyPlugin\RequirePropertyMethodPlugin::class
- \Popo\Plugin\PropertyPlugin\SetPropertyMethodPlugin::class
mappingPolicyPluginCollection:
none: \Popo\Plugin\MappingPolicy\NoneMappingPolicyPlugin::class
lower: \Popo\Plugin\MappingPolicy\LowerMappingPolicyPlugin::class
upper: \Popo\Plugin\MappingPolicy\UpperMappingPolicyPlugin::class
snake-to-camel: \Popo\Plugin\MappingPolicy\SnakeToCamelMappingPolicyPlugin::class
camel-to-snake: \Popo\Plugin\MappingPolicy\CamelToSnakeMappingPolicyPlugin::class
property: [
{name: schemaPath}
{name: namespace}
{name: namespaceRoot}
{name: outputPath}
{name: phpFilePluginCollection, type: array, itemType: string, itemName: phpFilePluginClass}
{name: namespacePluginCollection, type: array, itemType: string, itemName: namespacePluginClass}
{name: classPluginCollection, type: array, itemType: string, itemName: classPluginClass}
{name: propertyPluginCollection, type: array, itemType: string, itemName: propertyPluginClass}
{name: mappingPolicyPluginCollection, type: array, itemType: string, itemName: mappingPolicyPluginClass}
{name: schemaConfigFilename}
{name: schemaPathFilter}
{name: schemaFilenameMask, default: '*.popo.yml'}
{name: shouldIgnoreNonExistingSchemaFolder, type: bool}
]}}