Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
39.53% covered (danger)
39.53%
17 / 43
28.57% covered (danger)
28.57%
2 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
CachingObjectMapper
39.53% covered (danger)
39.53%
17 / 43
28.57% covered (danger)
28.57%
2 / 7
72.59
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 model
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 toStorageRow
40.00% covered (danger)
40.00%
4 / 10
0.00% covered (danger)
0.00%
0 / 1
7.46
 fromStorageRow
53.85% covered (warning)
53.85%
7 / 13
0.00% covered (danger)
0.00%
0 / 1
7.46
 get
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 normalizeRow
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 clear
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace Flow\Data\Mapper;
4
5use Flow\Data\ObjectManager;
6use Flow\Data\ObjectMapper;
7use Flow\Data\Utils\MultiDimArray;
8use Flow\Model\UUID;
9use InvalidArgumentException;
10use OutOfBoundsException;
11
12/**
13 * Rows with the same primary key always return the same object
14 * from self::fromStorageRow.  This means that if two parts of the
15 * code both load revision 123 they will receive the same object.
16 */
17class CachingObjectMapper implements ObjectMapper {
18    /**
19     * @var callable
20     */
21    protected $toStorageRow;
22
23    /**
24     * @var callable
25     */
26    protected $fromStorageRow;
27
28    /**
29     * @var string[]
30     */
31    protected $primaryKey;
32
33    /**
34     * @var MultiDimArray
35     */
36    protected $loaded;
37
38    /**
39     * @param callable $toStorageRow
40     * @param callable $fromStorageRow
41     * @param string[] $primaryKey
42     */
43    public function __construct( $toStorageRow, $fromStorageRow, array $primaryKey ) {
44        $this->toStorageRow = $toStorageRow;
45        $this->fromStorageRow = $fromStorageRow;
46        ksort( $primaryKey );
47        $this->primaryKey = $primaryKey;
48        $this->clear();
49    }
50
51    /**
52     * @param string $className Fully qualified class name
53     * @param string[] $primaryKey
54     * @return CachingObjectMapper
55     */
56    public static function model( $className, array $primaryKey ) {
57        return new self(
58            [ $className, 'toStorageRow' ],
59            [ $className, 'fromStorageRow' ],
60            $primaryKey
61        );
62    }
63
64    public function toStorageRow( $object ) {
65        $row = ( $this->toStorageRow )( $object );
66        $pk = ObjectManager::splitFromRow( $row, $this->primaryKey );
67        if ( $pk === null ) {
68            // new object may not have pk yet, calling code
69            // should call self::fromStorageRow with $object to load
70            // db assigned pk and store obj in $this->loaded
71        } elseif ( !isset( $this->loaded[$pk] ) ) {
72            // first time this id has been seen
73            $this->loaded[$pk] = $object;
74        } elseif ( $this->loaded[$pk] !== $object ) {
75            // loaded object of this id is not same object
76            $class = get_class( $object );
77            $id = json_encode( $pk );
78            throw new \InvalidArgumentException( "Duplicate '$class' objects for id $id" );
79        }
80        return $row;
81    }
82
83    public function fromStorageRow( array $row, $object = null ) {
84        $pk = ObjectManager::splitFromRow( $row, $this->primaryKey );
85        if ( $pk === null ) {
86            throw new \InvalidArgumentException( 'Storage row has no pk' );
87        } elseif ( !isset( $this->loaded[$pk] ) ) {
88            // unserialize the object
89            $this->loaded[$pk] = ( $this->fromStorageRow )( $row, $object );
90            return $this->loaded[$pk];
91        } elseif ( $object === null ) {
92            // provide previously loaded object
93            return $this->loaded[$pk];
94        } elseif ( $object !== $this->loaded[$pk] ) {
95            // loaded object of this id is not same object
96            $class = get_class( $object );
97            $id = json_encode( $pk );
98            throw new \InvalidArgumentException( "Duplicate '$class' objects for id $id" );
99        } else {
100            // object was provided, load $row into $object
101            // we already know $this->loaded[$pk] === $object
102            return ( $this->fromStorageRow )( $row, $object );
103        }
104    }
105
106    /**
107     * @param array $primaryKey
108     * @return object|null
109     * @throws InvalidArgumentException
110     */
111    public function get( array $primaryKey ) {
112        $primaryKey = UUID::convertUUIDs( $primaryKey, 'alphadecimal' );
113        ksort( $primaryKey );
114        if ( array_keys( $primaryKey ) !== $this->primaryKey ) {
115            throw new InvalidArgumentException;
116        }
117        try {
118            return $this->loaded[$primaryKey];
119        } catch ( OutOfBoundsException $e ) {
120            return null;
121        }
122    }
123
124    /**
125     * @inheritDoc
126     */
127    public function normalizeRow( array $row ) {
128        $object = ( $this->fromStorageRow )( $row );
129        return ( $this->toStorageRow )( $object );
130    }
131
132    public function clear() {
133        $this->loaded = new MultiDimArray;
134    }
135}