├── README.md ├── lib └── Transform │ ├── PropertyManipulator.php │ └── Transformer.php └── tests ├── Fixtures ├── LineItem.php └── Product.php ├── PropertyManipulatorTest.php └── TransformerTest.php /README.md: -------------------------------------------------------------------------------- 1 | # Object Transformation Library 2 | 3 | In a need to map object data from one system to another, 4 | I was always using temporary, throw-away solutions, which 5 | turned out to be pretty ugly. 6 | 7 | PHP 5.3 Reflection API changes the game... 8 | 9 | # Usage 10 | 11 | Assume you have a product and a line item. Line item gets 12 | added to the order and essentially represents the product, 13 | with some data frozen (price) for historical reasons - you 14 | don't want product price updates to change customer order 15 | history and financial information. 16 | 17 | You could write a manual getData() setData($data) type of 18 | transform, or you could use this Transform library. 19 | 20 | Assume you have two classes. 21 | Product class: 22 | 23 | id = $id; 30 | } 31 | public function setName($name) { 32 | $this->name = $name; 33 | } 34 | public function setPrice($price) { 35 | $this->price = $price; 36 | } 37 | } 38 | 39 | LineItem class: 40 | 41 | productId; 48 | } 49 | public function getName() { 50 | return $this->name; 51 | } 52 | public function getUnitPrice() { 53 | return $this->unitPrice; 54 | } 55 | } 56 | 57 | You need to define the properties map, that would guide the transformation, in a 58 | array('source property name' => 'target property name', ...) manner: 59 | 60 | 'productId', 63 | 'name' => 'name', 64 | 'price' => 'unitPrice', 65 | ); 66 | 67 | Instantiate your source class: 68 | 69 | $product = new Product(); 70 | $product->setId('unique-id'); 71 | $product->setName('T-Shirt'); 72 | $product->setPrice(49.99); 73 | 74 | Or just use array of data (useful when need to convert Web Services result): 75 | 76 | $product = array( 77 | 'id' => 'unique-id', 78 | 'name' => 'T-Shirt', 79 | 'price' => 49.99, 80 | ); 81 | 82 | And tranform: 83 | 84 | $transformer = new Transformer(); 85 | $transformer->setTransformationMap($map); 86 | 87 | $lineItem = $transformer->transform($product, 'LineItem'); 88 | // or 89 | //... 90 | $lineItem = new LineItem(); 91 | $transformer->transform($product, $lineItem); 92 | 93 | $lineItem->getProductId(); 94 | $lineItem->getName(); 95 | $lineItem->getUnitPrice(); 96 | 97 | Another useful example is when you have some input data from 98 | one of your data sources, which already has the correct mappings, 99 | then you could use the PropertyManipulator class: 100 | 101 | 'unique-id', 104 | 'name' => 'T-Shirt', 105 | 'unitPrice' => 49.99, 106 | ); 107 | 108 | $manipulator = new PropertyManipulator(); 109 | $lineItem = $manipulator->inject('LineItem', $data); 110 | // or 111 | //... 112 | $lineItem = new LineItem(); 113 | $manipulator->inject($lineItem, $data); 114 | 115 | The other manipulator method, that can come in handy is extract(): 116 | 117 | extract($lineItem)); 120 | // outputs: { productId: "unique-id", name: "T-Shirt", unitPrice: 49.99 } 121 | 122 | Happy coding! -------------------------------------------------------------------------------- /lib/Transform/PropertyManipulator.php: -------------------------------------------------------------------------------- 1 | filter = $filter; 23 | } 24 | /** 25 | * @param mixed $object 26 | * @param array $values 27 | * @return mixed $object 28 | */ 29 | public function inject($object, array $values) { 30 | if (is_array($object)) { 31 | foreach ($values as $name => $value) { 32 | $object[$name] = $value; 33 | } 34 | } else { 35 | $object = $this->getObject($object); 36 | $class = $this->getReflection($object); 37 | foreach ($class->getProperties($this->filter) as $property) { 38 | $this->ensureAccessible($property); 39 | if (isset ($values[$property->getName()])) { 40 | $property->setValue($object, $values[$property->getName()]); 41 | } 42 | } 43 | } 44 | return $object; 45 | } 46 | /** 47 | * @param mixed $object 48 | * @return array $data 49 | */ 50 | public function extract($object) { 51 | $object = $this->getObject($object); 52 | $class = $this->getReflection($object); 53 | $data = array(); 54 | foreach ($class->getProperties($this->filter) as $property) { 55 | $this->ensureAccessible($property); 56 | $data[$property->getName()] = $property->getValue($object); 57 | } 58 | return $data; 59 | } 60 | /** 61 | * @return int $filter 62 | */ 63 | public function getPropertyFilter() { 64 | return $this->filter; 65 | } 66 | 67 | private function getReflection($object) { 68 | static $reflections; 69 | if ( ! isset ($reflections)) { 70 | $reflections = array(); 71 | } 72 | $className = get_class($object); 73 | if ( ! isset ($reflections[$className])) { 74 | $reflections[$className] = new ReflectionObject($object); 75 | } 76 | return $reflections[$className]; 77 | } 78 | 79 | private function getObject($object) { 80 | if ( ! is_object($object) && ! class_exists($object)) { 81 | throw new \InvalidArgumentException('Class ' . (string) $object . 'doesn\'t exist'); 82 | } 83 | return is_object($object) ? $object : new $object; 84 | } 85 | 86 | private function ensureAccessible(ReflectionProperty $property) { 87 | if ($property->isPrivate() || $property->isProtected()) { 88 | $property->setAccessible(true); 89 | } 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /lib/Transform/Transformer.php: -------------------------------------------------------------------------------- 1 | 'productId', 8 | * 'price' => 'total', 9 | * 'name' => 'productName', 10 | * ); 11 | * // create new source object: 12 | * $product = new Product(); 13 | * $product->id = 1; 14 | * $product->name = 'T-Shirt'; 15 | * $product->price = 49.99; 16 | * // create transformer with the transformation map: 17 | * $transformer = new Transformer(); 18 | * $transformer->setMap($map); 19 | * // mutate product into LineItem instance: 20 | * $lineItem = $transformer->transform($product, 'LineItem'); 21 | * $lineItem->productId; 22 | * $lineItem->productName; 23 | * $lineItem->total; 24 | */ 25 | class Transformer { 26 | /** 27 | * @var array $transformationMap 28 | */ 29 | private $transformationMap = array(); 30 | /** 31 | * @var PropertyManipulator $manipulator 32 | */ 33 | private $manipulator; 34 | /** 35 | * @param PropertyManipulator $manipulator 36 | */ 37 | public function __construct(PropertyManipulator $manipulator = null) { 38 | if (isset($manipulator)) { 39 | $this->setManipulator($manipulator); 40 | } 41 | } 42 | /** 43 | * @param array $transformationMap 44 | */ 45 | public function setTransformationMap(array $transformationMap) { 46 | $this->transformationMap = $transformationMap; 47 | } 48 | /** 49 | * @return array $transformationMap 50 | */ 51 | public function getTransformationMap() { 52 | return $this->transformationMap; 53 | } 54 | /** 55 | * @param PropertyManipulator $manipulator 56 | */ 57 | public function setManipulator(PropertyManipulator $manipulator) { 58 | $this->manipulator = $manipulator; 59 | } 60 | 61 | /** 62 | * @return PropertyManipulator $manipulator 63 | */ 64 | public function getManipulator() { 65 | if ( ! isset ($this->manipulator)) { 66 | $this->manipulator = new PropertyManipulator(); 67 | } 68 | return $this->manipulator; 69 | } 70 | 71 | /** 72 | * @param mixed|array $in 73 | * @param mixed $out 74 | * @return mixed $out 75 | */ 76 | public function transform($in, $out) { 77 | if (is_array($in)) { 78 | $data = $in; 79 | } else { 80 | $data = $this->getManipulator()->extract($in); 81 | } 82 | $result = array(); 83 | foreach ($data as $key => $value) { 84 | if (isset ($this->transformationMap[$key])) { 85 | $result[$this->transformationMap[$key]] = $value; 86 | } 87 | } 88 | return $this->getManipulator()->inject($out, $result); 89 | } 90 | 91 | } -------------------------------------------------------------------------------- /tests/Fixtures/LineItem.php: -------------------------------------------------------------------------------- 1 | productId; 12 | } 13 | 14 | public function getName() { 15 | return $this->name; 16 | } 17 | 18 | public function getUnitPrice() { 19 | return $this->unitPrice; 20 | } 21 | } -------------------------------------------------------------------------------- /tests/Fixtures/Product.php: -------------------------------------------------------------------------------- 1 | id = $id; 12 | } 13 | 14 | public function getId() { 15 | return $this->id; 16 | } 17 | 18 | public function setName($name) { 19 | $this->name = $name; 20 | } 21 | 22 | public function getName() { 23 | return $this->name; 24 | } 25 | 26 | public function setPrice($price) { 27 | $this->price = $price; 28 | } 29 | 30 | public function getPrice() { 31 | return $this->price; 32 | } 33 | } -------------------------------------------------------------------------------- /tests/PropertyManipulatorTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(ReflectionProperty::IS_PUBLIC 15 | | ReflectionProperty::IS_PROTECTED 16 | | ReflectionProperty::IS_PRIVATE, 17 | $manipulator->getPropertyFilter()); 18 | } 19 | 20 | public function testConstructorSetsFilter() { 21 | $filter = ReflectionProperty::IS_STATIC; 22 | $manipulator = new PropertyManipulator($filter); 23 | $this->assertEquals($filter, $manipulator->getPropertyFilter()); 24 | } 25 | 26 | public function testExtract() { 27 | $product = new Fixtures\Product(); 28 | $product->setId('simple-product'); 29 | $product->setName('T-Shirt'); 30 | $product->setPrice(49.99); 31 | $manipulator = new PropertyManipulator(); 32 | $this->assertEquals(array( 33 | 'id' => 'simple-product', 34 | 'name' => 'T-Shirt', 35 | 'price' => 49.99, 36 | ), $manipulator->extract($product)); 37 | } 38 | 39 | public function testInject() { 40 | $product = new Fixtures\Product(); 41 | $manipulator = new PropertyManipulator(); 42 | $manipulator->inject($product, array( 43 | 'id' => 'simple-product', 44 | 'name' => 'T-Shirt', 45 | 'price' => 49.99, 46 | )); 47 | $this->assertEquals('simple-product', $product->getId()); 48 | $this->assertEquals('T-Shirt', $product->getName()); 49 | $this->assertEquals(49.99, $product->getPrice()); 50 | } 51 | 52 | public function testInjectArray() 53 | { 54 | $product = array(); 55 | $manipulator = new PropertyManipulator(); 56 | $product = $manipulator->inject($product, array( 57 | 'id' => 'simple-product', 58 | 'name' => 'T-Shirt', 59 | 'price' => 49.99, 60 | )); 61 | $this->assertEquals('simple-product', $product['id']); 62 | $this->assertEquals('T-Shirt', $product['name']); 63 | $this->assertEquals(49.99, $product['price']); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tests/TransformerTest.php: -------------------------------------------------------------------------------- 1 | assertNotNull($transformer->getManipulator()); 13 | } 14 | 15 | public function testDefaultManipulatorIsInstanceOfPropertyManupulator() { 16 | $transformer = new Transformer(); 17 | $this->assertTrue($transformer->getManipulator() instanceof PropertyManipulator); 18 | } 19 | 20 | public function assertHasEmptyTransformationMap() { 21 | $transformer = new Transformer(); 22 | $this->assertEquals(array(), $transformer->getTransformationMap()); 23 | } 24 | 25 | public function testSetManupulator() { 26 | $newManipulator = new PropertyManipulator(); 27 | $transformer = new Transformer(); 28 | $transformer->setManipulator($newManipulator); 29 | $this->assertSame($newManipulator, $transformer->getManipulator()); 30 | } 31 | 32 | public function testConstructorSetsManipulator() { 33 | $newManipulator = new PropertyManipulator(); 34 | $transformer = new Transformer($newManipulator); 35 | $this->assertSame($newManipulator, $transformer->getManipulator()); 36 | } 37 | 38 | public function testSetMap() { 39 | $map = array('property' => 'attribute'); 40 | $transformer = new Transformer(); 41 | $transformer->setTransformationMap($map); 42 | $this->assertEquals($map, $transformer->getTransformationMap()); 43 | } 44 | 45 | public function testTransform() { 46 | $map = array( 47 | 'id' => 'productId', 48 | 'name' => 'name', 49 | 'price' => 'unitPrice', 50 | ); 51 | $product = new Fixtures\Product(); 52 | $product->setId('product-1'); 53 | $product->setName('T-Shirt'); 54 | $product->setPrice(44.99); 55 | 56 | $transformer = new Transformer(); 57 | $transformer->setTransformationMap($map); 58 | 59 | $lineItem = $transformer->transform($product, 'Transform\Fixtures\LineItem'); 60 | 61 | $this->assertEquals($product->getId(), $lineItem->getProductId()); 62 | $this->assertEquals($product->getName(), $lineItem->getName()); 63 | $this->assertEquals($product->getPrice(), $lineItem->getUnitPrice()); 64 | } 65 | } 66 | --------------------------------------------------------------------------------