way to repeat operations for child entities in schema generator - api-platform.com

I have a schema generator project with hirearchical entities for a video game API. For example, I have an "Item" entity, and from that I have many different item types such as "WeaponItem" and "ArmourItem". I also have custom operations that are common among all of these inherited entities, for example "/armour_items/x/discard". I would like to know if there is a way to inherit these operations from the base entity, rather than defining the same operation on every single entity in the config. An example:
Item:
properties:
name: {range: string}
quantity: {range: int}
WeaponItem:
guessFrom: Item
operations:
item:
get:
put:
delete:
discard:
path: /weapon_items/{id}/discard
method: patch
controller: App\Controller\DiscardItemSlot
input_formats:
json: ['application/json']
openapi_context:
summary: Discard the contents of a slot.
description: >
Discard the contents of a slot.
You can specify the amount to discard.
By default the entire slot is discarded.
requestBody:
content:
application/json:
schema:
type: object
properties:
amount: { type: int, required: false }
example:
amount: 1
ArmourItem:
guessFrom: Item
operations:
item:
get:
put:
delete:
discard:
path: /armour_items/{id}/discard
method: patch
controller: App\Controller\DiscardItemSlot
input_formats:
json: ['application/json']
openapi_context:
summary: Discard the contents of a slot.
description: >
Discard the contents of a slot.
You can specify the amount to discard.
By default the entire slot is discarded.
requestBody:
content:
application/json:
schema:
type: object
properties:
amount: { type: int, required: false }
example:
amount: 1
As you can see, defining these operations for each endpoint is very repetative, and that's just one operations on two entities. I have many more!
Is there a way to reduce the amount of config here? Perhaps I could define the operation on the base entity, and inherit it on the child resources? Or maybe there's some API Platform config option that can help me out?

Related

Interface vs Union in GraphQL schema design

Suppose I am building a GraphQL API that serves a timeline of natural disaster events.
There are two different kinds of event right now:
Hurricane
Earthquake
All events have an ID and a date they occurred. I plan to have a paginated query for fetching events using cursors.
I can think of 2 different approaches to modelling my domain.
1. Interface
interface Event {
id: ID!
occurred: String! # ISO timestamp
}
type Earthquake implements Event {
epicenter: String!
magnitude: Int!
}
type Hurricane implements Event {
force: Int!
}
2. Union
type Earthquake {
epicenter: String!
magnitude: Int!
}
type Hurricane {
force: Int!
}
type EventPayload =
| Earthquake
| Hurricane
type Event {
id: ID!
occurred: String! # ISO timestamp
payload: EventPayload!
}
What are the trade-offs between the two approaches?
I believe that:
unions are about providing: a field / its resolver function resolves with an object, whose type belongs to a specific, known, set of types.
interfaces are about requesting: without, the clients would have to repeat the fields they are interested in, in every type fragment.
They serve different purposes, and they can be used together:
interface I {
id: ID!
}
type A implements I {
id: ID!
a: Int!
}
type B implements I {
id: ID!
b: Int!
}
type C implements I {
id: ID!
c: Int!
}
union Foo = A | C
type Query {
foo: Foo!
}
This schema declares that A, B, and C have some fields in common, so that it's easier for the client to request them, and that querying foo can only yield A or C.
Could you write foo: I! instead? While this would work seamlessly, I believe this leads to a bad development experience. If you're saying that foo provides an I object, your clients should be prepared for receiving any of the implementing types, including B, and would spend time to write and maintain a code that will never be called. If you know that foo can only yield A and C, please tell them explicitly.
The same holds if foo were to yield A, B, or C. It happens that it's exactly the list of types that implement I, so in this case, could you write foo: I!? No! Don't be fooled by that. Why? Because this list is expandable through federation / schema stitching! I believe it's a seldom used feature of some GraphQL frameworks, but whose adoption grows. If you've never used it, please try, it will open your mind to new ideas of inter-micro-service-communication and other Medium buzzwords. In short, imagine you're making a public API, or even somewhat-public within an organization. Someone else could "augment" your API by providing extra stuff. This may include new types implementing your interface. And so we're back to the previous paragraph.
So far, it looks like I'm in favor of your first code.
However, and this may be specific to this scenario, it seems to me that your definition of event mixes both data about its occurrence and about physics metrics. Your second code splits them into two type hierarchy. I like that. It feels more architecture-friendly. Your schema is more open. Imagine your API is about event history, and someone enhance it with forecasts: your EventPayload can be reused!
Besides, note that your first example is incomplete. Types implementing an interface must implement, i.e. repeat, every single field of this interface, like I wrote in the above code. This becomes harder to maintain as the number of fields and the number of implementing types grow.
So, the second solution also has some advantages. But doing so, the blah-blah I made earlier about being specific with returned types is hard to implement, because the payload, which is the one to be specific about, is embedded into another type, and there's no such thing as generics in GraphQL.
Here's a proposal to reconcile all of that:
interface HasForce {
force: Int!
}
type Earthquake {
epicenter: String!
magnitude: Int!
}
type Hurricane implements HasForce {
force: Int!
}
type Tsunami implements HasForce {
force: Int!
}
interface Event {
data: EventData!
}
type EventData {
id: ID!
occurred: String!
}
union HistoryMeteorologicalPhenomenon = Earthquake | Hurricane
type HistoryEvent implements Event {
data: EventData!
meteorologicalPhenomenon: HistoryMeteorologicalPhenomenon!
}
type Query {
historyEvents: [HistoryEvent!]!
}
It looks a bit more complex that both of your proposals, but it fulfills my needs. Also, it's rare to look at a schema from this height: more often, we know the entry point and dig down from there. For instance, I open the documentation at historyEvents, see that it yields phenomena of two kinds, fine, I'm not aware that other union types and event types exist.
If you were to write a lot of these union + event pairs, you could generate them with code instead, whereby one function call would declare a pair. Less error-prone, funnier to implement, and with more potential of Medium articles.
Note that the GraphQL structure is independent of your storage structure. It's possible to have multiple GraphQL objects providing data from the same insert-your-language-here object, e.g. yielded by your DB driver. There may be a tiny overhead that I haven't benchmarked, but providing a cleaner API outweighs that to me. The basic idea is that resolver functions just have to resolve with the same source, so that the resolver functions related to another type will be called with the same source object.

RAML dependent required query parameters

I have the following trait:
traits:
feedSortable:
queryParameters:
sortBy:
description: Sort the topic feed
required: false
default: 'created_at'
type: string
enum: [ 'most_recent', 'upvotes' ]
since:
description: Get topics since utc creation date. Required in case of upvotes
required: false
type: datetime-only
until:
description: Get topics until utc creation date
required: false
type: datetime-only
Since and until query parameters are only required when sortBy is defined as upvotes.
How do I enforce this best in my RAML definition?
More generally, I want to know how I define query parameter definitions dependent on other query parameters. Can't be that rare?
Doesn't look like there is a good way although this would seem to be a case that is relatively common. Swagger seems too restrictive, RAML seems to be missing options too.
I circumvented the issue by making the field non-required altogether, and adding more description to the default:
since:
description: Get topics since utc creation date
required: false
default: sortBy = recent -> null, sortBy = upvotes -> now() - 1 month
type: datetime-only

Doctrine2 + CodeIgniter and database table creation issue

I'm using Doctrine2 with codeIgniter, I've created some models in yml format. Using command line I've created the Proxies and Entities. When I'm trying to create the database tables, I'm getting the following error:
[Doctrine\ORM\Mapping\MappingException]
Invalid mapping file 'Entities.category.dcm.yml' for class
'Entities\category'.
Here's Entities.category.dcm.yml:
Entities\Category:
type: entity
table: categories
fields:
id:
type: integer
id: true
generator:
strategy: AUTO
name:
type: string
length: 50
nullable: false
description:
type: string
length: 255
First of all, check the paths configured for your entities and for the YML mapping driver.
Also, your Entities.category.dcm.yml contains mappings for Entities\Category, and not Entities\category.
As you can see in the base FileDriver Doctrine ORM does direct matching for mapped classes, and applies no normalization on the class names. Category and category are therefore different.

How do I setup query cache results for built in doctrine2 repository functions?

I have a site that is for a video game I play and am working on improving the performance of the site by implementing some additional caching. I've already been able to implement query result caching on custom repository functions, but haven't been able to find anywhere that explains how I can include query result caching on the built in functions (findOneById, etc). I'm interested in doing this because many of my database queries are executed from these 'native' repository functions.
So as an example I have a character entity object with the following properties: id, name, race, class, etc.
Race and class in this object are references to other entity objects for race and class.
When I load a character for display I get the character by name (findOneByName) and then in my template I display the character's race/class by $characterObject->getRace()->getName(). These method calls in the template result in a query being run on my Race/Class entity tables fetching the entity by id (findOneById I assume).
I've attempted to create my own findOneById function in the repository, but it is not called under these circumstances.
How can I setup doctrine/symfony such that these query results are cache-able?
I am running Symfony 2.1.3 and doctrine 2.3.x
I've found out that it isn't possible to enable query cache on doctrine build in functions. I will post a link which explains why later after I find it again.
Your entities probably look something like this:
MyBundle\Entity\Character:
type: entity
table: Character
fields:
id:
id: true
type: bigint
name:
type: string
length: 255
manyToOne:
race:
targetEntity: Race
joinColumns:
raceId:
referencedColumnName: id
MyBundle\Entity\Race:
type: entity
table: Race
fields:
id:
id: true
type: bigint
name:
type: string
length: 255
oneToMany:
characters:
targetEntity: Character
mappedBy: race
If that's the case, then modify your Character entity mapping so that it eagerly loads the Race entity as well:
MyBundle\Entity\Character:
...
manyToOne:
race:
targetEntity: Race
joinColumns:
raceId:
referencedColumnName: id
fetch: EAGER
Doctrine documentation on the fetch option: #ManyToOne

Doctrine YAML Data Fixture Question

where can i learn more abt creating database markup in yaml and data fixtures.
i followed a tutorial and they create a relationship like so: under relations in both User and Car. my qn is why is 'type: many' in Car. can i have it in User instead (just curious)?
abt data types. different database have different database support. i thought that in MySQL (InnoDB as used here) integer shld be tinyint(x), bigint(x), int(x) ... or string shld be varchar not string? isit not strict what i shld use here?
options:
type: INNODB
collate: utf8_general_ci
charset: utf8
User:
columns:
id:
type: integer
primary: true
autoincrement: true
name: string(300)
email: string(300)
phone: string(9)
car_id: integer
relations:
Car:
local: car_id
foreign: id
Car:
columns:
id:
type: integer
primary: true
autoincrement: true
brand: string(300)
relations:
Users:
class: User
foreign: car_id
local: id
type: many
UPDATE 1
"it is only necessary to specify the relationship on the end where the foreign key exists" in my example, that will be? do they mean the FK table (car) or the FK column (user)?
i dont see TEXT data type, is that clob (Character Large OBject)? – iceangel89 0 secs ago [delete this comment]
what is foreignAlias? is there a alias too?
UPDATE 2
this will be abit long, i just wish to clarify some of the code examples in the Doctrine YAML Schema Files docs page. focus on the relationships section -> in // comments
User:
columns:
username:
type: string(255)
password:
type: string(255)
contact_id:
type: integer
relations:
Contact:
class: Contact // if the table is named Contact, class will be Contact also?
local: contact_id
foreign: id
foreignAlias: User // whats alias for?
foreignType: one // one contact ... to ...
type: one // one user?
Contact:
columns:
first_name:
type: string(255)
last_name:
type: string(255)
phone:
type: string(255)
email:
type: string(255)
address:
type: string(255)
relations:
User:
class: User
local: id
foreign: contact_id
foreignAlias: Contact
foreignType: one
type: one
regarding the many to many example, what does the following mean?
attributes:
export: all
validate: true
tableName: group_table
refClass: GroupUser
where can i learn more abt creating database markup in yaml and data fixtures.
Doctrine manual, “YAML schema files” and “Data Fixtures” chapters.
can i have it in User instead (just curious)?
Yes, but this section will be called foreignType then. Here, an example:
User:
columns:
id:
type: integer
primary: true
autoincrement: true
name: string(300)
email: string(300)
phone: string(9)
car_id: integer
relations:
Car:
local: car_id
foreign: id
foreignType: many
abt data types...
Well, Doctrine column types and database column types are “slightly” different. Just compare list of Doctrine column types and, say, MySQL's one.
I know this is old, but these are things I've found confusing, and still do. In fact I'm not expert in all the possibilities, this is just based on what works for me. I think you may be looking for many-to-many relations, but I completely avoid the Doctrine support for them, and instead define my own association tables explicitly, so I only ever use one-to-many and one-to-one relations.
As noted in UPDATE1, you only specify the relationship on the end that has the foreign key.
In this case, User has a column car_id that is a foreign key that
refers to the id column of Car. So on the User end, the relation
is with Car, the local column containing the key value is
car_id, and the column in the other (foreign) table to which it refers is id.
Doctrine defines its own data types, and automatically maps them
onto the data types of the particular database you are using.
develop7 gave links to the documentation, or you can look in the
doctrine sources.
foreignAlias gives a name to the relation on the foreign side.
There is no alias because the name of the relation on the side
containing the foreign key is given by the name used at the level
below relations:, which is commonly specified as the name of the
table to which the foreign key refers.
Regarding UPDATE 2:
class: Contact The yaml for User says that it has a relation named Contact which refers to the class Contact. By default, class names and table names are the same; the yaml schema deals only with class names, though it is possible to tell it to use a different table name for a given class.
foreignAlias: User The name of the relation from Contact to User is "User". As explained above, there is nothing called "alias", the name of the relation from User to Contact is "Contact", because that's the name in the list of relations for User under which this line appears. Of course these default relation namings fall apart if you happen to have more than one relationship between the same two classes; you need the ability to give explicit relation names that differ from the class names. The names of relations are important because you use them in DQL joins.
foreignType: one A Contact (the foreign side) has one User
type: one A User (the local side) has one Contact.
Note that this example is a little unusual in showing explicitly both sides of the same relation. Normally, you'd show it only on the side containing the foreign key (the User side). Since a User contains a foreign key pointing to a Contact, the "type" can only be "one". But the foreignType could be "many", indicating that a given Contact could be pointed-to by many Users, though in this case it is specified that only one User can refer to a given Contact.
I don't actually know what would happen if you specified the type as "many". Implementing that would require an extra association table like many-to-many relations do, and I don't happen to know if Doctrine would create such a table "automatically" as it does for many-to-many relations. For my use of Doctrine, I avoid implicit machinery based on naming conventions that I don't understand as much as possible, so I turn off "detect_relations" and avoid many-to-many relations.

Resources