elazar / phanua
Builds Cycle ORM schemas from OpenAPI 3 component schemas
Requires
- php: >=7.4
- cycle/orm: ^1.5
- cycle/schema-builder: ^1.2
- jane-php/open-api-3: ^7.1
- pimple/pimple: ^3.4
- psr/container: ^1
- psr/log: ^1.1
Requires (Dev)
- ext-pdo_sqlite: *
- friendsofphp/php-cs-fixer: ^3.0
- mockery/mockery: ^1.4
- pestphp/pest: ^1.8
This package is auto-updated.
Last update: 2022-12-29 03:39:17 UTC
README
OpenAPI 3 + Jane + Cycle ORM = 🔥
Phanua builds Cycle ORM schemas from OpenAPI 3 component schemas.
Released under the MIT License.
WARNING: This project is in an alpha state of development and may be subject to changes that break backward compatibility. It may contain bugs, lack features or extension points, or be otherwise unsuitable for production use. User discretion is advised.
Requirements
Installation
Use Composer.
composer require elazar/phanua
Usage
<?php /** * 1. Configure the Phanua service provider. * * The next section covers this step in more detail. */ $provider = new \Elazar\Phanua\Service\Provider; // Configure $provider as needed here. /** * 2. Use the Phanua service provider to create a Phanua schema builder. */ $schemaBuilder = $provider->getSchemaBuilder(); /** * 3. Use the Phanua schema builder to generate a Cycle schema or ORM instance. */ // To configure an existing ORM instance to use the generated schema: $orm = new \Cycle\ORM\ORM(/* ... */); // ... $schema = $schemaBuilder->buildSchema(); $orm = $orm->withSchema($schema); // To have Phanua create a new ORM instance with the generated schema: $orm = $schemaBuilder->buildOrm();
Configuration
For its most basic use, Phanua needs three parameters:
- the path to an OpenAPI 3 specification file in JSON or YAML format;
- a PHP namespace used by generated Jane model class files (without the
Model
subnamespace added by Jane); and - a Cycle ORM database configuration.
You can pass these parameters to Phanua by using an instance of the Phanua service provider class Elazar\Phanua\Service\Provider
.
You can provide the path and namespace provided using your Jane configuration file or by passing them directly to the service provider instance.
<?php use Elazar\Phanua\Service\Provider; // To load the Jane configuration file, provide the path $provider = (new Provider) ->withJaneConfiguration('/path/to/.jane-openapi.php'); // If file is already loaded, provide the contained array $janeConfig = require '.jane-openapi.php'; $provider = (new Provider) ->withJaneConfiguration($janeConfig); // To pass the same values directly: $provider = (new Provider) ->withOpenApiSpecPath('path/to/openapi.json') ->withNamespace('\\Foo\\Generated');
You can provide the database configuration by specifying the same array passed to Spiral\Database\Config\DatabaseConfig
. See related Cycle documentation for an example of this array.
<?php $databaseConfig = [ /* ... */ ]; $provider = (new \Elazar\Phanua\Service\Provider) ->withDatabaseConfig($databaseConfig);
Providing the database configuration in other ways requires an explanation of how Phanua handles its dependencies.
Overriding Dependencies
To overriding a dependency of Phanua, you must provide a dependency injection container that includes that dependency.
Phanua can use any container that implements the PSR-11 standard. An example of such a container is Pimple, which Phanua uses internally. Pimple is the recommended container to use if you aren't already using a different one.
<?php // Create a Pimple or PSR-11 container instance $container = new \Pimple\Container; // Then configure Phanua to use it $provider = (new \Elazar\Phanua\Service\Provider) ->withDelegateContainer($container);
Phanua expects this container to use fully-qualified class names as entry identifiers. Below are examples of alternate methods of configuring Pimple to supply the database configuration.
<?php $container = new \Pimple\Container; // Compared to the earlier example of passing the database configuration to // Phanua as an array, this is the next easiest / most low-level method of doing // so if you're not already using a container in your application. use Spiral\Database\Config\DatabaseConfig; $container[DatabaseConfig::class] = fn() => new DatabaseConfig( // Pass the same array passed to Provider->withDatabaseConfig() in the earlier // example here. ); // If you're already using a container and it includes a configured instance of // the DatabaseManager class used by Cycle ORM, you can specify that instead. use Spiral\Database\Config\DatabaseManager; $container[DatabaseManager::class] = fn() => new DatabaseManager( new DatabaseConfig(/* ... */) ); // Or you can specify an implementation of Cycle\ORM\FactoryInterface, such as // the Cycle\ORM\Factory class. use Spiral\Database\Config\{Factory, FactoryInterface}; $container[FactoryInterface::class] = fn() => new Factory( new DatabaseManager(/* ... */) ); // Or you can specify an instance of Cycle\ORM\ORM. use Cycle\ORM\ORM; $container[ORM::class] = fn() => new ORM( new Factory(/* ... */) );
If you're already using Pimple and want Phanua to use dependencies you've registered in your container, you can register your configured Phanua service provider instance with your container as a provider.
<?php $provider = new \Elazar\Phanua\Service\Provider; // Configure $provider as needed here. $container = new \Pimple\Container; $container->register($provider);
Once you've configured the Phanua service provider with the necessary parameters, it's possible to use the container it builds independently.
<?php $provider = new \Elazar\Phanua\Service\Provider; // Configure $provider as needed here. // Pimple $container = $provider->getContainer(); // PSR-11 $psrContainer = $provider->getPsrContainer(); // To get the schema builder: use Elazar\Phanua\Schema\Builder; // Pimple $schemaBuilder = $container[Builder::class]; // PSR-11 $schemaBuilder = $container->get(Builder::class);
Role Resolver
Cycle ORM uses the term "role" to refer to a unique name for an entity.
Phanua defines the interface Elazar\Phanua\Entity\RoleResolverInterface
for classes used to determine the role for a given entity.
This interface contains a single method getRole()
which receives a single parameter: a string
containing the name of the OpenAPI component corresponding to the entity.
getRole()
must return a string
containing the determined role for the entity.
The implementation of this interface that Phanua uses by default is Elazar\Phanua\Entity\RoleResolver
. Its getRole()
method returns the OpenAPI component name it receives so that the component name and role are the same.
To override the default implementation, add an entry to your container using the key Elazar\Phanua\Entity\RoleResolverInterface
and have it resolve to an instance of a class that implements the interface.
Class Resolver
Entities in Cycle ORM schemas have corresponding classes.
Phanua defines the interface Elazar\Phanua\Entity\ClassResolverInterface
for classes used to determine the class for a given entity.
This interface contains a single method, getClass()
, which receives a single parameter: a string
containing the name of the OpenAPI component corresponding to the entity.
getClass()
must return a string
containing the fully-qualified name of the class for the entity.
The implementation of this interface that Phanua uses by default is Elazar\Phanua\Entity\ClassResolver
. Its getClass()
method returns a class name constructed with the namespace used by Jane from the Phanua service provider configuration and the OpenAPI component name it receives.
To override the default implementation, add an entry to your container using the key Elazar\Phanua\Entity\ClassResolverInterface
and have it resolve to an instance of a class that implements the interface.
Table Resolver
Each Cycle ORM entity has a corresponding table.
Phanua defines the interface Elazar\Phanua\Entity\TableResolverInterface
for classes used to determine the table associated with a given entity.
This interface contains a single method, getTable()
, which receives three parameters:
- a
string
containing the name of the OpenAPI component corresponding to the entity; - an instance of
Jane\Component\OpenApi3\JsonSchema\Model\Schema
representing the component schema; and - an instance of
Cycle\Schema\Definition\Entity
representing the entity.
getTable()
must return a string
containing the name of the table for the entity.
The implementation of this interface that Phanua uses by default is Elazar\Phanua\Entity\TableResolver
. Its getTable()
method returns the entity role, as determined by the instance of Elazar\Phanua\Entity\RoleResolverInterface
in use, so that the role and table name are the same. See the "Role Resolver" section above for further details.
To override the default implementation, add an entry to your container using the key Elazar\Phanua\Entity\TableResolverInterface
and have it resolve to an instance of a class that implements the interface.
Name Resolver
Entities in Cycle ORM contain fields that have names.
Phanua defines the interface Elazar\Phanua\Field\NameResolverInterface
for classes used to determine the name of a field.
This interface contains a single method, getName()
, which receives three parameters:
- a
string
containing the name of the OpenAPI component corresponding to the entity containing the field; - a
string
containing the name of the component property corresponding to the field; and - an instance of
Jane\Component\OpenApi3\JsonSchema\Model\Schema
representing the property schema.
getName()
must return a string
containing the name to assign to the field.
The implementation of this interface that Phanua uses by default is Elazar\Phanua\Field\NameResolver
. Its getName()
method returns the OpenAPI property name it receives so that the property name and field name are the same.
To override the default implementation, add an entry to your container using the key Elazar\Phanua\Field\NameResolverInterface
and have it resolve to an instance of a class that implements the interface.
Column Resolver
Cycle ORM entity fields have corresponding table columns.
Phanua defines the interface Elazar\Phanua\Field\ColumnResolverInterface
for classes used to determine the name of the column for a field.
This interface contains a single method, getColumn()
, which receives three parameters:
- a
string
containing the name of the OpenAPI component corresponding to the entity containing the field; - a
string
containing the name of the component property corresponding to the field; and - an instance of
Jane\Component\OpenApi3\JsonSchema\Model\Schema
representing the property schema.
getColumn()
must return a string
containing the name to assign to the column.
The implementation of this interface that Phanua uses by default is Elazar\Phanua\Field\ColumnResolver
. Its getColumn()
method returns the OpenAPI property name it receives so that the property name and column name are the same.
To override the default implementation, add an entry to your container using the key Elazar\Phanua\Field\ColumnResolverInterface
and have it resolve to an instance of a class that implements the interface.
Type Resolver
Each Cycle ORM entity field has a corresponding abstract type.
Phanua defines the interface Elazar\Phanua\Field\TypeResolverInterface
for classes used to determine the type for a field.
This interface contains a single method, getType()
, which receives three parameters:
- a
string
containing the name of the OpenAPI component corresponding to the entity containing the field; - a
string
containing the name of the component property corresponding to the field; and - an instance of
Jane\Component\OpenApi3\JsonSchema\Model\Schema
representing the property schema.
getType()
may return a string
containing the type to assign to the column or null
if it's unable to resolve the column to a type.
The implementation of this interface that Phanua uses by default is Elazar\Phanua\Field\TypeResolver
. Its getType()
method returns a type it derives from the property type and format as well as validation constraints on the property value (e.g. minimum
, exclusiveMinimum
, maximum
, or exclusiveMaximum
for numeric values, minLength
and maxLength
for string / text values). Lacking these constraints, getType()
returns the largest available type to allow for maximal possible values.
NOTE: This implementation does not handle nested objects, arrays, or references to other component schemas. This may change in the future.
In the event of being unable to resolve to a more specific type, getType()
supports falling back to a specified type. To reduce configuration needed for common use cases, the default value for this fallback type is 'string'
.
You may want change this fallback type if, for example, you want to compose TypeResolver
with your own resolver implementation to resolve types that TypeResolver
doesn't cover. In that case, you would set the fallback type to null
so that your resolver knows when TypeResolver
can't resolve a given type. Below is an example of overriding the fallback type using a Pimple container.
<?php use Elazar\Phanua\Field\TypeResolver; $container[TypeResolver::class] = new TypeResolver( null // fallback value goes here );
To override the default type resolver implementation with your own, add an entry to your container using the key Elazar\Phanua\Field\TypeResolverInterface
and have it resolve to an instance of a class that implements the interface.
If you want to use the default type resolver, your own resolver, or another resolver together, you can compose them using Elazar\Phanua\Field\CompositeFieldResolver
, which will use resolvers in turn until one returns a type.
<?php use Elazar\Phanua\Field\CompositeTypeResolver; use Elazar\Phanua\Field\TypeResolver; use Elazar\Phanua\Field\TypeResolverInterface; use My\CustomTypeResolver; // If you want your resolver to be tried first: $container[TypeResolverInterface::class] = new CompositeTypeResolver( new CustomTypeResolver(), new TypeResolver(), ); // If you want the default resolver to be tried first: $container[TypeResolverInterface::class] = new CompositeTypeResolver( new TypeResolver( null // Required for CompositeTypeResolver to fall back to your resolver ), new CustomTypeResolver(), );
Primary Resolver
Each table must have a primary index on one or more columns for a compiled Cycle ORM schema to include it.
This circumstance is a limitation of the compiler that handles compiling entities from the registry into a schema: it skips entities without a primary index.
To draw attention to this behavior, Phanua throws an exception if primary index resolution fails for a given entity. To avoid this, you must explicitly exclude any entities in your OpenAPI specification that you do not want to include in the generated Cycle ORM schema. The next section discusses this further.
Phanua defines the interface Elazar\Phanua\Field\PrimaryResolverInterface
for classes used to determine whether a given field should be part of the primary index of the table containing its respective column.
This interface contains a single method, isPrimary()
, which receives three parameters:
- a
string
containing the name of the OpenAPI component corresponding to the entity containing the field; - a
string
containing the name of the component property corresponding to the field; and - an instance of
Jane\Component\OpenApi3\JsonSchema\Model\Schema
representing the property schema.
isPrimary()
must return a boolean
value where a value of true
indicates that the given field is part of the primary index for the associated table and a value of false
indicates that it's not.
The implementation of this interface that Phanua uses by default is Elazar\Phanua\Field\PrimaryResolver
. Its isPrimary()
method returns true
if the given property name is id
or false
otherwise. This class assumes that the property and column names are the same, which is the case when using the default implementation of Elazar\Phanua\Field\ColumnResolverInterface
detailed in the "Column Resolver" section above.
If your property and column names are not consistent with each other, or if any of your tables have a primary index that includes more than one column, create your own implementation of this interface to suit the needs of your database.
To override the default implementation, add an entry to your container using the key Elazar\Phanua\Field\PrimaryResolverInterface
and have it resolve to an instance of a class that implements the interface.
Entity Resolver
Phanua represents the process of converting an OpenAPI component to a Cycle ORM entity with the interface Elazar\Phanua\Entity\EntityResolverInterface
.
This interface contains a single method, getEntity()
, which receives two parameters:
- a
string
containing the name of the OpenAPI component corresponding to the entity; and - an instance of
Jane\Component\OpenApi3\JsonSchema\Model\Schema
representing the component schema.
getEntity()
must return a populated instance of Cycle\Schema\Definition\Entity
representing the entity corresponding to the given component or null
if it fails to resolve the component to an entity.
The implementation of this interface that Phanua uses by default is Elazar\Phanua\Entity\EntityResolver
. It composes an implementation of each of Elazar\Phanua\Entity\RoleResolverInterface
and Elazar\Phanua\Entity\ClassResolverInterface
, which it uses to determine the entity role and class respectively.
It also provides for the exclusion of specific components from having an entity in the generated schema, such as those components where the corresponding entity would not have a primary index. The easiest way to exclude one or more components is by using the service provider: by default, it handles injecting a callback to filter components into EntityResolver
.
<?php use Jane\Component\OpenApi3\JsonSchema\Model\Schema; $componentFilter = fn ( string $componentName, Schema $componentSchema ): bool => !in_array($componentName, [ // These components are excluded from the schema 'component1', 'component2', // ... ]); $provider = (new \Elazar\Phanua\Service\Provider) ->withComponentFilter($componentFilter);
You can also exclude components by overriding the EntityResolver
instance used by default with one that includes a callback to filter entities in the $filterCallback
constructor parameter of EntityResolver
or does something similar for your own entity resolver implementation. The example below does this using a Pimple container.
<?php use Elazar\Phanua\{ Entity\ClassResolverInterface, Entity\EntityResolver, Entity\EntityResolverInterface, Entity\RoleResolverInterface, Service\Provider }; use Jane\Component\OpenApi3\JsonSchema\Model\Schema; $provider = new Provider; /** * 1. Get a container from the Phanua service provider to override some of the * default dependencies it defines. */ // Pimple $phanuaContainer = $provider->getContainer(); // PSR-11 $phanuaContainer = $provider->getPsrContainer(); /** * 2. In your own container, use the key EntityResolverInterface::class and * assign it an instance of EntityResolver or your own entity resolver * implementation, injecting any default Phanua dependencies that you need. * Below is an example of doing this using Pimple containers. */ $yourContainer = new \Pimple\Container; $yourContainer[EntityResolverInterface::class] = fn() => new EntityResolver( $phanuaContainer[RoleResolverInterface::class], $phanuaContainer[ClassResolverInterface::class], function ( string $componentName, string $propertyName, Schema $propertySchema ): bool { /* return TRUE to include or FALSE to exclude component */ } ); /** * 3. Set your container as the delegate container in the Phanua service * provider. */ $provider = $provider->withDelegateContainer($yourContainer); /** * 4. Get the schema builder as normal. It will use the custom dependencies * you've defined. */ $schemaBuilder = $provider->getSchemaBuilder();
It's entirely optional to have your entity resolver use other Phanua dependencies, as the default Phanua implementation does; your entity resolver can function in whatever way you like.
Another approach to consider is having your entity resolver implementation compose the default implementation that Phanua uses. By doing so, you can use the default implementation for entities that are compatible with it and your own implementation for those that are not. See the example of this below.
<?php /** * 1. Define your entity resolver implementation. */ use Cycle\Schema\Definition\Entity; use Elazar\Phanua\Entity\EntityResolverInterface; use Jane\Component\OpenApi3\JsonSchema\Model\Schema; class YourEntityResolver implements EntityResolverInterface { private EntityResolverInterface $entityResolver; // Have the constructor accept another resolver implementation as a // parameter. public function __construct( EntityResolverInterface $entityResolver, /* ... */ ) { $this->entityResolver = $entityResolver; /* ... */ } // Then, in the implementation of the interface method... public function getEntity( string $componentName, Schema $componentSchema ): ?Entity { // $entity = ... // ... if your implementation can't resolve an entity... if ($entity === null) { // ... then have it defer to the injected resolver. $entity = $this->entityResolver->getEntity( $componentName, $componentSchema ); } return $entity; } } /** * 2. Inject your implementation to have Phanua use it. */ // Get the Phanua container and configure your container as normal... $phanuaContainer = $provider->getContainer(); $yourContainer = new \Pimple\Container; $yourContainer[EntityResolverInterface::class] = fn() => new YourEntityResolver( // ... then inject the entity resolver implementation from the Phanua // container into your own implementation. $phanuaContainer[EntityResolverInterface::class] );
Field Resolver
Phanua includes a resolver for fields as it does for entities, represented by the Elazar\Phanua\Field\FieldResolverInterface
.
This interface contains a single method, getField()
, which receives three parameters:
- a
string
containing the name of the OpenAPI component corresponding to the entity containing the field; - a
string
containing the name of the component property corresponding to the field; and - an instance of
Jane\Component\OpenApi3\JsonSchema\Model\Schema
representing the property schema.
getField()
must return a populated instance of Cycle\Schema\Definition\Field
representing the field corresponding to the given property or null
if it fails to resolve the component to a field.
The implementation of this interface that Phanua uses by default is Elazar\Phanua\Field\FieldResolver
. It composes an implementation of each of Elazar\Phanua\Field\ColumnResolverInterface
, Elazar\Phanua\Field\TypeResolverInterface
, and Elazar\Phanua\Field\PrimaryResolverInterface
, which it uses to determine the field column, type, and presence in the primary index respectively.
It also handles setting other options for the field, such as its default value and whether it's nullable based on corresponding default
and nullable
values from the given property schema.
FieldResolver
also provides for the exclusion of specific properties from having a field in the generated schema. The easiest way to exclude one or more properties is by using the service provider: by default, it handles injecting a callback to filte properties into FieldResolver
.
<?php use Jane\Component\OpenApi3\JsonSchema\Model\Schema; $propertyFilter = fn ( string $componentName, string $propertyName, Schema $propertySchema ): bool => // Exclude all properties with this name $property !== 'propertyName' // Exclude the property only in the specified component && "$component.$property" !== 'componentName.propertyName'; $provider = (new \Elazar\Phanua\Service\Provider) ->withPropertyFilter($propertyFilter);
You can also exclude properties by overriding the FieldResolver
instance used by default with one that includes a callback to filter properties in the $filterCallback
constructor parameter of FieldResolver
. The example below does this using a Pimple container.
<?php use Elazar\Phanua\{ Field\ColumnResolverInterface, Field\FieldResolver, Field\FieldResolverInterface, Field\PrimaryResolverInterface, Field\TypeResolverInterface, Service\Provider }; use Jane\Component\OpenApi3\JsonSchema\Model\Schema; $provider = new Provider; /** * 1. Get a container from the Phanua service provider to override some of the * default dependencies it defines. */ // Pimple $phanuaContainer = $provider->getContainer(); // PSR-11 $phanuaContainer = $provider->getPsrContainer(); /** * 2. In your own container, use the key FieldResolverInterface::class and * assign it an instance of your entity resolver implementation, injecting * any default Phanua dependencies that you need. Below is an example of * doing this using Pimple containers. */ $yourContainer = new \Pimple\Container; $yourContainer[FieldResolverInterface::class] = fn() => new FieldResolver( $phanuaContainer[ColumnResolverInterface::class], $phanuaContainer[PrimaryResolverInterface::class], $phanuaContainer[TypeResolverInterface::class], function ( string $componentName, string $propertyName, Schema $propertySchema ): bool { /* return TRUE to include or FALSE to exclude property */ } ); /** * 3. Set your container as the delegate container in the Phanua service * provider. */ $provider = $provider->withDelegateContainer($yourContainer); /** * 4. Get the schema builder as normal. It will use the custom dependencies * you've defined. */ $schemaBuilder = $provider->getSchemaBuilder();
It's entirely optional to have your field resolver use other Phanua dependencies, as the default Phanua implementation does; your field resolver can function in whatever way you like.
Another approach to consider is having your field resolver implementation compose the default implementation that Phanua uses. By doing so, you can use the default implementation for fields that are compatible with it and your own implementation for those that are not. See the example of this below.
<?php /** * 1. Define your field resolver implementation. */ use Cycle\Schema\Definition\Field; use Elazar\Phanua\Field\FieldResolverInterface; use Jane\Component\OpenApi3\JsonSchema\Model\Schema; class YourFieldResolver implements FieldResolverInterface { private FieldResolverInterface $fieldResolver; // Have the constructor accept another resolver implementation as a // parameter. public function __construct( FieldResolverInterface $fieldResolver, /* ... */ ) { $this->fieldResolver = $fieldResolver; /* ... */ } // Then, in the implementation of the interface method... public function getField( string $componentName, string $propertyName, Schema $propertySchema ): ?Field { // $field = ... // ... if your implementation can't resolve an entity... if ($field === null) { // ... then have it defer to the injected resolver. $field = $this->fieldResolver->getField( $componentName, $propertyName, $propertySchema ); } return $field; } } /** * 2. Inject your implementation to have Phanua use it. */ // Get the Phanua container and configure your container as normal... $phanuaContainer = $provider->getContainer(); $yourContainer = new \Pimple\Container; $yourContainer[FieldResolverInterface::class] = fn() => new YourFieldResolver( // ... then inject the field resolver implementation from the Phanua // container into your own implementation. $phanuaContainer[FieldResolverInterface::class] );
Logger
Phanua supports any PSR-3 logger. By default, it uses Psr\Logger\NullLogger
, which discards any logged entries. To store these entries, you must override the default logger with one that does something with them. Below is an example of using the Pimple container to override the default logger with one from the Monolog library.
<?php use Elazar\Phanua\Service\Provider; use Monolog\Logger; use Pimple\Container; use Psr\Log\LoggerInterface; $yourContainer = new Container; $yourContainer[LoggerInterface::class] = function () { $logger = new Logger; // Configure $logger as needed here return $logger; }; $provider = (new Provider) ->withDelegateContainer($yourContainer);
Specification Loader
To convert an OpenAPI specification to a Cycle ORM schema, Phanua must first load and parse that specification.
Phanua defines the interface Elazar\Phanua\Schema\SpecLoaderInterface
for classes used to load and parse OpenAPI specification files.
This interface contains a single method load()
which receives a single parameter: a string
containing the path to an OpenAPI specification file.
load()
must return an instance of Jane\Component\OpenApi3\JsonSchema\Model\OpenApi
containing the parsed specification or throw an instance of Elazar\Phanua\Schema\Exception
if loading or parsing the specification fails.
The implementation of this interface that Phanua uses by default is Elazar\Phanua\Schema\SpecLoader
. It handles logging events related to loading and parsing the specification.
To override the default implementation, add an entry to your container using the key Elazar\Phanua\Schema\SpecLoaderInterface
and have it resolve to an instance of a class that implements the interface.
Registry
As Phanua converts OpenAPI components to Cycle ORM entities, it uses an instance of Cycle\Schema\Registry
to register the entities and link them to tables. This registry is ultimately used to compile the entities into an instance of Cycle\ORM\Schema
.
By default, Phanua uses an empty Registry
instance configured for your database. You want to use a different instance if, for example, you're compiling entities generated from other sources into the same schema with entities generated by Phanua.
In such situations, add an entry to your container using the key Cycle\Schema\Registry
and have it resolve to your desired Registry
instance to have Phanua use it. Note that you can use the Phanua container to either use a modified version of its default registry instance or to supply your registry's required implementation of Spiral\Database\DatabaseProviderInterface
using the same instance of Spiral\Database\DatabaseManager
that Phanua does. Below is an example of doing each with a Pimple container.
<?php use Cycle\Schema\Registry; use Phanua\Service\Provider; use Pimple\Container; use Spiral\Database\DatabaseProviderInterface; $provider = new Provider; // If you're using Phanua\Service\Provider as a Pimple provider: $yourContainer = new Container; $yourContainer->register($provider); $yourContainer->extend(Registry::class, function (Registry $registry) { // Change $registry as needed here }); // If you're not using Phanua\Service\Provider as a Pimple provider: $phanuaContainer = $provider->getContainer(); $yourContainer = new Container; $yourContainer[Registry::class] = function () use ($phanuaContainer) { $databaseProvider = $phanuaContainer[DatabaseProviderInterface::class]; $registry = new Registry($databaseProvider); // Configure $registry here as needed return $registry; }; $phanuaContainer[Registry::class] = fn() => $yourContainer[Registry::class];
Compiler Configuration
Phanua uses an instance of Cycle\Schema\Compiler
to compile a registry of entities into a schema. This compiler can use custom generators and defaults. By default, Phanua uses default values for each of these (i.e. empty arrays).
If you want to use custom values for either of these, Phanua provides Elazar\Phanua\Schema\CompilerConfiguration
to override them. Add an entry for this class to your container with a configured instance. Below is an example of doing this with a Pimple container.
<?php use Elazar\Phanua\Schema\CompilerConfiguration; use Elazar\Phanua\Service\Provider; use Pimple\Container; $yourContainer = new Container; $yourContainer[CompilerConfiguration::class] = fn() => (new CompilerConfiguration) ->withGenerators(/* ... */) ->withDefaults(/* ... */); $provider = (new Provider) ->withDelegateContainer($yourContainer);
FAQ
Why did you build this?
I was already using OpenAPI and Jane in some projects and wanted to use Cycle ORM as well. I was already using model classes generated by Jane and didn't want to have to manually annotate or otherwise change them to be usable with Cycle. I built this so I wouldn't have to.
Why the name "Phanua?"
It's taken from the latter half of the Swahili word kufafanua, which means "to define," and adapted to replace the "f" with "ph" as is common with PHP project names.
How do you pronounce "Phanua?"
Fah-NOO-ah.