Siesta is actually a code generating framework for entity relationship based scenarios. ORM is a pretty strong
use case for that approach. Siesta is organized in Generator and Plugins. A generator is used to generate
a class and a plugin is used to add member, methods etc. inside a class. Siesta ships with two Generators: one
for the entity and one for the service class. But you could define more generator to generate data transfer objects
or even Javascript classes that represent the entity.
A Siesta generator is simply a class that implements the Generator Interface. It only has 2 methods. addPlugin
allows to initialize it with the plugins that are configured for the specific generator and a method to perform
the magic (generate
). The entity object passed has all the information about the entity that needs to be generated.
namespace Siesta\Contract; interface Generator { public function addPlugin(Plugin $plugin); public function generate(Entity $entity, string $baseDir); }
Siesta generator are defined in a json file. This defines the implementing class and the plugins to be used.
Furthermore validator can be defined that must pass when reading the xml schema.
(Standard Config)
You can also change the behaviour of Siesta itself. If you do not want the toArray()
/fromArray()
methods generated just copy
the standard configuration modify it and point to it in the siesta configuration. You could also build your own plugins and replace
existing ones.
{ "generatorList": [ { "name": "Entity Generator", "className": "Siesta\\Generator\\EntityGenerator", "pluginList": [ "Siesta\\GeneratorPlugin\\Entity\\CollectionManyAccess", "Siesta\\GeneratorPlugin\\Entity\\ArePKIdenticalPlugin" ], "config": { }, "validatorList": [ "Siesta\\Validator\\DefaultDataModelValidator", ] } ] }
A plugin is the a class that add functionality to a generated class. For example the MemberPlugin
(Code)
will add the members to the generated class. The method getUseClassList
is supposed to return an array of strings
with all the classes that are used from the generated code. The getDependantPluginList
is not used at the moment.
The getInterfaceList
allows to return additional interfaces that the class implements as a result of the plugin.
Inside the generate
method the code is placed to add functionality to the class. The code
generator has methods to add methods to the generated class.
Have a look at the MemberPlugin.
/** * @author Gregor Müller */ interface Plugin { /** * @param Entity $entity * * @return array */ public function getUseClassNameList(Entity $entity) : array; /** * @return string[] */ public function getDependantPluginList() : array; /** * @return array */ public function getInterfaceList() : array; /** * @param Entity $entity * @param CodeGenerator $codeGenerator */ public function generate(Entity $entity, CodeGenerator $codeGenerator); }
The following example shows a plugin that transforms the entity data to xml. You can use the BasePlugin
Class,
it has already implementation of getUseClassNameList, getDependantPluginList and getInterfaceList. You
can simply overwrite them if you need custom behaviour. The given example is supposed to show the idea
of a plugin. In reality you would want references, collections etc. also serialized into XML.
First command is to create a method. This is done by invoking $codeGenerator->newPublicMethod("toXML")
with the method name (toXML). The code generator will return a method object, to which you can add
Parameters (\DOMElement) and their names in the method (element).
The entity allows you to iterate over all attributes. (see datamodel below for details). First some
base information is retrieved (type, name and memberName of the attribute).
Depending on the attributes type different coding is needed in the toXML method. If the php type
is bool, int, float or string, we add an if statement to the generated method ($method->addIfStart
)
You just pass the if condition. In this case we check if the member attribute is null. $memberName . ' !== null'
This is important in strict_types environment, because \DOMElement->setAttribute will not accept a null
value. The if statement is closed with $method->addIfEnd();
If the php type is SiestaDateTime (extends from \DateTime) then we need the null check as well. If
the datetime attribute is not null, we can invoke the method getSQLDateTime(). This is done in this line:
$method->addLine('$element->setAttribute("' . $name . '", ' . $memberName . '->getSQLDateTime());');
The addLine method allows you to write php code. The element variable is defined as a method parameter and we
invoke the setAttribute method on it. We pass as key the name of the attribute and as value the result of
getSQLDateTime.
<?php declare(strict_types = 1); namespace Siesta\GeneratorPlugin\Entity; use Siesta\CodeGenerator\CodeGenerator; use Siesta\GeneratorPlugin\BasePlugin; use Siesta\Model\Entity; use Siesta\Model\PHPType; class ToXMLPlugin extends BasePlugin { public function generate(Entity $entity, CodeGenerator $codeGenerator) { // create a new method and add a parameter $method = $codeGenerator->newPublicMethod("toXML"); $method->addParameter('\DOMElement', 'element'); // iterate all attributes foreach ($entity->getAttributeList() as $attribute) { $phpType = $attribute->getPhpType(); $name = $attribute->getPhpName(); $memberName = '$this->' . $name; // primitive types can be set directly if ($phpType === PHPType::BOOL || $phpType === PHPType::INT || $phpType === PHPType::FLOAT || $phpType === PHPType::STRING) { $method->addIfStart($memberName . ' !== null'); $method->addLine('$element->setAttribute("' . $name . '", ' . $memberName . ');'); $method->addIfEnd(); continue; } // datetime need method call if ($phpType === PHPType::SIESTA_DATE_TIME) { $method->addIfStart($memberName . ' !== null'); $method->addLine('$element->setAttribute("' . $name . '", ' . $memberName . '->getSQLDateTime());'); $method->addIfEnd(); } } // really important !!! $method->end(); } }
To activate your personal plugin you just have to follow the instructions in the next paragraph (Changing default behaviour). You have to add the name of the plugin to the generator in which you want to have the toXML() Method.
{ "name": "Entity Generator", "className": "Siesta\\Generator\\EntityGenerator", "pluginList": [ ... , "Siesta\\GeneratorPlugin\\Entity\\ToXMLPlugin" ], "config": { }, "validatorList": [ ..., ] }
To change the default behaviour, for example to remove plugins or add plugins, just copy the siesta.generator.config.json from (vendor/gm314/siesta/src/Siesta/Config) make the modifications you want and point in the siesta.config.json (the one generated with the init command) to your generic configuration. A value of null will use the default file in vendor/gm314/siesta/src/Siesta/Config
{ "connection": [ { ... } ], "generator": { "dropUnusedTables": true, "entityFileSuffix": ".entity.xml", "migrationTargetPath": "migration", "tableNamingStrategy": "Siesta\\NamingStrategy\\ToUnderScoreStrategy", "columnNamingStrategy": "Siesta\\NamingStrategy\\ToUnderScoreStrategy", "migrationMethod": "direct", "baseDir": "src", "connectionName": null, "genericGeneratorConfiguration": "config/myGenerator.json" }, "reverse": { ... } } }
Both Generator and Plugin interface will get the Entity object passed, that is subject to generation. The Entity
class represents a generated class respectively a table in the database. It has 1..n Attributes which in term
represent a member in the generated entity as well as a column in the corresponding table. An Index consists
of 1..n IndexPart which in term reference an attribute. An attribute/column can be part of serveral indexes.
A reference (foreign key) always refers a foreign entity and has 1..n ReferenceMapping. A referenceMapping
maps a local attribute (in the entity in which it is defined) and a foreign Attribute in the foreign entity.
The Entity has methods like
A little bit more complex are Collections and Many 2 Many Collections. An entity can have 0..n collections.
A collection always refers to a foreign entity and a foreign reference. So for example if you have the CartItem
object which references a Cart. (CartItem has a foreign key/ references to the cart object) A collection allows
you to collect all CartItems which refer the the given Cart. Therefore a collection needs to know the foreign entity
and the foreign reference.
An entity can also 0..n Collection Many. As an example you find in the tests the relationship between students
and exams. A student can participate in 0..n exams. and every exam can have 0..n students participating. This
is typically realized with a mapping table. Therefore a collection Many has a foreignEntity and a mapping entity.
Furthermore to foreignReference is the reference between the mapping table and the foreign entity. The mappingReference
is the reference between the entity and the mapping entity.
Also here you will find all needed methods to access these objects.
If your plugin needs validation logic you can add custom validators. A validator must implement one of the following interfaces.
Siesta\Contract\EntityValidator Siesta\Contract\AttributeValidator Siesta\Contract\ReferenceValidator Siesta\Contract\IndexValidator Siesta\Contract\CollectionValidator Siesta\Contract\CollectionManyValidator