More datamapper bugs...
[rock.divinelegy.git] / DataAccess / DataMapper / DataMapper.php
1 <?php
2
3 namespace DataAccess\DataMapper;
4
5 use Domain\Entities\IDivineEntity;
6 use DataAccess\IDatabaseFactory;
7 use DataAccess\DataMapper\IDataMapper;
8 use DataAccess\Queries\IQueryBuilder;
9 use DataAccess\DataMapper\Helpers\AbstractPopulationHelper;
10 use ReflectionClass;
11
12 class DataMapper implements IDataMapper
13 {
14 private $_db;
15 private $_maps;
16
17 public function __construct($maps, IDatabaseFactory $databaseFactory)
18 {
19 $this->_db = $databaseFactory->createInstance();
20 $this->_maps = include $maps;
21 }
22
23 public function map($entityName, IQueryBuilder $queryBuilder)
24 {
25 $queryString = $queryBuilder->buildQuery();
26 $statement = $this->_db->prepare(sprintf($queryString,
27 $this->_maps[$entityName]['table']
28 ));
29
30 $statement->execute();
31 $rows = $statement->fetchAll();
32
33 $entities = array();
34
35 foreach($rows as $row)
36 {
37 $className = $this->_maps[$entityName]['class']; //the entity to instantiate and return
38 $constructors = AbstractPopulationHelper::getConstrutorArray($this->_maps, $entityName, $row, $this->_db);
39
40 if(count($constructors) == 0)
41 {
42 $class = new $className;
43 } else {
44 $r = new ReflectionClass($className);
45 $class = $r->newInstanceArgs($constructors);
46 }
47
48 $class->setId($row['id']);
49 $entities[$row['id']] = $class;
50 }
51
52 return $entities;
53 }
54
55 public function save(IDivineEntity $entity)
56 {
57 $queries = AbstractPopulationHelper::generateUpdateSaveQuery($this->_maps, $entity, $entity->getId(), $this->_db);
58 $mergeMap = array();
59 $flattened = array();
60
61 foreach($queries as $index => $query)
62 {
63 $this_table = $query['table'];
64 $this_columns = $query['columns'];
65
66
67 for($i = $index+1; $i<count($queries); $i++)
68 {
69 if(
70 $queries[$i]['table'] == $this_table &&
71 !array_key_exists($i, $mergeMap) &&
72 !isset($query['id'])) //only merge create queries, updates are fine to run multiple times
73 {
74 //XXX: This whole biz is tricky. Basically the problem is that when creating a new simfile,
75 //the datamapper spews out a bunch of create queries. When parsing a simfile for example, there can
76 //be huge redundency - it may produce 5 queries that all create the same step artist, for example.
77 //We attempt to flatten equivalent queries. Originally I was basing it purely on the table name or something,
78 //but that is not enough. In the case of steps, it ends up mergin all the steps together, so we need to
79 //check if the arrays are equal as well, which is what this does.
80 if($this_columns === $queries[$i]['columns'])
81 {
82 //need to keep track of what we merged as future queries might reference the old ids.
83 $mergeMap[$i] = $index;
84 }
85
86 //XXX: Another thing that might happen is we have to create queries running on the same table, but with unique columns.
87 //In this case, we can take the columns of one and put it into the other. Otherwise we create two records when we really
88 //should have only one. An example of this is when a user is created, a query to add the country to users_meta is run,
89 //and then _another_ to add firstname, lastname and user_id. It should really all be done in one query.
90
91 //Make sure both queries are for the same table, and the both relate back to the main query
92 if($this_table == $queries[$i]['table'] && in_array('%MAIN_QUERY_ID%', $this_columns) && in_array('%MAIN_QUERY_ID%', $queries[$i]['columns']))
93 {
94 $this_column_names = array_keys($this_columns);
95 $other_column_names = array_keys($queries[$i]['columns']);
96 $combine = true;
97 foreach($this_column_names as $column_name)
98 {
99 if($this_columns[$column_name] != '%MAIN_QUERY_ID%' && in_array($column_name, $other_column_names))
100 {
101 $combine = false;
102 }
103 }
104
105 if($combine)
106 {
107 $this_columns = array_merge($this_columns, $queries[$i]['columns']);
108 $mergeMap[$i] = $index;
109 }
110 }
111 }
112 }
113
114 if(!array_key_exists($index, $mergeMap)) {
115 $prepared = isset($query['prepared']) ? $query['prepared'] : null;
116 $id = isset($query['id']) ? $query['id'] : null;
117
118 $flattened[] = array(
119 'columns' => $this_columns,
120 'table' => $this_table,
121 'prepared' => $prepared,
122 'id' => $id
123 );
124 }
125 }
126
127 $queries = array();
128
129 foreach($flattened as $info)
130 {
131 if(isset($info['id']))
132 {
133 $query = $info['prepared'];
134 $query = substr($query, 0, -2);
135 $query .= sprintf(' WHERE id=%u', $info['id']);
136 } else {
137 $query = sprintf('INSERT INTO %s (%s) VALUES (%s)',
138 $info['table'],
139 implode(', ', array_keys($info['columns'])),
140 implode(', ', $info['columns']));
141 }
142
143 $queries[] = $query;
144 }
145
146 // if($queries['TYPE'] == AbstractPopulationHelper::QUERY_TYPE_CREATE)
147 // {
148 $idMap = [];
149 foreach($queries as $index => $query)
150 {
151 $runQuery = true;
152 if (preg_match_all('/'.preg_quote('%').'(.*?)'.preg_quote('%').'/s', $query, $matches)) {
153 foreach($matches[1] as $index_ref)
154 {
155 if($index_ref != 'MAIN_QUERY_ID')
156 {
157 $index_id = str_replace('INDEX_REF_', '', $index_ref);
158 $query = str_replace('%INDEX_REF_' . $index_id . '%', $idMap['INDEX_REF_' . $index_id], $query);
159 } else {
160 $runQuery = false;
161 }
162 }
163 }
164
165 if($runQuery)
166 {
167 $statement = $this->_db->prepare($query);
168 $statement->execute();
169 //$refIndex = $index+1; This was being used as the index for idMap below. I have nfi why I was adding 1.
170 $idMap['INDEX_REF_' . $index] = $this->_db->lastInsertId();
171
172 foreach($mergeMap as $oldIndex => $mergedIndex) {
173 if($mergedIndex == $index) {
174 $idMap['INDEX_REF_' . $oldIndex] = $idMap['INDEX_REF_' . $index];
175 }
176 }
177
178 unset($queries[$index]);
179 } else {
180 //update query so that other references are resolved.
181 $queries[$index] = $query;
182 }
183 }
184
185 //at this point we have queries left that depend on the main query id
186 foreach($queries as $query)
187 {
188 $query = str_replace('%MAIN_QUERY_ID%', end($idMap), $query);
189 $statement = $this->_db->prepare($query);
190 $statement->execute();
191 }
192 //}
193
194 if(!$entity->getId()) $entity->setId(end($idMap));
195
196 return $entity;
197 }
198
199 //TODO: Implement
200 public function remove(IDivineEntity $entity) {
201 ;
202 }
203 }