-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
346 lines (290 loc) · 19.2 KB
/
Copy pathindex.html
File metadata and controls
346 lines (290 loc) · 19.2 KB
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
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<meta property="og:locale" content="en_US">
<meta property="og:type" content="website">
<meta property="og:title" content="Kyte Documentation">
<meta property="og:description" content="Learn how to create robust web apps with Kyte.">
<title>Kyte Documentation</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<style>
pre {
width: 100%;
padding: 10px 15px;
/* margin: 0; */
overflow: auto;
overflow-y: hidden;
font-size: 12px;
line-height: 20px;
background: #efefef;
border: 1px solid #777;
background: url(lines.png) repeat 0 0;
}
pre code {
color: #333;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
</head>
<body>
<div class="container">
<h1>Kyte</h1>
<small>version 3.x</small>
<!-- 1. Introduction -->
<h2>1. Introduction</h2>
<!-- 1.1 Directory Structure -->
<h3>1.1 Directory Structure</h3>
<p>The directory structure of Kyte projects are simple. Configurations are found at the root (<code>/</code>) directory, while all other components (controllers and models) live in the <code>/app</code> directory. Controllers are stored in <code>/app/controllers</code>, while models are stored in <code>/app/models</code>. To setup a workspace, simply create a folder for your project, and create the following folders inside: <code>/app/models</code> and <code>/app/controllers</code>. Next, create an <code>index.php</code> file at the root of your project folder and copy the following code:</p>
<pre><code><?php
require __DIR__ . '/vendor/autoload.php';
require __DIR__ . '/config.php';
$app = new \Kyte\Core\Api();
$app->route();
?>
</code></pre>
<p>Lastly, for all the magic to work, add the following <code>.htaccess</code> file also to the root of your project folder.</p>
<pre><code>FallbackResource /index.php</code></pre>
<p>The final directory structure should look like so:</p>
<pre><code>/
+ index.html
+ .htaccess
+ .gitignore
+ /app/
+ /models/
+ /controllers/
</code></pre>
<p>A <code>.gitignore</code> is highly recommended if you are using git for versioning. In the <code>.gitignore</code> file, include <code>config.php</code> so that this is not tracked if accidentally committed and pushed.</p>
<!-- 1.2 Creating a model -->
<h3>1.2 Creating a model</h3>
<p>By default, Kyte functions as an API out of the box, but isn't really useful until you customize it. The first step to customizing your API for your project is by definings the necessary models. Defining new models alone is sufficient for Kyte to function as it already has a default controller it uses to respond to and serve data, however this behaviour can also be modified using custom controllers (described later in this documentation).</p>
<p>Models are simply a PHP array definition that is stored as source files in <code>/app/models</code> of your project. Separate files for each model definition must be created (you cannot have a file with multiple model definitions).</p>
<p>Here's an example model that one might use to store device token information used for push notifications <code>/app/models/Device.php</code>.</p>
<pre><code>// /app/models/Device.php
$Device = [
'name' => 'Device',
'struct' => [
'token' => [
'type' => 's',
'required' => true,
'date' => false,
'size' => 512,
],
'user' => [
'type' => 'i',
'required' => false,
'date' => false,
'size' => 11,
'unsigned' => true,
'fk' => [
'model' => 'User',
'field' => 'id',
],
],
],
];
</code></pre>
<p>In the example above, we see that we have a model named <code>Device</code> with two properties: <code>token</code> and <code>user</code>. The property <code>user</code> is defined as a string that cannot be made null (<code>'requred' => true</code>) and is 512 bytes long. You also see that the property is explicitly marked as not being a date (<code>'date' => false</code>). The date attribute is required for all property definitions in Kyte, and must be explicitly defined even for properties that are not dates.</p>
<p>The next property that is defined in this example model is called <code>user</code>. This property is an unsigned integer of size 11, but this time the property can be set to null (<code>'required' => false</code>). You see the date attribute again explicitly defining the attribute as not being a date. Finally, the <code>fk</code> attribute indicates that this is a foreign key to a different model (table). Specifically, in this example, the <code>user</code> property is a foreign key to the <code>id</code> property of the <code>User</code> model.</p>
<p>Model definitions will always take the format shown below, where the Array variable name is the name of the model, containing <code>name</code> and <code>struct</code> elements.</p>
<pre><code>$ModelName = [
'name' => 'ModelName',
'struct' => [
/* property definition */
],
];
</code></pre>
<p>The <code>name</code> element holds the name of the model, which must be the same as the variable name and filename. The <code>struct</code> element holds the property definitions, like the ones we saw earlier in the <code>Device</code> model example. Let's next take a look at the different property types that can be defined in Kyte.</p>
<h4>1.2.1 Integers</h4>
<pre><code>'{propertyName}' => [
'type' => 'i',
'date' => false,
'required' => {true/false},
'size' => {size},
'unsigned' => {true/false},
],
</code></pre>
<h4>1.2.2 Strings</h4>
<pre><code>'{propertyName}' => [
'type' => 's',
'date' => false,
'required' => {true/false},
'size' => {size},
]
</code></pre>
<h4>1.2.3 Decimals</h4>
<pre><code>'{propertyName}' => [
'type' => 'd',
'date' => false,
'required' => {true/false},
'precision' => {precision},
'scale' => {scale},
]
</code></pre>
<p>For decimals, specify <code>'d'</code> for <code>type</code> and indicate the required precision and scale that you need. Precision is the total number of digits, while scale is the number of digits right of the decimal point. For example, the decimal number 12345.67 has a precision of 7 digits, and a scale of 2 digits.</p>
<h4>1.2.4 Text</h4>
<pre><code>'{propertyName}' => [
'type' => 't',
'date' => false,
'required' => {true/false},
]
</code></pre>
<h4>1.2.5 Dates</h4>
<pre><code>'{propertyName}' => [
'type' => 'i',
'date' => true,
'required' => {true/false},
]
</code></pre>
<p>Dates use <code>'i'</code> for <code>type</code> as the date is stored as UNIX time in secods as a long integer.</p>
<h4>1.2.6 Foreign Keys</h4>
<pre><code>'{propertyName}' => [
'type' => 'i',
'date' => false,
'size' => 11,
'unsigned' => true,
'required' => {true/false},
'fk' => [
'model' => '{modelName}',
'field' => '{propertyName}',
],
]
</code></pre>
<h4>1.2.7 Protected Flag</h4>
<pre><code>'{propertyName}' => [
'type' => 's',
'date' => false,
'protected' => true,
'required' => {true/false},
'size' => {size},
]
</code></pre>
<p>When a protected flag is present for a property, the value of that property is not returned by the controller unless the behaviour is customized with hooks or overrides. You may want to use this for properties that store sensitive information that should not be returned to the user, such as password hashes.</p>
<h4>1.2.8 Encrypted Flag (beta)</h4>
<pre><code>'{propertyName}' => [
'type' => 's',
'date' => false,
'encrypted' => true,
'required' => {true/false},
'size' => 255, // minimally recommended for storing cipher
]
</code></pre>
<p>The encrypted flag marks the property as needing encryption or decryption with AWS's KMS encryption services. When storing data, the property value will be encrypted using a KMS key specified in the configuration and the cipher stored in the dabatase. When retrieving the value, the cipher will be decrypted using AWS KMS and returned to the user.</p>
<!-- 1.3 Controllers -->
<h3>1.3 Controllers</h3>
<p>As mentioned earlier, model definitions are sufficient for Kyte to function as a default controller called <code>ModelController</code> handles all requests. By default, the <code>ModelController</code> handles GET, POST, PUT, and DELETE calls for authenticated users. However, the default controller behaviour at times may not be sufficient for your application and thus can be customized. There are two different ways to customize controller behaviour in Kyte: (1) using hooks, (2) defining a custom controller. For most use cases, the first option will provide plenty of flexibility. However, option 2 may be necessary when you require more complicated customizations or the use of virtual controllers is needed (explained later in this documentation).</p>
<p>Let's start by exploring the default behaviour.</p>
<h4>1.3.1 Default Behaviour</h4>
<p>The default controller behaviour requires users to be authenticated and have the necessary permissions (we'll discuss how to communicate with your API and create sessions later). When a request is made to a Kyte endpoint, the request is first validated (more details later). Next, the request type (GET, POST, PUT, DELETE) and any associated parameters and body data are parsed before handing off to the controller. The controller will then make the appropriate transaction and return the results back to the user. For GET, POST, and PUT requests, a successful return will include the model data (excepted for protected properties). For DELETE requests, an empty array is returned upon successful deletion of the specified object.</p>
<p>The prototype <code>ModelController</code> class that is used as the default controller, but also inherrited when customizing, has the following class methods for handling requests:</p>
<pre><code>public function new($data)
public function update($field, $value, $data)
public function get($field, $value)
public function delete($field, $value)
</code></pre>
<p><b>POST Request</b></p>
<p>POST requests are handled by <code>public function new($data)</code>, where the <code>$data</code> is the HTTP request body that is parsed and passed as an associative array. Upon successful completion, the newly created object is returned to the user via a referenced variable.</p>
<p><b>PUT Request</b></p>
<p>PUT requests are handled by <code>public function update($field, $value, $data)</code>, where the <code>$field</code> and <code>$value</code> are specified in the URL, and <code>$data</code> is the HTTP request body that is parsed and passed as an associative array. Upon successful completion, the updated model is returned to the user via a referenced variable.</p>
<p><b>GET Request</b></p>
<p>GET requests are handled by <code>public function get($field, $value)</code>, where the <code>$field</code> and <code>$value</code> are specified in the URL. Only for GET requests, both <code>$field</code> and <code>$value</code> can be set to null, which will result in all non-deleted model data being returned to the user via a referenced variable.</p>
<p><b>DELETE Request</b></p>
<p>DELETE requests are handled by <code>public function delete($field, $value)</code>, where the <code>$field</code> and <code>$value</code> are specified in the URL. Upon successful deletion, an empty array is returned to the user via a referenced variable.</p>
<h4>1.3.2 Controller Customization (using hooks)</h4>
<p>The recommended method for customizing controller behaviour is using hooks in an inherrited class. Hooks are preferred as they reduce code length, increase readability and code management. However, there are limits to the degree of customization possible with hooks, and as such you may need to define a custom controller for more advanced behaviours.</p>
<p>The first step to using hooks is to inherit the <code>ModelController</code> class as shown below:</p>
<pre><code><?php
// /app/controllers/{MyModelName}Controller.php
class {MyModelName}Controller extends \Kyte\Mvc\Controller\ModelController
{
/* ... hook definitions go here ... */
}
?>
</code></pre>
<p>It is very important to name your controller using the nomenclature shown in the example: <code>{MyModelName}Controller</code>. Both the file and class name need to have the name of the model for which you are creating a custom controller for appended with the word 'Controller'. If the controller name does not contain the model name, then Kyte will interpret it as a virtual controller (more described later in documentation). Once a custom controller class is created inherriting <code>ModelController</code>, then you are ready to start defining hooks to modify its behaviour.</p>
<p>There are currently five different hooks that can be defined to modify different phases of the controller behaviour.</p>
<pre><code>public function hook_init()
public function hook_auth()
public function hook_prequery($method, &$field, &$value, &$conditions, &$all, &$order)
public function hook_preprocess($method, &$r, &$o = null)
public function hook_response_data($method, $o, &$r = null, &$d = null)
public function hook_process_get_response(&$r)
</code></pre>
<p>Let's start with the first two: <code>hook_init()</code> and <code>hook_auth()</code>.</p>
<p>As the name suggests, <code>hook_init()</code> is executed after class instantiation prior to any requests being handled. This is where you can change class settings, such as modifying protected variables, turning off authentication and permissions checks. Below is a list of protected and public variables that can be modified:</p>
<pre><code>public $dateformat;
public $model;
// controller behaviour flags
protected $cascadeDelete;
protected $getFKTables;
protected $getExternalTables;
protected $requireAuth;
protected $requireRoles;
protected $requireAccount;
protected $failOnNull;
protected $allowableActions;
protected $checkExisting;
// array with error messages
protected $exceptionMessages;
</code></pre>
<h4>1.3.3 Custom Controllers</h4>
<p>When more advanced customizations are required for handling requests for a model, then overriding the public functions used to handle requests is required. The steps to creating a custom controller that overrides the public functions is the same as when creating custom controllers with hooks. The first step is to inherit the <code>ModelController</code> and naming both the class and file with the model name and the word 'Controller' appended as shown below:</p></p>
<pre><code><?php
// /app/controllers/{MyModelName}Controller.php
class {MyModelName}Controller extends \Kyte\Mvc\Controller\ModelController
{
/* ... function overrides go here ... */
}
?>
</code></pre>
<p>The functions that were introduced in the Default Behaviour section above can be overridden. We'll go through each one and explain how data can be returned back to the core API for the user to retrieve.</p>
<b>POST Request</b>
<pre><code>public function new($data) {
// initialize an empty array to store response data
$response = [];
/* ... process request ... */
// set the data element of Kyte's return value to the response data
$this->response['data'] = $response;
}
</code></pre>
<p>The function does not return any value but rather sets a reference variable that has been passed to the controller by the core API class during instantiation. This allows for further customization, if necessary, where the response values can be manipulated from the controller class.</p>
<b>PUT Request</b>
<pre><code>public function update($field, $value, $data) {
// initialize an empty array to store response data
$response = [];
/* ... process request ... */
// set the data element of Kyte's return value to the response data
$this->response['data'] = $response;
}
</code></pre>
<p>Just like in the above method for handling POST requests, the method for PUT requests also does not return any value but sets a referenced variable.</p>
<b>GET Request</b>
<pre><code>public function get($field, $value) {
// initialize an empty array to store response data
$response = [];
/* ... process request ... */
// set the data element of Kyte's return value to the response data
$this->response['data'] = $response;
}
</code></pre>
<p>The method for returning data for GET request is also the same.</p>
<b>DELETE Request</b>
<pre><code>public function delete($field, $value) {
// initialize an empty array to store response data
$response = [];
/* ... process request ... */
// set the data element of Kyte's return value to the response data
$this->response['data'] = $response;
}
</code></pre>
<p>The method for returning data for DELETE request is also the same, however, in the default controller, <code>$response</code> is never populated so an empty arrray is returned. This behaviour can be modified here as well if a certain response data is required.</p>
<h4>1.3.4 Virtual Controllers</h4>
<p>Virtual controllers are controllers that do not have a corresponding model (a one-to-one relationship with a model). What this means is that you can create a virtual controller that handles incoming requests for data that do not have a model definition, or data that may require two or more models (although that latter can be defined using hooks - see above section on controller customization using hooks). The steps to defining a virtual controller is the same as creating a custom controller, except the controller and file name must not correspond to an existing model name.</p>
<p>An example of when a virtual controller might be useful is when accepting payment transaction via Stripe. You could create a controller which accepts tokenized payment information to pass on to Stripe for payment processing while recording the transaction in your database (maybe an invoice model).</p>
<p>Another example might be a controller for reseting a user's password. The controller may accept the user's email address as incoming data and create a one-time password reset link that is emailed to the user.</p>
<p>As you can see, there are many different possibilies to use virtual controllers to fill in the gaps created by traditional controllers and make more advanced customizations that meet your application requirements.</p>
<hr />
<small>© KeyQ, Inc.</small>
</div>
</body>
</html>