-
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 which 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.
Often a foreign value is serialized in a different place, for example if we store Person and House in SQL database, they would be in different tables and Person table would only refer to a House id.
<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 in this case.
<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.
The is_main attribute permit to identify model as main model. Objects with main model may be stored in Main Object Collection.
<manifest is_main="true">
<properties/>
</manifest>{
"is_main": true,
"properties": []
}The is_serializable attribute permit to identify model as serializable model. Objects with serializable model may be serialized/deserialized (in sqlTable, JSON file, XML file...). A serializable model is automatically considered as main model.
<manifest is_serializable="true">
<properties/>
</manifest>{
"is_serializable": true,
"properties": []
}All models described with manifests and local types have an associated namespace. All namespace are prefixed by a name that correspond to a specific autoloading (we will see why in next chapter). For example all Comhon! models are prefixed by Comhon\. Here are some examples :
- the Fully qualified name of model
ConfigisComhon\Config - the Fully qualified name of model
SqlDatabaseisComhon\SqlDatabase
In a manifest you can refer to a model by using fully qualified name of a model or use name relative to current manifest namespace. A fully qualified name begin with a \ and relative name don't. Simple models doesn't have backslash and are not concerned by namespace (see list of simple models). Local types are concerned by namespace, they must be defined without namespace but you have to refer to them using fully qualified name or relative name.
For example we suppose that model Person namespace is prefixed by Test and it's fully qualified name is Test\Person. The fully qualified name of model Tattoo might be Test\Person\Tattoo and we could refer to it in manifest Person be using fully qualified name \Test\Person\Tattoo or relative name Tattoo.
<type name="Tattoo" extends="\Test\Person\BodyArt">
<!-- ... -->
</type>
<!-- ... -->
<property name="tattoo" type="\Test\Person\Tattoo"/>is equivalent to
<type name="Tattoo" extends="BodyArt">
<!-- ... -->
</type>
<!-- ... -->
<property name="tattoo" type="Tattoo"/>In php sources you have to always use fully qualified name and you have to omit first backslash :
$personModel = ModelManager::getInstance()->getInstanceModel('Test\Person');
$tattooModel = ModelManager::getInstance()->getInstanceModel('Test\Person\Tattoo');
$person = new Object('Test\Person');
$tattoo = new Object('Test\Person\Tattoo');Comhon! use autoloading to find and load manifests (like PHP does to find classes). Actually manifests are loaded only if needed. To do so you have to define your autoloading in Configuration file.
We want to have two different directories to store our manifests. We may have a config file that look like :
{
"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 file 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
<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="\Test\Person" name="tattooArtist"/>
</properties>
</type>
<type name="Piercing" extends="BodyArt">
<properties>
<property is_foreign="1" type="\Test\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="\Test\Place" name="birthPlace"/>
<property is_foreign="1" type="\Test\Person" name="bestFriend"/>
<property is_foreign="1" type="\Test\Person\Man" name="father"/>
<property is_foreign="1" type="\Test\Person\Woman" name="mother"/>
<property is_foreign="1" type="array" name="children">
<values type="\Test\Person" name="child"/>
</property>
<property is_foreign="1" type="array" name="homes">
<values type="\Test\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": "\\Test\\Person",
"is_foreign": true
}
]
},
{
"name": "Piercing",
"extends": "BodyArt",
"properties": [
{
"name": "piercer",
"type": "\\Test\\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": "\\Test\\Place",
"is_foreign": true
},
{
"name": "bestFriend",
"type": "\\Test\\Person",
"is_foreign": true
},
{
"name": "father",
"type": "\\Test\\Person\\Man",
"is_foreign": true
},
{
"name": "mother",
"type": "\\Test\\Person\\Woman",
"is_foreign": true
},
{
"name": "children",
"type": "array",
"values": {
"type": "\\Test\\Person",
"name": "child"
},
"is_foreign": true
},
{
"name": "homes",
"type": "array",
"values": {
"type": "\\Test\\Home",
"name": "home"
},
"is_foreign": true
},
{
"name": "bodyArts",
"type": "array",
"values": {
"type": "BodyArt",
"name": "bodyArt"
}
}
]
}