├── .env ├── .env.example ├── .gitignore ├── .htaccess ├── LICENSE ├── app ├── Http │ ├── Controllers │ │ ├── AppController.php │ │ └── AuthController.php │ ├── Middleware │ │ ├── ApiMiddleware.php │ │ └── Authenticate.php │ └── Requests │ │ └── LoginRequest.php ├── Models │ ├── ExampleModel.php │ └── User.php └── Providers │ ├── Image.php │ ├── MailServiceProvider.php │ └── Token.php ├── autoload.php ├── composer.json ├── composer.lock ├── config ├── app.php ├── database.php └── routes │ ├── api-error.php │ └── error.php ├── core ├── Application.php ├── Auth.php ├── Authorization │ └── Server.php ├── Command │ └── Cli.php ├── Controller │ └── Controller.php ├── Database │ └── Database.php ├── DatabaseModel.php ├── Exception │ └── Forbidden.php ├── Form │ ├── BaseField.php │ ├── Form.php │ ├── InputField.php │ └── TextareaField.php ├── Helpers.php ├── Http │ ├── Request.php │ ├── Response.php │ └── Router.php ├── Middleware │ └── BaseMiddleware.php ├── Model.php ├── Session.php └── Test.php ├── migrate.php ├── migrations └── users_migration.php ├── phpunit.xml ├── readMe.md ├── routes ├── assets │ ├── css │ │ └── bootstrap.min.css │ └── js │ │ ├── bootstrap.min.js │ │ └── jquery-3.3.1.min.js └── index.php ├── run └── .gitignore ├── smyphp ├── storage └── .gitignore ├── tests ├── Feature │ └── ExampleTest.php └── Unit │ └── ExampleTest.php └── views ├── home.php ├── layouts ├── auth.php └── main.php ├── login.php └── register.php /.env: -------------------------------------------------------------------------------- 1 | DB_CONNECTION=mysql 2 | DB_HOST=localhost 3 | DB_PORT=3306 4 | DB_DATABASE= 5 | 6 | DB_USER=root 7 | DB_PASSWORD= 8 | 9 | MAIL_HOST=smtp.gmail.com 10 | MAIL_PORT=465 11 | MAIL_USERNAME= 12 | MAIL_PASSWORD= 13 | MAIL_ENCRYPTION=ssl 14 | MAIL_FROM_ADDRESS= 15 | MAIL_FROM_NAME= 16 | 17 | JWT_KEY= -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | DB_CONNECTION=mysql 2 | DB_HOST=localhost 3 | DB_PORT=3306 4 | DB_DATABASE= 5 | 6 | DB_USER=root 7 | DB_PASSWORD= 8 | 9 | MAIL_HOST= 10 | MAIL_PORT= 11 | MAIL_USERNAME= 12 | MAIL_PASSWORD= 13 | MAIL_ENCRYPTION= 14 | MAIL_FROM_ADDRESS= 15 | MAIL_FROM_NAME= 16 | 17 | JWT_KEY= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor -------------------------------------------------------------------------------- /.htaccess: -------------------------------------------------------------------------------- 1 | RewriteCond %{REQUEST_FILENAME} !-d 2 | RewriteCond %{REQUEST_FILENAME} !-f 3 | RewriteRule ^(.+)$ routes/index.php?url=$1 [QSA,L] 4 | 5 | 6 | RewriteEngine on 7 | 8 | RewriteCond %{HTTP_HOST} ^(www.)?website.com$ 9 | RewriteCond %{REQUEST_URI} !^/routes/ 10 | 11 | RewriteCond %{REQUEST_FILENAME} !-f 12 | RewriteCond %{REQUEST_FILENAME} !-d 13 | 14 | RewriteRule ^(.*)$ /routes/$1 15 | 16 | RewriteCond %{HTTP_HOST} ^(www.)?website.com$ 17 | 18 | RewriteRule ^(/)?$ routes/index.php [L] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 SMYPHP 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /app/Http/Controllers/AppController.php: -------------------------------------------------------------------------------- 1 | 'DTC' 12 | ]; 13 | return $this->render('home', $params); 14 | } 15 | } -------------------------------------------------------------------------------- /app/Http/Controllers/AuthController.php: -------------------------------------------------------------------------------- 1 | authenticatedMiddleware(new Authenticate([''])); 17 | } 18 | 19 | public function login(Request $request, Response $response){ 20 | $loginUser = new LoginRequest(); 21 | if($request->isPost()){ 22 | $loginUser->loadData($request->getBody()); 23 | if($loginUser->validate() && $loginUser->login()){ 24 | $response->redirect('/home'); 25 | return; 26 | } 27 | } 28 | $this->setLayout('auth'); 29 | return $this->render('login', [ 30 | 'model' => $loginUser 31 | ]); 32 | } 33 | 34 | public function register(Request $request){ 35 | $this->setLayout('auth'); 36 | $user = new User(); 37 | if($request->isPost()){ 38 | $user->loadData($request->getBody()); 39 | 40 | if($user->validate() && $user->save()){ 41 | Application::$app->session->setFlash('success', 'Thanks for registering'); 42 | Application::$app->response->redirect('/home'); 43 | exit; 44 | } 45 | return $this->render('register', [ 46 | 'model' =>$user 47 | ]); 48 | } 49 | return $this->render('register', [ 50 | 'model' =>$user 51 | ]); 52 | } 53 | 54 | public function logout(Request $request, Response $response){ 55 | Application::$app->logout(); 56 | $response->redirect('/home'); 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /app/Http/Middleware/ApiMiddleware.php: -------------------------------------------------------------------------------- 1 | actions = $actions; 23 | } 24 | 25 | public function execute(){ 26 | if(in_array(Application::$app->controller->action, $this->actions)){ 27 | try { 28 | $response = new Response(); 29 | if(Server::getBearerToken()) { 30 | $authToken = Token::decode(Server::getBearerToken()); 31 | if(time() > $authToken['expires_at']){ 32 | throw new \JsonException($response->json([ 33 | "message" => "authentication token expired", 34 | "success" => false 35 | ], 401)); 36 | } 37 | }else{ 38 | throw new \JsonException($response->json([ 39 | "message" => "authentication required", 40 | "success" => false 41 | ], 401)); 42 | } 43 | } catch (\Exception $e) { 44 | throw $e; 45 | } 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /app/Http/Middleware/Authenticate.php: -------------------------------------------------------------------------------- 1 | actions = $actions; 21 | } 22 | 23 | public function execute(){ 24 | if(Application::$app->isGuest()){ 25 | if(empty($this->actions) || in_array(Application::$app->controller->action, $this->actions)){ 26 | throw new Forbidden(); 27 | } 28 | } 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /app/Http/Requests/LoginRequest.php: -------------------------------------------------------------------------------- 1 | ['required', 'email'], 17 | 'password' => ['required'] 18 | ]; 19 | } 20 | 21 | public function login(){ 22 | $user = (new User)->findOne(['email' => $this->email]); 23 | if(!$user){ 24 | $this->throwError('email', 'User does not exist'); 25 | return false; 26 | } 27 | if(!password_verify($this->password, $user->password)){ 28 | $this->throwError('password', 'Incorrect password'); 29 | return false; 30 | } 31 | return Application::$app->login($user); 32 | } 33 | 34 | public function labels(): array 35 | { 36 | return [ 37 | 'email' => 'Your Email', 38 | 'password' => 'Enter Password' 39 | ]; 40 | } 41 | } -------------------------------------------------------------------------------- /app/Models/ExampleModel.php: -------------------------------------------------------------------------------- 1 | password = password_hash($this->password, PASSWORD_DEFAULT); 27 | return parent::save(); 28 | } 29 | 30 | public function rules(): array{ 31 | return [ 32 | 'name' => ['required'], 33 | 'email' => ['required', 'email', ['unique', 'table' => self::class]], 34 | 'password' => ['required', ['min', 'min' => 8]], 35 | 'cpassword' => ['required', ['match', 'match' => 'password']], 36 | ]; 37 | } 38 | 39 | public function attributes(): array{ 40 | return ['name', 'email', 'password']; //return the fields that should be saved in the database 41 | } 42 | 43 | public function labels(): array 44 | { 45 | return [ 46 | 'name' => 'Your Name', 47 | 'email' => 'Your Email', 48 | 'password' => 'Your Password', 49 | 'cpassword' => 'Confirm Password' 50 | ]; 51 | } 52 | 53 | public function getDisplayName(): string 54 | { 55 | return $this->name; 56 | } 57 | } -------------------------------------------------------------------------------- /app/Providers/Image.php: -------------------------------------------------------------------------------- 1 | SMTPDebug = 0; 19 | //Set PHPMailer to use SMTP. 20 | $mail->isSMTP(true); 21 | //Set SMTP host name 22 | $mail->Host = $_ENV['MAIL_HOST'] ; 23 | //Set this to true if SMTP host requires authentication to send email 24 | $mail->SMTPAuth = true; 25 | //Provide username and password 26 | $mail->Username = $_ENV['MAIL_FROM_ADDRESS']; 27 | $mail->Password = $_ENV['MAIL_PASSWORD']; 28 | //If SMTP requires TLS encryption then set it 29 | $mail->SMTPSecure = $_ENV['MAIL_ENCRYPTION']; 30 | //Set TCP port to connect to 31 | $mail->Port = $_ENV['MAIL_PORT']; 32 | 33 | //From email address and name 34 | $mail->From = $_ENV['MAIL_FROM_ADDRESS']; 35 | $mail->FromName = $_ENV['MAIL_FROM_NAME']; 36 | 37 | //To address and name 38 | $mail->addAddress($recipient_email, $recipient_name); 39 | 40 | //Send HTML or Plain Text email 41 | $mail->isHTML(true); 42 | 43 | //Email subject 44 | $mail->Subject = $subject; 45 | 46 | //uncomment the next lines if mail will be sent from this file 47 | // $mail->Body = "Mail body in HTML"; 48 | // $mail->AltBody = "This is the plain text version of the email content"; 49 | 50 | $body = file_get_contents($email_template); 51 | if($email_variables != null && is_array($email_variables)) { 52 | foreach ($email_variables as $key => $value) { 53 | $body = str_replace("{{{$key}}}", $value, $body); 54 | } 55 | } 56 | 57 | $mail->Body = $body; 58 | 59 | if (!$mail->send()) { 60 | $status = false; 61 | throw new \Exception($mail->ErrorInfo, 1); 62 | }else{ 63 | $status = true; 64 | } 65 | 66 | return $status; 67 | } 68 | 69 | } -------------------------------------------------------------------------------- /app/Providers/Token.php: -------------------------------------------------------------------------------- 1 | pushHandler(new \Whoops\Handler\JsonResponseHandler); 15 | $whoops->register(); 16 | $dotenv = Dotenv\Dotenv::createImmutable(dirname(__DIR__)); 17 | $dotenv->load(); 18 | require dirname(__DIR__).'/Core/Helpers.php'; 19 | require dirname(__DIR__).'/autoload.php'; 20 | 21 | $config = import(dirname(__DIR__)."/config/database.php"); 22 | 23 | Server::convertPayload(); 24 | $app = new Application(dirname(__DIR__), $config); -------------------------------------------------------------------------------- /config/database.php: -------------------------------------------------------------------------------- 1 | \App\Models\User::class, 12 | 'db' => [ 13 | 'connection' => $_ENV['DB_CONNECTION'], 14 | 'host' => $_ENV['DB_HOST'], 15 | 'port' => $_ENV['DB_PORT'], 16 | 'database' => $_ENV['DB_DATABASE'], 17 | 'user' => $_ENV['DB_USER'], 18 | 'password' => $_ENV['DB_PASSWORD'] 19 | ] 20 | ]; -------------------------------------------------------------------------------- /config/routes/api-error.php: -------------------------------------------------------------------------------- 1 | 8 | getMessage()?> 9 | -------------------------------------------------------------------------------- /config/routes/error.php: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 |
11 |
12 |

getCode() ?> - getMessage()?>

13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /core/Application.php: -------------------------------------------------------------------------------- 1 | userClass = $config['userClass']; 34 | self::$ROOT_DIR = $root; 35 | self::$app = $this; 36 | $this->request = new Request(); 37 | $this->response = new Response(); 38 | $this->session = new Session(); 39 | $this->router = new Router($this->request, $this->response); 40 | 41 | $this->db = new Database($config['db']); 42 | 43 | $primaryValue = $this->session->get('user'); 44 | if($primaryValue){ 45 | $primaryKey = (new $this->userClass)->uniqueKey(); 46 | $this->user = (new $this->userClass)->findOne([$primaryKey => $primaryValue]); 47 | }else{ 48 | $this->user = null; 49 | } 50 | } 51 | 52 | public function isGuest(){ 53 | return !self::$app->user; 54 | } 55 | 56 | public function run(){ 57 | try{ 58 | echo $this->router->resolve(); 59 | }catch(Forbidden $e){ 60 | $this->response->setStatus($e->getCode()); 61 | echo $this->router->getAppViews("error", [ 62 | 'exception' => $e 63 | ]); 64 | }catch(\JsonException $e){ 65 | echo $this->router->getApiError("api-error",[ 66 | 'exception' => $e 67 | ]); 68 | } 69 | } 70 | 71 | /** 72 | * @return SmyPhp\Core\Controller\Controller 73 | */ 74 | public function getController(){ 75 | return $this->controller; 76 | } 77 | 78 | /** 79 | * @param SmyPhp\Core\Controller\Controller $controller 80 | * 81 | */ 82 | public function setController(Controller $controller){ 83 | $this->controller = $controller; 84 | } 85 | 86 | public function login(DatabaseModel $user){ 87 | $this->user = $user; 88 | $primaryKey = $user->uniqueKey(); 89 | $primaryValue = $user->{$primaryKey}; 90 | $this->session->set('user', $primaryValue); 91 | return true; 92 | } 93 | 94 | public function logout(){ 95 | $this->user = null; 96 | $this->session->remove('user'); 97 | } 98 | } -------------------------------------------------------------------------------- /core/Auth.php: -------------------------------------------------------------------------------- 1 | $authToken['expires_at']){ 16 | throw new \JsonException($response->json([ 17 | "message" => "authentication token expired", 18 | "success" => false 19 | ], 401)); 20 | } 21 | $stmt = DatabaseModel::prepare("SELECT * FROM users WHERE id = :id LIMIT 1"); 22 | $stmt->bindParam(':id', $authToken['id']); 23 | $stmt->execute(); 24 | $user = $stmt->fetchAll(\PDO::FETCH_ASSOC); 25 | if(count($user) > 0) $authenticatedUser = $user[0]; 26 | return $authenticatedUser["id"]; 27 | } 28 | } 29 | } 30 | 31 | 32 | ?> -------------------------------------------------------------------------------- /core/Authorization/Server.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/Command/Cli.php: -------------------------------------------------------------------------------- 1 | layout = $layout; 21 | } 22 | 23 | public function render($view, $params = []){ 24 | return Application::$app->router->getView($view, $params); 25 | } 26 | 27 | public function authenticatedMiddleware(BaseMiddleware $middleware){ 28 | $this->middlewares[] = $middleware; 29 | } 30 | 31 | /** 32 | * @return \Smyphp\Core\Middleware\BaseMiddleware[] 33 | */ 34 | public function getMiddlewares(): array 35 | { 36 | return $this->middlewares; 37 | } 38 | } -------------------------------------------------------------------------------- /core/Database/Database.php: -------------------------------------------------------------------------------- 1 | pdo = new \PDO($dsn, $user, $password); 22 | $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); 23 | 24 | } 25 | 26 | public function saveMigrations(){ 27 | $this->createMigrations(); 28 | $savedMigrations = $this->getSavedMigrations(); 29 | $newMigrations = []; 30 | $files = scandir(Application::$ROOT_DIR.'/migrations'); 31 | $toSave = array_diff($files, $savedMigrations); 32 | foreach ($toSave as $migration){ 33 | if($migration === '.' || $migration === '..'){ 34 | continue; 35 | } 36 | require_once Application::$ROOT_DIR.'/migrations/'.$migration; 37 | $filename = pathinfo($migration, PATHINFO_FILENAME); 38 | $instance = new $filename(); 39 | $this->log("$migration migrating"); 40 | $instance->up(); 41 | $this->log("$migration migrated"); 42 | $newMigrations[] = $migration; 43 | } 44 | if(!empty($newMigrations)){ 45 | $this->storeMigrations($newMigrations); 46 | }else{ 47 | $this->log("Migrations up to date"); 48 | } 49 | } 50 | 51 | public function createMigrations(){ 52 | $this->pdo->exec("CREATE TABLE IF NOT EXISTS migrations ( 53 | id INT AUTO_INCREMENT PRIMARY KEY, 54 | migration VARCHAR(255), 55 | created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 56 | ) ENGINE=INNODB;"); 57 | } 58 | 59 | public function getSavedMigrations(){ 60 | $stmt = $this->pdo->prepare("SELECT migration FROM migrations"); 61 | $stmt->execute(); 62 | return $stmt->fetchAll(\PDO::FETCH_COLUMN); 63 | } 64 | 65 | public function storeMigrations(array $migrations){ 66 | $str = implode(",", array_map(fn($n) => "('$n')", $migrations)); 67 | $stmt = $this->pdo->prepare("INSERT INTO migrations (migration) VALUES $str"); 68 | $stmt->execute(); 69 | } 70 | 71 | public function prepare($sql){ 72 | return $this->pdo->prepare($sql); 73 | } 74 | 75 | protected function log($message){ 76 | echo '['.date('Y-m-d H:i:s').'] - '.$message.PHP_EOL; 77 | } 78 | 79 | } -------------------------------------------------------------------------------- /core/DatabaseModel.php: -------------------------------------------------------------------------------- 1 | tableName(); 22 | $attributes = $this->attributes(); 23 | $params = array_map(fn($attr) => ":$attr", $attributes); 24 | $statement = self::prepare("INSERT INTO $tableName (".implode(',', $attributes).") 25 | VALUES (".implode(',', $params).")"); 26 | foreach ($attributes as $attribute) { 27 | $statement->bindValue(":$attribute", $this->{$attribute}); 28 | } 29 | $statement->execute(); 30 | return true; 31 | } 32 | 33 | public function findOne($where){ 34 | $tableName = static::tableName(); 35 | $attributes = array_keys($where); 36 | $sql = implode(" AND ", array_map(fn($attr) => "$attr = :$attr", $attributes)); 37 | $stmt = self::prepare("SELECT * FROM $tableName WHERE $sql"); 38 | foreach ($where as $key => $item) { 39 | $stmt->bindValue(":$key", $item); 40 | } 41 | $stmt->execute(); 42 | return $stmt->fetchObject(static::class); 43 | } 44 | 45 | public function findOneOrWhere($where, $orWhere){ 46 | $tableName = static::tableName(); 47 | $attributes = array_keys($where); 48 | $orpart = array_keys($orWhere); 49 | $sql2 = implode(" AND ", array_map(fn($or) => "$or = :$or", $orpart)); 50 | $sql = implode(" AND ", array_map(fn($attr) => "$attr = :$attr", $attributes)); 51 | $stmt = self::prepare("SELECT * FROM $tableName WHERE $sql or $sql2"); 52 | foreach ($where as $key => $item) { 53 | $stmt->bindValue(":$key", $item); 54 | } 55 | foreach ($orWhere as $keys => $items) { 56 | $stmt->bindValue(":$keys", $items); 57 | } 58 | $stmt->execute(); 59 | return $stmt->fetchObject(static::class); 60 | } 61 | 62 | public function findAll(){ 63 | $tableName = static::tableName(); 64 | $stmt = self::prepare("SELECT * FROM $tableName ORDER BY id DESC"); 65 | $stmt->execute(); 66 | return $stmt->fetchAll(\PDO::FETCH_ASSOC); 67 | } 68 | 69 | public function findAllWhere($where){ 70 | $tableName = static::tableName(); 71 | $attributes = array_keys($where); 72 | $sql = implode(" AND ", array_map(fn($attr) => "$attr = :$attr", $attributes)); 73 | $stmt = self::prepare("SELECT * FROM $tableName WHERE $sql ORDER BY id DESC"); 74 | foreach ($where as $key => $item) { 75 | $stmt->bindValue(":$key", $item); 76 | } 77 | $stmt->execute(); 78 | return $stmt->fetchAll(\PDO::FETCH_ASSOC); 79 | } 80 | 81 | public function findAllOrWhere($where, $orWhere) { 82 | $tableName = static::tableName(); 83 | $whereConditions = implode(" AND ", array_map(fn($attr) => "$attr = :where_$attr", array_keys($where))); 84 | $orWhereConditions = implode(" OR ", array_map(fn($attr) => "$attr = :orWhere_$attr", array_keys($orWhere))); 85 | $sql = "SELECT * FROM $tableName"; 86 | if (!empty($whereConditions)) { 87 | $sql .= " WHERE $whereConditions"; 88 | } 89 | if (!empty($orWhereConditions)) { 90 | if (!empty($whereConditions)) { 91 | $sql .= " OR $orWhereConditions"; 92 | } else { 93 | $sql .= " WHERE $orWhereConditions"; 94 | } 95 | } 96 | $stmt = self::prepare($sql); 97 | foreach ($where as $key => $value) { 98 | $stmt->bindValue(":where_$key", $value); 99 | } 100 | foreach ($orWhere as $key => $value) { 101 | $stmt->bindValue(":orWhere_$key", $value); 102 | } 103 | $stmt->execute(); 104 | return $stmt->fetchAll(\PDO::FETCH_ASSOC); 105 | } 106 | 107 | public function count(){ 108 | $tableName = static::tableName(); 109 | $stmt = self::prepare("SELECT count(*) FROM $tableName"); 110 | $stmt->execute(); 111 | return $stmt->fetchColumn(); 112 | } 113 | 114 | public function countWhere($where){ 115 | $tableName = static::tableName(); 116 | $attributes = array_keys($where); 117 | $sql = implode(" AND ", array_map(fn($attr) => "$attr = :$attr", $attributes)); 118 | $stmt = self::prepare("SELECT count(*) FROM $tableName WHERE $sql"); 119 | foreach ($where as $key => $item) { 120 | $stmt->bindValue(":$key", $item); 121 | } 122 | $stmt->execute(); 123 | return $stmt->fetchColumn(); 124 | } 125 | 126 | public function countOrWhere($where, $orWhere){ 127 | $tableName = static::tableName(); 128 | $whereConditions = implode(" AND ", array_map(fn($attr) => "$attr = :where_$attr", array_keys($where))); 129 | $orWhereConditions = implode(" OR ", array_map(fn($attr) => "$attr = :orWhere_$attr", array_keys($orWhere))); 130 | $sql = "SELECT count(*) FROM $tableName"; 131 | if (!empty($whereConditions)) { 132 | $sql .= " WHERE $whereConditions"; 133 | } 134 | if (!empty($orWhereConditions)) { 135 | if (!empty($whereConditions)) { 136 | $sql .= " OR $orWhereConditions"; 137 | } else { 138 | $sql .= " WHERE $orWhereConditions"; 139 | } 140 | } 141 | $stmt = self::prepare($sql); 142 | foreach ($where as $key => $value) { 143 | $stmt->bindValue(":where_$key", $value); 144 | } 145 | foreach ($orWhere as $key => $value) { 146 | $stmt->bindValue(":orWhere_$key", $value); 147 | } 148 | $stmt->execute(); 149 | return $stmt->fetchColumn(); 150 | } 151 | 152 | public function delete($where){ 153 | $tableName = static::tableName(); 154 | $attributes = array_keys($where); 155 | $sql = implode(" AND ", array_map(fn($attr) => "$attr = :$attr", $attributes)); 156 | $stmt = self::prepare("DELETE FROM $tableName WHERE $sql"); 157 | foreach ($where as $key => $item) { 158 | $stmt->bindValue(":$key", $item); 159 | } 160 | return $stmt->execute(); 161 | } 162 | 163 | public function deleteOrWhere($where, $orWhere){ 164 | $tableName = static::tableName(); 165 | $whereConditions = implode(" AND ", array_map(fn($attr) => "$attr = :where_$attr", array_keys($where))); 166 | $orWhereConditions = implode(" OR ", array_map(fn($attr) => "$attr = :orWhere_$attr", array_keys($orWhere))); 167 | $sql = "DELETE FROM $tableName"; 168 | if (!empty($whereConditions)) { 169 | $sql .= " WHERE $whereConditions"; 170 | } 171 | if (!empty($orWhereConditions)) { 172 | if (!empty($whereConditions)) { 173 | $sql .= " OR $orWhereConditions"; 174 | } else { 175 | $sql .= " WHERE $orWhereConditions"; 176 | } 177 | } 178 | 179 | $stmt = self::prepare($sql); 180 | foreach ($where as $key => $value) { 181 | $stmt->bindValue(":where_$key", $value); 182 | } 183 | foreach ($orWhere as $key => $value) { 184 | $stmt->bindValue(":orWhere_$key", $value); 185 | } 186 | return $stmt->execute(); 187 | } 188 | 189 | public function update($data, $where){ 190 | $tableName = static::tableName(); 191 | $attributes = array_keys($where); 192 | $set = array_keys($data); 193 | $setData = implode(", ", array_map(fn($d) => "$d = :$d", $set)); 194 | $sql = implode(" AND ", array_map(fn($attr) => "$attr = :$attr", $attributes)); 195 | $stmt = self::prepare("UPDATE $tableName SET $setData WHERE $sql"); 196 | foreach ($data as $keys => $items) { 197 | $stmt->bindValue(":$keys", $items); 198 | } 199 | foreach ($where as $key => $item) { 200 | $stmt->bindValue(":$key", $item); 201 | } 202 | return $stmt->execute(); 203 | } 204 | 205 | public function updateOrWhere($data, $where, $orWhere){ 206 | $tableName = static::tableName(); 207 | $set = array_keys($data); 208 | $whereConditions = implode(" AND ", array_map(fn($attr) => "$attr = :where_$attr", array_keys($where))); 209 | $orWhereConditions = implode(" OR ", array_map(fn($attr) => "$attr = :orWhere_$attr", array_keys($orWhere))); 210 | $setData = implode(", ", array_map(fn($d) => "$d = :$d", $set)); 211 | $sql = "UPDATE $tableName SET $setData"; 212 | if (!empty($whereConditions)) { 213 | $sql .= " WHERE $whereConditions"; 214 | } 215 | if (!empty($orWhereConditions)) { 216 | if (!empty($whereConditions)) { 217 | $sql .= " OR $orWhereConditions"; 218 | } else { 219 | $sql .= " WHERE $orWhereConditions"; 220 | } 221 | } 222 | $stmt = self::prepare($sql); 223 | foreach ($data as $key => $value) { 224 | $stmt->bindValue(":$key", $value); 225 | } 226 | foreach ($where as $key => $value) { 227 | $stmt->bindValue(":where_$key", $value); 228 | } 229 | foreach ($orWhere as $key => $value) { 230 | $stmt->bindValue(":orWhere_$key", $value); 231 | } 232 | 233 | return $stmt->execute(); 234 | } 235 | 236 | public static function prepare($sql){ 237 | return Application::$app->db->pdo->prepare($sql); 238 | } 239 | } -------------------------------------------------------------------------------- /core/Exception/Forbidden.php: -------------------------------------------------------------------------------- 1 | model = $model; 24 | $this->attribute = $attribute; 25 | } 26 | 27 | abstract public function renderInput(): string; 28 | 29 | public function __toString(){ 30 | return sprintf(' 31 |
32 | 33 | %s 34 |
35 | %s 36 |
37 |
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 '
';
245 |         var_dump($request->getParams());
246 |         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:''}),on=l({},Xe.DefaultType,{content:"(string|element|function)"}),rn="fade",sn="show",an=".popover-header",ln=".popover-body",cn={HIDE:"hide"+Je,HIDDEN:"hidden"+Je,SHOW:"show"+Je,SHOWN:"shown"+Je,INSERTED:"inserted"+Je,CLICK:"click"+Je,FOCUSIN:"focusin"+Je,FOCUSOUT:"focusout"+Je,MOUSEENTER:"mouseenter"+Je,MOUSELEAVE:"mouseleave"+Je},hn=function(t){function i(){return t.apply(this,arguments)||this}!function(t,e){t.prototype=Object.create(e.prototype),(t.prototype.constructor=t).__proto__=e}(i,t);var e=i.prototype;return e.isWithContent=function(){return this.getTitle()||this._getContent()},e.addAttachmentClass=function(t){g(this.getTipElement()).addClass(tn+"-"+t)},e.getTipElement=function(){return this.tip=this.tip||g(this.config.template)[0],this.tip},e.setContent=function(){var t=g(this.getTipElement());this.setElementContent(t.find(an),this.getTitle());var e=this._getContent();"function"==typeof e&&(e=e.call(this.element)),this.setElementContent(t.find(ln),e),t.removeClass(rn+" "+sn)},e._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},e._cleanTipClass=function(){var t=g(this.getTipElement()),e=t.attr("class").match(en);null!==e&&0=this._offsets[o]&&("undefined"==typeof this._offsets[o+1]||trouter->get('/', function(){ 18 | return "Hello world"; 19 | }); 20 | $app->router->get('/home', [AppController::class, 'home']); 21 | $app->router->get('/login', [AuthController::class, 'login']); 22 | $app->router->post('/login', [AuthController::class, 'login']); 23 | $app->router->get('/register', [AuthController::class, 'register']); 24 | $app->router->post('/register', [AuthController::class, 'register']); 25 | $app->router->get('/logout', [AuthController::class, 'logout']); 26 | 27 | $app->run(); -------------------------------------------------------------------------------- /run/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /smyphp: -------------------------------------------------------------------------------- 1 | load(); 12 | 13 | require 'core/Helpers.php'; 14 | 15 | require 'autoload.php'; 16 | $run = new SmyPhp\Core\Command\Cli(); -------------------------------------------------------------------------------- /storage/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /tests/Feature/ExampleTest.php: -------------------------------------------------------------------------------- 1 | router->get('/', function(){ 17 | //...code 18 | }); 19 | 20 | // $response->assertStatus(200); 21 | $this->assertNotNull($response, "Not Null"); 22 | } 23 | } -------------------------------------------------------------------------------- /tests/Unit/ExampleTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 15 | } 16 | } -------------------------------------------------------------------------------- /views/home.php: -------------------------------------------------------------------------------- 1 | 4 |

Home

5 |

Welcome

6 | isGuest()): ?> 7 | Register 8 | Login 9 | 10 |

Welcome user->getDisplayName() ?>

11 | Logout 12 | -------------------------------------------------------------------------------- /views/layouts/auth.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Auth 11 | 12 | 13 | 14 | 15 | {{content}} 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /views/layouts/main.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | SMYPHP 11 | 12 | 13 | 14 |
15 | session->getFlash('success')): ?> 16 |
17 | session->getFlash('success') ?> 18 |
19 | 20 |
21 | {{content}} 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /views/login.php: -------------------------------------------------------------------------------- 1 | isGuest()) { 4 | Application::$app->response->redirect('/'); 5 | } 6 | ?> 7 |
8 |
9 |

Login

10 |
11 | 12 | input($model, 'email') ?> 13 | input($model, 'password')->Password() ?> 14 |
15 |
16 | 17 |
18 | 19 |
20 |
21 |
22 | -------------------------------------------------------------------------------- /views/register.php: -------------------------------------------------------------------------------- 1 | isGuest()) { 4 | Application::$app->response->redirect('/'); 5 | } 6 | ?> 7 |
8 |
9 |

Register

10 |
11 | 12 | input($model, 'name') ?> 13 | input($model, 'email')?> 14 | input($model, 'password')->Password() ?> 15 | input($model, 'cpassword')->Password() ?> 16 |
17 |
18 | 19 |
20 | 21 |
22 |
23 |
--------------------------------------------------------------------------------