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