Serialize the way how you create an object, not the object itself!
Haringo is a libary that aims to solve main problems with classical serialization:
- lost references which cause presence of __wakeup() methods which use global variables
- data corruption after moving or renaming classes of serialized objects
Haringo allows to describe how an object should be built and then serialize that description instead of the created object. Thanks to this fact, any object built based on the serialized build plan doesn't need to know anything about things like global registry of resources or the current user session.
With Haringo you can make your serialized data totally free of things like:
- class paths
- method names
- parameter names
It's possible thanks to configurable map based sources and selectors.
- Building Haringo
- Using Haringo
- Extending Haringo
Use composer to get the latest version:
$ composer require lukaszmakuch/haringo
To build Haringo, you can use the default implementation of the \lukaszmakuch\Haringo\Builder\HaringoBuilder interface.
<?php
use lukaszmakuch\Haringo\Builder\Impl\HaringoBuilderImpl;
$haringoBuilder = new HaringoBuilderImpl();
If you're not going to somehow extend your Haringo instance, you can directly call the HaringoBuilder::build():Haringo method.
<?php
use lukaszmakuch\Haringo\Builder\HaringoBuilder;
/* @var $haringoBuilder HaringoBuilder */
$haringo = $haringoBuilder->build();
In order to use a ClassPathFromMap, MethodFromMap or a ParamFromMap, you need to build Haringo with instances of maps you are able to configure.
All you need to create a map of class path sources, is to create a new ClassPathSourceMap instance.
<?php
use lukaszmakuch\Haringo\ClassSourceResolver\Impl\ClassPathFromMapResolver\ClassPathSourceMap;
use lukaszmakuch\Haringo\ClassSource\Impl\ExactClassPath;
$map = new ClassPathSourceMap();
The new map must be passed to the Haringo builder before using it to build a new Haringo. However, it may be configured later, as it's passed as a reference.
<?php
use lukaszmakuch\Haringo\Builder\HaringoBuilder;
use lukaszmakuch\Haringo\ClassSourceResolver\Impl\ClassPathFromMapResolver\ClassPathSourceMap;
/* @var $haringoBuilder HaringoBuilder */
/* @var $map ClassPathSourceMap */
$haringoBuilder->setClassSourceMap($map);
All you need to create a map of class path sources, is to create a new MethodSelectorMap instance.
<?php
use lukaszmakuch\Haringo\MethodSelectorMatcher\Impl\MethodFromMap\MethodSelectorMap;
$map = new MethodSelectorMap();
The new map must be passed to the Haringo builder before using it to build a new Haringo. However, it may be configured later, as it's passed as a reference.
<?php
use lukaszmakuch\Haringo\Builder\HaringoBuilder;
use lukaszmakuch\Haringo\MethodSelectorMatcher\Impl\MethodFromMap\MethodSelectorMap;
/* @var $haringoBuilder HaringoBuilder */
/* @var $map MethodSelectorMap */
$haringoBuilder->setMethodSelectorMap($map);
All you need to create a map of class path sources, is to create a new ParamSelectorMap instance.
<?php
use lukaszmakuch\Haringo\ParamSelectorMatcher\Impl\ParamFromMapMatcher\ParamSelectorMap;
$map = new ParamSelectorMap();
The new map must be passed to the Haringo builder before using it to build a new Haringo. However, it may be configured later, as it's passed as a reference.
<?php
use lukaszmakuch\Haringo\Builder\HaringoBuilder;
use lukaszmakuch\Haringo\ParamSelectorMatcher\Impl\ParamFromMapMatcher\ParamSelectorMap;
/* @var $haringoBuilder HaringoBuilder */
/* @var $map ParamSelectorMap */
$haringoBuilder->setParamSelectorMap($map);
It's possible to add support of different ValueSource implementations. Every extension must implement the \lukaszmakuch\Haringo\Builder\Extension\ValueSourceExtension interface and must be passed to the Haringo builder before using it to build a new Haringo. See the description of creating your own Haringo extension.
<?php
use lukaszmakuch\Haringo\Builder\HaringoBuilder;
use lukaszmakuch\Haringo\Builder\Extension\ValueSourceExtension;
/* @var $haringoBuilder HaringoBuilder */
/* @var $extension ValueSourceExtension */
$haringoBuilder->addValueSourceExtension($extension);
Every build plan describes how to get an instance of some class. For documenting (and testing) purposes let's use that simple class:
<?php
namespace lukaszmakuch\Haringo;
class TestClass
{
public $memberA;
public $memberB;
public $passedToConstructor;
public function __construct($passedToConstructor = null)
{
$this->passedToConstructor = $passedToConstructor;
}
public function setMembers($newA = null, $newB = null)
{
$this->memberA = $newA;
$this->memberB = $newB;
}
}
Describes how a new instance of some class should be built. It takes a source of class to instantiate and optional method calls which will be called on the new instance.
Code equal to the result of the following build plan:
<?php
use lukaszmakuch\Haringo\TestClass;
$obj = new TestClass(constructorParam);
$obj->setMembers("firstParamVal", "secondParamVal");
<?php
use lukaszmakuch\Haringo\BuildPlan\Impl\NewInstanceBuildPlan;
use lukaszmakuch\Haringo\ClassSource\Impl\ExactClassPath;
use lukaszmakuch\Haringo\MethodCall\MethodCall;
use lukaszmakuch\Haringo\MethodSelector\Impl\MethodByExactName;
use lukaszmakuch\Haringo\ParamSelector\Impl\ParamByExactName;
use lukaszmakuch\Haringo\ParamValue\AssignedParamValue;
use lukaszmakuch\Haringo\ValueSource\Impl\ScalarValue;
use lukaszmakuch\Haringo\TestClass;
$plan = (new NewInstanceBuildPlan())
->setClassSource(new ExactClassPath(TestClass::class))
->addMethodCall(
(new MethodCall(new MethodByExactName("setMembers")))
->assignParamValue(new AssignedParamValue(
new ParamByExactName("newB"),
new ScalarValue("secondParamVal")
))
->assignParamValue(new AssignedParamValue(
new ParamByExactName("newA"),
new ScalarValue("firstParamVal")
))
)
->addMethodCall(
(new MethodCall(new MethodByExactName("__construct")))
->assignParamValue(new AssignedParamValue(
new ParamByExactName("passedToConstructor"),
new ScalarValue("constructorParam")
))
);
Describes how to get a product of a static factory method. It requires two things:
- class source of the factory
- method call of the static factory method which returns the product
Let's create a class which provides a static factory method:
<?php
use lukaszmakuch\Haringo\TestClass;
class TestStaticFactory
{
public static function getProduct($configValue)
{
return new TestClass($configValue);
}
}
Code equal to the result of the following build plan:
<?php
$obj = TestStaticFactory::getProduct("paramValue");
<?php
use lukaszmakuch\Haringo\BuildPlan\Impl\StaticFactoryProductBuildPlan;
use lukaszmakuch\Haringo\ClassSource\Impl\ExactClassPath;
use lukaszmakuch\Haringo\MethodCall\MethodCall;
use lukaszmakuch\Haringo\MethodSelector\Impl\MethodByExactName;
use lukaszmakuch\Haringo\ParamSelector\Impl\ParamByExactName;
use lukaszmakuch\Haringo\ParamValue\AssignedParamValue;
use lukaszmakuch\Haringo\TestClass;
use lukaszmakuch\Haringo\ValueSource\Impl\ScalarValue;
$plan = (new StaticFactoryProductBuildPlan())
->setFactoryClass(new ExactClassPath(TestStaticFactory::class))
->setFactoryMethodCall(
(new MethodCall(new MethodByExactName("getProduct")))
->assignParamValue(new AssignedParamValue(
new ParamByExactName("configValue"),
new ScalarValue("paramValue")
))
);
Describes how to use a factory object to get some product. It requires two things:
- value source which is resolved to a factory object
- method call which returns the product
Let's create a class which provides a factory method:
<?php
use lukaszmakuch\Haringo\TestClass;
class TestFactoryClass
{
public function getProduct($configValue)
{
return new TestClass($configValue);
}
}
Code equal to the result of the following build plan:
<?php
$factory = new TestFactoryClass();
$obj = $factory->getProduct("paramValue");
<?php
use lukaszmakuch\Haringo\BuildPlan\Impl\FactoryObjectProductBuildPlan;
use lukaszmakuch\Haringo\BuildPlan\Impl\NewInstanceBuildPlan;
use lukaszmakuch\Haringo\ClassSource\Impl\ExactClassPath;
use lukaszmakuch\Haringo\MethodCall\MethodCall;
use lukaszmakuch\Haringo\MethodSelector\Impl\MethodByExactName;
use lukaszmakuch\Haringo\ParamSelector\Impl\ParamByExactName;
use lukaszmakuch\Haringo\ParamValue\AssignedParamValue;
use lukaszmakuch\Haringo\TestClass;
use lukaszmakuch\Haringo\ValueSource\Impl\BuildPlanResultValue;
use lukaszmakuch\Haringo\ValueSource\Impl\ScalarValue;
$plan = (new FactoryObjectProductBuildPlan())
->setFactoryObject(
//build TestFactoryClass instance
new BuildPlanResultValue((new NewInstanceBuildPlan())
->setClassSource(new ExactClassPath(TestFactoryClass::class)
))
)
->setBuildMethodCall(
(new MethodCall(new MethodByExactName("getProduct")))
->assignParamValue(new AssignedParamValue(
new ParamByExactName("configValue"),
new ScalarValue("paramValue")
))
);
Describes how to use a builder object to get some product. There are two mandatory elements of plan:
- value source which is resolved to a builder object
- method call which returns the product As it's a builder, it's also possible to call some methods which determine the further result state.
Let's create a builder:
<?php
use lukaszmakuch\Haringo\TestClass;
class TestBuilder
{
private $param;
public function setConstructorParam($param) { $this->param = $param; }
public function build()
{
return new TestClass($this->param);
}
}
Code equal to the result of the following build plan:
<?php
$builder = new TestBuilder();
$builder->setConstructorParam("paramValue");
$obj = $builder->build();
<?php
use lukaszmakuch\Haringo\BuildPlan\Impl\BuilderObjectProductBuildPlan;
use lukaszmakuch\Haringo\BuildPlan\Impl\NewInstanceBuildPlan;
use lukaszmakuch\Haringo\ClassSource\Impl\ExactClassPath;
use lukaszmakuch\Haringo\MethodCall\MethodCall;
use lukaszmakuch\Haringo\MethodSelector\Impl\MethodByExactName;
use lukaszmakuch\Haringo\ParamSelector\Impl\ParamByExactName;
use lukaszmakuch\Haringo\ParamValue\AssignedParamValue;
use lukaszmakuch\Haringo\TestClass;
use lukaszmakuch\Haringo\ValueSource\Impl\BuildPlanResultValue;
use lukaszmakuch\Haringo\ValueSource\Impl\ScalarValue;
$plan = (new BuilderObjectProductBuildPlan())
->setBuilderSource(
//build TestBuilder object
new BuildPlanResultValue((new NewInstanceBuildPlan())
->setClassSource(new ExactClassPath(TestBuilder::class)
))
)
->addSettingMethodCall(
(new MethodCall(new MethodByExactName("setConstructorParam")))
->assignParamValue(new AssignedParamValue(
new ParamByExactName("param"),
new ScalarValue("paramValue")
))
)
->setBuildMethodCall(
(new MethodCall(new MethodByExactName("build")))
);
Represents a full class path of some class.
<?php
use lukaszmakuch\Haringo\ClassSource\Impl\ExactClassPath;
//source of the \DateTime class
$classSrc = new ExactClassPath(\DateTime::class);
Allows to assign any class path to a string key.
The key stay unchanged while you move or rename your class, so a previously serialized build plan doesn't get out of date.
In order to Haringo be able to resolve a class path from a map, it must be built with the map.
<?php
use lukaszmakuch\Haringo\ClassSourceResolver\Impl\ClassPathFromMapResolver\ClassPathSourceMap;
use lukaszmakuch\Haringo\ClassSource\Impl\ExactClassPath;
/* @var $map ClassPathSourceMap */
//add the \DateTime class source under the "date_time" key
$map->addSource(
//key within the map
"date_time",
//actual class path source
new ExactClassPath(\DateTime::class)
);
<?php
use lukaszmakuch\Haringo\ClassSource\Impl\ClassPathFromMap;
//class source stored under the "date_time" key
$classSrc = new ClassPathFromMap("date_time");
Selects the constructor.
<?php
use lukaszmakuch\Haringo\MethodSelector\Impl\ConstructorSelector;
//constructor of any class
$methodSelector = ConstructorSelector::getInstance();
Selects some method by it's exact name. It may be "__constructor" as well, but for convenience it's preferable to use the ConstructorSelector.
<?php
use lukaszmakuch\Haringo\MethodSelector\Impl\MethodByExactName;
//method with name "myMethodName"
$methodSelector = new MethodByExactName("myMethodName");
Allows to assign any full method identifier to a string key. A full method identifier is a full class path source together with some method selector.
Because every full method identifier contains a class path source, it's possible to have two mapped methods under the same key which will represent different methods of different classes.
The key stays unchanged while you rename your method, so a previously serialized build plan doesn't get out of date.
In order to Haringo be able to get a method selector from a map, it must be built with the map.
<?php
use lukaszmakuch\Haringo\MethodSelectorMatcher\Impl\MethodFromMap\MethodSelectorMap;
use lukaszmakuch\Haringo\MethodSelectorMatcher\Impl\MethodFromMap\FullMethodIdentifier;
use lukaszmakuch\Haringo\MethodSelector\Impl\MethodByExactName;
use lukaszmakuch\Haringo\ClassSource\Impl\ExactClassPath;
/* @var $map MethodSelectorMap */
//add the \DateTime::modify method selector under the "date_time.modify" key
$map->addSelector(
//key within the map
"date_time.modify",
//full method identifier
new FullMethodIdentifier(
//source of the class which contains this method
new ExactClassPath(\DateTime::class),
//actual method selector
new MethodByExactName("modify")
)
);
<?php
use lukaszmakuch\Haringo\MethodSelector\Impl\MethodFromMap;
//method selector stored under the "date_time.modify" key
$methodSelector = new MethodFromMap("date_time.modify");
Selects one parameter by it's position from left (from index 0).
<?php
use lukaszmakuch\Haringo\ParamSelector\Impl\ParamByPosition;
//second parameter of some method
$paramSelector = new ParamByPosition(1);
Selects one parameter by it's exact name.
<?php
use lukaszmakuch\Haringo\ParamSelector\Impl\ParamByExactName;
//parameter called "name"
$paramSelector = new ParamByExactName("name");
Allows to assign any full parameter identifier to a string key. A full parameter identifier is a full class path source together with some method selector and parameter selector.
Because every full parameter identifier contains a class path source and a method selector, it's possible to have two mapped parameters under the same key which will represent different parameters of different methods (even of different classes).
The key stays unchanged while you rename or move the parameter or method, so a previously serialized build plan doesn't get out of date.
In order to Haringo be able to get a parameter selector from a map, it must be built with the map.
<?php
use lukaszmakuch\Haringo\ParamSelectorMatcher\Impl\ParamFromMapMatcher\ParamSelectorMap;
use lukaszmakuch\Haringo\ParamSelectorMatcher\Impl\ParamFromMapMatcher\FullParamIdentifier;
use lukaszmakuch\Haringo\MethodSelectorMatcher\Impl\MethodFromMap\FullMethodIdentifier;
use lukaszmakuch\Haringo\ParamSelector\Impl\ParamByPosition;
/* @var $map ParamSelectorMap */
//add the first parameter of the \DateTime::modify method
//under the "date_time.modify.first_param" key
$map->addSelector(
//key of the mapped selector
"date_time.modify.first_param",
//full identifier of the parameter
new FullParamIdentifier(
//full identifier of the method
new FullMethodIdentifier(
//class source
new ExactClassPath(\DateTime::class),
//method selector
new MethodByExactName("modify")
),
//actual parameter selector
new ParamByPosition(0)
)
);
<?php
use lukaszmakuch\Haringo\ParamSelector\Impl\ParamFromMap;
//parameter selector stored under the "date_time.modify.first_param" key
$paramSelector = new ParamFromMap("date_time.modify.first_param");
Represents a value like: boolean, integer, float, string.
<?php
use lukaszmakuch\Haringo\ValueSource\Impl\ScalarValue;
//string "Hello Haringo!"
$valueSource = new ScalarValue("Hello Haringo!");
//integer 42
$valueSource = new ScalarValue(42);
//float 36.6
$valueSource = new ScalarValue(36.6);
//boolean true
$valueSource = new ScalarValue(true);
Represents an array of other ValueSource objects. Other sources may also be ArrayValue objects. Keys may be both integers and strings.
<?php
use lukaszmakuch\Haringo\ValueSource\Impl\ArrayValue;
use lukaszmakuch\Haringo\ValueSource\Impl\ScalarValue;
//[123 => "one two three", "anotherKey" => true]
$valueSource = new ArrayValue();
$valueSource->addValue(123, new ScalarValue("one two three"));
$valueSource->addValue("anotherKey", new ScalarValue(true));
Represents a value which is the result of building something based the given BuildPlan object. It may be used in order to create a build plan of a complex composite.
<?php
use lukaszmakuch\Haringo\BuildPlan\Impl\NewInstanceBuildPlan;
use lukaszmakuch\Haringo\ClassSource\Impl\ExactClassPath;
use lukaszmakuch\Haringo\ValueSource\Impl\BuildPlanResultValue;
//create a build plan of a new \DateTime
$plan = new NewInstanceBuildPlan();
$plan->setClassSource(new ExactClassPath(\DateTime::class));
//create a value source based on this plan
$valueSource = new BuildPlanResultValue($plan);
To get the product, call the buildObjectBasedOn method with a BuildPlan:
<?php
use lukaszmakuch\Haringo\Haringo;
use lukaszmakuch\Haringo\BuildPlan\BuildPlan;
use lukaszmakuch\Haringo\Exception\UnableToBuild;
/* @var $haringo Haringo */
/* @var $buildPlan BuildPlan */
try {
$buitObject = $haringo->buildObjectBasedOn($buildPlan);
} catch (UnableToBuild $e) {
//it was impossible to build an object based on the given build plan
}
There are two methods which allow to serialize and deserialize build plans:
- Haringo::serializeBuildPlan(BuildPlan): String
- Haringo::deserializeBuildPlan(String): BuildPlan
<?php
use lukaszmakuch\Haringo\Haringo;
use lukaszmakuch\Haringo\BuildPlan\BuildPlan;
use lukaszmakuch\Haringo\Exception\UnableToDeserialize;
use lukaszmakuch\Haringo\Exception\UnableToSerialize;
/* @var $haringo Haringo */
/* @var $buildPlan BuildPlan */
try {
$serializedBuildPlan = $haringo->serializeBuildPlan(buildPlan);
$deserializedBuildPlan = $haringo->deserializeBuildPlan(serializedBuildPlan);
} catch (UnableToSerialize $e) {
//it was impossible to serialize this build plan
} catch (UnableToDeserialize $e) {
//it was impossible to deserialize this build plan
}
It's possible to figure out what was the build plan used to built some object:
<?php
use lukaszmakuch\Haringo\Haringo;
use lukaszmakuch\Haringo\Exception\BuildPlanNotFound;
use lukaszmakuch\Haringo\Exception\UnableToBuild;
use lukaszmakuch\Haringo\BuildPlan\BuildPlan;
/* @var $haringo Haringo */
/* @var $buildPlan BuildPlan */
try {
$buitObject = $haringo->buildObjectBasedOn($buildPlan);
$fetchedBuildPlan = $haringo->getBuildPlanUsedToBuild(buitObject);
} catch (UnableToBuild $e) {
//it was impossible to build an object based on the given build plan
} catch (BuildPlanNotFound $e) {
//it was impossible to fetch the build plan used to build the given object
}
Haringo is easily extensible!
It's possible to add support of a totally new ValueSource. All what you need to do, is to implement the \lukaszmakuch\Haringo\Builder\Extension\ValueSourceExtension interface and add it to the HaringoBuilder as described in the Building Haringo chapter. For more details, check documentation of thats interface.