Download the PHP package buffalokiwi/magicgraph without Composer

On this page you can find all versions of the php package buffalokiwi/magicgraph. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?
buffalokiwi/magicgraph
Rate from 1 - 5
Rated 5.00 based on 2 reviews

Informations about the package magicgraph

BuffaloKiwi Magic Graph

Behavioral-based object modeling, mapping and persistence library for PHP 8

OSL 3.0 License


Table of Contents

Generated Documentation

Documentation is a work in progress.

  1. Overview
  2. Installation
  3. Dependencies
  4. Definitions
  5. Getting Started
    1. Hello Model
    2. Basic Database and Repository Setup
  6. Property Configuration
    1. Property Configuration Array Attributes
    2. Property Data Types
    3. Property Flags
    4. Property Behavior
    5. Quick Models
    6. Annotations
  7. Repositories
    1. Mapping Object Factory
    2. Saveable Mapping Object Factory
    3. SQL Repository
    4. Decorating Repositories
    5. Serviceable Repository
    6. Composite Primary Keys
  8. Transactions
    1. Overview
    2. Creating a Transaction
    3. Transaction Factory
  9. Model service providers
    1. Serviceable Model
    2. Serviceable Repository
  10. Relationships
    1. One to One
    2. One to Many
    3. Many to Many
    4. Nested Relationship Providers
    5. How Editing and Saving Works
  11. Extensible Models
    1. Property configuration interface
    2. Property configuration implementation
    3. Using multiple property configurations
    4. Model interface
    5. Model implementation
  12. Behavioral Strategies
  13. Database Connections
    1. PDO
    2. MySQL PDO
    3. Connection Factories
  14. Working with Currency
  15. Creating HTML elements
  16. Magic Graph Setup
  17. Entity-Attribute-Value (EAV)
  18. Searching
  19. Extending Magic Graph
    1. The Config Mapper
  20. Tutorial

Overview

Magic graph is an object mapping and persistence library written in pure PHP. Magic Graph makes it easy to design and use rich hierarchical domain models, which may incorporate various independently designed and tested behavioral strategies.

Goals

This is the original set of goals for this project:

  1. Easily create self-validating models
  2. Dynamically create models at runtime
  3. Separate model behavior from the model object
  4. Allow models to be extended by third parties without modifying the model object or subclassing

Persistence

Magic Graph persistence uses the repository and unit of work patterns. Currently Magic Graph includes MySQL/MariaDB adapters out of the box, and additional adapters will be added in future releases.


Installation


Dependencies

Magic Graph requires one third party and three BuffaloKiwi libraries.

  1. BuffaloKiwi/buffalotools_ioc - A service locator
  2. BuffaloKiwi/buffalotools_types - Enum and Set support
  3. BuffaloKiwi/buffalotools_date - DateTime factory/wrappers
  4. MoneyPHP/Money - PHP implementation of Fowler's Money pattern

Definitions

What is a Model?

Magic Graph models are extensible and self-contained programs. They are designed to encapsulate all properties and behavior associated with any single source of data, but the models have zero knowledge of how to load or persist data. Don't worry too much about how these components work under the hood, we'll go over that in a future chapter.

Magic Graph models are composed of 4 main components:

  1. Property Definitions and base behavior
  2. Properties bundled into a Property Set
  3. The Model object
  4. Behavioral Strategies

Properties

At the core of every Magic Graph model, you will find a series of properties. Much like a standard class property, Magic Graph properties have a name, a data type and a value. Unlike standard class properties, Magic Graph properties are first class objects. They fully encapsulate all behavior associated with their data type, are extensible, reusable, self-validating and have configurable behaviors.

Property Set

The model properties are bundled into a Set-backed object called a Property Set . The property set provides methods for accessing property objects, their meta data, flags, configuration data and the ability to add and remove properties at run time.

Model Objects

All models must implement the IModel interface. Magic Graph models are essentially wrappers for the property set, and they expose properties within the set as if they were public members of the model class. Adding getter and setter methods are optional, but recommended. In addition to providing access to properties, models keep track of new and/or edited properties, have their own validation method, and can have additional behavioral strategy objects coupled to them.

Behavioral Strategies

Strategies are programs that modify the behavior of a model or property, and implement the INamedPropertyBehavior interface Strategies are passed to the model during object construction, and models will call the strategy methods. For example, say you had an order object, and you wanted to send the customer a receipt after they submit an order. A strategy could be created that sends an email after the order is successfully created and saved. Both IModel and INamedPropertyBehavior can be extended to add additional events as necessary.


Getting Started

Hello Model

This is one of many ways to write models in Magic Graph. As you read through the documentation, we will gradually shift towards writing more robust and extensible models. The following model example is used to illustrate the internal structure of models.

For now, let's take a look at some basic model creation code.

In this example, the following objects are used:
buffalokiwi\magicgraph\DefaultModel
buffalokiwi\magicgraph\property\DefaultIntegerProperty
buffalokiwi\magicgraph\property\DefaultStringProperty
buffalokiwi\magicgraph\property\PropertyListSet

First step is to decide the names and data types of the properties to be included within the model. In our example, we will add two properties: An integer property named "id", and a string property named "name". We will use DefaultIntegerProperty, and DefaultStringProperty. To create the model, each property is passed to the PropertyListSet constructor, which is then passed to DefaultModel.

A model with two properties has now been created. The properties are now available as public class properties.

Now, what happens if we try to assign a value of the wrong type to one of the properties? An exception is thrown! The following code will result in a ValidationException being thrown with the message: "Value foo for property id must be an integer. Got string.".

Models are self-validating, and ValidationException will be thrown immediately when attempting to set an invalid value. There are many validation options attached to the various default properties included with Magic Graph, which we will cover in the Validation chapter.


Basic Database and Repository Setup

So, what if we want to persist this data in a MySQL database? Without going into too much detail, we can create a SQL repository, which doubles as an object factory for the above-defined model.

The following objects are used:

buffalokiwi\magicgraph\pdo\IConnectionProperties
Defines connection properties used to establish a database connection

buffalokiwi\magicgraph\pdo\IDBConnection
Defines a generic database connection

buffalokiwi\magicgraph\pdo\MariaConnectionProperties
MariaDB/MySQL connection properties

buffalokiwi\magicgraph\pdo\MariaDBConnection
A database connection and statement helper library for MariaDB/MySQL

buffalokiwi\magicgraph\pdo\PDOConnectionFactory
A factory for creating database connection instances

First step is to create a database connection.

Next step is to create a table for our test model:

Finally, we create an instance of InlineSQLRepo, which is a repository for handling model construction, loading and saving data. You may notice that we are now using a PrimaryIntegerProperty instead of an IntegerProperty for id. This is because repositories require at least one property to be flagged as a primary key, and PrimaryIntegerProperty automatically sets that flag.

Now we create and save!

Create a new model from our new repository like this:

We can also initialize properties with the create method:

Set the property values

Since id is defined as a primary key, we do not want to set that value. The repository will take care of assigning that for us. Save the model by passing it to the repository save() method.

When saving, the repository first validates the model by calling the validate() method attached to the model. Then, on a successful save, the repository will assign the id (automatically generated by the database) to the id property.

Assuming the id of the newly created record was 1, we can retrieve the model:

The getting started section shows the most basic way of working with Magic Graph. While that's nice and all, it's pretty useless for anything other than a simple program. The next several chapters will detail how to use Magic Graph in larger applications.


Property Configuration

Property configuration files are a way to define properties and property-specific behavior, and must implement the IPropertyConfig interface. The configuration objects are similar to PHP traits, where we define partial objects. These objects can be assigned to IModel instances and define the property set (properties used within) and behavior of the associated model.

In the following example, we will create a sample property set with two properties: "id" and "name".

Id will be an integer property, have a default value of zero, be flagged as a primary key, and will read only if the value is non-zero.
Name will be a string property, have a default value of an empty string and be flagged as required.

In this example, these additional classes and interfaces are used:

The base property configuration is the base class used when defining property configurations. It provides constants, common property configurations and several methods for working with behaviors. buffalokiwi\magicgraph\property\BasePropertyConfig

IPropertyFlags defines various flags available to properties. This interface can be extended to add additional flags and functionality. buffalokiwi\magicgraph\property\IPropertyFlags

IPropertyType defines the available property types. Each type maps to a property object via the IConfigMapper interface.
buffalokiwi\magicgraph\property\IPropertyType

StandardPropertySet uses the default IConfigMapper and IPropertyFactory implementations to provide an easy way to instantiate IPropertySet instances when creating IModel instances. buffalokiwi\magicgraph\property\StandardPropertySet

A property configuration object descends from BasePropertyConfig and/or implements the IPropertyConfig instance. Only a single method createConfig() is required to be implemented in the descending class, and must return an array with zero or more property definitions.

createConfig() returns a map of property name to property configuration data. When defining the property confuration, 'type' (BasePropertyConfig::TYPE) is the only required attribute.

BasePropertyConfig::FLAGS maps to an array, which contains constants from IPropertyFlags. Zero or more flags may be supplied, and each will modify how a property is validated.

Default values can be set with the BasePropertyConfig::VALUE attribute, and assigning the value as the desired default value.

After creating the property definitions, we can then assign them to a property set, which is assigned to a model. Multiple IPropertyConfig instances can be passed to a StandardPropertySet.

BasePropertyConfig contains a few helper constants, which can be used to simplify the creation of property configuration objects. For example, the previous example could be rewritten as:

FINTEGER_PRIMARY will create an integer property, flagged as a primary key, with a default value of zero
FSTRING_REQUIRED will create a string property, flagged as required, with a default value of an empty string.


Property Configuration Array Attributes

The BasePropertyConfig class contains a series of constants used within the array returned by createConfig() to create properties for models. Certain attributes are for specific data types, and using them with other types will have no effect.

Caption

Property caption/label to be used at the application level.
Magic Graph does not read this value for any specific purpose.

Id

An optional unique identifier for some property. This is simply a tag, and is to be used at the application level. Magic Graph does not read this value for any specific purpose.

Default Value

Default value.
If no value is supplied during model construction, or if the IProperty::reset() method is called, property value will be assigned to the default value listed in the property configuration object.

Setter Callback

When a property value is set, any supplied setters will be called in the order in which they were defined.
Each property can define a single setter within the configuration array, but multiple setters can be added by supplying property behavior objects to the property configuration object constructor.

Setter callbacks are called by IProperty::setValue(), and can be used to modify an incoming value prior to validation. When chaining setters, the result of the previous setter is used as the value argument for the subsequent setter.

Getter Callback

When a property value is retrieved, any supplied getters will be called in the order in which they were defined. Each property can define a single getter within the configuration array, but multiple getters can be added by supplying property behavior objects to the property configuration object constructor.

Getter callbacks are called by IProperty::getValue(), and can be used to modify a value prior to being returned by getValue(). When chaining getters, the result of the previous getter is used as the value argument for the subsequent getter.

Model Setter Callback

Model setters are the same as property setters, but they are called at the model level. The difference between a model setter and a property setter is that model setters have access to other properties, and property setters do not. Since full model validation is only called on save, this can be used to validate state within an object, and prevent any modifications by throwing a ValidationException.

  1. When calling IModel::setValue (or setting a value via IModel::__set()), model setters are called in the order in which they were defined.
  2. Model setters are called prior to property setters and prior to property validation.
  3. When chaining model setters, the result of the previous setter is used as the value argument for the subsequent model setter.

Model Getter Callback

Model getters are the same as property getters, but they are called at the model level. The difference between a model getter and a property getter is that model getters have access to other properties, and property getters do not.

  1. When calling IModel::getValue (or getting a value via IModel::__get()), model getters are called in the order in which they were defined.
  2. Model getters are called after property getters.
  3. When chaining model getters, the result of the previous getter is used as the value argument for the subsequent model getter.

Property Data Type

This must map to a valid value of IPropertyType. For more information see the Property Data Types section.

Property Flags

This must map to a comma-delimited list of valid IPropertyFlags values.
For more information see the Property Flags section.

Class name for properties returning objects

When using properties backed by a descendant of ObjectProperty, the clazz attribute must be used. The value should be a fully namespaced class name.

For example, when the property type is defined as Enum or Set, clazz would equal some enum class name.

Initialize Callback

When IProperty::reset() is called, this function is called with the default value. This is a way to modify the default value prior to it being assigned as the initial property value. The value returned by the init callback is the new default value.

Minimum value/length

This is used with both Integer and String properties, and is the minimum value or minimum string length.

Maximum value/length

This is used with both Integer and String properties, and is the maximum value or minimum string length.

Validation

Validate callbacks are for validating individual property values prior to save or when IProperty::callback() is called. Validate callbacks are called prior to the backing property object validation call, and can either return a boolean representing validity, or throw a ValidationException. Returning false will automatically throw a ValidationException with an appropriate message.

Regular Expressions

When using string properties, the "pattern" attribute can be used to supply a regular expression, which will be used during property validation. Only values matching the supplied pattern can be committed to the property.

Custom configuration data

A config array. This is implementation specific, and is currently only used with Runtime Enum data types (IPropertyType::RTEnum). This can be used for whatever you want within your application.

Embedded model prefix

A prefix used by the default property set, which can proxy a get/set value call to a nested IModel instance. For example, say you had a customer model, and wanted to embed an address inside. Instead of copy/pasting properties or linking the customer to addresses, you can assign a prefix to a property named 'address' in the customer configuration, and add a CLAZZ property containing the class name of the address model. The customer model will then embed the address model inside of the customer model, and all address model functionality will be included. Furthermore, each address property will appear to be a member of the customer model, and have the defined prefix.

On change event

After a property value is successfully set, change events will be called in the order in which they were supplied.

For a given property, create an htmlproperty\IElement instance used as an html form input. Basically, generate an html input for a property and return that as a string, which can be embedded in some template.

Empty check

This is an optional callback that can be used to determine if a property can be considered "empty". The result of the supplied function is the result of an empty check.

Tagging

An optional tag for the attribute.
This can be any string, and is application specific. Nothing in Magic Graph will operate on this value by default.


Property Data Types

Property data type definitions define which data type object a property is backed by. All of the available definitions are within the the buffalokiwi\magicgraph\property\IPropertyType interface.

Here is a list of the built in property types that ship with Magic Graph:

Boolean

The 'bool' property type will be backed by an instance of IBooleanProperty. Unless specified as null, boolean properties will have a default value of false.

Integer

Backed by IIntegerProperty

Decimal

Backed by IFloatProperty

String

Backed by IStringProperty

Enum

Backed by IEnumProperty Column must list a class name implementing the IEnum interface in the 'clazz' attribute. For more information see BuffaloKiwi Types.

Runtime Enum

Backed by IEnumProperty Enum members are configured via the "config" property and is backed by a RuntimeEnum instance. Runtime Enum instances do not use the "clazz" attribute. For more information see BuffaloKiwi Types.

Array

Backed by ArrayProperty Array properties are mostly used by Magic Graph relationship providers. While it's possible to define array properties for arbitrary data, it is recommended to create a relationship or model service provider to manage the data contained within array properties.
Array properties can read the "clazz" argument to restrict the array members to objects of the specified type.

Set

Set properties are backed by ISetProperty, and will read/write instances of ISet (or descendants of ISet as specified by the "clazz" attribute). For more information see BuffaloKiwi Types.

Date/Time

Backed by IDateProperty, and can be used to represent a date and/or time. This would commonly be used with timestamp or DateTime SQL column types.

Currency.

A property backed by IMoneyProperty, containing an object implementing the IMoney interface. This property type requires use of an service locator and have the MoneyPHP/Money dependency installed.

IModel

Backed by IModelProperty and contains an object implementing the IModel interface. Model properties are commonly managed by a OneOnePropertyService.

Object

A property that only accepts instances of a specified object type.
It is recommended to extend the ObjectProperty class to create properties that handle specific object types instead of using the generic ObjectProperty object. In the future, I may mark ObjectProperty as abstract to prevent direct instantiation.


Property Flags

Property Flags are a series of modifiers for properties. Zero or more flags may be assigned to any property, and each will modify the validation strategy used within the associated model. Each flag is a constant defined within the buffalokiwi\magicgraph\property\IPropertyFlags interface.

No Insert

This property may never be inserted

No Update

This property may never be updated.
This can also be considered as "read only".

Required

This property requires a value

Allow Null

Property value may include null

Primary Key

Primary key (one per property set)

Sub config

Magic Graph does not use this flag, but it is here in case some property is loaded from some sub/third party config and you want to do something with those. For example, this is used in Retail Rack to identify properties loaded from configurations stored within a database.

Write Empty

Calling setValue() on the model will throw a ValidationException if the stored value is not empty.

No Array Output

Set this flag to prevent the property from being printed during a call to IModel::toArray(). toArray() is used to copy and save models, and not all properties should be read. ie: the property connects to some api on read and the returned value should not be saved anywhere.


Property Behavior

Each property has a series of callbacks as previously defined in Property Configuration Array Attributes. When creating instances of objects descending from BasePropertyConfig, it is possible to pass instances of INamedPropertyBehavior to the constructor.

The purpose of this is to create different strategies for an object. Strategies are independent, self-contained, and testable programs. Zero or more strategies may be attached to a property configuration object, may modify properties of the associated model, and may cause side effects.

For example, say you wanted to add a debug message to a log file when a model was saved in your development environment. We can create a class that extends GenericNamedPropertyBehavior, and overrides the getAfterSaveCallback() method.

After creating our strategy, we can attach it to a model via it's property configuration object.

When the model is saved via some IRepository instance, the after save callback will be executed in the strategy, and the log message will be added.

There are several callbacks, which together can be used to create rich models by using decoupled strategy objects.
See Property Configuration Array Attributes for details.

Adding behavioral strategies for individual properties is the same process as the above, except we would expose the "name" argument from the GenericNamedPropertyBehavior constructor.

Then to use the strategy:

Now any time the "name" property is set, the debug log will show "[Property name] changed to [new value]"


Quick Models

Quick models can be useful when you need to create a temporary model, or if you need to quickly create a mock model. Quick models accept standard configuration arrays, and can do anything a standard model can do. In the following example, we create a model with two properties: "id" and "name", and we add some behavior to the name property. When setting the name property, "-bar" is appended to the incoming value. When retrieving the name property, "-baz" is appended to the outgoing value.

Annotations

PHP 8 added a wonderful new feature called attributes. These snazzy things let us tag properties with things like the backing object type, default values, flags, etc. If you are willing to allow Magic Graph to make some assumptions, you can skip making property sets and configuration arrays/files. An annotated model would look something like this:

The above example mixes php attributes, a configuration array and a public IProperty intance. All three ways can be used to create models if you extend the AnnotatedModel class.

In a future release, the annotations package will be extended to include all available property configuration options and to configure relationships.


Repositories

Magic Graph repositories are an implementation of the Repository Pattern.
Repositories are an abstraction that encapsulates the logic for accessing some persistence layer. Similar to a collection, repositories provide methods for creating, saving, removing and retrieving IModel instances. Repositories are object factories, and are designed to produce a single object type. However, in a SQL setting, repositories may work with multiple tables from a database to produce a single model instance. When creating aggregate repositories, it's your choice if you wish to create a single repository per table, or a single repository that access several tables. It's worth noting that a repository may reference other repositories that access different persistence layers.

Repositories implement the IRepository interface.

Mapping Object Factory

Data mappers map data retrieved from some persistence layer to an IModel instance, and implement the IModelMapper interface. In Magic Graph, data mappers also double as object factories.

The MappingObjectFactory is normally the base class for all repositories, and implements the IObjectFactory interface. The mapping object factory is responsible for holding a reference to a data mapper and a property set defining some model, and using those references can create instances of a single IModel implementation. The create() method accepts raw data from the persistence layer, creates a new IModel instance, and maps the supplied data to the newly created model.

IObjectFactory implementations should not directly access any persistence layer. Instead, extend this interface and define an abstraction for accessing a specific type of persistence layer. For example, in Magic Graph, there is a SQLRepository for working with a MySQL database.

Note: If you simply want an object factory for creating models, MappingObjectFactory can be directly instantiated.

Saveable Mapping Object Factory

SaveableMappingObjectFactory is an abstract class extending IObjectFactory, implements ISaveableObjectFactory, and adds the ability to save an IModel instance. All repositories in Magic Graph extend this class. The saveable mapping object factory adds the save() and saveAll() methods, which outlines the repository save event as follows:

  1. Test the supplied model matches the implementation of IModel managed by the repository. This prevents a model of an incorrect type from being saved.
  2. Call the protected beforeValidate() method. This can be used to prepare a model for validation in extending repositories.
  3. Validate the model by calling IModel::validate()
  4. Call the protected beforeSave() method. This can be used to prepare a model for save.
  5. Call the protected saveModel() method. This is required to be implemented in all extending repositories
  6. Call the protected afterSave() method. This can be used to clean up anything after a save. This method should not have side effects.

Calling saveAll() is a bit different than the save method. After testing the model types, the save process is split into three parts:

  1. For each supplied model, call beforeValidate(), validate() and beforeSave().
  2. For each supplied model, call saveModel()
  3. For each supplied model, call afterSave()

SQL Repository

The SQLRepository is the IRepository, used for working with MariaDB/MySQL databases. The SQLRepository also extends the ISQLRepository interface, which adds additional methods for working with SQL databases.

In the following example, we will be using the same table and database connection outlined in Basic Database and Repository Setup.

Instantiating a SQLRepository:

The above-setup is similar to the InlineSQLRepo, but it allows us much more fine-grained control over which components are used. Here, we are able to define which data mapper is used and how the property set is created. For more information, please see Extensible Models.

Decorating Repositories

Magic Graph provides several proxy classes, which can be used as base classes for repository decorators.

  1. ObjectFactoryProxy
  2. RepositoryProxy
  3. SaveableMappingObjectFactoryProxy
  4. ServiceableRepository
  5. SQLRepositoryProxy
  6. SQLServiceableRepository

Each of the above-listed proxy classes accept the an associated repository instance as a constructor argument, and will map the method calls to the supplied repository instance. The proxy classes should be extended to provide additional functionality to a repository.
ServiceableRepository and SQLServiceableRepository are implementations of proxy classes, and provide ways to further extend functionality of repositories. These are discussed in the next section.

I plan on adding more decorators in a future Magic Graph release, and currently there is a single decorator included in the current version:

CommonObjectRepo extends the RepositoryProxy class, and is used to prevent multiple database lookups. Each time a model is retrieved from the repository, it is cached in memory, and any subsequent retrieval calls will return the cached version of the model.

Here's an example of how to use a decorator:

Decorating repositories is easy and fun!

Serviceable Repository

The serviceable repositories are used for repository relationships, which are discussed in the Relationships section.

Essentially, serviceable repositories accept ITransactionFactory and zero or more IModelPropertyProvider instances, which are used to extend the functionality of certain properties.

For example, say you have a model A with an IModel property B. By default the repository for model A does not know repository B or model B exists. A IModelPropertyProvider instance could add a lazy loading scheme for retrieving model B when the property is accessed from model A. Additionally, the model property provider could provide a way to save edits to model B when model A is saved.

Composite Primary Keys

Magic Graph fully supports composite primary keys, and certain methods of IRepository and ISQLRepository will contain variadic id arguments for passing multiple primary key values. Composite primary keys are assigned via the IPropertyFlags::PRIMARY attribute as follows:

Note: when supplying primary key values to repository methods, they are accepted in the order in which they were defined. I will create a way to not have to depend on the order of arguments in a future release.


Transactions

Transactions Overview

Transactions represent some unit of work, and are typically used to execute save operations against some persistence layer. Similar to a database transaction, Magic Graph transactions will:

  1. Start a transaction in the persistence layer when available
  2. Execute arbitrary code against the persistence layer
  3. Commit the changes
  4. Roll back the changes on failure

Transactions are based on a single interface ITransaction, and can have multiple implementations used to support various persistence layers. Magic Graph fully supports using multiple, and different, persistence layers concurrently. Transactions can be considered an adapter, which executes persistence-specific commands used to implement the required commit and rollback functionality.

Currently, Magic Graph ships with a single transaction type: MySQLTransaction

Creating a Transaction

At the heart of any transaction is the code to be executed. In Magic Graph, the interface IRunnable is used to define the code to be executed within a transaction. This type exists, because each persistence type will require a subclass of IRunnable to be created. These types are used to group transactions by persistence type, and to expose persistence-specific methods that may be required when working with the transactions. For example, ISQLRunnable is used for persistence layers that utilize SQL. ISQLRunnable adds a single method getConnection(), which can be used to access the underlying database connection.

In it's simplest form, a transaction is a function passed to a Transaction object. The supplied function is executed when Transaction::run() is called. It is worth noting that Transaction can accept multiple functions. Transaction::run() will call each of the supplied functions in the order in which they were received.

Executing the transaction

The default Transaction object shipped with Magic Graph does not connect to any specific persistence layer, and the implementations of beginTransaction, commit and rollBack do nothing.

Since we want methods to actually do things, this is an example of how to run a transaction against MySQL/MariaDB. A MySQL transaction is passed an instance of ISQLRunnable. Currently, there is a single implementation of ISQLRunnable, and that is MySQLRunnable.
MySQL runnable differs from the Transaction object used in the previous example by adding a constructor argument that accepts an instance of ISQLRepository. The repository is used to obtain an instance of buffalokiwi\magicgraph\pdo\IDBConnection, which is used to execute the transaction.

The following is a full example of how to execute a transaction against a SQLRepository instance:

While transactions against a single persistence engine could be more simply coded directly against the database, the Magic Graph Transaction abstraction provides a way for us to run transactions against multiple database connections, or even different persistence engines.

Transaction Factory

The transaction factory generates instances of some subclass of ITransaction. The idea is to pass ITransactionFactory::createTransactions() a list of IRunnable instances, and the transaction factory will then group them by persistence type (registered subclass).
Transactions are executed as follows:

  1. Begin transaction is executed for each ITransaction instance
  2. Run is called for each ITransaction instance
  3. Commit is called for each ITransaction Instance
  4. If an exception is thrown at any time, rollback is called for each ITransaction instance and the exception is rethrown.

If $repo->save() were to throw an exception in the previous example, then the transaction would have been rolled back. If the following code is executed, then you will see how the row is never added to the database due to rollback being called when the exception is thrown.


Model Relationship Providers

Similar to a foreign key in a relational database, relationships allow us to create associations between domain objects. In Magic Graph, a model (IModel) may contain zero or more properties that reference a single or list of associated IModel objects. The parent model may contain IModelProperty and/or ArrayProperty properties, which can hold referenced model objects.

For example, the following configuration array contains a model property and an array property.

Both Model and Array properties must include the "clazz" configuration property, which must equal the class name of the object or objects in the array. This is used to determine which object to instantiate within the relationship provider, and to ensure that only objects of the specified type are accepted when setting the property value.

Notice that both properties are marked with "noinsert" and "noupdate". This is required for both model and array properties, and will prevent the properties from being used in insert and update database queries. If these values are omitted, IModel properties will persist as IModel::__toString() and ArrayProperty will be encoded as json.

Assigning the values to the parent model goes something like this:

Once we have some model or array of models property, we may want to automate the loading and saving of those models. For example, when accessing a model property, we can load the model from the database and return it. We could also save any edits to the referenced model when the parent model is saved. This behavior is accomplished through implementing the IModelPropertyProvider interface.

The IModelPropertyProvider defines several methods for initialization of a model, retrieving the value, setting the value and persisting the value.
Model property providers must be used with supporting models and repositories.

Serviceable Model

A Serviceable Model extends the DefaultModel, and modifies the DefaultModel constructor to accept zero or more IModelPropertyProvider instances. The passed providers are associated with properties defined within the parent model configuration, and will handle loading and saving of the associated model(s).

Serviceable Repository

Serviceable Repository and SQL Serviceable Repository (for SQL repositories) are repository decorators, which add support for IModelPropertyProviders. When models are created in the repository object factory, the model property provider instances are passed to the ServiceableModel constructor. Additionally, when the repository save method is called, the model property providers save functions will be included as part of the save transaction.

The next section will describe the model property providers included with Magic Graph.


Relationships

See Model service providers for information about model properties and IModelPropertyProvider.

The following tables are used in the One to One and One to Many example sections:

One to One

The OneOnePropertyService provides the ability to load, attach, edit and save a single associated model.

One to Many

The OneManyPropertyService provides the ability to load, attach, edit and save multiple associated models.

Many to Many

Sometimes we have lots of things that can map to lots of other things. For example, in can ecommerce setting, products may map to multiple categories, and categories may contain multiple products. In this instance, we would require a junction table to store those mappings. Thankfully, this is fairly easy in Magic Graph.

First, we start with a standard junction table. If we use the following table definition, we can use the built in models for a junction table.

  1. "id" contains the primary key
  2. "link_parent" is the id of the parent model. ie: a product id
  3. "link_target" is the id of the target model. ie: a category id

Now we create two other tables. One for parent and the other one for the target. For fun, we'll add a name column to both.

The category table

Now we add product1 to category1, and product2 to category2

Nested Relationship Providers

Nesting is accomplished by using the same methods outlined in the Relationships chapter.

As I'm sure you've noticed in the above many to many example, relationship providers can be used to create a series of nested objects. Relationship providers can be plugged into any property in any model, which means we can use them to create a snazzy tree of objects. Relationship providers can be used to back any model property

First, we start by creating 3 simple tables. For this example, the tables will only contain an id column.

Create some tables and insert a few values. To keep this simple, we will use an id of "1" for everything.

Next, we create a serviceable model and corresponding property set for each of the tables. We are going to assume that we have a variable $dbFactory, which is an instance of IConnectionFactory. There is also a variable $tfact,which is an instance of ITransactionFactory. These are detailed in examples from previous chapters.

After creating the models, we will need to create a repository for each type of model. For this, we will use the DefaultSQLServiceableRepository, which along with ServiceableModel, allows us to use relationship providers. Repositories controlling models located at the edges of the object graph will need to be created first. ie: TableC, then TableB, then TableA.

Finally, we get the model from tablea, and we print the graph:

Any relationship provider will work in exactly the same way as the one to one provider.

How Editing and Saving Works

As detailed in the Saveable Mapping Object Factory section, models can be saved somewhere by calling the ISaveableObjectFactory save method. This section will deal with how editing and saving works when using relationship providers.

Editing and saving is controlled both by the relationship provider and the edited property tracking system built into ServiceableModel.
When a serviceable model is saved by some serviceable repository, calling the save method causes the serviceable repository to fetch save functions from the relationship providers.

Relationship providers must implement the IModelPropertyProvider interface. The method IModelPropertyProvider::getSaveFunction will return a function which is passed to a ITransactionFactory where the save function is called, and the related model is persisted.

The One to One relationship provider is relatively straightforward. Any model properties backed by a OneOnePropertyService will be automatically saved when the parent model is saved via some repository. This will work with new models and also existing models loaded by the provider.

The Many to Many relationship providers will manage array properties containing IModel instances. This is slightly more complicated than the one to one provider. In addition to saving edited models, the one to many and many to many providers will also manage inserts and deletes. If new models are added to the array property, they will be inserted into the database. If an existing model is removed from the array property, it will be deleted from the database. If any filters or limits are used when loading related models, the delete functionality is disabled, and models must be manually unlinked via the repository controlling that model type.

Saves will automatically cascade when using nested relationship providers. Any nested model at any position in the object graph can be edited, and when the top-most model is saved, it will


Extensible Models

We're finally through the foundational concepts, woohoo!

Magic Graph models are designed to be as flexible as possible. As I'm sure you've noticed, there are several ways to configure models, and each of those ways have different levels of extensibility. For example, models can be created by using simple property annotations, or they can be created at runtime by using property configuration objects. While using the annotated properties is quick and easy, it is nowhere near as scalable as using property configuration objects.

Through the use of property configuration objects we can:

  1. Define the properties that will existing within a given model
  2. Provide run time type information for properties. For example, config objects can implement methods to return property names, which can be used to query property meta data in a model's property set.
  3. Add additional meta data to properties
  4. Provide the ability to swap out the list of properties used for persistence. For example, if we want to share a model between multiple persistence types that have different property names, we can swap out the configuration object.
  5. Attach simple behaviors to individual properties. ie: get, set, change, etc.
  6. Extend save functionality through functions like: before save, after save, on save, save function, etc.
  7. Property configuration can be dynamically generated at runtime, which allows us to implement patterns such as EAV.
  8. Multiple property configuration objects can be used to create a single model. This allows developers to create model extensions or to separate concerns into different packages.

Property Configuration Interface

For configuration array definitions, please see Property Configuration.

Property configuration objects must all implement the IPropertyConfig interface. This interface is used by implementations of IPropertySetFactory to create IPropertySet instances, which contain all of the relevant properties, meta data, and behaviors used by IModel instances.

The IPropertyConfig interface currently contains four methods:

getConfig() is called by IPropertySetFactory, and returns the property configuration array. This array contains all of the property definitions, meta data, and optional event handlers.

getPropertyNames() will return a list of strings containing each property name defined by this configuration object.

beforeSave() is called by an IRepository prior to a model being persisted. This is an opportunity to modify the model's state or add additional validation prior to save. When creating the beforeSave() handler, the IPropertyConfig implementation SHOULD iterate over the properties defined in the configuration array, and call each property-level beforeSave handler.

afterSave() is called by an IRepository after a model has been persisted, but before commit(). This can be used to clean up after a save, check the results of a save, etc. When creating the afterSave() handler, the IPropertyConfig implementation SHOULD iterate over the properties defined in the configuration array, and call each property-level afterSave handler.

Property Configuration Implementation

Now that we know how a property configuration object, and the configuration array is defined, let's build out a complete implementation. Magic Graph ships with a abstract base class BasePropertyConfig, which contains constants for commonly used property configurations and adds the ability to incorporate behavioral strategies by passing INamedPropertyBehavior instances to the constructor.

Let's make a basic rectangle model. In this example, we will create two classes: Rectangle and RectangleProperties.
Rectangle is the value object, and RectangleProperties defines the properties contained within the Rectangle value object.

The above example is fairly straightforward. A configuration object defines two required properties, height and width. When the model is instantiated, height and width are both zero. This is because the default value each property is zero, and default values will bypass property validation. When IModel::validate() is called, both properties are validated and will throw a ValidationException.

That's great and all, but properties should validate when they are set, right? The required property flag will only validate when IModel::validate() is called, so if we want to validate when the property is set, we must add a validation callback.

We can rewrite the createConfig function like this:

After the model as been created using the above change, setting height or width equal to zero will throw an exception. This does not affect the default property value of zero. Default values will bypass validation when a property instance is created.

What if we wanted to make a rectangle behave as a square? Since square is a specialization of a rectangle, we can create a behavioral strategy which will be used to enforce the height must equal width rule. When height is set, width will automatically be set to the value of height and vise versa.

First we will want to create an interface for RectangleProperties. This will define two methods getHeight() and getWidth(), which will return the model property names for height and width. This interface is how we will ensure that only rectangles are used with the behavioral strategy, and it's also a great way to decouple property names from the database column names.

And our modified RectangleProperties class now looks like this:

Now that the property configuration is properly configured, we can create a behavioral strategy that will make height always equal to width in any model that uses IRectangleProperties. To accomplish this, we will create a class called BehaveAsSquare, which descends from GenericNamedPropertyBehavior. GenericNamedPropertyBehavior is normally used to attach behavior to a single property.

For our square behavior, we want to use the model setter callback (called any time a property value is set by IModel::setValue()). This means we need to pass static::class to the GenericNamedPropertyBehavior constructor. When the property name is equal to the class name, the behavior will be applied to every property in a model. This will allow us to write a single handler for multiple properties.

To make our rectangle behave as a square, we can initialize it like this:

Since we all know a rectangle and a square aren't the same thing, we can use the same property configuration, and our new behavioral strategy to create two new models: Rectangle and Square.

And finally, we can create an instance of a square:

Using multiple property configurations

It's possible to use multiple IPropertyConfig objects to create a single model. This is one of the more useful features of property configuration objects. It's possible for one package to define a model, and have other packages extend that model by adding properties and behavior. Each property configuration can also incorporate any inline event handlers and zero or more behavioral strategies. Think of this as a sort of plugin system. Here's an example:

First we create two property configurations:

Now we can pass an instance of each configuration object to a model (or property set) constructor.

Properties from both configurations will appear in the model.

We can also add properties at runtime. Here's a third configuration we'll add to the model.

Adding a configuration object at runtime is done through the property set:


Model Interface

All models in Magic Graph must implement the IModel interface. The interface itself is fairly simple and straightforward.

IModel focuses on a few key areas, Properties, Validation, State, Serialization and Cloning:

  1. Properties
    1. instanceOf() - Tests that a IPropertyConfig instance is a or implements the supplied class or interface name. This is used to test if a model is "of some type".
    2. getPropertySet() - Retrieve the internal IPropertySet instance containing the properties used in the model
    3. getPropertyNameSet() - Retrieve an instance of IBigSet containing a list of property names in the property set. This is used for methods that utilize model property names. ie: toArray() can return a limited list of properties by supplying an instance of IBigSet containing active bits for each of the desired properties.
    4. getPropertyNameSetByFlags() - The same as getPropertyNameSet() and includes the ability to filter by enabled property flags.
    5. getPropertyConfig() - Retrieve an array containing the property configuration used to create the model properties
    6. getIterator() - Retrieve an iterator used to iterate over any non-array and non-model properties and values contained in the model.
  2. Validation
    1. validate() - Individually validates each property, and the first property to test as invalid will throw a ValidationException
    2. validateAll() - Validates each property in the model and stores the results in a list. Properties with failed validation are returned as a map.
  3. State
    1. getModifiedProperties() - Retrieve an instance of IBigSet with the bits for any edited properties enabled
    2. getInsertProperties() - Retrieve an instance of IBigSet with the bits for any properties required for a database "insert".
    3. hasEdits() - Tests if any properties have been edited since initialization
  4. Serialization
    1. toArray() - Used for persistence, debugging and other fun things. Converts the IModel instance into a multi-dimensional array.
    2. toObject() - Used for JSON Serialization, converts IModel to an object graph.
    3. fromArray() - Used to initialize the model with data from the persistence layer. Populates any matching IModel properties with the supplied values.
    4. jsonSerialize() - Usually calls toObject().
  5. Cloning
    1. __clone() - IModel instances are cloneable.
    2. createCopy() - Preferred over __clone, this can be used to clone or copy (without primary keys) models and also cause them to be read only.

Model Implementation

Models are composed of a few components, a property set containing all various object property instances and the model implementation. Currently, every property set extends DefaultPropertySet and every model extends DefaultModel.

It is worth noting that DefaultModel contains quite a bit of functionality. Instead of directly implementing IModel, it is recommended to extend all models from DefaultModel.

At the time of writing, Magic Graph ships with 7 IModel implementations and two decorators:

  1. DefaultModel - The base model. Every model should extend from this class.
    1. ServiceableModel - Extends DefaultModel and adds the necessary functionality required to support relationship providers.
    2. AnnotatedModel - Can use attributes in php8 to configure and initialize model properties.
    3. GenericModel - Quick way to create a model using IPropertyConfig.
    4. QuickModel - Quick way to create a model with nothing other than the property configuration array.
    5. QuickServiceableModel
    6. ProxyModel - Used to decorate IModel instances
      1. ReadOnlyModelWrapper - A decorator for IModel that disables setting property values
      2. ServiceableModelWrapper - A decorator for IModel that can add relationship providers to model instances.

The quick and generic model variants are easier to instantiate, but using these models prevents you from selecting the property set, config mapper and property factory. Internally, quick and generic models all use instances of DefaultPropertySet, DefaultConfigMapper and PropertyFactory.


Behavioral Strategies

This has already been detailed in the Property Behavior section, but since this might be the most important topic in all of Magic Graph, we're going to go over it again.

The goals of behavioral strategies are the following:

  1. Reduce the complexity of models
  2. Increase the ease of writing tests
  3. Introduce or replace functionality without extending or modifying the model

We've all seen models that try to do it all. The messy code, the stinky code. Things like support for third party packages hacked into models, ignoring separation of concerns, or referencing objects the model should know nothing about. There are many solutions to these problems, but most of the time I see developers write a service used to join several packages together. This is great and all, but it still tightly couples packages and adds complexity. If repositories are in use, and there's a separate service on top of said repository, which one should the developer use to save the model? What happens if some code is written that doesn't know about the service? Shenanigans ensue.

Behavioral Strategies are an attempt to simplify inter-package relationships. Think of a strategy like an adapter. We can write a program with tests, then attach the program to a model. The model and/or repository will then dispatch events, which the strategy program will use to either change the model's state and/or introduce side effects.

In this context, side effects may not be a bad thing. For example, say we have an ecommerce platform, and we want to generate a shipping label when an order has been packaged and is ready to ship. We could write a strategy that monitors an order's status, knows how to interact with some shipping api, and generates a shipping label when the order's state moves to "ready to ship". This strategy is simply attached to the repository and model during object creation in composition root. We now have an independently-testable program, which adds support for shipping api's to the order model without needing to modify the order model, repository or create a service layer.

Behavioral strategy programs are basically event handlers for various events fired by IProperty, IModel and IRepository. Currently, there are a few behavior interfaces.

IPropertyBehavior is primarily used by the property configuration array, and contains several callbacks related to a single property. All callbacks will include an argument IProperty, which is the property that triggered the callback.

Validation Callback

The validation callback is called any time IProperty::validate() is invoked.

Setter Callback

The setter callback is called before IProperty::validate(). The purpose of this callback is to modify the value prior to it being written to the backing property object. Think of this as serializing a property value.

Getter Callback

The getter callback is called prior to returning a value from IProperty::getValue(). This is to modify the value stored in the backing property object prior to using it. Think of this as deserializing a property value. Notice the $context argument on the getter callback. IModel::setValue() contains a context argument, and this can be used to set some arbitrary context/meta data/state/etc used in the getter callbacks.

Init Callback

The init callback is used to modify the default value prior to it being written to the backing object. This is called when IProperty::reset() is called. This is never run through IProperty::validate(), so be careful with default values.

Empty Callback

The empty callback is useful in situations where empty() does not return true, but whatever the value is should still be considered empty. For example, if the property is a object representing a primitive, then empty() would return false even if the objects internal value was actually empty.

Change Callback

When a property value changes, this callback is fired. It is worth noting, that this happens at the property level, not inside of any models. Therefore, this event will have no access to other properties in the model. Due to this restriction, there may be limited uses for this callback. If you need access to other properties in a model, use the model level getter/setter callbacks.

HTML Property Package Callbacks

HTML Input Callback

As part of a fun little bonus to Magic Graph, all IProperty instances can be converted into their HTML counterparts. When using IElementFactory to generate HTML inputs, this callback will be used to override the default html generated by the element factory. We will go over this further in the [Creating HTML elements](#creating-html-elements] chhapter.

Model-level callbacks

The following callbacks are invoked by IModel implementations.

To Array Callback

IModel::toArray() is used for persistence and serialization. The toArray callback is invoted when converting property values to their persisted state.

Model Setter Callback

The model setter callback is the same as the property setter callback, except it adds access to the model and it is invoked by DefaultModel instead of AbstractProperty.

Model Getter Callback

The model getter callback is the same as the property getter callback, except it adds access to the model and it is invoked by DefaultModel instead of AbstractProperty.

Named Property Behavior

The INamedPropertyBehavior interface extends IPropertyBehavior, adds additional model-level callbacks.

The following callbacks are invoked by ISaveableMappingObjectFactory implementations.

Model Validation Callback

This is called when IModel::validate() is invoked, and is an opportunity to validate the state of a model. Any validation errors must throw a ValidationException

Before Save Callback

This is what it sounds like. When a model is saved by some ISaveableMappingObjectFactory implementation, before save is called prior to the model being persisted.
Note: In the default repository implementations, save is part of a transaction and any exceptions thrown will trigger a rollback.

After Save Callback

This is the same thing as before save, but it happens after the model is saved.

There are a few ways of implementing INamedPropertyBehavior:

  1. Extend buffalokiwi\magicgraph\property\GenericNamedPropertyBehavior
  2. Create an anonymous strategy with the NamedPropertyBehaviorBuilder
  3. Write your own implementation

Extending GenericNamedPropertyBehavior is the preferred method of creating behavioral strategies. By default, every callback will return null. Override any of the methods in some subclass and return the callback closures. In the following example, we will create a model called Test with a property name. We'll create a strategy that will set name equal to "bar" if name is set to "foo", and if name is set to "baz" and exception is thrown.

When using any of the behavior callbacks, you can replace IModel and mixed types with any derived type. It is also worth noting that if you wanted a behavior to work with all properties, you can pass static::class as the property name to the PropertyBehavior constructor from GenericNamedPropertyBehavior. This will only work for model level callbacks, and when the strategy class name matches the supplied property name, the strategy is applied to every property in the model.


Database Connections

Magic Graph provides a simple abstraction over the PHP PDO library. First, lets go over the four interfaces, then we'll go over the MySQL implementation.

IConnectionProperties

The connection properties interface is used to define the criteria used to connect to some database engine. You'll find super fancy methods like getHost() and getDSN(). Truly mind-blowing stuff here. It has everything you'd expect in a property bag for a database connection.

IConnectionFactory

The connection factory is exactly what seems. This interface may go through a revision in the near future. The concept is to have a factory that creates database connections. In it's current form, it is probably best that one factory provides connections for one persistence type. In the future, this interface will be revised to more easily support multiple persistence types in a single factory. Note: This does support multiple types in a single factory, but it's not easy to work with. For now, keep it one to one and it works nice.

IDBConnection

This is an interface for the PDO object that ships with PHP, but with one additional method:

executeQuery() is a simple way to execute a simple statement without parameters.

IPDOConnection

IPDOConnection extends IDBConnection and it adds several methods to make it easier to work with common query types. Let's take a look.

Delete

The delete method is used to delete rows. This method will only delete by primary key, and composite primary keys are supported.

Update

Update updates matching rows in some table. This is also matched by primary key, and composite keys are supported.

Functions can be added to columns by appending ':func' to any column name. Multiple functions can be chained like this: ':func1:func2'

Insert

Insert is similar to update except that it inserts new records! Wooooooo!

Cursors

Have you ever wanted to iterate over each row in some table? You're in luck!

Note: The $scroll argument is deprecated and will be removed in a future release. Scroll was supposed to allow bidirectional cursor movement, but not all drivers support scrollable cursors (mysql does not) and therefore $scroll should not be included in a generic interface.

Select

Surprise! We can select things too! Pass your statement and bindings to the select method, and BAM! you get results.

Select Multiple Result Sets

Queries that return multiple result sets are also fully supported. This can be a stored procedure that returns multiple result sets or simply adding semicolons between the statements. Be careful with this one. Semicolons can do nasty things.

Execute

Executes some arbitrary statement without a result set.

MySQL PDO

Magic Graph currently ships with a single database adapter for MySQL, MariaDBConnection, which extends the abstract base class PDOConnection, implements IPDOConnection, and adds the necessary driver-specific sql statements. This is the PDO implementation to use for all things MySQL/MariaDB.

Connection Factories

Connection factories generate database connections for use with some driver. I'm sure you've seen the examples throughout this readme, but in case you haven't, here it is:

The idea is to create a factory using some connection properties, and have that generic factory return a PDO implementation of the correct type. Nothing ground breaking here.


Working with Currency

Currency is something that doesn't always work properly. There are many ways to solve the currency problem (which we will not discuss here) Fortunately we have this awesome library MoneyPHP,which is based on Martin Fowler's money pattern, uses strings internally to represent currency, and the best part is that money objects are immutable.

One downside to MoneyPHP is that it does not have any type of interface. It is simply the Money object. In Magic Graph, there is an interface for Money IMoney, which is implemented by MoneyProxy, which accepts an instance of Money and proxies all calls to the underlying Money object. This is because at some point we may want to swap out MoneyPHP for some other library, and we can't do that without a proper abstraction.

Since MoneyPHP handles different currencies and formats, we need a money factory to have an easy way of generating money instances of the same currency. In Magic Graph, we have a factory MoneyFactory, which implements IMoneyFactory.

Here's an example of how to set up a money factory for US Dollars

Now we can create money and format it for the configured currency:

Using currency properties in Magic Graph is easy. Simply use the 'money' property type in your property configuration arrays.

Note: Due to the use of MoneyFactory, a service locator will need to be passed to the config mapper. This will allow the config mapper to find the money factory (and other things) when creating property objects. See Magic Graph Setup for more information. Here's a quick example for reference:


Creating HTML Elements

This package should not have been part of Magic Graph, and instead should have been released as a separate extension. However, the package is here and it is fully integrated with properties, and therefore it's not going anywhere.

By using IElementFactory implementations, it is possible to convert IProperty instances to IElement and eventually to a string containing the HTML.

The default implementation of IElementFactory is ElementFactory, which accepts a list of IElementFactoryComponent. IELementFactoryComponent instances are used to map a subclass of IProperty to a function resposible for converting the IProperty instance to an instance of IElement.

There is a default mapping class called DefaultComponentMap, which can be used to quickly get started with ElementFactory.

Here's an example:

The above-example will generate five html inputs:

If you want to add element factory components for any custom properties, or if you want to override the default components, you can pass this to the constructor of DefaultComponentMap. Any matching properties are internally overridden.

For example, while this is the default handler for IStringProperty, it could be overridden if passed to the constructor.

The definition for the callbacks is as follows:


Magic Graph Setup

Magic Graph was designed to support the composition root pattern. The idea is to have every call to "new object" in a single file called composition root. While Magic Graph object instantiation may look complicated, you only have to write that code once, and all of it ends up in one place. Instances of various Magic Graph components are then injected into other classes as a dependency.

Here's what the composition root section for magic graph may look like.

  1. Create the service locator container. This is used to provide various factories (like DateFactory and MoneyFactory) to the config mapper.
  2. Create a database connection factory. This will be used by repositories.
  3. Add DateFactory to the container. This is used within IDateProperty.
  4. Add MoneyFactory to the container. This is used within IMoneyProperty.
  5. Create the config mapper. This is an instance of IConfigMapper, and is responsible for creating instances of IProperty based on types listed in the property configuration arrays.
  6. Create the PropertyFactory instance. This creates IPropertySet instances with the appropriate IConfigMapper. Property sets contain the properties used by IModel instances.
  7. Add ITransactionFactory to the container. This will be used by various relationship providers and things that need to unify saves across multiple repositories.
  8. Optionally create local variables for IDBConnection and IDBFactory. This can make writing composition root a little easier and reduce calls to the container.

Once Magic Graph has been initialized, you can start adding repositories to the container.

The following example is based on this table:


Entity Attribute Value

This is the buffalokiwi\magicgraph\eav package.

Searching

This is the buffalokiwi\magicgraph\search and buffalokiwi\magicgraph\eav\search packages.

Searching works with relationships, etc. It's baked into IRepository implementations and the query builders can be swapped out when the repo is constructed.


All versions of magicgraph with dependencies

PHP Build Version
Package Version
Requires php Version ~8.0
moneyphp/money Version ~3.2
buffalokiwi/buffalotools_types Version ~1.0
buffalokiwi/buffalotools_ioc Version ~1.0
buffalokiwi/buffalotools_date Version ~1.0
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package buffalokiwi/magicgraph contains the following files

Loading the files please wait ....