Reading some of the issues (#700 , #576 ), and still having some problems myself, I decided to setup a simple data model. Defined are the following models and traits:
Note: because of the mentioned problem of separate namespaces, I applied that as well, since our production environment uses a similar approach.
As I will mention in the result section I ran the code on both the 3.0.3 version and the dev-trait-reference-fix branch. The result was then checked through https://editor.swagger.io/.
Dog.php
<?php
use OpenApi\Annotations as OA;
namespace animals\object;
/**
* Class Dog
* @package animals\object
* @OA\Schema(schema="Dog",
* allOf={
* @OA\Schema(ref="#/components/schemas/Pet"),
* }
* )
*/
class Dog extends Pet
{
}
Pet.php
<?php
use OpenApi\Annotations as OA;
namespace animals\object;
use animals\traits\FoodTrait;
use animals\traits\FurTrait;
/**
* Class Pet
* @package animals\object
* @OA\Schema(schema="Pet",
* allOf={
* @OA\Schema(ref="#/components/schemas/HasFood"),
* @OA\Schema(ref="#/components/schemas/Fur")
* }, required={"name"}
* )
*/
class Pet
{
use FoodTrait;
use FurTrait;
/**
* @OA\Property(
* type="string"
* )
* @var string
*/
protected $name;
}
FurTrait.php
<?php
use OpenApi\Annotations as OA;
namespace animals\traits;
/**
* Trait FurTrait
* @package animals\traits
* @OA\Schema(schema="Fur", allOf={
* @OA\Schema(ref="#/components/schemas/FurColor")
* }
* )
*/
trait FurTrait
{
use FurColorTrait;
/**
* @OA\Property(type="string")
* @var string
*/
public $fur;
}
FurColor.php
<?php
use OpenApi\Annotations as OA;
namespace animals\traits;
/**
* Trait FurColorTrait
* @package animals\traits
* @OA\Schema(schema="FurColor")
*/
trait FurColorTrait
{
/**
* @OA\Property(type="string")
* @var string
*/
public $color;
}
FoodTrait.php
<?php
use OpenApi\Annotations as OA;
namespace animals\traits;
/**
* Trait FoodTrait
* @package animals\traits
* @OA\Schema(schema="HasFood")
*/
trait FoodTrait
{
/**
* @OA\Property(type="string")
* @var string
*/
public $food;
}
Using 3.0.3
json output
{
"openapi": "3.0.0",
"info": {
"title": "Trait test",
"version": "0.0.1"
},
"paths": {
"/": {}
},
"components": {
"schemas": {
"Dog": {
"properties": {
"food": {
"type": "string"
}
},
"type": "object",
"allOf": [
{
"$ref": "#/components/schemas/Pet"
}
]
},
"Pet": {
"required": [
"name"
],
"properties": {
"food": {
"type": "string"
}
},
"type": "object",
"allOf": [
{
"$ref": "#/components/schemas/HasFood"
},
{
"$ref": "#/components/schemas/Fur"
},
{
"properties": {
"name": {
"type": "string"
}
}
}
]
},
"HasFood": {},
"FurColor": {
"properties": {
"color": {
"type": "string"
}
},
"type": "object"
},
"Fur": {
"allOf": [
{
"$ref": "#/components/schemas/FurColor"
},
{
"properties": {
"fur": {
"type": "string"
}
}
}
]
}
}
}
}
Notice that Fur has is properties in the "allOf" part.
Also note that although Pet uses all traits (direct or indirect like FurColor), the "food" property is on the object level both within "Dog" and "Pet", but the "name" property is also within the allOf part, only within "Pet".
Also note that HasFood has no properties at all, whereas FurColor still has the property "color".
Validating through editor.swagger.io shows the different models as expected, albeit HasFood is still empty. Both Dog and Pet have all properties, as expected.
Using dev-trait-reference-fix
json output
{
"openapi": "3.0.0",
"info": {
"title": "Trait test",
"version": "0.0.1"
},
"paths": {
"/": {}
},
"components": {
"schemas": {
"Dog": {
"allOf": [
{
"$ref": "#/components/schemas/Pet"
}
]
},
"Pet": {
"required": [
"name"
],
"properties": {
"food": {
"type": "string"
},
"fur": {
"type": "string"
}
},
"type": "object",
"allOf": [
{
"$ref": "#/components/schemas/HasFood"
},
{
"$ref": "#/components/schemas/Fur"
},
{
"properties": {
"name": {
"type": "string"
}
}
}
]
},
"HasFood": {},
"FurColor": {
"properties": {
"color": {
"type": "string"
}
},
"type": "object"
},
"Fur": {
"allOf": [
{
"$ref": "#/components/schemas/FurColor"
}
]
}
}
}
}
Again, notice HasFood is still empty. But more important notice that Fur is missing its own property "fur".
FurColor on the contrairy still has its own property "color". The "fur" property is now attached directly to "Pet".
"Dog" now has no properties attached, only a reference to "Pet" as I would expect.
Validation through editor.swagger.io results in "Dog" and "Pet" having all properties, but only the trait that is used within another trait has its own property ("FurColor" with "color").
To be honest, in my opinion neither of these results are correct. Sure, the objects contain all properties. But the traits itself, which are also exported into the json output, are incomplete in both cases.
If possible, please provide feedback on the file hereinabove, and/or provide a full example using traits, both with "bare" traits and traits using other traits.
Hopefully this issue and any feedback can be of service in the future.
Still struggling...
To add to the discussion: I would expect the swagger to be something like the following. IMHO ImportTraits and AugmentSchemas don't apply traits to be able to result in this. Specifically the traits using traits causes issues atm.
openapi: 3.0.0
info:
title: 'Trait test'
version: 0.0.1
paths:
/: { }
components:
schemas:
Dog:
type: object
allOf:
-
$ref: '#/components/schemas/Pet'
Pet:
required:
- name
properties:
name:
type: string
type: object
allOf:
-
$ref: '#/components/schemas/HasFood'
-
$ref: '#/components/schemas/Fur'
HasFood:
properties:
food:
type: string
FurColor:
properties:
color:
type: string
Fur:
properties:
fur:
type: string
allOf:
-
$ref: '#/components/schemas/FurColor'
The current trait implementation is buggy and needs an overhaul.
I've started to look at the trait handling and can confirm what @mroeling describes above.
While it is (somewhat) easy to see what goes wrong, I am not quite sure yet what the correct way is either. I guess there are two key questions:
Not sure how the current parent/child resolution works, but I guess it would be simpler to to follow that lead. Having said that, typically child classes override the parent. With traits its somewhat the other way around in that traits override inner traits and classes override the traits they use... fun times.
The second question is trickier it seems. From a reusability point traits should stay as-is as components and classes using then get them as allOf. If so, that _fixes_ the inheritance although I am not sure what the specs say about clashes of native properties and same name properties in allOf refs.
Hope that makes some sense at least.
There is the current PR which should be used for further discussion...
Fixed via #818, #804, #798, #782, #779
If you still see problems please raise a new issue. Thanks.
Most helpful comment
I've started to look at the trait handling and can confirm what @mroeling describes above.
While it is (somewhat) easy to see what goes wrong, I am not quite sure yet what the correct way is either. I guess there are two key questions:
Not sure how the current parent/child resolution works, but I guess it would be simpler to to follow that lead. Having said that, typically child classes override the parent. With traits its somewhat the other way around in that traits override inner traits and classes override the traits they use... fun times.
The second question is trickier it seems. From a reusability point traits should stay as-is as components and classes using then get them as
allOf. If so, that _fixes_ the inheritance although I am not sure what the specs say about clashes of native properties and same name properties inallOfrefs.Hope that makes some sense at least.