38 | ',
39 | $this->model->getLabel($this->attribute),
40 | $this->renderInput(),
41 | $this->model->getFirstError($this->attribute),
42 | );
43 | }
44 | }
--------------------------------------------------------------------------------
/core/Form/Form.php:
--------------------------------------------------------------------------------
1 | ', $action, $method);
15 | return new Form();
16 | }
17 |
18 | public static function stop(){
19 | echo '';
20 | }
21 |
22 | public function input(Model $model, $attribute){
23 | return new InputField($model, $attribute);
24 | }
25 | }
--------------------------------------------------------------------------------
/core/Form/InputField.php:
--------------------------------------------------------------------------------
1 | type = self::TYPE_TEXT;
32 | parent::__construct($model, $attribute);
33 | }
34 |
35 | public function Password(){
36 | $this->type = self::TYPE_PASSWORD;
37 | return $this;
38 | }
39 |
40 | public function TypeNumber(){
41 | $this->type = self::TYPE_NUMBER;
42 | return $this;
43 | }
44 |
45 | public function CheckBox(){
46 | $this->type = self::TYPE_CHECK;
47 | return $this;
48 | }
49 |
50 | public function TypeDate(){
51 | $this->type = self::TYPE_DATE;
52 | return $this;
53 | }
54 |
55 | public function TypeFile(){
56 | $this->type = self::TYPE_FILE;
57 | return $this;
58 | }
59 |
60 | public function TypeRadio(){
61 | $this->type = self::TYPE_RADIO;
62 | return $this;
63 | }
64 |
65 | public function renderInput(): string
66 | {
67 | return sprintf('',
68 | $this->type,
69 | $this->attribute,
70 | $this->model->{$this->attribute},
71 | $this->model->hasError($this->attribute) ? 'is-invalid' : '',
72 | );
73 | }
74 | }
--------------------------------------------------------------------------------
/core/Form/TextareaField.php:
--------------------------------------------------------------------------------
1 | %s',
17 | $this->attribute,
18 | $this->model->hasError($this->attribute) ? 'is-invalid' : '',
19 | $this->model->{$this->attribute},
20 | );
21 | }
22 | }
--------------------------------------------------------------------------------
/core/Helpers.php:
--------------------------------------------------------------------------------
1 | method() === 'get';
25 | }
26 |
27 | public function isPost(){
28 | return $this->method() === 'post';
29 | }
30 |
31 | public function getBody(){
32 | $body = [];
33 | if($this->method() === 'get'){
34 | foreach ($_GET as $key => $value) {
35 | $body[$key] = \filter_input(INPUT_GET, $key, FILTER_SANITIZE_SPECIAL_CHARS);
36 | }
37 | }
38 | if($this->method() === 'post'){
39 | foreach ($_POST as $key => $value) {
40 | $body[$key] = \filter_input(INPUT_POST, $key, FILTER_SANITIZE_SPECIAL_CHARS);
41 | }
42 | }
43 |
44 |
45 | return $body;
46 | }
47 |
48 | public function setRouteParameters($params){
49 | $this->params = $params;
50 | return $this;
51 | }
52 |
53 | public function getParams(){
54 | return $this->params;
55 | }
56 |
57 | }
--------------------------------------------------------------------------------
/core/Http/Response.php:
--------------------------------------------------------------------------------
1 | request = new Request();
23 | $this->response = new Response();
24 | }
25 |
26 | public function get($path, $callback){
27 | $this->routes['get'][$path] = $callback;
28 | }
29 |
30 | public function post($path, $callback){
31 | $this->routes['post'][$path] = $callback;
32 | }
33 |
34 | public function getCallback(){
35 | $path = $this->request->getPath();
36 | $method = $this->request->method();
37 | //trim slashes
38 | $path = trim($path, '/');
39 |
40 | $routes = $this->routes[$method] ?? [];
41 | $params = false;
42 |
43 | foreach ($routes as $route => $callback) {
44 | //trim slashes
45 | $route = trim($route, '/');
46 | $routeNames = [];
47 | if(!$route){
48 | continue;
49 | }
50 |
51 | if(preg_match_all('/\{(\w+)(:[^}]+)?}/', $route, $matches)){
52 | $routeNames = $matches[1];
53 | }
54 | // convert route name to regex pattern
55 | $routeRegex = "@^".preg_replace_callback('/\{\w+(:([^}]+))?}/', fn($m) => isset($m[2]) ? "({$m[2]})" : '(\w+)', $route)."$@";
56 | //match current route
57 | if(preg_match_all($routeRegex, $path, $valueMatches)){
58 | $values = [];
59 | for ($i=1; $i < count($valueMatches); $i++) {
60 | $values[] = $valueMatches[$i][0];
61 | }
62 | $params = array_combine($routeNames, $values);
63 | $this->request->setRouteParameters($params);
64 | return $callback;
65 | }
66 | }
67 | return false;
68 | }
69 |
70 | public function resolve(){
71 | $path = $this->request->getPath();
72 | $method = $this->request->method();
73 | $callback = $this->routes[$method][$path] ?? false;
74 | if($callback === false){
75 | $callback = $this->getCallback();
76 | if($callback === false){
77 | $this->response->setStatus(404);
78 | return "Cannot GET $path";
79 | }
80 | }
81 | if(is_string($callback)){
82 | return $this->getView($callback);
83 | }
84 | if(is_array($callback)){
85 | /**
86 | * @var \Smyphp\Core\Controller\Controller $controller
87 | * */
88 | $controller = new $callback[0]();
89 | Application::$app->controller = $controller;
90 | $controller->action = $callback[1];
91 | $callback[0] = $controller;
92 | foreach($controller->getMiddlewares() as $middleware){
93 | $middleware->execute();
94 | }
95 | }
96 | return call_user_func($callback, $this->request, $this->response);
97 | }
98 |
99 | public function getView($view, $params = []){
100 | $layout = $this->viewLayout();
101 | $viewContent = $this->getOnlyView($view, $params);
102 | return str_replace('{{content}}', $viewContent, $layout);
103 | }
104 |
105 | public function renderContent($viewContent){
106 | $layoutContent = $this->viewLayout();
107 | return str_replace('{{content}}', $viewContent, $layoutContent);
108 | }
109 |
110 | protected function viewLayout(){
111 | $layout = Application::$app->layout;
112 | if(Application::$app->controller){
113 | $layout = Application::$app->controller->layout;
114 | }
115 | ob_start();
116 | include_once Application::$ROOT_DIR."/views/layouts/$layout.php";
117 | return ob_get_clean();
118 | }
119 |
120 | protected function getOnlyView($view, $params){
121 | foreach($params as $key => $value){
122 | $$key = $value;
123 | }
124 | ob_start();
125 | include_once Application::$ROOT_DIR."/views/$view.php";
126 | return ob_get_clean();
127 | }
128 |
129 | public function getAppViews($view, $params = []){
130 | $layout = $this->viewLayout();
131 | $viewContent = $this->getOnlyAppViews($view, $params);
132 | return str_replace('{{content}}', $viewContent, $layout);
133 | }
134 |
135 | protected function getOnlyAppViews($view, $params){
136 | foreach($params as $key => $value){
137 | $$key = $value;
138 | }
139 | ob_start();
140 | include_once Application::$ROOT_DIR."/config/routes/$view.php";
141 | return ob_get_clean();
142 | }
143 |
144 | public function getApiError($view, $params = []){
145 | foreach($params as $key => $value){
146 | $$key = $value;
147 | }
148 | ob_start();
149 | include_once Application::$ROOT_DIR."/config/routes/$view.php";
150 | return ob_get_clean();
151 | }
152 |
153 | }
--------------------------------------------------------------------------------
/core/Middleware/BaseMiddleware.php:
--------------------------------------------------------------------------------
1 | $value) {
20 | if(property_exists($this, $key)){
21 | $this->{$key} = $value;
22 | }
23 | }
24 | }
25 |
26 | abstract public function rules(): array;
27 |
28 | public function labels(): array{
29 | return [];
30 | }
31 |
32 | public function getLabel($attribute){
33 | return $this->labels()[$attribute] ?? $attribute;
34 | }
35 |
36 | public array $errors = [];
37 |
38 | public function validate(){
39 | foreach ($this->rules() as $attribute => $rules){
40 | $value = $this->{$attribute};
41 | foreach ($rules as $rule) {
42 | $ruleName = $rule;
43 | if(!is_string($ruleName)){
44 | $ruleName = $rule[0];
45 | }
46 | if($ruleName === self::REQUIRED_RULE && !$value){
47 | $this->addError($attribute, self::REQUIRED_RULE);
48 | }
49 | if($ruleName === self::EMAIL_RULE && !filter_var($value, FILTER_VALIDATE_EMAIL)){
50 | $this->addError($attribute, self::EMAIL_RULE);
51 | }
52 | if($ruleName === self::MIN_RULE && strlen($value) < $rule['min']){
53 | $this->addError($attribute, self::MIN_RULE, $rule);
54 | }
55 | if($ruleName === self::MAX_RULE && strlen($value) < $rule['max']){
56 | $this->addError($attribute, self::MAX_RULE, $rule);
57 | }
58 | if($ruleName === self::MATCH_RULE && $value !== $this->{$rule['match']}){
59 | $rule['match'] = $this->getLabel($rule['match']);
60 | $this->addError($attribute, self::MATCH_RULE, $rule);
61 | }
62 | if($ruleName === self::UNIQUE_RULE){
63 | $className = $rule['table'];
64 | $uniqueAttr = $rule['attribute'] ?? $attribute;
65 | $tableName = $className::tableName();
66 | $stmt = Application::$app->db->prepare("SELECT * FROM $tableName WHERE $uniqueAttr = :attr");
67 | $stmt->bindValue(":attr", $value);
68 | $stmt->execute();
69 | $record = $stmt->fetchObject();
70 | if($record){
71 | $this->addError($attribute, self::UNIQUE_RULE, ['field' => $this->getLabel($attribute)]);
72 | }
73 | }
74 | }
75 | }
76 |
77 | return empty($this->errors);
78 | }
79 |
80 | private function addError(string $attribute, string $rule, $params = []){
81 | $message = $this->errorMessage()[$rule] ?? '';
82 | foreach ($params as $key => $value) {
83 | $message = str_replace("{{$key}}", $value, $message);
84 | }
85 | $this->errors[$attribute][] = $message;
86 | }
87 |
88 | public function throwError(string $attribute, string $message){
89 | $this->errors[$attribute][] = $message;
90 | }
91 |
92 | public function errorMessage(){
93 | return[
94 | self::REQUIRED_RULE => 'This field is required',
95 | self::EMAIL_RULE => 'This field must contain a valid email address',
96 | self::MIN_RULE => 'Minimum length of this field must be {min}',
97 | self::MAX_RULE => 'Maximum length of this field must be {max}',
98 | self::MATCH_RULE => 'This field must be the same as {match}',
99 | self::UNIQUE_RULE => 'This {field} already exists',
100 | ];
101 | }
102 |
103 | public function hasError($attribute){
104 | return $this->errors[$attribute] ?? false;
105 | }
106 |
107 | public function getFirstError($attribute){
108 | return $this->errors[$attribute][0] ?? false;
109 | }
110 | }
--------------------------------------------------------------------------------
/core/Session.php:
--------------------------------------------------------------------------------
1 | &$flashMessage) {
16 | $flashMessage['remove'] = true;
17 | }
18 | $_SESSION[self::FLASH_KEY] = $flashMessages;
19 | }
20 |
21 | public function setFlash($key, $message){
22 | $_SESSION[self::FLASH_KEY][$key] = [
23 | 'remove' => false,
24 | 'value' => $message
25 | ];
26 | }
27 |
28 | public function getFlash($key){
29 | return $_SESSION[self::FLASH_KEY][$key]['value'] ?? false;
30 | }
31 |
32 | public function set($key, $value){
33 | $_SESSION[$key] = $value;
34 | }
35 |
36 | public function get($key){
37 | return $_SESSION[$key] ?? false;
38 | }
39 |
40 | public function remove($key){
41 | unset($_SESSION[$key]);
42 | }
43 |
44 | public function __destruct(){
45 | $flashMessages = $_SESSION[self::FLASH_KEY] ?? [];
46 | foreach ($flashMessages as $key => &$flashMessage) {
47 | if($flashMessage['remove']){
48 | unset($flashMessages[$key]);
49 | };
50 | }
51 | $_SESSION[self::FLASH_KEY] = $flashMessages;
52 | }
53 | }
--------------------------------------------------------------------------------
/core/Test.php:
--------------------------------------------------------------------------------
1 | load();
9 |
10 | $config = import(__DIR__."/config/database.php");
11 | $app = new Application(__DIR__, $config);
12 |
13 | $app->db->saveMigrations();
14 |
--------------------------------------------------------------------------------
/migrations/users_migration.php:
--------------------------------------------------------------------------------
1 | db;
10 | $sql = "CREATE TABLE users (
11 | id INT AUTO_INCREMENT PRIMARY KEY,
12 | name VARCHAR(255) NOT NULL,
13 | email VARCHAR(255) UNIQUE NOT NULL,
14 | password VARCHAR(255) NOT NULL,
15 | status TINYINT NOT NULL,
16 | is_verified TINYINT(1) NOT NULL DEFAULT FALSE,
17 | created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
18 | ) ENGINE=INNODB";
19 | $db->pdo->exec($sql);
20 | }
21 |
22 | public function down(){
23 | $db = \SmyPhp\Core\Application::$app->db;
24 | $sql = "DROP TABLE users";
25 | $db->pdo->exec($sql);
26 | }
27 |
28 | }
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 | ./tests/Unit
9 |
10 |
11 |
12 | ./tests/Feature
13 |
14 |
15 |
--------------------------------------------------------------------------------
/readMe.md:
--------------------------------------------------------------------------------
1 | # SMYPHP
2 |
3 | - [Description](#description)
4 | - [Requirements](#requirements)
5 | - [Installation](#installation)
6 | - [Usage](#usage)
7 | 1. [Starting Application](#starting-application)
8 | 2. [Database Migration](#database-migration)
9 | 3. [Routes](#routes)
10 | - [Rendering Pages](#rendering-pages)
11 | - [Rendering Pages With Parameters](#rendering-pages-with-parameters)
12 | - [Views and Layouts](#views-and-layouts)
13 | - [Defining Custom Layout for views](#defining-custom-layout-for-views)
14 | - [Passing params into routes](#passing-params-into-routes)
15 | 4. [Forms](#forms)
16 | - [Form Builder](#form-builder)
17 | - [Input Types](#input-types)
18 | - [Custom Form Labels](#custom-form-labels)
19 | - [Handling Form Data](#handling-form-data)
20 | 5. [SQL queries](#sql-queries)
21 | - [Query Builders](#query-builders)
22 | - [Writing Custom SQL Queries](#writing-custom-sql-queries)
23 | 6. [Middlewares](#middlewares)
24 | 7. [Sending Mails](#sending-mail)
25 | 8. [Flash Messages](#flash-messages)
26 | 9. [Image conversion](#image-conversion)
27 | 10. [Sending Json responses in API](#Sending-Json-responses-in-API)
28 | 11. [Getting Authenticated users in API](#Getting-Authenticated-users-in-API)
29 | - [Contributing and Vulnerabilities](#contributing-and-vulnerabilities)
30 | - [License](#license)
31 |
32 | # DESCRIPTION
33 |
34 | Smyphp is a lightweight PHP framework built for developers who need a simple framework to create web applications
35 |
36 | # REQUIREMENTS
37 |
38 | - php 7.3^
39 | - composer
40 |
41 | # INSTALLATION
42 |
43 | ```shell
44 | $ composer create-project seguncodes/smyphp yourProjectName
45 | ```
46 | # USAGE
47 |
48 | ### STARTING APPLICATION
49 |
50 | CD into your projects directory and run your application using the command below
51 |
52 | ```shell
53 | $ php smyphp --start
54 | ```
55 | Now you open [http://localhost:8000](http://localhost:8000) in your browser to see your application.
56 |
57 | OR open with your preferred port
58 |
59 | ```shell
60 | $ php smyphp --start --port 3344
61 | ```
62 | Now you open [http://localhost:3344](http://localhost:3344) in your browser to see your application.
63 |
64 | Run the following command for help
65 |
66 | ```shell
67 | $ php smyphp --help
68 | ```
69 |
70 |
71 | ### DATABASE MIGRATION
72 |
73 | All migration files should be saved in the `migrations` folder. `The user_migrations.php` is a default migration file and can be used as a boiler plate for creating other migration files.
74 |
75 | To migrate the migration files, `cd` into your projects directory and use this command to perform a database migration
76 |
77 | ```shell
78 | $ php migrate.php
79 | ```
80 |
81 | ### ROUTES
82 | The routes folder contains the assets folder where css, javascript, image and other files can be stored. The routes folder also contains the `index.php` file which is used to handle all routing.
83 |
84 | ### Rendering Pages
85 |
86 | Rendering can be done directly in the `index.php` file , an example is this
87 |
88 | ```php
89 | $app->router->get('/hello', function(){
90 | return "Hello world";
91 | });
92 | ```
93 | Visit [http://localhost:8000/hello](http://localhost:8000/hello). You're done.
94 |
95 | OR rendering could be done using the MVC method , an example is this
96 |
97 | `index.php` file
98 |
99 | ```php
100 | use App\Http\Controllers\ExampleController;
101 |
102 | $app->router->get('/hello', [ExampleController::class, 'examplePage']);
103 | ```
104 |
105 | then in the `ExampleController.php` file
106 |
107 | ```php
108 | namespace App\Http\Controllers;
109 | use SmyPhp\Core\Controller\Controller;
110 |
111 | class ExampleController extends Controller{
112 |
113 | public function examplePage(){
114 | return $this->render('yourFileName');
115 | }
116 | }
117 | ```
118 |
119 | finally in the views folder, in the `yourFileName.php` file
120 |
121 | ```php
122 |
123 |
Hello World
124 | ```
125 |
126 | Visit [http://localhost:8000/hello](http://localhost:8000/hello). You're done.
127 |
128 | ### Rendering Pages With Parameters
129 |
130 | Pages can be rendered with parameters using the MVC method...
131 |
132 |
133 | `index.php` file
134 |
135 | ```php
136 | use App\Http\Controllers\ExampleController;
137 |
138 | $app->router->get('/hello', [ExampleController::class, 'examplePage']);
139 | ```
140 |
141 | then in the `ExampleController.php` file
142 |
143 | ```php
144 | namespace App\Http\Controllers;
145 | use SmyPhp\Core\Controller\Controller;
146 |
147 | class ExampleController extends Controller{
148 |
149 | public function examplePage(){
150 | return $this->render('yourFileName', [
151 | 'text' => 'hello world'
152 | ]);
153 | }
154 | }
155 | ```
156 |
157 | finally in the views folder, in the `yourFileName.php` file
158 |
159 | ```php
160 |
161 |
162 |
163 | ```
164 |
165 | Visit [http://localhost:8000/hello](http://localhost:8000/hello). You're done.
166 |
167 | ### Views and Layouts
168 |
169 | The Views folder contains the layouts folder, and also contains files that will be displayed on the browser. The layouts folder contains layouts files. NOTE: `main.php` file is the default file.
170 | Here is an example of defining a layout for a view file:
171 |
172 | `example.php` file
173 |
174 | ```php
175 |
176 |
Hello World
177 |
178 | ```
179 | In layouts folder
180 | `main.php` file
181 |
182 | ```php
183 |
184 |
185 |
186 |
187 |
188 |
189 | Test
190 |
191 |
192 |
193 | {{content}}
194 |
195 |
196 |
197 |
198 | ```
199 | The `{{content}}` is used to display the content of `example.php` with the layouts from `main.php` file.
200 |
201 | ### Defining Custom Layout for views
202 |
203 | If you do not wish to use the `main.php` file to render files, then do the following:
204 | - create a new file in the layouts folder
205 | - define this new layout file in the controller function that is handling its rendering
206 |
207 | `ExampleController.php` file
208 |
209 | ```php
210 | namespace App\Http\Controllers;
211 | use SmyPhp\Core\Controller\Controller;
212 |
213 | class ExampleController extends Controller{
214 | public function examplePage(){
215 | $this->setLayout('yourLayoutName');
216 | return $this->render('yourFileName');
217 | }
218 | }
219 | ```
220 |
221 | The `$this->setLayout()` function is used to set the layout for a particular page, and should be called before the rendering of the page you are setting a layout for.
222 |
223 | ### Passing params into routes
224 | Params can be passed into routes and queried in controllers, here is an example:
225 |
226 | `index.php` file
227 |
228 | ```php
229 | use App\Http\Controllers\ExampleController;
230 |
231 | $app->router->get('/hello/{id}', [ExampleController::class, 'examplePage']);
232 | ```
233 |
234 | then in the `ExampleController.php` file
235 |
236 | ```php
237 | namespace App\Http\Controllers;
238 | use SmyPhp\Core\Controller\Controller;
239 | use SmyPhp\Core\Http\Request;
240 |
241 | class ExampleController extends Controller{
242 |
243 | public function examplePage(Request $request){
244 | echo '
';
247 | return $this->render('yourFileName');
248 | }
249 | }
250 | ```
251 |
252 | `$request->getParams()` is used to get the parameters passed in the url
253 |
254 | ### FORMS
255 | Forms can be used in the framework using the default HTML forms or using the Framework's form builder method
256 |
257 | ### Form Builder
258 | Using the Form builder method, in any of your view files , for example a login form...
259 | in `login.php` in views directory
260 | ```php
261 |
262 | input($model, 'email') ?>
263 | input($model, 'password')->Password() ?>
264 |
265 |
266 |
267 |
268 |
269 | ```
270 |
271 | The `Form::start()` method is used to start the form and takes two arguments `(action, method)`.
272 | The `$form->input()` method is used to call an input field in the form, it takes in two arguments `(model, inputName)`. The `model` parameter is used to reference the Model handling the request for that form; while the `inputName` is the `name` for that input field.
273 |
274 | ### Handling Form Data
275 | Form data is handled using controllers. Here is an example:
276 |
277 | in `register.php` in views directory
278 | ```php
279 |
280 | input($model, 'email') ?>
281 | input($model, 'password')->Password() ?>
282 |
283 |
284 |
285 |
286 |
287 | ```
288 | Then in `index.php` the route is defined
289 | ```php
290 | use App\Http\Controllers\ExampleController;
291 |
292 | $app->router->post('/register', [ExampleController::class, 'register']);
293 | ```
294 | finally in the `ExampleController.php` file
295 |
296 | ```php
297 | namespace App\Http\Controllers;
298 | use SmyPhp\Core\Controller\Controller;
299 | use SmyPhp\Core\Http\Request;
300 | use App\Models\User;
301 |
302 | class ExampleController extends Controller{
303 |
304 | public function register(Request $request){
305 | $this->setLayout('auth');
306 | $user = new User();
307 | //$user references the User model
308 | if($request->isPost()){
309 | //your registration logic comes here
310 | return $this->render('register', [
311 | 'model' =>$user //this is the model being sent to the form in the register page
312 | ]);
313 | }
314 | return $this->render('register', [
315 | 'model' =>$user //this is the model being sent to the form in the register page
316 | ]);
317 | }
318 | }
319 | ```
320 |
321 |
322 | ### Input Types
323 | The form builder also comes with various input types
324 | ```php
325 |
326 | input($model, 'password')->Password() ?>
327 | input($model, 'number')->TypeNumber() ?>
328 | input($model, 'checkBox')->CheckBox() ?>
329 | input($model, 'date')->TypeDate() ?>
330 | input($model, 'file')->TypeFile() ?>
331 | input($model, 'radio')->TypeRadio() ?>
332 |
333 |
334 |
335 |
336 |
337 |
338 | //for text area field
339 | echo new TextareaField($model, 'textarea')
340 | ```
341 |
342 | ### Custom Form Labels
343 | The default labels of input fields in the form builder method are the inputNames of the field. The labels can be changed in the model referenced in the `input()` method.
344 |
345 | in `login.php` in views directory
346 | ```php
347 |
348 | input($model, 'email') ?>
349 | input($model, 'password')->Password() ?>
350 |
351 |
352 |
353 |
354 |
355 | ```
356 |
357 | in the model being referenced in the controller handling the form data, there is a `labels()` method, where the labels can be customized
358 |
359 | `Model.php`
360 | ```php
361 | 'Your Email',
374 | 'password' => 'Your Password',
375 | ];
376 | }
377 | }
378 | ```
379 |
380 | ### SQL QUERIES
381 | Writing SQL queries in the framework can be achieved using the framework's query builders or default SQL statements
382 |
383 | ### Query Builders
384 | The framework comes with various query builders
385 | `save()`
386 | The save function saves data into database
387 | `findOne()`
388 | finds row WHERE argument exists and returns only 1
389 | ```php
390 | use App\Models\User;
391 | $user = (new User)->findOne(['email' => 'youremail@email.com']); //finds row that email exists and returns only 1
392 |
393 | /*
394 | |--------------------------------------------------------------------------
395 | | More than one conditions can be passed in
396 | |
397 | */
398 | $user = (new User)->findOne([
399 | 'email' => 'youremail@email.com',
400 | 'id' => 2
401 | ]); //finds where row that email AND id exists
402 | ```
403 | `findOneOrWhere()`
404 | This takes in two arguments with an OR condition
405 | ```php
406 | use App\Models\User;
407 | $user = (new User)->findOneOrWhere([
408 | 'email' => 'youremail@email.com'
409 | ], ['id' => 2]); //finds where row that email OR id exists
410 | ```
411 | `findAll()`
412 | This performs the basic SELECT all functionality in descending order of id
413 | ```php
414 | use App\Models\User;
415 | $user = (new User)->findAll(); //finds where row that email OR id exists
416 | ```
417 | `findAllWhere()`
418 | This performs the findAll functionality with a WHERE clause
419 | ```php
420 | use App\Models\User;
421 | $user = (new User)->findAllWhere([
422 | 'email' => 'youremail@email.com'
423 | ]); //finds ALL rows that email
424 | ```
425 | `findAllOrWhere()`
426 | This performs the findAll functionality with a WHERE clause and an OR condition
427 | ```php
428 | use App\Models\User;
429 | $user = (new User)->findAllOrWhere([
430 | 'email' => 'youremail@email.com'
431 | ], ['id' => 2]); //finds rows where email OR id exists
432 | ```
433 | `count()`
434 | This counts the number of columns in a table
435 | ```php
436 | use App\Models\User;
437 | $user = (new User)->count(); //returns the number of columns
438 | ```
439 | `countWhere()`
440 | This counts the number of columns with a WHERE clause
441 | ```php
442 | use App\Models\User;
443 | $user = (new User)->countWhere(['name'=>'john']); //returns the number of columns with name of john
444 | ```
445 | `countOrWhere()`
446 | This counts the number of columns with a WHERE clause and an OR condition
447 | ```php
448 | use App\Models\User;
449 | $user = (new User)->countOrWhere([
450 | 'name'=>'john'
451 | ], [
452 | 'status' => 1
453 | ]); //returns the number of columns with name of john or a status of 1
454 | ```
455 | `delete()`
456 | This takes a WHERE clause and deletes a row or rows
457 | ```php
458 | use App\Models\User;
459 | $user = (new User)->delete([
460 | 'name'=>'john'
461 | ]); //deletes the row(s) with name of john
462 | ```
463 | `deleteOrWhere()`
464 | This takes a WHERE clause and deletes a row or rows
465 | ```php
466 | use App\Models\User;
467 | $user = (new User)->deleteOrWhere([
468 | 'name'=>'john'
469 | ], [
470 | 'email' => 'youremail@email.com'
471 | ]); //deletes the row(s) with name of john or email of youremail@email.com
472 | ```
473 | `update()`
474 | This takes two arguments, the data to be updated and a WHERE clause
475 | ```php
476 | use App\Models\User;
477 | $user = (new User)->update([
478 | 'name'=>'john',
479 | 'status'=> 1
480 | ], [
481 | 'email' => 'youremail@email.com'
482 | ]); //sets status to 1 and name to john where the email is youremail@email.com
483 | ```
484 | `updateOrWhere()`
485 | This takes three arguments, the data to be updated, a WHERE clause and an OR condition
486 | ```php
487 | use App\Models\User;
488 | $user = (new User)->updateOrWhere([
489 | 'name'=>'john',
490 | 'status'=> 1
491 | ], [
492 | 'email' => 'youremail@email.com'
493 | ], [
494 | 'id' => 4
495 | ]); //sets status to 1 and name to john where the email is youremail@email.com OR id is 4
496 | ```
497 |
498 |
499 | ### Writing Custom SQL queries
500 | Unlike using the query builders, custom SQL statements can be written, here is an example:
501 | ```php
502 | use SmyPhp\Core\DatabaseModel;
503 |
504 | $stmt = DatabaseModel::prepare("SELECT count(*) FROM users WHERE id = 2");
505 | // $stmt->bindParam(); //this can be called if you are binding
506 | $stmt->execute();
507 | ```
508 |
509 | ### MIDDLEWARES
510 | The framework includes a middleware that verifies if the user of your application is authenticated. If the user is not authenticated, the middleware will redirect the user to your application's login screen. However, if the user is authenticated, the middleware will allow the request to proceed further into the application.
511 |
512 | The first middleware `ApiMiddleware` is used to check for authenticated users on your api and it is called in the controller, the method handling the route that should not be accesible by the user is passed in array of `new Authenticate([''])`.
513 |
514 | In `ExampleController.php` file
515 |
516 | ```php
517 | namespace App\Http\Controllers;
518 | use SmyPhp\Core\Controller\Controller;
519 | use App\Http\Middleware\ApiMiddleware;
520 |
521 | class ExampleController extends Controller{
522 |
523 | public function __construct(){
524 | $this->authenticatedMiddleware(new ApiMiddleware(['']));
525 | }
526 | }
527 | ```
528 |
529 | The second middleware `Authenticate` middleware is called in the controller and it's used when dealing with dynamic webpages on the framework, the method handling the route that should not be accesible by the user is passed in array of `new Authenticate([''])`.
530 |
531 | In `ExampleController.php` file
532 |
533 | ```php
534 | namespace App\Http\Controllers;
535 | use SmyPhp\Core\Controller\Controller;
536 | use App\Http\Middleware\Authenticate;
537 |
538 | class ExampleController extends Controller{
539 |
540 | public function __construct(){
541 | $this->authenticatedMiddleware(new Authenticate(['']));
542 | }
543 | }
544 | ```
545 |
546 | To prevent a user from accessing a page after login, add the following code to the top of the file rendering that page; or to set restrictions for users who are logged in or not
547 | ```php
548 | use SmyPhp\Core\Application;
549 | if (!Application::$app->isGuest()) {
550 | Application::$app->response->redirect('/');
551 | }
552 | ```
553 | The `isGuest()` function is used to check if there is an existing session
554 |
555 | ### SENDING MAIL
556 | Sending mails in the framework is achieved using PHPMAILER. To send a mail from the controller , the `MailServiceProvider` class is called.
557 |
558 | in `ExampleController.php` file
559 |
560 | ```php
561 | namespace App\Http\Controllers;
562 | use SmyPhp\Core\Controller\Controller;
563 | use App\Providers\MailServiceProvider;
564 |
565 | class ExampleController extends Controller{
566 |
567 | public function sendMail(){
568 | $subject = "subject";
569 | $email = "youremail@email.com";
570 | $name = "your name";
571 | $email_template = Application::$ROOT_DIR."/views/email.php"; //if the email will be sent in a template
572 | $send = (new MailServiceProvider)->Mail($subject, $email, $name, $email_template);
573 | }
574 | }
575 | ```
576 | To send an hand coded mail, the `MailServiceProvider.php` file in the app/Providers directory can be edited
577 |
578 | ### Flash Messages
579 |
580 | Sending flash messages after a successful request can be achieved from the controller by calling the
581 | `setflash()` method which takes in two arguments `(key, message)`.
582 | in `ExampleController.php` file
583 |
584 | ```php
585 | namespace App\Http\Controllers;
586 | use SmyPhp\Core\Controller\Controller;
587 | use SmyPhp\Core\Application;
588 |
589 | class ExampleController extends Controller{
590 |
591 | public function sendFlash(){
592 | Application::$app->session->setFlash('success', 'Thanks for joining');
593 | Application::$app->response->redirect('/'); //this redirects to the route where the flash message will appear
594 | exit;
595 | }
596 | }
597 | ```
598 |
599 | ### Image Conversion
600 | When dealing with images using the framework, the image file has to be sent in base64 format to the backend API. To convert an image from the base64 format , the `Image` class is called.
601 |
602 | in `ExampleController.php` file
603 |
604 | ```php
605 | namespace App\Http\Controllers;
606 | use SmyPhp\Core\Controller\Controller;
607 | use App\Providers\Image;
608 |
609 | class ExampleController extends Controller{
610 |
611 | public function sendMail(){
612 | $base64Image = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAUDBAQEAwUEBAQFBQUGBwwIBwcHBw8LCwkMEQ8SEh";
613 | $path = Application::$ROOT_DIR."/routes/assets/uploads";
614 | $filename = "uploads_".uniqid().".jpg";
615 | $convertImage = Image::convert($base64Image, $path, $filename);
616 | }
617 | }
618 | ```
619 | To use the `Image` class , make sure the `extension=gd` in your php.ini file is enabled.
620 |
621 | ### Sending Json responses in API
622 | To send json responses in API using the framework is quite simple and very similar to most php frameworks
623 |
624 | in `ExampleController.php` file
625 |
626 | ```php
627 | namespace App\Http\Controllers;
628 | use SmyPhp\Core\Controller\Controller;
629 | use SmyPhp\Core\Http\Request;
630 | use SmyPhp\Core\Http\Response;
631 |
632 | class ExampleController extends Controller{
633 |
634 | public function sendResponse(Request $request, Response $response){
635 | return $response->json([
636 | "success" => false,
637 | "message" => "All fields are required"
638 | ], 400);
639 | }
640 | }
641 | ```
642 | The `json` method takes two arguments, the array data to be returned and the status code
643 |
644 | ### Getting Authenticated user in API
645 |
646 | Getting the authenticated user in your API on the framework is quite simple and similar to most php frameworks
647 | in `ExampleController.php` file
648 |
649 | ```php
650 | namespace App\Http\Controllers;
651 | use SmyPhp\Core\Controller\Controller;
652 | use SmyPhp\Core\Http\Request;
653 | use SmyPhp\Core\Http\Response;
654 | use SmyPhp\Core\Auth;
655 |
656 | class ExampleController extends Controller{
657 |
658 | public function sendResponse(Request $request, Response $response){
659 | $user = Auth::User();
660 | return $response->json([
661 | "success" => false,
662 | "user" => $user
663 | ], 400);
664 | }
665 | }
666 | ```
667 | `Auth::User()` returns the id of the authenticated user.
668 |
669 | # Contributing & Vulnerabilities
670 | If you would like to contribute or you discover a security vulnerability in the SmyPhp FrameworK, your pull requests are welcome. However, for major changes or ideas on how to improve the library, please create an issue.
671 |
672 | # License
673 |
674 | The SmyPhp framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
--------------------------------------------------------------------------------
/routes/assets/js/bootstrap.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v4.4.1 (https://getbootstrap.com/)
3 | * Copyright 2011-2019 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
5 | */
6 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("jquery"),require("popper.js")):"function"==typeof define&&define.amd?define(["exports","jquery","popper.js"],e):e((t=t||self).bootstrap={},t.jQuery,t.Popper)}(this,function(t,g,u){"use strict";function i(t,e){for(var n=0;nthis._items.length-1||t<0))if(this._isSliding)g(this._element).one(Y.SLID,function(){return e.to(t)});else{if(n===t)return this.pause(),void this.cycle();var i=ndocument.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},t._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},t._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=t.left+t.right
',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent",sanitize:!0,sanitizeFn:null,whiteList:Se,popperConfig:null},Fe="show",Ue="out",We={HIDE:"hide"+Oe,HIDDEN:"hidden"+Oe,SHOW:"show"+Oe,SHOWN:"shown"+Oe,INSERTED:"inserted"+Oe,CLICK:"click"+Oe,FOCUSIN:"focusin"+Oe,FOCUSOUT:"focusout"+Oe,MOUSEENTER:"mouseenter"+Oe,MOUSELEAVE:"mouseleave"+Oe},qe="fade",Me="show",Ke=".tooltip-inner",Qe=".arrow",Be="hover",Ve="focus",Ye="click",ze="manual",Xe=function(){function i(t,e){if("undefined"==typeof u)throw new TypeError("Bootstrap's tooltips require Popper.js (https://popper.js.org/)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var t=i.prototype;return t.enable=function(){this._isEnabled=!0},t.disable=function(){this._isEnabled=!1},t.toggleEnabled=function(){this._isEnabled=!this._isEnabled},t.toggle=function(t){if(this._isEnabled)if(t){var e=this.constructor.DATA_KEY,n=g(t.currentTarget).data(e);n||(n=new this.constructor(t.currentTarget,this._getDelegateConfig()),g(t.currentTarget).data(e,n)),n._activeTrigger.click=!n._activeTrigger.click,n._isWithActiveTrigger()?n._enter(null,n):n._leave(null,n)}else{if(g(this.getTipElement()).hasClass(Me))return void this._leave(null,this);this._enter(null,this)}},t.dispose=function(){clearTimeout(this._timeout),g.removeData(this.element,this.constructor.DATA_KEY),g(this.element).off(this.constructor.EVENT_KEY),g(this.element).closest(".modal").off("hide.bs.modal",this._hideModalHandler),this.tip&&g(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,this._activeTrigger=null,this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},t.show=function(){var e=this;if("none"===g(this.element).css("display"))throw new Error("Please use show on visible elements");var t=g.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){g(this.element).trigger(t);var n=_.findShadowRoot(this.element),i=g.contains(null!==n?n:this.element.ownerDocument.documentElement,this.element);if(t.isDefaultPrevented()||!i)return;var o=this.getTipElement(),r=_.getUID(this.constructor.NAME);o.setAttribute("id",r),this.element.setAttribute("aria-describedby",r),this.setContent(),this.config.animation&&g(o).addClass(qe);var s="function"==typeof this.config.placement?this.config.placement.call(this,o,this.element):this.config.placement,a=this._getAttachment(s);this.addAttachmentClass(a);var l=this._getContainer();g(o).data(this.constructor.DATA_KEY,this),g.contains(this.element.ownerDocument.documentElement,this.tip)||g(o).appendTo(l),g(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new u(this.element,o,this._getPopperConfig(a)),g(o).addClass(Me),"ontouchstart"in document.documentElement&&g(document.body).children().on("mouseover",null,g.noop);var c=function(){e.config.animation&&e._fixTransition();var t=e._hoverState;e._hoverState=null,g(e.element).trigger(e.constructor.Event.SHOWN),t===Ue&&e._leave(null,e)};if(g(this.tip).hasClass(qe)){var h=_.getTransitionDurationFromElement(this.tip);g(this.tip).one(_.TRANSITION_END,c).emulateTransitionEnd(h)}else c()}},t.hide=function(t){function e(){n._hoverState!==Fe&&i.parentNode&&i.parentNode.removeChild(i),n._cleanTipClass(),n.element.removeAttribute("aria-describedby"),g(n.element).trigger(n.constructor.Event.HIDDEN),null!==n._popper&&n._popper.destroy(),t&&t()}var n=this,i=this.getTipElement(),o=g.Event(this.constructor.Event.HIDE);if(g(this.element).trigger(o),!o.isDefaultPrevented()){if(g(i).removeClass(Me),"ontouchstart"in document.documentElement&&g(document.body).children().off("mouseover",null,g.noop),this._activeTrigger[Ye]=!1,this._activeTrigger[Ve]=!1,this._activeTrigger[Be]=!1,g(this.tip).hasClass(qe)){var r=_.getTransitionDurationFromElement(i);g(i).one(_.TRANSITION_END,e).emulateTransitionEnd(r)}else e();this._hoverState=""}},t.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},t.isWithContent=function(){return Boolean(this.getTitle())},t.addAttachmentClass=function(t){g(this.getTipElement()).addClass(Pe+"-"+t)},t.getTipElement=function(){return this.tip=this.tip||g(this.config.template)[0],this.tip},t.setContent=function(){var t=this.getTipElement();this.setElementContent(g(t.querySelectorAll(Ke)),this.getTitle()),g(t).removeClass(qe+" "+Me)},t.setElementContent=function(t,e){"object"!=typeof e||!e.nodeType&&!e.jquery?this.config.html?(this.config.sanitize&&(e=we(e,this.config.whiteList,this.config.sanitizeFn)),t.html(e)):t.text(e):this.config.html?g(e).parent().is(t)||t.empty().append(e):t.text(g(e).text())},t.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t=t||("function"==typeof this.config.title?this.config.title.call(this.element):this.config.title)},t._getPopperConfig=function(t){var e=this;return l({},{placement:t,modifiers:{offset:this._getOffset(),flip:{behavior:this.config.fallbackPlacement},arrow:{element:Qe},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){return e._handlePopperPlacementChange(t)}},{},this.config.popperConfig)},t._getOffset=function(){var e=this,t={};return"function"==typeof this.config.offset?t.fn=function(t){return t.offsets=l({},t.offsets,{},e.config.offset(t.offsets,e.element)||{}),t}:t.offset=this.config.offset,t},t._getContainer=function(){return!1===this.config.container?document.body:_.isElement(this.config.container)?g(this.config.container):g(document).find(this.config.container)},t._getAttachment=function(t){return Re[t.toUpperCase()]},t._setListeners=function(){var i=this;this.config.trigger.split(" ").forEach(function(t){if("click"===t)g(i.element).on(i.constructor.Event.CLICK,i.config.selector,function(t){return i.toggle(t)});else if(t!==ze){var e=t===Be?i.constructor.Event.MOUSEENTER:i.constructor.Event.FOCUSIN,n=t===Be?i.constructor.Event.MOUSELEAVE:i.constructor.Event.FOCUSOUT;g(i.element).on(e,i.config.selector,function(t){return i._enter(t)}).on(n,i.config.selector,function(t){return i._leave(t)})}}),this._hideModalHandler=function(){i.element&&i.hide()},g(this.element).closest(".modal").on("hide.bs.modal",this._hideModalHandler),this.config.selector?this.config=l({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},t._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");!this.element.getAttribute("title")&&"string"==t||(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},t._enter=function(t,e){var n=this.constructor.DATA_KEY;(e=e||g(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),g(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusin"===t.type?Ve:Be]=!0),g(e.getTipElement()).hasClass(Me)||e._hoverState===Fe?e._hoverState=Fe:(clearTimeout(e._timeout),e._hoverState=Fe,e.config.delay&&e.config.delay.show?e._timeout=setTimeout(function(){e._hoverState===Fe&&e.show()},e.config.delay.show):e.show())},t._leave=function(t,e){var n=this.constructor.DATA_KEY;(e=e||g(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),g(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusout"===t.type?Ve:Be]=!1),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=Ue,e.config.delay&&e.config.delay.hide?e._timeout=setTimeout(function(){e._hoverState===Ue&&e.hide()},e.config.delay.hide):e.hide())},t._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},t._getConfig=function(t){var e=g(this.element).data();return Object.keys(e).forEach(function(t){-1!==je.indexOf(t)&&delete e[t]}),"number"==typeof(t=l({},this.constructor.Default,{},e,{},"object"==typeof t&&t?t:{})).delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),_.typeCheckConfig(Ae,t,this.constructor.DefaultType),t.sanitize&&(t.template=we(t.template,t.whiteList,t.sanitizeFn)),t},t._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},t._cleanTipClass=function(){var t=g(this.getTipElement()),e=t.attr("class").match(Le);null!==e&&e.length&&t.removeClass(e.join(""))},t._handlePopperPlacementChange=function(t){var e=t.instance;this.tip=e.popper,this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},t._fixTransition=function(){var t=this.getTipElement(),e=this.config.animation;null===t.getAttribute("x-placement")&&(g(t).removeClass(qe),this.config.animation=!1,this.hide(),this.show(),this.config.animation=e)},i._jQueryInterface=function(n){return this.each(function(){var t=g(this).data(Ne),e="object"==typeof n&&n;if((t||!/dispose|hide/.test(n))&&(t||(t=new i(this,e),g(this).data(Ne,t)),"string"==typeof n)){if("undefined"==typeof t[n])throw new TypeError('No method named "'+n+'"');t[n]()}})},s(i,null,[{key:"VERSION",get:function(){return"4.4.1"}},{key:"Default",get:function(){return xe}},{key:"NAME",get:function(){return Ae}},{key:"DATA_KEY",get:function(){return Ne}},{key:"Event",get:function(){return We}},{key:"EVENT_KEY",get:function(){return Oe}},{key:"DefaultType",get:function(){return He}}]),i}();g.fn[Ae]=Xe._jQueryInterface,g.fn[Ae].Constructor=Xe,g.fn[Ae].noConflict=function(){return g.fn[Ae]=ke,Xe._jQueryInterface};var $e="popover",Ge="bs.popover",Je="."+Ge,Ze=g.fn[$e],tn="bs-popover",en=new RegExp("(^|\\s)"+tn+"\\S+","g"),nn=l({},Xe.Default,{placement:"right",trigger:"click",content:"",template:'