-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.php
218 lines (188 loc) · 5.74 KB
/
index.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
<?php
namespace Feedfm;
use Exception;
use mysqli;
/**
* Notes:
*
* 1. I'm presenting the simplest, most basic PHP solution. Not using libraries,
* abstractions, object models, etc., that would all be present in a real
* application.
*
* 2. There is rudimentary error handling. Ideally, errors should be logged,
* (and if critical enough, someone alerted) but not shown to the user as that
* could expose sensitive data.
*
* 3. It is bad practice to store db connection details in code. These would
* normally be in an environment-specific configuration, such as a .env file.
*
* 4. In most modern applications, the raw SQL used here would not be necessary
* at all, as the framework used would have some sort of ORM (ex. Eloquent in
* Laravel) or query builder.
*
* 5. Creating, using & closing a db connection would not normally be in the
* function, but rather abstracted out and used selectively, as needed.
*/
/**
* Database connection class. In most cases only a single connection is ever
* required, so this class could be turned into a singleton.
*
* @package Feedfm
*/
class FeedFm {
// Database connection info.
protected $host = '';
protected $user = '';
protected $pass = '';
protected $database = '';
// Database connection itself.
protected $mysqli = null;
/**
* FeedFm constructor.
*
* @param $host
* @param $user
* @param $pass
* @param $database
*/
public function __construct($host, $user, $pass, $database) {
$this->host = $host;
$this->user = $user;
$this->pass = $pass;
$this->database = $database;
}
/**
* Opens database connection.
*
* @throws Exception
*/
public function open() {
// Temporarily hide all errors & warnings. Rely on exception for cleaner
// error handling.
$errorLevel = error_reporting();
error_reporting(0);
$this->mysqli = new mysqli($this->host, $this->user, $this->pass, $this->database);
if ($this->mysqli->connect_errno) {
throw new Exception('Error connecting to MySQL: ' . $this->mysqli->connect_error);
}
error_reporting($errorLevel);
}
/**
* Closes MySQL connection.
*/
public function close() {
if ($this->mysqli === null) {
return;
}
$this->mysqli->close();
}
/**
* Executes query.
*
* @param $query
* @return mixed Query response.
* @throws Exception
*/
public function query($query) {
if ($this->mysqli === null) {
throw new Exception('No open MySQL connection.');
}
// sprintf the query arguments to minimize risk of sql injection attacks
$args = func_get_args();
$finalQuery = call_user_func_array('sprintf', $args);
$response = $this->mysqli->query($finalQuery);
if (!$response) {
throw new Exception('Error executing query.');
}
return $response;
}
/**
* Magic method. Performs cleanup operations.
*/
public function __destruct() {
$this->close();
}
}
/**
* Find widgets that contain a tag.
*
* @param $tag String
* @param $offset int
* @param $max int
*/
function findWidgetsWithTag($tag, $offset, $max) {
try {
$db = new FeedFm('localhost', 'root', '', 'feed_fm');
$db->open();
$query = '
SELECT w.id,
w.name,
GROUP_CONCAT(DISTINCT t2.id ORDER BY t2.id ASC) AS tag_ids,
GROUP_CONCAT(DISTINCT d.id ORDER BY d.id ASC) AS dongle_ids
FROM widget w
-- Inner join tag map and tag tables to filter by given $tag value. The
-- inner join will exclude widgets that do not have any tags.
JOIN widget_tag_map wtm
ON wtm.widget_id = w.id
JOIN tag t
ON t.id = wtm.tag_id
-- Join tag map and tag tables to get all associated tags. Since we
-- already know these widgets have at least one tag, we can use
-- inner/left/right join.
JOIN widget_tag_map wtm2
ON wtm2.widget_id = w.id
JOIN tag t2
ON t2.id = wtm2.tag_id
-- Left join dongle map and dongle tables. We do not want to exclude
-- widgets that do not have a dongle, so a left join is necessary here.
LEFT JOIN widget_dongle_map wdm
ON wdm.widget_id = w.id
LEFT JOIN dongle d
ON d.id = wdm.dongle_id
-- Filter out widgets not associated with given $tag.
WHERE t.tag = "%s"
-- Filter out deleted widgets.
AND w.deleted = 0
GROUP BY w.id
LIMIT %d, %d
';
$response = $db->query($query, $tag, (int) $offset, (int) $max);
$objects = [];
while ($row = $response->fetch_object()) {
/*
!! Unsure of what 'collection' means in the context of this assignment.
Uncomment this block if you'd like to get full tag & dongle
objects instead of just their IDs. !!
// Pull in the full tag objects.
$row->tags = [];
if ($row->tag_ids) {
$tagQuery = 'SELECT * FROM tag WHERE id IN (%s)';
$tagsResponse = $db->query($tagQuery, $row->tag_ids);
while ($tag = $tagsResponse->fetch_object()) {
$row->tags[] = $tag;
}
}
unset($row->tag_ids);
// Pull in the full dongle objects.
$row->dongles = [];
if ($row->dongle_ids) {
$dongleQuery = 'SELECT * FROM dongle WHERE id IN (%s)';
$dongleResponse = $db->query($dongleQuery, $row->dongle_ids);
while ($dongle = $dongleResponse->fetch_object()) {
$row->dongles[] = $dongle;
}
}
unset($row->dongle_ids);
*/
$objects[] = $row;
}
return $objects;
}
catch (Exception $e) {
echo $e->getMessage();
}
}
$widgets = findWidgetsWithTag('tag5', 0, 20);
echo '<pre>';
print_r($widgets);
echo '</pre>';