-
Notifications
You must be signed in to change notification settings - Fork 0
Object management
- Preamble
- Root model
- Share id
- Object Collection
- Main Object Collection
- Object validation
- Object serialization
- Object interfacing
- Object cast
For the most of following examples we will use Test\Person model defined in manifest person example. We will use Test\Person\Man and Test\Person\Woman too that inherit from Test\Person. We suppose that these three models have the same serialization.
The root model Comhon\Root is a specific model that doesn't have properties. If you define a manifest without extends model(s), it will extends Comhon\Root automatically. It permit to have a unique parent model for all models. And you can interface easily any object from this model.
The share id permit to share identifiers between several models. if models share identifiers, comhon objects can't have same identifiers. For example if we have Test\Person\Woman and Test\Person\Man models and they are serialized in same SQL table person, they can't have same identifiers. On the contrary if they are NOT serialized in same SQL table, they may have same identifiers.
Actually when models share same serialization, they automatically share ids (you don't have to define it).
Only inherited models may share ids, so to share ids between Test\Person\Woman and Test\Person\Man these models must actually inherit from same model Test\Person.
The share id is used in object collection storage and during object interfacing.
The share id must be defined in manifest (see share id chapter).
An Object Collection permit to store objects and find them quickly and easily. Objects are indexed by model and by id. Obviously an object without id property cannot be stored in Object Collection.
Example of an Object Collection structure :
[
Test\Person : [
an_id_one : person_object_one,
an_id_two : person_object_two
],
Test\Town : [
an_id_one : town_object_one
],
Test\House : [
an_id_one : house_object_one,
an_id_two : house_object_two
]
]
Example of usage :
$person = new ComhonObject('Test\Person');
$person->setId(1);
$objectCollection = new OjectCollection();
$objectCollection->addObject($person);
$objectCollection->hasObject(1, 'Test\Person'); // return true
$object = $objectCollection->getObject(1, 'Test\Person'); // return object previously added
$objectCollection->hasObject(2, 'Test\Person'); // return false
$object = $objectCollection->getObject(2, 'Test\Person'); // return null
$objectCollection->hasObject(1, 'Test\House'); // return false
$object = $objectCollection->getObject(1, 'Test\House'); // return nullBe careful if you update your object id, it will not be reindexed (you have to do it yourself if necessary).
$person = new ComhonObject('Test\Person');
$person->setId(1);
$objectCollection = new OjectCollection();
$objectCollection->addObject($person);
$person->setId(2);
$objectCollection->hasObject(1, 'Test\Person'); // return true
$object = $objectCollection->getObject(1, 'Test\Person'); // return object previously added
$objectCollection->hasObject(2, 'Test\Person'); // return false
$object = $objectCollection->getObject(2, 'Test\Person'); // return nullTo explain inheritance with share id in Object Collection let's start with an example. We remind that, Test\Person\Man that inherit from Test\Person and these models have the same serialization (so they share identifiers).
If an Object with model Test\Person\Man is added in Object Collection, you can access it by using model name Test\Person.
$man = new ComhonObject('Test\Person\Man');
$man->setId(1);
$objectCollection = new OjectCollection();
$objectCollection->addObject($man);
$objectCollection->hasObject(1, 'Test\Person'); // return true
$object = $objectCollection->getObject(1, 'Test\Person'); // return object previously addedIt works in the other way
$person = new ComhonObject('Test\Person');
$person->setId(2);
$objectCollection = new OjectCollection();
$objectCollection->addObject($person);
$objectCollection->hasObject(2, 'Test\Person\Man'); // return true
$object = $objectCollection->getObject(2, 'Test\Person\Man'); // return object previously addedNote that it is possible only if models have the same serialization.
MainObjectCollection is a singleton that extends ObjectCollection. It store automatically objects with main models having id property(ies). This singleton is useful to know which object already exists and avoid redundant deserializations.
Important !!! Only objects, with models that have been described by manifests with is_main attribute or with attached serialization, are stored in singleton MainObjectCollection (see Manifest pages).
To retrieve an object call :
$object = MainObjectCollection::getInstance()->getObject('an_id', 'a_model_name'); // null if object not foundObjects are automatically added when :
- object is deserialized (see deserialization section)
$model = ModelManager::getInstance()->getInstanceModel('Test\Person');
$person1 = MainObjectCollection::getInstance()->getObject(1, 'Test\Person'); // null
$person2 = $model->loadObject(1);
$person3 = MainObjectCollection::getInstance()->getObject(1, 'Test\Person');
echo $person2 === $person3 ? 'same instance' : 'different instance';
// output 'same instance'- when import interfaced object (see Object interfacing section)
$model = ModelManager::getInstance()->getInstanceModel('Test\Person');
$stdInterfacer = new StdObjectInterfacer();
$stdOject = json_decode('{"id":1,"firstName":"John"}');
$person1 = MainObjectCollection::getInstance()->getObject(1, 'Test\Person'); // null
$person2 = $stdInterfacer->import($stdOject, $model);
$person3 = MainObjectCollection::getInstance()->getObject(1, 'Test\Person');
echo $person2 === $person3 ? 'same instance' : 'different instance';
// output 'same instance'- when you set id of an object
$person1 = MainObjectCollection::getInstance()->getObject(1, 'Test\Person'); // null
$person2 = new ComhonObject('Test\Person');
$person2->setValue('id', 1);
$person3 = MainObjectCollection::getInstance()->getObject(1, 'Test\Person');
echo $person2 === $person3 ? 'same instance' : 'different instance';
// output 'same instance'Contrary to an instance of ObjectCollection that you have instanciated yourself, Comhon! is able to reindex objects in MainObjectCollection if their id have been updated.
$person = new ComhonObject('Test\Person');
$person->setValue('id', 1);
$person2 = MainObjectCollection::getInstance()->getObject(1, 'Test\Person'); // return same instance as $person
$person->setValue('id', 2);
$person2 = MainObjectCollection::getInstance()->getObject(1, 'Test\Person'); // return null
$person2 = MainObjectCollection::getInstance()->getObject(2, 'Test\Person'); // return same instance as $personNote that if an instance is already added in MainObjectCollection it will not be replaced.
$person = new ComhonObject('Test\Person');
$person->setValue('id', 1);
$person2 = new ComhonObject('Test\Person');
$person2->setValue('id', 1);
$person3 = MainObjectCollection::getInstance()->getObject(1, 'Test\Person');
echo $person === $person3 ? 'same instance' : 'different instance';
// output 'same instance'
echo $person2 === $person3 ? 'same instance' : 'different instance';
// output 'different instance'Comhon objects, Comhon arrays and Comhon properties may be validated.
- Comhon object validation look at required values, depencencies and conflicts
- Property value validation look at model value and verify if restrictions are satisfied (not null, regex...)
- Comhon array validation look at array size
- Comhon array element validation look at model value and verify if restrictions are satisfied (not null, regex...)
Some validations are done automatically, and if validation is not satisfied, an exception is thrown. For example :
- When a value is set on a Comhon object it is automatically validated
- A Comhon object is automatically validated before serialization (except for patch operation)
- Everything is validated by default when import/export Comhon objects or Comhon arrays
The validation might be done manually with two function :
-
validate: throw an exception if validation is not satisfied. -
isValid: returntrueif validation is satisfied,falseotherwise.
$myArray->isValid();
$myArray->validate();
$myObject->isValid();
$myObject->validate();
$myObject->getPorperty('firstName')->isValid('john');
$myObject->getPorperty('firstName')->validate('*fgf/*d-g/-');Note : the manual validation is not "deep", it validate only current object, array or property.
If you want to validate your object or array "deeply" by validating nested objects you have to use object validator.
During "deep" validation, if object is not "deeply" valid, an exception is thrown with informations of the first validation not satisied encountered.
$visitor = new ObjectValidator();
$visitor->execute($myObject);If you want, object validator permit to :
- verify if foreign objects have a complete id
- verify if foreign objects are referenced (i.e. if there is same instance object on a NOT foreign property)
$visitor = new ObjectValidator();
$visitor->execute($myArray, [ObjectValidator::VERIF_REFERENCES => true, ObjectValidator::VERIF_FOREIGN_ID => true]);Whatever is your serialzation you just have to call one function save() to serialize your object. Obviously the model associated to your object must have a defined serialization.
Let's comhon find the operation : create or update (not patch)
$object->save();provide the operation
$object->save(SerializationUnit::CREATE);Note : Properties defined as not serializable and aggregations properties are not serialized.
There are three available operation for serialization : create, update or patch an object.
The create operation permit to create a new object (create a file or add a row in SQL table...). The associated constant is :
\Comhon\Serialization\SerializationUnit::CREATEThe update operation permit to update completely an object. All values are exported even if they are not updated. A file would be entirely replaced and each column of a SQL table row would be replaced (if a value is not set in instanciated comhon object, null is set in associated SQL table row's column). The associated constant is :
\Comhon\Serialization\SerializationUnit::UPDATEThe patch operation permit to update partially an object. Only updated values or/and casted objects are exported. Patch is not available for file serialization. For SQL table only wanted columns will be updated (if a value is not set AND flagged as updated in instanciated comhon object, null is set in associated SQL table row's column). The associated constant is :
\Comhon\Serialization\SerializationUnit::PATCHNote : only root object might be partial, nested objects must be complete.
You can specify an operation but if you call save() function without operation, there are two cases to identify :
- if your Object doesn't have id set, operation create is chosen and an
INSERTwill be executed. ifINSERTis successfull, id will be set in your object. - if Object has id set, operation update (not patch) is chosen and an
UPDATEwill be executed
you must specify your operation (insert, update or patch)
// (SqlTable extends from SerializationUnit so constant is accessible from SqlTable)
$object->save(SqlTable::INSERT);
$object->save(SqlTable::UPDATE);
$object->save(SqlTable::PATCH);Whatever is your serialzation you just have to call a simple function to deserialize your object. Obviously the model associated to your object must have a serialization.
$model = ModelManager::getInstance()->getInstanceModel('a_model_name');
$object = $model->loadObject('an_id');
// if your model has several id properties you must specify them in a json encoded array
// order of values must be the same as order of id properties in manifest
$object = $modelTwo->loadObject('["an_id_1","an_id_2"]');When you load an object, this object is stored in MainObjectCollection. And thanks to MainObjectCollection if an object has already been loaded it will not be loaded again (except if you force it).
// load object
$object = $model->loadObject(12);
// return same instance object retrieve from MainObjectCollection
$object = $model->loadObject(12);
// like previous instruction but force to call deserialization
// and merge current object instance with serialized object
$object = $model->loadObject(12, true);$object = new ComhonObject('a_model_name', false); // second parameter flag object as NOT loaded
$object->setId('an_id');
$object->load(); // return true if object is successfully loadedYou can filter properties during deserialization (with first parameter).
If object is flagged as loaded, deserialization is not launched except if you force it (with second parameter).
When an object is loaded, foreign properties of this object that have their own serialization are instanciate but are not loaded. Like in previous section, if a value has already been loaded it will not be loaded again (except if you force it).
For example you have loaded a Test\Person, values like mother or children are instanciated but are not loaded.
$model = ModelManager::getInstance()->getInstanceModel('Test\Person');
$person = $model->loadObject(1);
echo $person->isLoaded(); // output true
echo $person->getValue('mother')->isLoaded(); // output false
echo $person->getValue('children')->isLoaded(); // output falseTo load a unique value call loadValue function :
$person->loadValue('mother');
echo $person->getValue('mother')->isLoaded(); // output true- To fully load an aggregation value call
loadValuefunction :
$person->loadValue('children');
echo $person->getValue('children')->isLoaded(); // output true
foreach ($person->getValue('children') as $child) {
// each child has all values set, and is loaded
echo $child->isLoaded(); // output true
}- To load only aggregation ids call
loadAggregationIdsfunction :
$person->loadAggregationIds('children');
echo $person->getValue('children')->isLoaded(); // output true
foreach ($person->getValue('children') as $child) {
// each child has id set, but is not loaded
echo $child->isLoaded(); // output false
}For most (all?) SGBD connection you can define your time zone. to set a specific time zone for your connection you must specify it in file config.json (by default time zone connection is UTC).
Whatever is your database time zone you don't have to take care of it, serialization/deserialization will automatically affect good time zone to all your values with model dateTime.
Interfacing consist to import entry in specific format to populate a comhon object or conversely export comhon object to specific format.
Comhon! provide three interfacer :
| interfacer | handled format | handled parsed format |
|---|---|---|
| StdObjectInterfacer | JSON or YAML | stdClass |
| AssocArrayInterfacer | JSON or YAML | array |
| XMLInterfacer | XML | DOMElement |
You may instanciate interfacer in two ways :
- Call Interfacer::getInstance
// get AssocArrayInterfacer instance by providing true as second parameter
// and handle json format by providing 'json' as first parameter
$interfacer = Interfacer::getInstance('json', true);
// get StdObjectInterfacer instance by providing false as second parameter (default false)
// and handle yaml format by providing 'yaml' as first parameter
$interfacer = Interfacer::getInstance('yaml', false);
// get XMLInterfacer instance by providing 'xml' as first parameter
$interfacer = Interfacer::getInstance('xml');- Call constructors
$interfacer = new AssocArrayInterfacer('yaml', true);
$interfacer = new StdObjectInterfacer('json');
$interfacer = new XMLInterfacer();You may provide mime type instead of format :
-
json:application/json -
xml:application/xml -
yaml:application/x-yaml
All interfacers extends Interfacer so they are very similar and have the same management. We will give example with a random interfacer but keep in mind that every examples are possible with all interfacers.
Let's start with an example
// create object that will be exported and add simple values
$person = new ComhonObject('Test\Person\Man');
$person->setValue('id', 1); // by setting id, object is added in MainObjectCollection
$person->setValue('firstName', 'John');
$person->setValue('lastName', 'Doe');
// create and add mother value
$woman = new ComhonObject('Test\Person\Woman');
$woman->setValue('id', 2); // by setting id, object is added in MainObjectCollection
$woman->setValue('firstName', 'Jane');
$woman->setValue('lastName', 'Doe');
$person->setValue('mother', $woman);
// create and add children values
$childOne = new ComhonObject('Test\Person\Woman');
$childOne->setValue('id', 3); // by setting id, object is added in MainObjectCollection
$childTwo = new ComhonObject('Test\Person\Man');
$childTwo->setValue('id', 4); // by setting id, object is added in MainObjectCollection
$children = $person->initValue('children');
$children->pushValue($childOne);
$children->pushValue($childTwo);
// create and add bodyArts values
$tattooOne = new ComhonObject('Test\Person\Tattoo');
$tattooOne->setValue('type', 'dragon');
$tattooOne->setValue('location', 'back');
$tattooTwo = new ComhonObject('Test\Person\Tattoo');
$tattooTwo->setValue('type', 'flower');
$tattooTwo->setValue('location', 'arm');
$bodyArts = $person->initValue('bodyArts');
$bodyArts->pushValue($tattooOne);
$bodyArts->pushValue($tattooTwo);Now object is built, we can export it
$XMLInterfacer = new XMLInterfacer();
// via interfacer
$exportedPerson = $XMLInterfacer->export($person); // return instance of DOMNode
// via comhon object
$exportedPerson = $person->export($XMLInterfacer); // return instance of DOMNode
// via model (be careful when you use this method, $model and $person model must match)
$exportedPerson = $model->export($person, $XMLInterfacer); // return instance of DOMNode
// stringify exported object
$xml = $XMLInterfacer->toString($exportedPerson); // return XML string
echo $xml;output :
<root id="1" firstName="John" lastName="Doe">
<bodyArts>
<bodyArt type="dragon" location="back" inheritance-="Test\Person\Tattoo"/>
<bodyArt type="flower" location="arm" inheritance-="Test\Person\Tattoo"/>
</bodyArts>
<mother>2</mother>
<children>
<child id="3" inheritance-="Test\Person\Man"/>
<child id="4" inheritance-="Test\Person\Woman"/>
</children>
</root>As you can see in previous example, value mother is exported as a simple id even if mother is actually an object. Foreign properties are exported like that for a simple reason, value can be defined elsewhere, and if we describe all object, same value would be described several times, it can be ambiguous and dangerous. If exported object had several id properties, it would be exported has JSON encoded array.
Children are foreign too, but exported node is not a simple id value, we will explain why in following section.
Like described in manifest person example bodyArts property has Test\Person\BodyArt unique model. And In previous example we have added tattoos objects. Actually we can add tattoo because it inherit from Test\Person\BodyArt. So interfaced object tattoo must include it model name otherwise we would not be able to recognize if object is a tattoo, a piercing or simply a bodyArt. The object model name is specified under the key inheritance-.
Like described in manifest person example children property has Test\Person unique model. And In previous example we have added Test\Person\Woman and Test\Person\Man objects. Actually we can add them because they inherit from Test\Person. Like "normal" object properties we have to specify model name, so instead of export only an id, we have to export an object that contain inheritance- and id. Note that id is always id, even if a model would have an id named "my_id" or several id.
The root object may be exported via a parent model. For example an object with model Test\Person\Woman may be exported via Test\Person model. The key inheritance- is added when exporting from parent model.
// create object that will be exported and add simple values
$woman = new ComhonObject('Test\Person\Woman');
$woman->setValue('id', 1);
$woman->setValue('firstName', 'Jane');
$woman->setValue('lastName', 'Doe');
// create and add mother value
$mother = new ComhonObject('Test\Person\Woman');
$mother->setValue('id', 2);
$woman->setValue('mother', $mother);
// export via parent model
$modelPerson = ModelManager::getInstance()->getInstanceModel('Test\Person');
$exportedPerson = $modelPerson->export($woman, $XMLInterfacer); // return instance of DOMNode
// stringify exported object
$xml = $XMLInterfacer->toString($exportedPerson); // return XML string
echo $xml;output :
<root id="1" firstName="Jane" lastName="Doe" inheritance-="Test\Person\Woman">
<mother>2</mother>
</root>$model = ModelManager::getInstance()->getInstanceModel('Test\Person');
$stdOject = json_decode('{"id":1,"firstName":"John"}');
$stdInterfacer = new StdObjectInterfacer();
// via interfacer
$importedPerson = $stdInterfacer->import($stdOject, $model); // return instance of ComhonObject
echo $importedPerson->getValue('firstName'); // output 'John'
// via model
$importedPerson = $model->import($stdOject, $stdInterfacer); // return instance of ComhonObject
echo $importedPerson->getValue('firstName'); // output 'John'
// via comhon object
$person = new ComhonObject($model);
$person->setValue('lastName', 'Doe');
$person->fill($stdOject, $stdInterfacer); // fill current instance of ComhonObject
echo $person->getValue('firstName'); // output 'John'
echo $person->getValue('lastName'); // output 'Doe'When a value corresponding to a foreign property is imported, it has only id information and optionally inheritance- information (seen in previous section). Import will automatically try to find if referenced object exists in current object or in Main Object Collection and get this instance, otherwise it will create a new object.
To explain described behavior we add an id property id to Test\Person\BodyArt and we add a foreign property foreignTattoo with model Test\Person\Tattoo to Test\Person. It doesn't make sens in model but do not take this into account, it's just an example.
$model = ModelManager::getInstance()->getInstanceModel('Test\Person');
$stdOject = json_decode('{
"id": 10,
"firstName": "John",
"lastName": "Doe",
"mother": 20,
"children": [
{"inheritance-": "Test\Person\Man","id": 30},
{"inheritance-": "Test\Person\Woman","id": 40}
],
"bodyArts": [
{"inheritance-":"Test\Person\Tattoo","id":1,"type":"dragon","location":"back"},
{"inheritance-":"Test\Person\Tattoo","id":2,"type":"flower","location":"arm"}
],
"foreignTattoo":1
}');
$woman = new ComhonObject($model);
$woman->setValue('id', 20); // by setting id, object is added in MainObjectCollection
$woman->setValue('firstName', 'Jane');
$woman->setValue('lastName', 'Doe');
$stdInterfacer = new StdObjectInterfacer();
$importedPerson = $stdInterfacer->import($stdOject, $model); // return instance of ComhonObject
echo $importedPerson->getValue('children')->getValue(1)->getValue('id');
// output 40
echo $importedPerson->getValue('mother')->getValue('firstName');
// output 'Jane'
echo $woman === $importedPerson->getValue('mother') ? 'same instance' : 'different instance';
// output 'same instance'
echo $importedPerson->getValue('foreignTattoo')->getValue('type');
// output 'dragon'
echo $importedPerson->getValue('foreignTattoo') === $importedPerson->getValue('bodyArts')->getValue(0)
? 'same instance' : 'different instance';
// output 'same instance'You can interface (import and export) comhon arrays like comhon objects.
// create comhon array that will be exported
$persons = new ComhonArray('Test\Person');
// create and add person values
$woman = new ComhonObject('Test\Person\Woman');
$woman->setValue('id', 3);
$woman->setValue('firstName', 'Jane');
$woman->setValue('lastName', 'Doe');
$man = new ComhonObject('Test\Person\Man');
$man->setValue('id', 4);
$man->setValue('firstName', 'John');
$man->setValue('lastName', 'Doe');
$persons->pushValue($woman);
$persons->pushValue($man);Now comhon array is built, we can export it
$XMLInterfacer = new XMLInterfacer();
// via interfacer
$exportedPersons = $XMLInterfacer->export($persons); // return instance of DOMNode
// via comhon object
$exportedPersons = $persons->export($XMLInterfacer); // return instance of DOMNode
// via model array (be careful when you use this method, $model and $persons model must match)
$exportedPersons = $model->export($persons, $XMLInterfacer); // return instance of DOMNode
// stringify exported object
$xml = $XMLInterfacer->toString($exportedPersons); // return XML string
echo $xml;output :
<root>
<person id="3" firstName="Jane" lastName="Doe" inheritance-="Test\Person\Woman"/>
<person id="4" firstName="John" lastName="Doe" inheritance-="Test\Person\Man"/>
</root>You can define some preferences to apply during interfacing. All preferences have their constantes defined in Interfacer class to permit you to find them easily.
there is two ways to define interfacer preferences :
- by calling setter :
$stdInterfacer = new StdObjectInterfacer();
$stdInterfacer->setPrivateContext(true);
$stdInterfacer->setDateTimeZone('UTC');- during interfacing :
$stdInterfacer = new StdObjectInterfacer();
$preferences = [
Interfacer::PRIVATE_CONTEXT => true,
Interfacer::DATE_TIME_ZONE => 'UTC',
];
$stdObject = $stdInterfacer->export($person, $preferences);If you don't specify preference, the default preference will be used.
If you use same instance of interfacer for several import/export, preferences previously set will be used (or default value)
$stdInterfacer = new StdObjectInterfacer();
// default value for 'privateContext' is boolean false
$stdObject = $stdInterfacer->export($person); // export is NOT in private context
$stdObject = $stdInterfacer->export($person, ['privateContext' => true]); // export is in private context
$stdObject = $stdInterfacer->export($person); // export still in private contextDefine private context (context might be private or public).
Private properties are interfaced only in private context.
| type | boolean |
| default | false |
| availability | import and export |
| constant | Interfacer::PRIVATE_CONTEXT |
// here we suppose 'id' is a private property
$person = new ComhonObject('Test\Person');
$person->setValue('id', 1);
$person->setValue('firstName', 'John');
$stdInterfacer = new StdObjectInterfacer();
$stdObject = $stdInterfacer->export($person);
echo $stdInterfacer->toString($stdObject);
// output '{"firstName":"John"}'
$stdObject = $stdInterfacer->export($person, [Interfacer::PRIVATE_CONTEXT => true]);
echo $stdInterfacer->toString($stdObject);
// output '{"id":1,"firstName":"John"}'
$stdObject = json_decode({"id":2,"firstName":"Jane"});
$person = $stdInterfacer->import($stdObject, $model, [Interfacer::PRIVATE_CONTEXT => false]);
echo $person->hasValue('id'); // output false
echo $person->getValue('Jane'); // output 'Jane'
$person = $stdInterfacer->import($stdObject, $model, [Interfacer::PRIVATE_CONTEXT => true]);
echo $person->getValue('id'); // output 2
echo $person->getValue('Jane'); // output 'Jane'Define serial context. This preference is used implicitly for serialization/deserialization.
Properties are interfaced with their serialization name.
Aggregation properties are not interfaced in serial context.
If exists, interface inheritance value as defined in serialization manifest.
| type | boolean |
| default | false |
| availability | import and export |
| constant | Interfacer::SERIAL_CONTEXT |
// here we suppose 'children' is an aggregation property
// and property 'firstName' has serialization name 'first_name'
$person = new ComhonObject('Test\Person\Woman');
$person->setValue('firstName', 'John');
$childOne = new ComhonObject('Test\Person');
$childOne->setValue('id', 2);
$childTwo = new ComhonObject('Test\Person');
$childTwo->setValue('id', 3);
$children = $person->initValue('children');
$children->pushValue($childOne);
$children->pushValue($childTwo);
$stdInterfacer = new StdObjectInterfacer();
$stdObject = $stdInterfacer->export($person);
echo $stdInterfacer->toString($stdObject);
// output '{"firstName":"John","children":[2,3]}'
$stdObject = $stdInterfacer->export($person, [Interfacer::SERIAL_CONTEXT => true]);
echo $stdInterfacer->toString($stdObject);
// output '{"first_name":"John","gender":"Test\\Person\\Woman"}'Define date time format.
dateTime values will be exported in specified date time format.
| type | string |
| default | ISO 8601 |
| availability | export |
| constant | Interfacer::DATE_TIME_FORMAT |
$person = new ComhonObject('Test\Person');
$person->setValue('firstName', 'John');
$birthDate = new ComhonDateTime('1988-09-16 16:30', new DateTimeZone('Europe/Paris'));
$person->setValue('birthDate', $birthDate);
$stdInterfacer = new StdObjectInterfacer();
$stdObject = $stdInterfacer->export($person);
echo $stdInterfacer->toString($stdObject);
// output '{"firstName":"John","birthDate":"1988-09-16T16:30:00+02:00"}'
$stdObject = $stdInterfacer->export($person, [Interfacer::DATE_TIME_FORMAT => 'Y-m-d H:i']);
echo $stdInterfacer->toString($stdObject);
// output '{"firstName":"John","birthDate":"1988-09-16 16:30"}'Define date time zone.
dateTime values will be interfaced in specified date time zone.
| type | string |
| default | your PHP time zone |
| availability | import and export |
| constant | Interfacer::DATE_TIME_ZONE |
$person = new ComhonObject('Test\Person');
$person->setValue('firstName', 'John');
$birthDate = new ComhonDateTime('1988-09-16 16:30', new DateTimeZone('Europe/Paris'));
$person->setValue('birthDate', $birthDate);
$stdInterfacer = new StdObjectInterfacer();
$stdObject = $stdInterfacer->export($person, [Interfacer::DATE_TIME_ZONE => 'Europe/Paris']);
echo $stdInterfacer->toString($stdObject);
// output '{"firstName":"John","birthDate":"1988-09-16T16:30:00+02:00"}'
$stdObject = $stdInterfacer->export($person, [Interfacer::DATE_TIME_ZONE => 'UTC']);
echo $stdInterfacer->toString($stdObject);
// output '{"firstName":"John","birthDate":"1988-09-16T14:30:00+00:00"}'
// import dateTime that doesn't specify it time zone (Y-m-d H:i:s)
$stdObject = json_decode('{"firstName":"John","birthDate":"1988-09-16 16:30:00"}');
$person = $stdInterfacer->import($stdObject, $model, [Interfacer::DATE_TIME_ZONE => 'Europe/Paris']);
echo $person->getValue($stdObject)->format('c');
// output '1988-09-16T16:30:00+02:00' (+02:00 => 'Europe/Paris')
$person = $stdInterfacer->import($stdObject, $model, [Interfacer::DATE_TIME_ZONE => 'UTC']);
echo $person->getValue($stdObject)->format('c');
// output '1988-09-16T16:30:00+00:00' (+00:00 => 'UTC')Define if interfacer have to export only updated values (ids are always exported even if they are not updated).
This preference is applied only on current exported object. If there are "sub"-objects in current exported object, all values (of these sub-objects) will be exported even if they are not flagged as updated.
This preference is used typically for sql serialization.
| type | boolean |
| default | false |
| availability | export |
| constant | Interfacer::ONLY_UPDATED_VALUES |
$person = new ComhonObject('Test\Person');
// third parameter of setValue() force status of value
// if we specify false, value we be considered as not updated
$person->setValue('id', 1, false);
$person->setValue('firstName', 'John');
$person->setValue('lastName', 'Doe', false);
$stdInterfacer = new StdObjectInterfacer();
$stdObject = $stdInterfacer->export($person, [Interfacer::ONLY_UPDATED_VALUES => true]);
echo $stdInterfacer->toString($stdObject);
// output '{"id":1,"firstName":"John"}'
// lastName is not exported, and id is exported even if it's not updatedDefine a filter of values that will be exported.
| type | array |
| default | null |
| availability | export |
| constant | Interfacer::PROPERTIES_FILTERS |
$person = new ComhonObject('Test\Person');
$person->setValue('id', 1);
$person->setValue('firstName', 'John');
$person->setValue('lastName', 'Doe');
$person->setValue('age', 21);
$stdInterfacer = new StdObjectInterfacer();
$filter = ['firstName', 'age'];
$stdObject = $stdInterfacer->export($person, [Interfacer::PROPERTIES_FILTERS => $filter]);
echo $stdInterfacer->toString($stdObject);
// output '{"id":1,"firstName":"John","age":"21"}'
// lastName is not exported
// id is automatically added even if it's not in filterDefine if interfaced complex values (object or array) are flattened (stringified).
This preference is used typically during sql serialization.
| type | boolean |
| default | false |
| availability | export and import |
| constant | Interfacer::FLATTEN_VALUES |
$person = new ComhonObject('Test\Person');
$person->setValue('id', 1);
$person->setValue('firstName', 'John');
$tattooOne = new ComhonObject('Test\Person\Tattoo');
$tattooOne->setValue('type', 'dragon');
$tattooOne->setValue('location', 'arm');
$bodyArts = $person->initValue('bodyArts');
$bodyArts->pushValue($tattooOne);
$stdInterfacer = new StdObjectInterfacer();
$stdObject = $stdInterfacer->export($person, [Interfacer::FLATTEN_VALUES => true]);
echo $stdInterfacer->toString($stdObject);
// output '{"id":1,"firstName":"John","bodyArts":"[{\"type\":\"dragon\",\"location\":\"arm\",\"inheritance-\":\"Test\Person\Tattoo\"}]"}'Define if all interfaced values are stringified.
| type | boolean |
| default | false |
| availability | export and import |
| constant | Interfacer::STRINGIFIED_VALUES |
$person = new ComhonObject('Test\Person');
$person->setValue('id', 1);
$person->setValue('firstName', 'John');
$tattooOne = new ComhonObject('Test\Person\Tattoo');
$tattooOne->setValue('type', 'dragon');
$tattooOne->setValue('location', 'arm');
$bodyArts = $person->initValue('bodyArts');
$bodyArts->pushValue($tattooOne);
$stdInterfacer = new StdObjectInterfacer();
$stdObject = $stdInterfacer->export($person, [Interfacer::STRINGIFIED_VALUES => true]);
echo $stdInterfacer->toString($stdObject);
// output '{"id":"1","firstName":"John","bodyArts":"[{\"type\":\"dragon\",\"location\":\"arm\",\"inheritance-\":\"Test\Person\Tattoo\"}]"}'
// as you can see id is exported as string even if it is defined a integerDefine if imported values will be flagged as updated.
| type | boolean |
| default | true |
| availability | import |
| constant | Interfacer::FLAG_VALUES_AS_UPDATED |
$stdInterfacer = new StdObjectInterfacer();
$stdObject = json_decode('{"id":1,"firstName":"John","bodyArts":[{"type":"dragon","location":"arm","inheritance-":"Test\Person\Tattoo"}]}');
$person = $stdInterfacer->import($stdObject, $model);
echo $person->isUpdatedValue('firstName'); // output true
echo $person->getValue('bodyArts')->getValue(0)->isUpdatedValue('type'); // output true
$stdObject = json_decode('{"id":2,"firstName":"Jane","bodyArts":[{"type":"dragon","location":"arm","inheritance-":"Test\Person\Tattoo"}]}');
$person = $stdInterfacer->import($stdObject, $model, [Interfacer::FLAG_VALUES_AS_UPDATED => false]);
echo $person->isUpdatedValue('firstName'); // output false
echo $person->getValue('bodyArts')->getValue(0)->isUpdatedValue('type'); // output falseDefine if imported object will be flagged as loaded.
| type | boolean |
| default | true |
| availability | import |
| constant | Interfacer::FLAG_OBJECT_AS_LOADED |
$stdInterfacer = new StdObjectInterfacer();
$stdObject = json_decode('{"id":1,"firstName":"John","bodyArts":[{"type":"dragon","location":"arm","inheritance-":"Test\Person\Tattoo"}]}');
$person = $stdInterfacer->import($stdObject, $model);
echo $person->isLoaded(); // output true
echo $person->getValue('bodyArts')->getValue(0)->isLoaded(); // output true
$stdObject = json_decode('{"id":2,"firstName":"Jane","bodyArts":[{"type":"dragon","location":"arm","inheritance-":"Test\Person\Tattoo"}]}');
$person = $stdInterfacer->import($stdObject, $model, [Interfacer::FLAG_OBJECT_AS_LOADED => false]);
echo $person->isLoaded(); // output false
echo $person->getValue('bodyArts')->getValue(0)->isLoaded(); // output trueDefine if interfaced object must be validated or not. This setting concern only root object or root array and these first level elements. Any "deeper" objects are automatically validated anyway.
| type | boolean |
| default | true |
| availability | export and import |
| constant | Interfacer::VALIDATE |
We suppose a model Test\Person with a property 'lastName' defined as required.
$stdInterfacer = new StdObjectInterfacer();
// no validation
$person = $stdInterfacer->import($stdObject, $model, [Interfacer::VALIDATE => false]);
// validation
$stdObject = json_decode('{"id":1,"firstName":"John"}');
$person = $stdInterfacer->import($stdObject, $model, [Interfacer::VALIDATE => true]); // an exception is thrownThere are two merge type :
-
merge :
If imported object has id, try to find instance of comhon object with same id inMainObjectCollection.- If found, get existing comhon object and add imported values to existing values.
- If an object value without id properties already exists, object is replaced by new instance.
- If an object value with id properties already exists and has same id(s), object instance is kept but values are reset.
- otherwise create new instance and populate it.
- If found, get existing comhon object and add imported values to existing values.
-
overwrite :
If imported object has id, try to find instance of comhon object with same id inMainObjectCollection.- If found, get existing comhon object, delete existing values and populate it.
- otherwise create new instance and populate it.
| type | enumeration (1,2,3) (Interfacer::MERGE,Interfacer::OVERWRITE,Interfacer::NO_MERGE) |
| default | Interfacer::MERGE |
| availability | import |
| constant | Interfacer::MERGE_TYPE |
$person = new ComhonObject('Test\Person');
$person->setValue('id', 1); // object is added to MainObjectCollection
$person->setValue('firstName', 'John');
$stdObject = json_decode('{"id":1,"lastName":"Doe"}');
$stdInterfacer = new StdObjectInterfacer();
$importedPerson = $stdInterfacer->import($stdObject, $model, [Interfacer::MERGE_TYPE => Interfacer::MERGE]);
echo $importedPerson->getValue('firstName'); // output 'John'
echo $importedPerson->getValue('lastName'); // output 'Doe'
echo $person === $importedPerson ? 'same instance' : 'different instance';
// output 'same instance'
$importedPerson = $stdInterfacer->import($stdObject, $model, [Interfacer::MERGE_TYPE => Interfacer::OVERWRITE]);
echo $importedPerson->hasValue('firstName'); // output false
echo $importedPerson->getValue('lastName'); // output 'Doe'
echo $person === $importedPerson ? 'same instance' : 'different instance';
// output 'same instance'Verify if foreign values in comhon object (export) or interfaced object (import) have existing reference in current object. Foreign values with main model are not concerned and may be interfaced without reference even if this setting is set to true. If a foreign value (not main) is not referenced, and setting is set to true, an exception is thrown.
| type | boolean |
| default | true |
| availability | export and import |
| constant | Interfacer::VERIFY_REFERENCES |
Like in Foreign properties import chapter, let's say we have a foreign property foreignTattoo with model Test\Person\Tattoo on model Test\Person.
$man = new ComhonObject('Test\Person\Man');
$father = $man->initValue('father');
$father->setId(1);
$bodyArts = $man->initValue('bodyArts');
$tattoo = new ComhonObject('Test\Person\Tattoo');
$tattoo->setId(1);
$bodyArts->pushValue($tattoo);
$man->setValue('foreignTattoo', $tattoo);
// tattoo with id '1' is referenced and father has main model
// so it works
$stdInterfacer = new StdObjectInterfacer();
$man->export($stdInterfacer);
$tattoo = new ComhonObject('Test\Person\Tattoo');
$tattoo->setId(10);
$man->setValue('foreignTattoo', $tattoo);
// tattoo with id '10' is not referenced
// so exception will be thrown
$stdInterfacer = new StdObjectInterfacer();
$man->export($stdInterfacer);$man = new ComhonObject('Test\Person\Man');
$stdInterfacer = new StdObjectInterfacer();
// tattoo with id '1' is referenced and father has main model
// so it works
$stdObject = json_decode('{
"bodyArts":[{"id":"1","inheritance-":"Test\\\\Person\\\\Tattoo"}],
"foreignTattoo":1,
"mother":2
}');
$man->fill($stdObject, $stdInterfacer);
// tattoo with id '10' is not referenced
// so exception will be thrown
$stdObject = json_decode('{"foreignTattoo":10}');
$man->fill($stdObject, $stdInterfacer);Duplicated objects are objects with same model or with share id and with same ids that are defined several times in same object. Duplicated objects are forbidden during object interfacing.
For Example let's say model Test\Person\Tattoo has an id property id. if we export a person with two tattoos that have same id, an exception will be thrown.
$man = new ComhonObject('Test\Person\Man');
$stdInterfacer = new StdObjectInterfacer();
$bodyArts = $man->initValue('bodyArts');
$tattooOne = new ComhonObject('Test\Person\Tattoo');
$tattooOne->setId(1);
$tattooTwo = new ComhonObject('Test\Person\Tattoo');
$tattooTwo->setId(1);
$bodyArts->pushValue($tattooOne);
$bodyArts->pushValue($tattooTwo);
// several tattoos with same id '1'
// so exception will be thrown
$man->export($stdInterfacer);Isolated values feature permit to have duplicated objects values inside an object. For example if in model Test\Person, value bodyArt in array bodyArts was defined as isolated, we would be able to interface several body arts with same id (unlike in previous chapter). You may define a value as isolated in manifest, see isolated value chapter.
An interfaced object may come from an unknown source and may contain errors (for example a REST API that accept JSON content to create resources). To avoid any irrelevant values, Comhon! import is strongly strict and may throw exception if any error is detected.
Import Exception is formatted to permit to know what kind of error is detected on which property. The import Exception thrown is always an instance of ImportException, has a code (one of code list), a message, and contain original exception. Actually import exception encapsulate original exception, and property where error is detected.
Example : we try to import a person with boolean value on a tattoo type property instead of string value.
$model = ModelManager::getInstance()->getInstanceModel('Test\Person');
$stdOject = json_decode('{"id":1,"bodyArts":[{"type":true,"inheritance-":"Test\\\\Person\\\\Tattoo"}]}');
$stdInterfacer = new StdObjectInterfacer();
try {
$importedPerson = $stdInterfacer->import($stdOject, $model);
} catch(ImportException $e) {
echo $e->getCode();
// output => 203
echo $e->getMessage();
// output => value must be a string, boolean 'true' given
echo json_encode($e->getStackProperties());
// output => ["type",0,"bodyArts"] (note that stack start from property and rewind to root object)
echo $e->getStringifiedProperties();
// output => .bodyArts.0.type (note that string start from root object to final property)
echo get_class($e->getOriginalException());
// output => Comhon\Exception\UnexpectedValueTypeException
}Comhon! allow a kind of cast by affecting a new model to object. The new model must inherit from previous object model. For example you have instanciate an object with model Test\Person you might cast this object with model Test\Person\Woman. It's often used automatically with foreign properties and sql serialization.
For this example we will assume that model Test\Person\Woman has an additional property than Test\Person. This property is maidenName.
$person = new ComhonObject('Test\Person');
$person->getModel()->getName(); // output 'Test\Person'
$person->setValue('maidenName', 'Doe'); // throw exception because 'maidenName' doesn't exist in model 'Test\Person'
$modelWoman = ModelManager::getInstance()->getInstanceModel('Test\Person\Woman');
$person->cast($modelWoman);
echo $person->getModel()->getName(); // output 'Test\Person\Woman'
$person->setValue('maidenName', 'Doe');
echo $person->getValue('maidenName'); // output 'Doe'If you have defined your own class Person and Woman. Model will be updated like we said before but Object will keep same class and it can be confusing.
$person = new Person();
$person->getModel()->getName(); // output 'Test\Person'
echo get_class($person); // output 'Your\Namespace\Person'
$modelWoman = ModelManager::getInstance()->getInstanceModel('Test\Person\Woman');
$person->cast($modelWoman);
echo $person->getModel()->getName(); // output 'Test\Person\Woman'
echo get_class($person); // output 'Your\Namespace\Person'As we have mentioned before, some cast might be made automatically. Let's take an example :
Like described in manifest person example, model Test\Person has a property bestFriend with model Test\Person (actually it might be a Test\Person\Man or a Test\Person\Woman).
Objects with models Test\Person, Test\Person\Man and Test\Person\Woman are serialized in same sql table person. This table have a column best_friend_id (with integer type) that is a foreign key of person.id.
If we load a person we are not able to know if his best friend is a woman or a man except with an other database request. So best friend will be instanciated with model Test\Person.
If later we load best friend object, it will be automatically casted to Test\Person\Woman or a Test\Person\Man.
Here a simplified example of serialized persons :
| id | gender | first_name | last_name | best_friend_id |
|---|---|---|---|---|
| 1 | Test\Person\Man | john | doe | 2 |
| 2 | Test\Person\Man | john | smith | 1 |
First we load person with id 1 and then person with id 2
$modelPerson = ModelManager::getInstance()->getInstanceModel('Test\Person');
$modelPerson->loadObject(1);
echo $person->getValue('lastName'); // output 'doe'
echo $person->getValue('bestFriend')->isLoaded(); // output false
echo $person->getValue('bestFriend')->getModel()->getName(); // output 'Test\Person'
echo $person->getValue('bestFriend')->getValue('id'); // output 2
echo $person->getValue('bestFriend')->getValue('lastName'); // output NULL
// three equivalent ways to load 'bestFriend' value (first one is recommended)
$person->loadValue('bestFriend');
$person->getValue('bestFriend')->load();
$modelPerson->loadObject($person->getValue('bestFriend')->getId());
echo $person->getValue('bestFriend')->isLoaded(); // output true
echo $person->getValue('bestFriend')->getModel()->getName(); // output 'Test\Person\Man'
echo $person->getValue('bestFriend')->getValue('lastName'); // output 'smith'
// bonus
echo $person === $person->getValue('bestFriend')->getValue('bestFriend') ? 'same instance' : 'different instance';
// output 'same instance'In general, automatic casts are made during deserialization and import.