-
Notifications
You must be signed in to change notification settings - Fork 0
Manifest
A Manifest permit to describe a concept by listing its properties. Manifests can be defined in XML or JSON format. for each example we will illustrate XML and JSON format.
To explain what is a Manifest we will build a manifest that describe a person as example. In this chapter we will omit namespaces and autoloading to simplify explanations.
You have to specify the manifest version to determine witch parser will be used to parse the document. There is currently only one version allowed 2.0.
<manifest version="2.0">
<properties>
...
</properties>
</manifest>{
"version": "2.0",
"properties": []
}A property has a name and a type. The type is Either a name of another complex model that you have defined (for example House, Company or Film...), or a pre-defined simple type :
- boolean
- integer
- index
- float
- string
- dateTime
- percentage
<manifest>
<properties>
<property name="firstName" type="string"/>
<property name="home" type="House"/>
</properties>
</manifest>{
"properties": [
{
"name": "firstName",
"type": "string"
}
{
"name": "home",
"type": "House"
}
]
}We want an id to find easily a person, so we need to add an id property.
An id property is a part of a model id. Commonly models have only one id property but you can define several id properties.
To define a property as id property, you just have to add an attribute is_id (id must be a float, integer or string).
<manifest>
<properties>
<property name="id" type="string" is_id="1"/>
</properties>
</manifest>{
"properties": [
{
"name": "id",
"type": "string",
"is_id": true
}
]
}A Foreign property is a property that contain the key is_foreign, it refers another Object that has an existence elsewhere. Like described before Person may have a property home, and this property is basically a foreign property because a house may exist without it's owner.
<manifest>
<properties>
<property name="home" type="House" is_foreign="1"/>
</properties>
</manifest>{
"properties": [
{
"name": "home",
"type": "House",
"is_foreign": true
}
]
}We want to have a property middleNames. actually a person can have a second name, third name...
so we can define an array property to store these names.
<manifest>
<properties>
<property name="middleNames" type="array">
<values name="middleName" type="string"/>
</property>
</properties>
</manifest>{
"properties": [
{
"name": "middleNames",
"type": "array",
"values": {
"name": "middleName",
"type": "string"
}
}
]
}As you can see in node values, the name of each element must be specified (basically it's the singular of property name)
Now we want to have a property children, and a child is a person that can exist elsewhere so we can build a foreign property array.
<manifest>
<properties>
<property name="children" type="array" is_foreign="1">
<values name="child" type="Person"/>
</property>
</properties>
</manifest>{
"properties": [
{
"name": "children",
"type": "array",
"values": {
"name": "child",
"type": "Person"
},
"is_foreign": true
}
]
}A restricted property is a property with a simple type (integer, string ....) but with a restricted range of allowed values
We want to have a property sex and this property can have only 2 values : male or female. So we must define an enumeration.
<manifest>
<properties>
<property name="sex" type="string">
<enum>
<value>male</value>
<value>female</value>
</enum>
</property>
</properties>
</manifest>{
"properties": [
{
"name": "sex",
"type": "string",
"enum": [
"male",
"female"
]
}
]
}Enumerations are allowed on integer, float and string
We want to have a property age so type might be integer but there is only a reasonable range of possible values. Actually a person can't be 300 years old or -20 (negative). So we must add an interval of allowed values.
To define an interval, we use mathematic notation :
-
[a,b]meansa <= value <= b -
]a,b[meansa < value < b -
[a,b[meansa <= value < b -
]a,b]meansa < value <= b -
]a,]meansvalue > a -
[,a]meansvalue <= a
<manifest>
<properties>
<property name="age" type="integer" interval="[0,130]"/>
</properties>
</manifest>{
"properties": [
{
"name": "age",
"type": "integer",
"interval": "[0,130]"
}
]
}Intervals are allowed on integer, float and ComhonDateTime
Earlier we have defined properties firstName and middleNames with type string. But a name can only contain alphabetic characters and possibly a - (we suppose we use only latin script), so to restrict range of values we can define a pattern that will refer to a regular expression.
<manifest>
<properties>
<property name="firstName" type="string" pattern="name"/>
<property name="middleNames" type="array">
<values name="middleName" pattern="name" type="string"/>
</property>
</properties>
</manifest>{
"properties": [
{
"name": "firstName",
"type": "string",
"pattern": "name"
},
{
"name": "middleNames",
"type": "array",
"values": {
"name": "middleName",
"type": "string",
"pattern": "name"
}
}
]
}At this point we have specified the pattern but we don't have defined the regular expression. All regular expressions that you want to define must be regrouped in a unique json file. So regular expressions file content should at least look like this :
{
"name": "/^[a-zA-Z0-9_-]$/"
}The regular expression must be compatible for function preg_match.
You can register your regex file where you want on your system, you just have to reference it in config file (already seen in Configuration page).
We want to add a property tattoos. A tattoo is not a simple thing, we must define a model to define it. But where define this model ? In Another Manifest file ? no because a tattoo exists only on Person body (on domestic animal too but to simply we ignore it). So we define a local property type inside Person manifest. By the way we could define another manifest file, but it's more efficient and more understandable to define a local property type.
<manifest>
<types>
<type name="Tattoo">
<properties>
<property name="type" type="string"/>
<property name="location" type="string"/>
<property name="tattooArtist" type="Person" is_foreign="1"/>
</properties>
</type>
<!-- a local property type can contain another one -->
<type name="example">
<properties>
<property name="tattoo" type="Tattoo" />
<property name="a_name" type="string"/>
</properties>
</type>
</types>
<properties>
<property name="tattoos" type="array">
<values name="tattoo" type="Tattoo"/>
</property>
<property name="example" type="example"/>
</properties>
</manifest>{
"types": [
{
"name": "Tattoo",
"properties": [
{
"name": "type",
"type": "string"
},
{
"name": "location",
"type": "string"
},
{
"name": "tattooArtist",
"type": "Person",
"is_foreign": true
}
]
}
],
"properties": [
{
"name": "tattoos",
"type": "array",
"values": {
"name": "tattoo",
"type": "Tattoo"
}
}
]
}As you can see in xml example, a local type can contain other local types.
Use local types if they are not too complex and if they are (almost) always used. Otherwise It's preferable to define other manifests that are loaded only if needed and it avoid to have too complex manifest.
A model can extends another one. All properties of parent model are added to inherited model. Inherited model can override an existing property in parent model. Inheritance can be defined on manifests and on local types.
Before we have defined a property sex with an enumeration, but another way to do is to define two new manifests Man and Woman that extends Person.
Woman manifest may look like :
<manifest extends="Person">
<properties>
<property name="pregnant" type="boolean">
</properties>
</manifest>{
"extends": "Person",
"properties": [
{
"name": "pregnant",
"type": "boolean"
}
]
}Before we have defined a property tattoos but we can add a property piercings for example, and those two property are very similar so we can inherit models tattoo and piercing from bodyArt.
<manifest>
<types>
<type name="BodyArt">
<properties>
<property name="type" type="string"/>
<property name="location" type="string"/>
</properties>
</type>
<type name="Tattoo" extends="BodyArt">
<properties>
<property name="tattooArtist" type="Person" is_foreign="1"/>
</properties>
</type>
<type name="Piercing" extends="BodyArt">
<properties>
<property name="piercer" type="Person" is_foreign="1"/>
</properties>
</type>
</types>
<properties/>
</manifest>{
"types": [
{
"name": "BodyArt",
"properties": [
{
"name": "type",
"type": "string"
},
{
"name": "location",
"type": "string"
}
]
},
{
"name": "Tattoo",
"extends": "BodyArt",
"properties": [
{
"name": "tattooArtist",
"type": "Person",
"is_foreign": true
}
]
},
{
"name": "Piercing",
"extends": "BodyArt",
"properties": [
{
"name": "piercer",
"type": "Person",
"is_foreign": true
}
]
}
],
"properties": []
}if you want to associate a class to your manifest you can define it in the attribute object. The class must contain namespace.
<manifest object="\Fully\Qualified\Name\Class">
<properties/>
</manifest>{
"object": "\\Fully\\Qualified\\Name\\Class",
"properties": []
}Take a look at Getting started page to know how to define a class object and how to instanciate it.
Comhon! use autoloading like PHP autoloading to found and load manifests. Actually manifests are loaded only if needed. To do so you have to define your autoloading in Configuration file.
{
"autoload": {
"manifest":{
"Test":"../relative/path/to/manifests/test",
"Tutorial":"/absolute/path/to/manifests/tutorial"
}
}
}- All manifest described in directory
../relative/path/to/manifests/testwill have namespace prefixed byTest; - All manifest described in directory
/absolute/path/to/manifests/tutorialwill have namespace prefixed byTutorial;
In file system each manifest must respect following rules :
- manifest is contained in directory and the directory name must correspond to the model name
- manifest must be named
manifest.jsonormanifest.xml(depends on manifest format defined in Configuration file)
- We want to add a manifest to describe model
Personin namespaceTest, it must be saved at :
../relative/path/to/manifests/test/Person/manifest.json
or
../relative/path/to/manifests/test/Person/manifest.xml
- We want to add a manifest to describe model
Guitarein namespaceTutorial\Instrument, it must be saved at :
/absolute/path/to/manifests/tutorial/Instrument/Guitare/manifest.json
or
/absolute/path/to/manifests/tutorial/Instrument/Guitare/manifest.xml
All models described with manifests and local types have an associated namespace.
You have probably seen \person in previous examples, the backslash means that we have a fully qualified name. If there is no backslash that means it is an unqualified name and the current namespace is applied (for example in the manifest of person, the current namespace is \person). There is an exception, simple models doesn't have backslash and are not concerned by namespace (see list of simple models).
We have previously defined the local type tattoo in manifest of person so the fully qualified name of tattoo is \person\tattoo.
In manifest of person
<property type="tattoo" name="tattoo"/>is equivalent to
<property type="\person\tattoo" name="tattoo"/>If tattoo was defined in an external manifest and had its own local types, these models should have a fully qualified name like \person\tattoo\a_name
In php sources you have to omit first backslash :
$personModel = ModelManager::getInstance()->getInstanceModel('person');
$tattooModel = ModelManager::getInstance()->getInstanceModel('person\tattoo');
$person = new Object('person');
$tattoo = new Object('person\tattoo');<manifest version="2.0" object="Comhon\Object\Person">
<types>
<type name="bodyArt">
<properties>
<property type="string" name="type"/>
<property type="string" name="location"/>
</properties>
</type>
<type name="tattoo" extends="bodyArt">
<properties>
<property is_foreign="1" type="\person" name="tattooArtist"/>
</properties>
</type>
<type name="piercing" extends="bodyArt">
<properties>
<property is_foreign="1" type="\person" name="piercer"/>
</properties>
</type>
</types>
<properties>
<property type="integer" is_id="1" name="id"/>
<property type="string" name="firstName"/>
<property type="string" name="lastName"/>
<property type="dateTime" name="birthDate"/>
<property is_foreign="1" type="\place" name="birthPlace"/>
<property is_foreign="1" type="\person" name="bestFriend"/>
<property is_foreign="1" type="\man" name="father"/>
<property is_foreign="1" type="\woman" name="mother"/>
<property is_foreign="1" type="array" name="children">
<values type="\person" name="child"/>
</property>
<property is_foreign="1" type="array" name="homes">
<values type="\home" name="home"/>
</property>
<property type="array" name="bodyArts">
<values type="bodyArt" name="bodyArt"/>
</property>
</properties>
</manifest>{
"version": "2.0",
"object": "Comhon\\Object\\Person",
"types": [
{
"name": "bodyArt",
"properties": [
{
"name": "type",
"type": "string"
},
{
"name": "location",
"type": "string"
}
]
},
{
"name": "tattoo",
"extends": "bodyArt",
"properties": [
{
"name": "tattooArtist",
"type": "\\person",
"is_foreign": true
}
]
},
{
"name": "piercing",
"extends": "bodyArt",
"properties": [
{
"name": "piercer",
"type": "\\person",
"is_foreign": true
}
]
}
],
"properties": [
{
"name": "id",
"type": "integer",
"is_id": true
},
{
"name": "firstName",
"type": "string"
},
{
"name": "lastName",
"type": "string"
},
{
"name": "birthDate",
"type": "dateTime"
},
{
"name": "birthPlace",
"type": "\\place",
"is_foreign": true
},
{
"name": "bestFriend",
"type": "\\person",
"is_foreign": true
},
{
"name": "father",
"type": "\\man",
"is_foreign": true
},
{
"name": "mother",
"type": "\\woman",
"is_foreign": true
},
{
"name": "children",
"type": "array",
"values": {
"type": "\\person",
"name": "child"
},
"is_foreign": true
},
{
"name": "homes",
"type": "array",
"values": {
"type": "\\home",
"name": "home"
},
"is_foreign": true
},
{
"name": "bodyArts",
"type": "array",
"values": {
"type": "bodyArt",
"name": "bodyArt"
}
}
]
}