1. Go to this page and download the library: Download buffalokiwi/magicgraph library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
buffalokiwi / magicgraph example snippets
$model = new DefaultModel( //..Create the model
new PropertyListSet( //..Create the property set
new DefaultIntegerProperty( 'id' ), //..Add the id property
new DefaultStringProperty( 'name' ) //..Add the name property
));
//..Set the id and name property values
$model->id = 1;
$model->name = 'Hello Model';
//..Get the id and property values
var_dump( $model->id ); //..Outputs: "int 1"
var_dump( $model->name ); //..Outputs: "string 'Hello Model' (length=11)"
$model->id = 'foo'; //..id is not a string.
$dbFactory = new PDOConnectionFactory( //..A factory for managing and sharing connection instances
new MariaConnectionProperties( //..Connection properties for MariaDB / MySQL
'localhost', //..Database server host name
'root', //..User name
'', //..Password
'testdatabase' ), //..Database
//..This is the factory method, which is used to create database connection instances
//..The above-defined connection arguments are passed to the closure.
function( IConnectionProperties $args ) {
//..Return a MariaDB connection
return new MariaDBConnection( $args );
});
$repo = new InlineSQLRepo(
'inlinetest', //..Database table name
$dbFactory->getConnection(), //..Database connection
//..Model properties follows
PrimaryIntegerProperty( 'id' ), //..Primary id property
DefaultStringProperty( 'name' )); //..Optional string property
class SamplePropertyConfig extends BasePropertyConfig
{
//..Returns an array detailing the properties to add
protected function createConfig() : array
{
//..A map of property name to configuration
return [
//..The Id Property
'id' => [
self::TYPE => IPropertyType::TINTEGER, //..The data type
self::FLAGS => [IPropertyFlags::PRIMARY], //..Flags
self::VALUE => 0 //..Default value
],
'name' => [
self::TYPE => IPropertyType::TSTRING,
self::FLAGS => [IPropertyFlags::REQUIRED],
self::VALUE => ''
]
];
}
}
$model = new DefaultModel( new StandardPropertySet( new SamplePropertyConfig()));
class SamplePropertyConfig extends BasePropertyConfig
{
//..Returns an array detailing the properties to add
protected function createConfig() : array
{
//..A map of property name to configuration
return [
//..The Id Property
'id' => self::FINTEGER_PRIMARY,
'name' => self::FSTRING_REQUIRED
];
}
}
//..Sample enum class
class SampleEnum extends Enum {}
//..Property configuration
'enum_property' => [
'type' => 'enum',
'clazz' => SampleEnum::class
]
/**
* Attach this strategy to any model to add a debug log message when the model is saved.
*/
class DebugLogSaveStrategy extends GenericNamedPropertyBehavior
{
/**
* The log
* @var LoggerInterface
*/
private LoggerInterface $log;
/**
* @param LoggerInterface $log
*/
public function __construct( LoggerInterface $log )
{
//..Since this is a save event, we simply pass the name of the class as the property name.
//..Save events are called regardless of the supplied name.
parent::__construct( static::class );
$this->log = $log;
}
/**
* Retrieve the after save function
* @return Closure|null function
*/
public function getAfterSaveCallback() : ?Closure
{
return function( IModel $model ) : void {
//..Get the primary key value from the model
$priKey = $model->getValue( $model->getPropertySet()->getPrimaryKey()->getName());
//..Add the log message
$this->log->debug( 'Model with primary key value: ' . $priKey . ' successfully saved.' );
};
}
}
//..Create the property config object and attach the strategy
$config = new SamplePropertyConfig( new DebugLogSaveStrategy( new LoggerInterfaceImpl()));
//..Create a model using the configuration
$model = new DefaultModel( new StandardPropertySet( $config ));
/**
* Attach this strategy to a model to print a log message when a value was set
*/
class DebugSetterStrategy extends GenericNamedPropertyBehavior
{
/**
* The log
* @var LoggerInterface
*/
private LoggerInterface $log;
/**
* @param string $name Property name
* @param LoggerInterface $log
*/
public function __construct( string $name, LoggerInterface $log )
{
//..Pass the property name
parent::__construct( $name );
$this->log = $log;
}
/**
* Callback used to set a value.
* This is called prior to IProperty::validate() and the return value will
* replace the supplied value.
*
* f( IProperty, value ) : mixed
*
* @return Closure callback
*/
public function getSetterCallback() : ?Closure
{
return function( IProperty $prop, $value ) {
//..Add the log message
$this->log->debug( $prop->getName() . ' changed to ' . (string)$value );
//..Return the unmodified value.
//..Setters can modify this value if desired
return $value;
};
}
}
//..Create the property config object and attach the strategy for the "name" property
$config = new SamplePropertyConfig( new DebugSetterStrategy( 'name', new LoggerInterfaceImpl()));
//..Create a model using the configuration
$model = new DefaultModel( new StandardPropertySet( $config ));
//..Create a new quick model
$q = new \buffalokiwi\magicgraph\QuickModel([
//..Id property, integer, primary key
'id' => [
'type' => 'int',
'flags' => ['primary']
],
//..Name property, string
'name' => [
'type' => 'string',
//..Append -bar to the name property value when seting
'setter' => function( IProperty $prop, string $value ) : string {
return $value . '-bar';
},
//..Append -baz to the name property value when retrieving
'getter' => function( IProperty $prop, string $value ) : string {
return $value . '-baz';
}
]
]);
//..Set the name attribute
$q->name = 'foo';
echo $q->name; //..Outputs "foo-bar-baz"
use buffalokiwi\magicgraph\AnnotatedModel;
use buffalokiwi\magicgraph\property\annotation\IntegerProperty;
use buffalokiwi\magicgraph\property\annotation\BooleanProperty;
use buffalokiwi\magicgraph\property\annotation\DateProperty;
use buffalokiwi\magicgraph\property\annotation\ArrayProperty;
use buffalokiwi\magicgraph\property\annotation\EnumProperty;
use buffalokiwi\magicgraph\property\annotation\FloatProperty;
use buffalokiwi\magicgraph\property\annotation\SetProperty;
use buffalokiwi\magicgraph\property\annotation\StringProperty;
use buffalokiwi\magicgraph\property\annotation\USDollarProperty;
class Test extends AnnotatedModel
{
#[IntegerProperty]
private int $id;
#[BooleanProperty]
private bool $b;
#[DateProperty('d', '1/1/2020')]
private IDateTime $d;
#[ArrayProperty('a','\stdClass')]
private array $a;
#[EnumProperty('e','\buffalokiwi\magicgraph\property\EPropertyType','int')]
private \buffalokiwi\magicgraph\property\EPropertyType $e;
#[FloatProperty]
private float $f;
#[SetProperty('set','\buffalokiwi\magicgraph\property\SPropertyFlags',['noinsert','noupdate'])]
private \buffalokiwi\buffalotools\types\ISet $set;
#[USDollarProperty]
private buffalokiwi\magicgraph\money\IMoney $money;
#[StringProperty]
private string $str;
public \buffalokiwi\magicgraph\property\IIntegerProperty $pubProp;
public function __construct()
{
$this->pubProp = new buffalokiwi\magicgraph\property\DefaultIntegerProperty( 'pubProp', 10 );
parent::__construct( new \buffalokiwi\magicgraph\property\QuickPropertySet([
'name' => [
'type' => 'string',
'value' => 'qp string'
]
]));
}
}
$a = new Test();
$aa = $a->a;
$aa[] = new \stdClass();
$a->a = $aa;
$a->id = 22;
$a->b = true;
$a->d = '10/10/2020';
$a->f = 1.123;
$a->set->add( 'primary' );
$a->str = 'foo';
$a->e->setValue( 'string' );
$a->pubProp->setValue( 11 );
$a->money = '3.50';
var_dump( $a->toArray(null, true, true));
Outputs:
array (size=11)
'name' => string 'qp string' (length=9)
'id' => int 22
'b' => int 1
'd' =>
object(DateTimeImmutable)[644]
public 'date' => string '2020-10-10 00:00:00.000000' (length=26)
public 'timezone_type' => int 3
public 'timezone' => string 'UTC' (length=3)
'a' =>
array (size=1)
0 =>
object(stdClass)[701]
'e' => string 'string' (length=6)
'f' => float 1.123
'set' => string 'primary,noupdate,noinsert' (length=25)
'money' => string '3.50' (length=4)
'str' => string 'foo' (length=3)
'pubProp' => int 11
$testSQLRepo = new SQLRepository( //..Create the SQL Repository
'inlinetest', //..Table Name
new DefaultModelMapper( function( IPropertySet $props ) { //..Create a data mapper
return new DefaultModel( $props ); //..Object factory
}, IModel::class ), //..Type of model being returned
$dbFactory->getConnection(), //..SQL database connection
new QuickPropertySet([ //..Property set defining properties added to the model
//..Id property, integer, primary key
'id' => [ //.."id" is a property
'type' => 'int', //..Id is an integer
'flags' => ['primary'] //..Id is the primary key
],
//..Name property, string
'name' => [ //.."name" is a property
'type' => 'string', //..Name is a string
]
])
);
$testSQLRepo = new CommonObjectRepo( new SQLRepository( //..Create the SQL Repository and add the caching decorator
'inlinetest', //..Table Name
new DefaultModelMapper( function( IPropertySet $props ) { //..Create a data mapper
return new DefaultModel( $props ); //..Object factory
}, IModel::class ), //..Type of model being returned
$dbFactory->getConnection(), //..SQL database connection
new QuickPropertySet([ //..Property set defining properties added to the model
//..Id property, integer, primary key
'id' => [ //.."id" is a property
'type' => 'int', //..Id is an integer
'flags' => ['primary'] //..Id is the primary key
],
//..Name property, string
'name' => [ //.."name" is a property
'type' => 'string', //..Name is a string
]
])
));
[
//..Id property, integer, primary key
'id' => [ //.."id" is a property
'type' => 'int', //..Id is an integer
'flags' => ['primary'] //..Id is the primary key
],
'id2' => [ //.."id2" is a property
'type' => 'int', //..Id2 is an integer
'flags' => ['primary'] //..Id2 is the other primary key
],
]
//..Get some data, model, etc.
$data = 'This represents a model or some other data being saved';
//..Create a new transaction. and write the contents of $data to a file when Transaction::run() is executed.
$transaction = new buffalokiwi\magicgraph\persist\Transaction( new buffalokiwi\magicgraph\persist\Runnable( function() use($data) {
file_put_contents( 'persistence.txt', $data );
}));
//..Start a new transaction inside of the persistence layer
$transaction->beginTransaction();
try {
//..Execute the code
$transaction->run();
//..Commit any changes in the persistence layer
$transaction->commit();
} catch( \Exception $e ) {
//..OH NO! An Error!
//..Revert any changes in the persistence layer
$transaction->rollBack();
}
//..Create a repository
$testSQLRepo = new SQLRepository(
'inlinetest',
new DefaultModelMapper( function( IPropertySet $props ) {
return new DefaultModel( $props );
}, IModel::class ),
$dbFactory->getConnection(),
new QuickPropertySet([
//..Id property, integer, primary key
'id' => [
'type' => 'int',
'flags' => ['primary']
],
//..Name property, string
'name' => [
'type' => 'string',
]
])
);
//..Create a new model and assign some property values
$model = $testSQLRepo->create([]);
$model->name = 'test';
//..Create a transaction
$transaction = new \buffalokiwi\magicgraph\persist\MySQLTransaction(
new \buffalokiwi\magicgraph\persist\MySQLRunnable(
$testSQLRepo,
function() use( $testSQLRepo, $model ) {
$testSQLRepo->save( $model );
}
));
//..Start a new transaction inside of the persistence layer
$transaction->beginTransaction();
try {
//..Execute the code
$transaction->run();
//..Commit any changes in the persistence layer
$transaction->commit();
} catch( \Exception $e ) {
//..OH NO! An Error!
//..Revert any changes in the persistence layer
$transaction->rollBack();
}
//..Create a database connection factory for some MySQL database
$dbFactory = new PDOConnectionFactory(
new MariaConnectionProperties(
'localhost', //..Host
'root', //..User
'', //..Pass
'retailrack' ), //..Database
function(IConnectionProperties $args ) {
return new MariaDBConnection( $args );
});
//..Create a quick test repository for a table named "inlinetest", with two columns id (int,primary,autoincrement) and name(varchar).
$repo = new InlineSQLRepo(
'inlinetest',
$dbFactory->getConnection(),
new PrimaryIntegerProperty( 'id' ),
new DefaultStringProperty( 'name' )
);
//..Create a new model and set the name property value to "test"
$model = $repo->create([]);
$model->name = 'test';
//..Create a new transaction factory
//..The supplied map is used within the TransactionFactory::createTransactions() method, and will generate ITransaction
// instances of the appropriate type based on a predefined subclass of IRunnable
//..Instances passed to TransactionFactory must be ordered so that the most generic IRunnable instances are last.
$tf = new TransactionFactory([
//..Supplying ISQLRunnable instances will generate instaces of MySQLTransaction
ISQLRunnable::class => function( IRunnable ...$tasks ) { return new MySQLTransaction( ...$tasks ); },
//..Supplying instances of IRunnable will generate a Transaction instance
IRunnable::class => function( IRunnable ...$tasks ) { return new Transaction( ...$tasks ); }
]);
//..Execute a mysql transaction
//..This will use a database transaction to save the model
//..If any exceptions are thrown by the supplied closure, then rollback is called. Otherwise, commit is called
//..upon successful completion of the closure
$tf->execute( new MySQLRunnable( $repo, function() use($repo, $model) {
$repo->save( $model );
}));
$tf->execute( new MySQLRunnable( $repo, function() use($repo, $model) {
$repo->save( $model );
throw new \Exception( 'No save for you' );
}));
//..Assuming $model was created using the above config and that $ref1 and $ref2 are both instances of DefaultModel
//..Ok
$model->one = $ref1;
//..Throws exception
$model->one = 'foo';
//..Multiple models can be added as an array
$model->many = [$ref1, $ref2];
//..A sample child model. This uses a unique class name instead of QuickModel because IModelProperty will attempt
// to instantiate an instance of the model when assigning the default value, and quick model is generic.
class ChildModel extends buffalokiwi\magicgraph\QuickModel {
public function __construct() {
parent::__construct([
'name' => [
'type' => 'string',
'value' => 'child model'
]
]);
}
}
//..The parent model ame' => string 'child model' (length=11)
//..When using model property providers / relationships, models MUST extend ServiceableModel. ServiceableModel
// extends DefaultModel, and adds the aph\ServiceableModel {};
//..Create a SQL Database connection
$dbFactory = new buffalokiwi\magicgraph\pdo\PDOConnectionFactory( //..A factory for managing and sharing connection instances
new buffalokiwi\magicgraph\pdo\MariaConnectionProperties( //..Connection properties for MariaDB / MySQL
'localhost', //..Database server host name
'root', //..User name
'', //..Password
'retailrack' ), //..Database
//..This is the factory method, which is used to create database connection instances
//..The above-defined connection arguments are passed to the closure.
function( buffalokiwi\magicgraph\pdo\IConnectionProperties $args ) {
//..Return a MariaDB connection
return new buffalokiwi\magicgraph\pdo\MariaDBConnection( $args );
}
);
//..Create the transaction factory
$tFact = new \buffalokiwi\magicgraph\persist\DefaultTransactionFactory();
//..Table2 Repository
//..This must be initialized prior to Table1Repo because Table1Repo depends on Table2Repo
$table2Repo = new buffalokiwi\magicgraph\persist\DefaultSQLRepository(
'table2',
$dbFactory->getConnection(),
Table2Model::class,
$table2Properties
);
//..Create properties for Table1Model
$table1Properties = new buffalokiwi\magicgraph\property\QuickPropertySet([
//..Primary key
'id' => [
'type' => 'int',
'flags' => ['primary']
],
//..A name
'name' => [
'type' => 'string'
],
//..Property containing the primary key for a Table2Model
'childid' => [
'type' => 'int',
'value' => 0
],
//..Child model property.
//..A model from Table2Repository is pulled by the id defined in the "childid" property
'child' => [
'type' => 'model',
'flags' => ['noinsert','noupdate','null'], //..Since Table2Model
//..When using model property providers / relationships, models MUST extend ServiceableModel. ServiceableModel
// extends DefaultModel, and adds the aph\ServiceableModel {};
//..Model properties for Table1
$table1Properties = new buffalokiwi\magicgraph\property\QuickPropertySet([
'id' => [
'type' => 'int',
'flags' => ['primary']
],
'name' => [
'type' => 'string'
],
'children' => [
'type' => 'array',
'flags' => ['noinsert','noupdate'],
'clazz' => Table2Model::class
]
]);
//..Model properties for table 2
$table2Properties = new buffalokiwi\magicgraph\property\QuickPropertySet([
'id' => [
'type' => 'int',
'flags' => ['primary']
],
'link_table1' => [
'type' => 'int',
'value' => 0
],
'name' => [
'type' => 'string'
]
]);
//..Create a SQL Database connection
$dbFactory = new buffalokiwi\magicgraph\pdo\PDOConnectionFactory( //..A factory for managing and sharing connection instances
new buffalokiwi\magicgraph\pdo\MariaConnectionProperties( //..Connection properties for MariaDB / MySQL
'localhost', //..Database server host name
'root', //..User name
'', //..Password
'retailrack' ), //..Database
//..This is the factory method, which is used to create database connection instances
//..The above-defined connection arguments are passed to the closure.
function( buffalokiwi\magicgraph\pdo\IConnectionProperties $args ) {
//..Return a MariaDB connection
return new buffalokiwi\magicgraph\pdo\MariaDBConnection( $args );
}
);
//..Create the transaction factory
$tFact = new \buffalokiwi\magicgraph\persist\DefaultTransactionFactory();
//..Table2 Repository
//..This must be initialized prior to Table1Repo because Table1Repo depends on Table2Repo
$table2Repo = new buffalokiwi\magicgraph\persist\DefaultSQLRepository(
'table2',
$dbFactory->getConnection(),
Table2Model::class,
$table2Properties
);
//..Table1 Repository
//..A sql database repository that can
//..Define the category model
class CategoryModel extends \buffalokiwi\magicgraph\ServiceableModel {}
//..Create the category model property configuration
//..In this instance, we are using QuickJunctionPropertyConfig because we want to use this model as a junction table target
//..QuickJunctionPropertyConfig implements IJunctionTargetProperties, which exposes the primary id property name and is used
// to generate database queries.
$cProps = new \buffalokiwi\magicgraph\property\QuickPropertySet( new \buffalokiwi\magicgraph\junctionprovider\QuickJunctionPropertyConfig([
'id' => [
'type' => 'int',
'flags' => ['primary']
],
'name' => [
'type' => 'string'
],
//..This is the list of products contained within a category
'products' => [
'type' => 'array',
'flags' => ['noinsert','noupdate'],
'clazz' => ProductModel::class
]
],
'id' //..Primary key property name used as the junction link target
));
//..Define the product model
class ProductModel extends \buffalokiwi\magicgraph\ServiceableModel {}
//..Create the product model property configuration
$pProps = new \buffalokiwi\magicgraph\property\QuickPropertySet( new \buffalokiwi\magicgraph\junctionprovider\QuickJunctionPropertyConfig([
'id' => [
'type' => 'int',
'flags' => ['primary']
],
'name' => [
'type' => 'string'
],
//..The list of categories containing some product
'categories' => [
'type' => 'array',
'flags' => ['noinsert','noupdate'],
'clazz' => CategoryModel::class
]
],
'id' //..Primary key property name used as the junction link target
));
//..Create the transaction factory
$tFact = new \buffalokiwi\magicgraph\persist\DefaultTransactionFactory();
//..Create a SQL Database connection
$dbFactory = new buffalokiwi\magicgraph\pdo\PDOConnectionFactory( //..A factory for managing and sharing connection instances
new buffalokiwi\magicgraph\pdo\MariaConnectionProperties( //..Connection properties for MariaDB / MySQL
'localhost', //..Database server host name
'root', //..User name
'', //..Password
'retailrack' ), //..Database
//..This is the factory method, which is used to create database connection instances
//..The above-defined connection arguments are passed to the closure.
function( buffalokiwi\magicgraph\pdo\IConnectionProperties $args ) {
//..Return a MariaDB connection
return new buffalokiwi\magicgraph\pdo\MariaDBConnection( $args );
}
);
//..Create the repository for the junction table
$jRepo = new buffalokiwi\magicgraph\junctionprovider\DefaultMySQLJunctionRepo(
'product_category_link',
$dbFactory->getConnection()
);
//..Create the product repository
$pRepo = new buffalokiwi\magicgraph\persist\DefaultSQLServiceableRepository(
'product',
$dbFactory->getConnection(),
ProductModel::class,
$pProps,
$tFact
);
//..Create the category repository
$cRepo = new buffalokiwi\magicgraph\persist\DefaultSQLServiceableRepository(
'product_category',
$dbFactory->getConnection(),
CategoryModel::class,
$cProps,
$tFact
);
//..Since we want both models to reference each other, we cannot instantiate the junction providers until
// both parent and target repositories have been created.
//..There is a handy method for adding these: addModelPropertyProvider()
//
//..If we were only referencing the target models in the parent repository or vice versa, we would have passed the junction
//..model instance directly to the serviceable repository constructor
//..Add the junction model property provider
$pRepo->addModelPropertyProvider(
new buffalokiwi\magicgraph\junctionprovider\MySQLJunctionPropertyService(
new buffalokiwi\magicgraph\junctionprovider\JunctionModelPropSvcCfg(
'id',
'categories' ),
$jRepo,
$cRepo
));
$cRepo->addModelPropertyProvider(
new buffalokiwi\magicgraph\junctionprovider\MySQLJunctionPropertyService(
new buffalokiwi\magicgraph\junctionprovider\JunctionModelPropSvcCfg(
'id',
'products' ),
$jRepo,
$pRepo
));
//..Get and print the product model
$p1 = $pRepo->get('1');
var_dump( $p1->toArray(null,true,true));
Outputs:
array (size=3)
'id' => int 1
'name' => string 'product1' (length=8)
'categories' =>
array (size=1)
0 =>
array (size=3)
'id' => int 1
'name' => string 'category1' (length=9)
'products' =>
array (size=1)
0 =>
array (size=3)
'id' => int 1
'name' => string 'product1' (length=8)
'categories' =>
array (size=1)
...
//..Get and print the category model
$c1 = $cRepo->get('1');
var_dump( $p1->toArray(null,true,true));
Outputs:
array (size=3)
'id' => int 1
'name' => string 'category1' (length=9)
'products' =>
array (size=1)
0 =>
array (size=3)
'id' => int 1
'name' => string 'product1' (length=8)
'categories' =>
array (size=1)
0 =>
array (size=3)
'id' => int 1
'name' => string 'category1' (length=9)
'products' =>
array (size=1)
...
//..There are no relationships in tableC
$t3Repo = new buffalokiwi\magicgraph\persist\DefaultSQLRepository(
'tablec',
$dbFactory->getConnection(),
Table3Model::class,
$t3Props,
);
$t2Repo = new buffalokiwi\magicgraph\persist\DefaultSQLServiceableRepository(
'tableb',
$dbFactory->getConnection(),
Table2Model::class,
$t2Props,
$tfact,
new buffalokiwi\magicgraph\OneOnePropertyService( new \buffalokiwi\magicgraph\OneOnePropSvcCfg(
$t3Repo,
'id',
'table3model'
)));
$t1Repo = new buffalokiwi\magicgraph\persist\DefaultSQLServiceableRepository(
'tablea',
$dbFactory->getConnection(),
Table1Model::class,
$t1Props,
$tfact,
new buffalokiwi\magicgraph\OneOnePropertyService( new \buffalokiwi\magicgraph\OneOnePropSvcCfg(
$t2Repo,
'id',
'table2model'
)));
/**
* Property configuration for a rectangle value object
*/
class RectangleProperties extends buffalokiwi\magicgraph\property\BasePropertyConfig
{
/**
* Height property name
*/
const HEIGHT = 'height';
/**
* Width property name
*/
const WIDTH = 'width';
/**
* Returns the property configuration array
* @return array
*/
protected function createConfig() : array
{
return [
self::HEIGHT => self::FINTEGER_REQUIRED,
self::WIDTH => self::FINTEGER_REQUIRED
];
}
}
/**
* Rectangle Value Object
*/
class Rectangle extends buffalokiwi\magicgraph\GenericModel {}
//..Create the rectangle model instance
$rectangle = new Rectangle( new RectangleProperties());
/**
* Outputs:
* array (size=2)
* 'height' => int 0
* 'width' => int 0
*/
var_dump( $rectangle->toArray());
/**
* Throws Exception with message:
* "height" property of class "Rectangle" of type "int" is REQUIRED and must not be empty.
*/
$rectangle->validate();
protected function createConfig() : array
{
//..Validation callback that will throw an exception when setting an integer property value to zero
$vInt = fn( buffalokiwi\magicgraph\property\IProperty $prop, int $value ) : bool => !empty( $value );
return [
self::HEIGHT => self::FINTEGER_REQUIRED + [self::VALIDATE => $vInt],
self::WIDTH => self::FINTEGER_REQUIRED + [self::VALIDATE => $vInt]
];
}
//..Set height to zero
$rectangle->height = 0;
//..Throws an exception like:
// Behavior validation failure in closure: RectangleProperties in file test.php on line 71
/**
* This interface defines a property configuration object for a Rectangle.
*/
interface IRectangleProperties extends \buffalokiwi\magicgraph\property\IPropertyConfig
{
/**
* Get the height property name
* @return string
*/
public function getHeight() : string;
/**
* Get the width property name
* @return string
*/
public function getWidth() : string;
}
/**
* Property configuration for a rectangle value object
*/
class RectangleProperties extends buffalokiwi\magicgraph\property\BasePropertyConfig implements IRectangleProperties
{
/**
* Height property name in the database
*/
const HEIGHT = 'height';
/**
* Width property name in the database
*/
const WIDTH = 'width';
/**
* Get the height property name
* @return string
*/
public function getHeight() : string
{
return self::HEIGHT;
}
/**
* Get the width property name
* @return string
*/
public function getWidth() : string
{
return self::WIDTH;
}
/**
* Returns the property configuration array
* @return array
*/
protected function createConfig() : array
{
//..Zero is no longer allowed
$vInt = fn( buffalokiwi\magicgraph\property\IProperty $prop, int $value ) : bool => !empty( $value );
return [
self::HEIGHT => self::FINTEGER_REQUIRED + [self::VALIDATE => $vInt],
self::WIDTH => self::FINTEGER_REQUIRED + [self::VALIDATE => $vInt]
];
}
}
/**
* Causes rectangles to behave as squares.
* This uses the model setter callback to force height and width to always be equal.
*/
class BehaveAsSquare extends buffalokiwi\magicgraph\property\GenericNamedPropertyBehavior
{
/**
* Model setter callback
* @var \Closure
*/
private \Closure $mSetter;
public function __construct()
{
parent::__construct( static::class );
$this->mSetter = $this->createModelSetterCallback();
}
/**
* Return the model setter callback
* @return \Closure|null
*/
public function getModelSetterCallback(): ?\Closure
{
return $this->mSetter;
}
/**
* Creates the model setter callback.
* No need to create this every time the setter is called.
* @return \Closure
*/
private function createModelSetterCallback() : \Closure
{
//..This setter is a circular reference, so we want to know if we're already in the closure
$inClosure = false;
return function(
\buffalokiwi\magicgraph\IModel $model,
\buffalokiwi\magicgraph\property\IProperty $prop,
$value ) use(&$inClosure) : mixed
{
//..Return if already in closure
if ( $inClosure )
return $value;
//..Set the state
$inClosure = true;
//..Get the rectangle property config
//..This will throw an exception if rectangleproperties are not used in the model.
/* @var $props IRectangleProperties */
$props = $model->getPropertyConfig( IRectangleProperties::class );
//..Set the other dimension
switch( $prop->getName())
{
case $props->getHeight():
$model->setValue( $props->getWidth(), $value );
break;
case $props->getWidth():
$model->setValue( $props->getHeight(), $value );
break;
}
try {
return $value;
} finally {
//..Reset state
$inClosure = false;
}
};
}
}
//..Create the rectangle model instance and make it a square
$rectangle = new Rectangle( new RectangleProperties( new BehaveAsSquare()));
//..Set one dimension
$rectangle->height = 10;
/**
* Outputs:
* array (size=2)
* 'height' => int 10
* 'width' => int 10
*/
var_dump( $rectangle->toArray());
/**
* Rectangle Value Object
*/
class Rectangle extends buffalokiwi\magicgraph\GenericModel
{
private IRectangleProperties $props;
public function __construct( \buffalokiwi\magicgraph\property\IPropertyConfig ...$config )
{
parent::__construct( ...$config );
//..Here we ensure that the model is actually a rectangle, and we get the property names.
$this->props = $this->getPropertyConfig( IRectangleProperties::class );
}
/**
* Sets the rectangle dimensions
* @param int $height Height
* @param int $width Width
* @return void
*/
public function setDimensions( int $height, int $width ) : void
{
$this->setValue( $this->props->getHeight(), $height );
$this->setValue( $this->props->getWidth(), $width );
}
/**
* Gets the height
* @return int
*/
public function getHeight() : int
{
return $this->getValue( $this->props->getHeight());
}
/**
* Gets the width
* @return int
*/
public function getWidth() : int
{
return $this->getValue( $this->props->getWidth());
}
}
/**
* Square value object
* Height and width are always equal
*/
class Square extends buffalokiwi\magicgraph\GenericModel
{
private IRectangleProperties $props;
public function __construct( \buffalokiwi\magicgraph\property\IPropertyConfig ...$config )
{
parent::__construct( ...$config );
//..Here we ensure that the model is actually a rectangle, and we get the property names.
$this->props = $this->getPropertyConfig( IRectangleProperties::class );
}
/**
* Sets the rectangle dimensions
* @param int $height Height
* @param int $width Width
* @return void
*/
public function setDimension( int $heightAndWidth ) : void
{
//..Our BehaveAsSquare will handle this
//..We could have just as easily set both properties here, but this is an example of how strategies work.
$this->setValue( $this->props->getHeight(), $heightAndWidth );
}
/**
* Gets the height
* @return int
*/
public function getHeight() : int
{
return $this->getValue( $this->props->getHeight());
}
}
$square = new Square( new RectangleProperties( new BehaveAsSquare()));
$square->setDimension( 10 );
/**
* Outputs:
* array (size=2)
* 'height' => int 10
* 'width' => int 10
*/
var_dump( $square->toArray());
class FooProps extends buffalokiwi\magicgraph\property\BasePropertyConfig
{
protected function createConfig(): array
{
return [
'foo' => self::FSTRING
];
}
}
class BarProps extends buffalokiwi\magicgraph\property\BasePropertyConfig
{
protected function createConfig(): array
{
return [
'bar' => self::FSTRING
];
}
}
//..Create the model instance with both property configuration objects
$model = new buffalokiwi\magicgraph\GenericModel( new FooProps(), new BarProps());
/**
* Outputs:
* array (size=2)
* 'foo' => string '' (length=0)
* 'bar' => string '' (length=0)
*/
var_dump( $model->toArray());
class BazProps extends buffalokiwi\magicgraph\property\BasePropertyConfig
{
protected function createConfig(): array
{
return [
'baz' => self::FSTRING
];
}
}
function getValidationCallback() : ?Closure
{
/**
* Validate some property value
* @param buffalokiwi\magicgraph\property\IProperty $prop Property being validated
* @param mixed $value Value to validate
* @return bool is valid
*/
return function( IProperty $prop, mixed $value ) : bool {
//..Validate $value
return false; //..Not valid, throws an exception
};
}
function getSetterCallback() : ?Closure
{
/**
* Modify a property value prior to being written to the backing property
* @param buffalokiwi\magicgraph\property\IProperty $prop Property being set
* @param mixed $value Value to set
* @return mixed modified value
*/
return function( buffalokiwi\magicgraph\property\IProperty $prop, mixed $value ) : mixed {
//..Ensure that any incoming value is a string, then append 'bar'
return (string)$value . 'bar';
};
}
function getGetterCallback() : ?Closure
{
/**
* Modify a property value prior to being written to the backing property
* @param buffalokiwi\magicgraph\property\IProperty $prop Property being set
* @param mixed $value Value to set
* @param array $context The context
* @return mixed modified value
*/
return function( buffalokiwi\magicgraph\property\IProperty $prop, mixed $value, array $context ) : mixed {
//..Ensure that any incoming value is a string, then append 'bar'
return (string)$value . 'bar';
};
}
function getInitCallback() : ?Closure
{
/**
* Modify the default value
* @param mixed $value The default value
* @return mixed default value
*/
return function ( mixed $value ) : mixed {
return $value;
};
}
function getIsEmptyCallback() : ?Closure
{
/**
* Basic empty check that returns true if the value is empty or the value is equal to the default property value.
* @param buffalokiwi\magicgraph\property\IProperty $prop Property being tested
* @param mixed $value The value to test
* @param mixed $defaultValue The default value for the property.
* @return bool is empty
*/
return function ( buffalokiwi\magicgraph\property\IProperty $prop, mixed $value, mixed $defaultValue ) : bool {
return empty( $value ) || $value === $defaultValue;
};
}
function getOnChangeCallback() : ?Closure
{
/**
* @param buffalokiwi\magicgraph\property\IProperty $prop The property being changed
* @param mixed $oldValue The value prior to the change
* @param mixed $newValue The value after the change
*/
return function ( buffalokiwi\magicgraph\property\IProperty $prop, mixed $oldValue, mixed $newValue ) : void {
//..Do something interesting
};
}
function getHTMLInputCallback() : ?Closure
{
/**
* Convert IProperty to IElement for HTML output
* @param \buffalokiwi\magicgraph\IModel $model Model property belongs to
* @param buffalokiwi\magicgraph\property\IProperty $prop Property to convert
* @param string $name HTML element name attribute value
* @param string $id HTML element id attribute value
* @param mixed $value Property value
* @return \buffalokiwi\magicgraph\property\htmlproperty\IElement The HTML element
*/
return function (
\buffalokiwi\magicgraph\IModel $model,
\buffalokiwi\magicgraph\property\IProperty $prop,
string $name,
string $id,
mixed $value ) : \buffalokiwi\magicgraph\property\htmlproperty\IElement {
return new buffalokiwi\magicgraph\property\htmlproperty\TextAreaElement( $name, $id, $value );
};
}
function getToArrayCallback() : ?Closure
{
/**
* @param \buffalokiwi\magicgraph\IModel $model Model being converted to an array
* @param buffalokiwi\magicgraph\property\IProperty $prop Property the value belongs to
* @param mixed $value Value to modify
* @return mixed modified value
*/
return function(
\buffalokiwi\magicgraph\IModel $model,
buffalokiwi\magicgraph\property\IProperty $prop,
mixed $value ) : mixed {
//..Return the modified value
return $value;
};
}
function getModelSetterCallback() : ?Closure
{
/**
* @param \buffalokiwi\magicgraph\IModel $model The model the property belongs to
* @param buffalokiwi\magicgraph\property\IProperty $prop The property being set
* @param mixed $value The value being written
* @return mixed The modified value to write to the backing property
*/
return function(
\buffalokiwi\magicgraph\IModel $model,
\buffalokiwi\magicgraph\property\IProperty $prop,
mixed $value ) : mixed {
//..Return modified value
return $value;
};
}
function getModelGetterCallback() : ?Closure
{
/**
* @param \buffalokiwi\magicgraph\IModel $model The model the property belongs to
* @param buffalokiwi\magicgraph\property\IProperty $prop The property being retrieved
* @param mixed $value The value being retrieved
* @return mixed The modified value to retrieve
*/
return function(
\buffalokiwi\magicgraph\IModel $model,
\buffalokiwi\magicgraph\property\IProperty $prop,
mixed $value ) : mixed {
//..Return modified value
return $value;
};
}
function getModelValidationCallback() : ?Closure
{
/**
* @param \buffalokiwi\magicgraph\IModel $model The model to validate
*/
return function( \buffalokiwi\magicgraph\IModel $model ) : void {
if ( !$valid )
throw new \buffalokiwi\magicgraph\ValidationException( 'Model is invalid' );
};
}
function getBeforeSaveCallback() : ?Closure
{
/**
* @param \buffalokiwi\magicgraph\IModel $model The model to save
*/
return function( \buffalokiwi\magicgraph\IModel $model ) : void {
//..Do something with the model before it's saved
};
}
function getAfterSaveCallback() : ?Closure
{
/**
* @param \buffalokiwi\magicgraph\IModel $model The model to save
*/
return function( \buffalokiwi\magicgraph\IModel $model ) : void {
//..Do something with the model after it's saved
};
}
/**
* Property definition for TestModel
*/
class TestProperties extends buffalokiwi\magicgraph\property\BasePropertyConfig
{
const NAME = 'name';
public function getName() : string
{
return self::NAME;
}
protected function createConfig() : array
{
return [
self::NAME => self::FSTRING
];
}
}
/**
* Test model
*/
class TestModel extends \buffalokiwi\magicgraph\GenericModel
{
/**
* Property definitions
* @var TestProperties
*/
private TestProperties $props;
public function __construct( \buffalokiwi\magicgraph\property\IPropertyConfig ...$config )
{
parent::__construct( ...$config );
$this->props = $this->getPropertyConfig( TestProperties::class );
}
public function getName() : string
{
return $this->getValue( $this->props->getName());
}
public function setName( string $name ) : void
{
$this->setValue( $this->props->getName(), $name );
}
}
/**
* If the name property equals "foo", it is set to "bar".
* If theh name property equals "baz", a ValidationException is thrown
*/
class TestModelBehavior extends buffalokiwi\magicgraph\property\GenericNamedPropertyBehavior
{
public function getValidateCallback(): ?\Closure
{
return function( buffalokiwi\magicgraph\property\IProperty $prop, string $name ) : bool {
//..If $name equals baz, then an exception is thrown
return $name != 'baz';
};
}
public function getSetterCallback(): ?\Closure
{
return function( buffalokiwi\magicgraph\property\IProperty $prop, string $name ) : string {
//..Returns bar if name equals foo.
return ( $name == 'foo' ) ? 'bar' : $name;
};
}
}
//..Create an instance of test model with the test behavior.
//..The behavior is wired to the name property.
$model = new TestModel( new TestProperties( new TestModelBehavior( TestProperties::NAME )));
//..Set the name
$model->setName( 'The name' );
/**
* Outputs:
* array (size=1)
* 'name' => string 'The name' (length=8)
*/
var_dump( $model->toArray());
//..Set the name to "foo"
$model->setName( 'foo' );
/**
* Outputs:
* array (size=1)
* 'name' => string 'bar' (length=3)
*/
var_dump( $model->toArray());
//..Set to baz and an exception will be thrown
//..Throws: "baz" of type "buffalokiwi\magicgraph\property\StringProperty" is not a valid value for the "name" property.
// Check any behavior callbacks, and ensure that the property is set to the correct type. IPropertyBehavior::getValidateCallback() failed.
//..This will also generate an error "Behavior validation failure in closure: TestProperties in file XXX"
$model->setName( 'baz' );
executeQuery( string $statement ) : Generator
/**
* Execute a delete query for a record using a compound key.
* @param string $table table name
* @param array $pkPairs primary key to value pairs
* @param int $limit limit
* @return int affected rows
* @throws InvalidArgumentExcepton if table or col or id are empty or if col
* contains invalid characters or if limit is not an integer or is less than
* one
* @throws DBException if there is a problem executing the query
*/
function delete( string $table, array $pkCols, int $limit = 1 ) : int;
//..Example:
$affectedRows = delete( 'mytable', ['pkcol1' => 'value1', 'pkcol2' => 'value2'], 1 );
//..Generates the statement:
// delete from mytable where pkcol1=? and pkcol2=? limit 1;
/**
* Build an update query using a prepared statement.
* @param string $table Table name
* @param array $pkPairs list of [primary key => value] for locating records to update.
* @param array $pairs Column names and values map
* @param int $limit Limit to this number
* @return int the number of affected rows
* @throws InvalidArgumentException
* @throws DBException
*/
function update( string $table, array $pkPairs, array $pairs, int $limit = 1 ) : int;
//..Example
$affectedRows = update( 'mytable', ['id' => 1], ['name' => 'foo', 'md5name:md5' => 'foo'], 1 );
//..Generates the statement:
// update mytable set name=?, md5name=md5(?) where id=? limit 1;
/**
* Build an insert query using a prepared statement.
* This will work for most queries, but if you need to do something
* super complicated, write your own sql...
*
*
* @param string $table Table name
* @param array $pairs Column names and values map
* @return int last insert id for updates
* @throws InvalidArgumentException
* @throws DBException
*/
function insert( string $table, array $pairs ) : string;
//..Example:
$lastInsertId = insert( 'mytable', ['col1' => 'value1', 'col2:md5' => 'value2'] );
//..generates statement:
// insert into mytable (col1, col2) values(?,md5(?));
/**
* Creates a cursor over some result set
* @param string $statement Statement
* @param type $options Parameters
* @param type $scroll Enable Scroll
* @return Generator Results
*/
function forwardCursor( string $statement, $options = null, $scroll = false ) : Generator;
//..Use it like this:
foreach( forwardCursor( 'select * from mytable where col=?', ['foo'] ) as $row )
{
//..Do something with $row
//..$row is an associative array containing column names and values.
}
/**
* Select some stuff from some database
* @param string $statement sql statement
* @param type $opt Bindings for prepared statement. This can be an object or an array
*/
function select( string $statement, $opt = null ) : \Generator;
//..Use like this:
foreach( select( 'select * from mytable where col=?', ['foo'] ) as $row )
{
//..Do something with $row
//..$row is an associative array containing column names and values.
}
//..Generates the statement:
// select * from mytable where col=?
/**
* Execute a sql statement that has multiple result sets
* ie: a stored procedure that has multiple selects, or one of those snazzy
* subquery statements
* @param string $sql SQL statement to execute
* @param array $bindings Column bindings
* @return Generator array results
* @throws DBException if there is one
*/
public function multiSelect( string $sql, array $bindings = [] ) : Generator
//..Example:
foreach( multiSelect( 'select * from mytable where id=?; select * from mytable where id=?', [1,2] ) as $rowSet )
{
//..Each $rowSet entry contains a set of rows to iterate over.
foreach( $rowSet as $row )
{
//..$row is an associative array of column => value
}
}
/**
* Executes a query with no result set.
* @param string $statement Statement to execute
* @param array $opt Map of bindings
* @return int
*/
function execute( string $statement, $opt = null ) : int;
$dbFactory = new buffalokiwi\magicgraph\pdo\PDOConnectionFactory( //..A factory for managing and sharing connection instances
new buffalokiwi\magicgraph\pdo\MariaConnectionProperties( //..Connection properties for MariaDB / MySQL
'localhost', //..Database server host name
'root', //..User name
'', //..Password
'fancydatabase' ), //..Database
//..This is the factory method, which is used to create database connection instances
//..The above-defined connection arguments are passed to the closure.
function( buffalokiwi\magicgraph\pdo\IConnectionProperties $args ) {
//..Return a MariaDB connection
return new buffalokiwi\magicgraph\pdo\MariaDBConnection( $args );
}
);
$currencies = new \Money\Currencies\ISOCurrencies();
//..Money formatter
$intlFmt = new Money\Formatter\IntlMoneyFormatter(
new \NumberFormatter( 'en_US', \NumberFormatter::CURRENCY ),
$currencies
);
$decFmt = new Money\Formatter\DecimalMoneyFormatter( $currencies );
//..Money factory
//..This is used to lock the system down to a certain type of currency,
// and to provide an abstract wrapper for the underlying money implementation.
$dollarFactory = new \buffalokiwi\magicgraph\money\MoneyFactory(
function( string $amount ) use($intlFmt,$decFmt) : buffalokiwi\magicgraph\money\IMoney {
return new buffalokiwi\magicgraph\money\MoneyProxy(
Money::USD( $amount ),
$intlFmt,
$decFmt );
}
);
//..Service locator
$ioc = new buffalokiwi\buffalotools\ioc\IOC();
//..Default Magic Graph Configuration Mapper.
//..This creates the property objects.
$configMapper = new buffalokiwi\magicgraph\property\DefaultConfigMapper( $ioc );
//..Factory wraps the config mapper and can combine config arrays.
// Uses the config mapper to produce properties.
$propertyFactory = new \buffalokiwi\magicgraph\property\PropertyFactory( $configMapper );
//..Use $propertyFactory to create instances of IPropertySet
//..Create a simple model with a few properties
$model = new buffalokiwi\magicgraph\QuickModel([
'numberinput' => ['type' => 'int'],
'stringinput' => ['type' => 'string'],
'dateinput' => ['type' => 'date'],
'boolinput' => ['type' => 'bool'],
'enuminput' => ['type' => 'rtenum', 'config' => ['test1','test2','test3'], 'value' => 'test1']
]);
$elementFactory = new buffalokiwi\magicgraph\property\htmlproperty\ElementFactory( ...( new buffalokiwi\magicgraph\property\htmlproperty\DefaultComponentMap())->getMap());
foreach( $model->getPropertySet()->getProperties() as $prop )
{
echo $elementFactory->createElement( $model, $prop, $prop->getName(), null, (string)$model->getValue( $prop->getName()))->build();
echo '<br />';
}
/**
* Converts IProperty to IElement
* @param \buffalokiwi\magicgraph\property\IProperty $prop Property to convert
* @param string $name property/html form input name
* @param string|null $id html element id attribute value
* @param string $value Property value as a string
* @return buffalokiwi\magicgraph\property\htmlproperty\IElement HTML Element
*/
function( \buffalokiwi\magicgraph\property\IProperty $prop, string $name,
?string $id, string $value ) : buffalokiwi\magicgraph\property\htmlproperty\IElement;
/*********************/
/* IoC Container */
/*********************/
$ioc = new \buffalokiwi\buffalotools\ioc\IOC();
/**********************/
/* Database */
/**********************/
$ioc->addInterface(buffalokiwi\magicgraph\pdo\IConnectionFactory::class, function() {
return new \buffalokiwi\magicgraph\pdo\PDOConnectionFactory(
new buffalokiwi\magicgraph\pdo\MariaConnectionProperties(
'localhost', //..Host
'root', //..User
'', //..Pass
'magicgraph' ), //..Database
function(\buffalokiwi\magicgraph\pdo\IConnectionProperties $args ) {
return new buffalokiwi\magicgraph\pdo\MariaDBConnection( $args, function(buffalokiwi\magicgraph\pdo\IDBConnection $c ) { $this->closeConnection($c); });
});
});
/**********************/
/* Dates */
/**********************/
$ioc->addInterface( \buffalokiwi\buffalotools\date\IDateFactory::class, function() {
return new \buffalokiwi\buffalotools\date\DateFactory();
});
/*********************/
/* Money Factory */
/*********************/
$ioc->addInterface( \buffalokiwi\magicgraph\money\IMoneyFactory::class, function() {
$currencies = new Money\Currencies\ISOCurrencies();
//..Money formatter
$intlFmt = new \Money\Formatter\IntlMoneyFormatter(
new \NumberFormatter( 'en_US', \NumberFormatter::CURRENCY ),
$currencies );
$decFmt = new \Money\Formatter\DecimalMoneyFormatter( $currencies );
//..Money factory
//..This is used to lock the system down to a certain type of currency,
// and to provide an abstract wrapper for the underlying money implementation.
return new \buffalokiwi\magicgraph\money\MoneyFactory( function( string $amount ) use($intlFmt,$decFmt) : \buffalokiwi\magicgraph\money\IMoney {
return new \buffalokiwi\magicgraph\money\MoneyProxy( \Money\Money::USD( $amount ), $intlFmt, $decFmt );
});
});
/*********************/
/* Magic Graph Setup */
/*********************/
//..Converts IPropertyConfig config arrays into properties
//..If creating custom propeties, this must be replaced with a custom implementation.
$configMapper = new buffalokiwi\magicgraph\property\DefaultConfigMapper( $ioc );
//..Factory wraps the config mapper and can combine config arrays.
// Uses the config mapper to produce properties.
$propertyFactory = new \buffalokiwi\magicgraph\property\PropertyFactory( $configMapper );
//..The property set factory is
//..Test repository interface
//..We always need a unique name for the service locator
interface ITestRepo extends \buffalokiwi\magicgraph\persist\IRepository {}
//..Test repository implementation
class TestRepo extends \buffalokiwi\magicgraph\persist\SQLRepository implements ITestRepo {};
//..Test model
class TestModel extends buffalokiwi\magicgraph\DefaultModel {}
//..Add ITestRepo to the container
$ioc->addInterface( ITestRepo::class, function() use ($dbc,$propertyFactory) {
return new TestRepo(
'testtable',
new \buffalokiwi\magicgraph\DefaultModelMapper( function( buffalokiwi\magicgraph\property\IPropertySet $props ) {
return new TestModel( $props );
}, TestModel::class ),
$dbc,
new buffalokiwi\magicgraph\property\DefaultPropertySet(
$propertyFactory,
new buffalokiwi\magicgraph\property\QuickPropertyConfig([
'id' => ['type' => 'int', 'flags' => ['primary']],
'name' => ['type' => 'string']]))
);
});
//..And now if we wanted to use this
$testRepo = $ioc->getInstance( ITestRepo::class );
/* @var $testRepo \buffalokiwi\magicgraph\persist\IRepository */
//..Create a new model
$testModel = $testRepo->create();
//..Set the name property
$testModel->name = 'test';
//..Save the model
$testRepo->save( $testModel );
//..Get the id of the new model
//..Outputs "1"
echo $testModel->id;
IPropertyFlags::USE_NULL = 'null'
Loading please wait ...
Before you can download the PHP files, the dependencies should be resolved. This can take some minutes. Please be patient.