PHP code example of buffalokiwi / magicgraph

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 

$model = $repo->create();

$model = $repo->create(['name' => 'foo']));

$model->name = 'foo';

$repo->save( $model );

echo $model->id;  //..Prints 1 

$model = $repo->get('1');

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' );
}));



[
  'one' => [
      'type' => 'model',
      'flags' => ['noinsert','noupdate'],
      'clazz' => \buffalokiwi\magicgraph\DefaultModel::class
  ]

  'many' => [
      'type' => 'array',
      'flags' => ['noinsert','noupdate'],
      'value' => [],
      'clazz' => \buffalokiwi\magicgraph\DefaultModel::class
  ]
]


  //..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)
                      ...




class Table1Model extends buffalokiwi\magicgraph\ServiceableModel {}
class Table2Model extends buffalokiwi\magicgraph\ServiceableModel {}
class Table3Model extends buffalokiwi\magicgraph\DefaultModel {}

$t1Props = new buffalokiwi\magicgraph\property\QuickPropertySet([
   'id' => [
       'type' => 'int',
       'flags' => ['primary']
   ],
    
   'table2model' => [
       'type' => 'model',
       'clazz' => Table2Model::class,
       'flags' => ['noinsert','noupdate','null'], //..Table2Model ]
]);



//..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'
)));



$model1 = $t1Repo->get("1");

var_dump( $model1->toArray( null, true, true ));

Outputs:

array (size=2)
  'id' => int 1
  'table2model' => 
    array (size=2)
      'id' => int 1
      'table3model' => 
        array (size=1)
          'id' => int 1


getConfig(): array

getPropertyNames(): array

beforeSave( IModel $model ) : void;

afterSave( IModel $model ) : void;

/**
 * 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
    ];
  }  
}

$model->getPropertySet()->addPropertyConfig( new BazProps());

/**
 * Outputs:
 * array (size=2)
 *   'foo' => string '' (length=0)
 *   'bar' => string '' (length=0)
 *   'baz' => string '' (length=0)
 */
var_dump( $model->toArray());

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 );
  }
);   

$treeFiddy = $dollarFactory->getMoney( '3.50' );

/**
 * Outputs:
 * object(buffalokiwi\magicgraph\money\MoneyProxy)[600]
 *  private 'money' => 
 *    object(Money\Money)[601]
 *      private 'amount' => string '350' (length=3)
 *      private 'currency' => 
 *        object(Money\Currency)[602]
 *          private 'code' => string 'USD' (length=3)
 *  private 'formatter' => 
 *    object(Money\Formatter\IntlMoneyFormatter)[595]
 *      private 'formatter' => 
 *        object(NumberFormatter)[596]
 *      private 'currencies' => 
 *        object(Money\Currencies\ISOCurrencies)[594]
 *  private 'decFmt' => 
 *    object(Money\Formatter\DecimalMoneyFormatter)[597]
 *      private 'currencies' => 
 *        object(Money\Currencies\ISOCurrencies)[594]
 */
var_dump( $treeFiddy );

//..Outputs: 3.50
echo (string)$treeFiddy;

//..Outputs: $3.50
echo $treeFiddy->getFormattedAmount();

//..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 />';
}

new buffalokiwi\magicgraph\property\htmlproperty\DefaultComponentMap([
  buffalokiwi\magicgraph\property\IStringProperty::class => function( 
    buffalokiwi\magicgraph\property\IStringProperty $prop, 
    string $name, 
    ?string $id, 
    string $value 
  ) : buffalokiwi\magicgraph\property\htmlproperty\IElement {
    $attrs = [];

    if ( $prop->getMin() != -1 )
      $attrs['minlength'] = $prop->getMin();

    if ( $prop->getMax() != -1 )
      $attrs['maxlength'] = $prop->getMax();

    if ( !empty( $prop->getPattern()))
      $attrs['pattern'] = $prop->getPattern();

    if ( $prop->getFlags()->hasVal( \buffalokiwi\magicgraph\property\IPropertyFlags::REQUIRED ))
      $attrs['

/**
 * 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'