facut pana la video 7

This commit is contained in:
Cerbu Andrei Mihnea
2023-05-10 00:47:48 +03:00
parent 1abdc313f4
commit 90f12ab916
40 changed files with 1644 additions and 41 deletions
+91
View File
@@ -0,0 +1,91 @@
<?php
namespace app\core;
use app\core\exceptions\ForbiddenException;
use app\core\exceptions\NotFoundException;
class Application
{
public static string $ROOT_DIR;
public string $userClass;
public string $layout = 'main';
public Router $router;
public Request $request;
public Response $response;
public static Application $app;
public ?Controller $controller = null;
public Database $db;
public Session $session;
public ?DbModel $user;
public function __construct($rootPath, array $config)
{
self::$ROOT_DIR = $rootPath;
$this->userClass = $config['userClass'];
self::$app = $this;
$this->request = new Request();
$this->response = new Response();
$this->router = new Router($this->request, $this->response);
$this->db = new Database($config['db']);
$this->session = new Session();
$primaryValue = $this->session->get('user');
if ($primaryValue) {
$userObj = new $this->userClass();
$primaryKey = $userObj->primaryKey();
$this->user = $userObj->findOne([$primaryKey => $primaryValue]);
} else {
$this->user = null;
}
}
public function setController($controller)
{
$this->controller = $controller;
}
public function getController()
{
return $this->controller;
}
public function login(DbModel $user)
{
$this->user = $user;
$primaryKey = $user->primaryKey();
$primaryValue = $user->{$primaryKey};
$this->session->set('user', $primaryValue);
return true;
}
public function logout()
{
$this->user = null;
$this->session->remove('user');
}
public static function isLoggedIn()
{
return !self::$app->user;
}
public function run()
{
try{
echo $this->router->resolve();
}catch(ForbiddenException $e){
$this->response->setStatusCode(403);
echo $this->router->renderView('_error', [
'exception' => $e
]);
}catch(NotFoundException $e){
$this->response->setStatusCode(404);
echo $this->router->renderView('_error', [
'exception' => $e
]);
}
}
}
+26
View File
@@ -0,0 +1,26 @@
<?php
namespace app\core;
use app\core\middlewares\BaseMiddleware;
class Controller{
public string $layout = 'main';
public string $action = '';
protected array $middlewares = [];
public function render($view, $params = []){
return Application::$app->router->renderView($view, $params);
}
public function setLayout($layout){
$this->layout = $layout;
}
public function registerMiddleware(BaseMiddleware $middleware){
$this->middlewares[] = $middleware;
}
public function getMiddlewares(){
return $this->middlewares;
}
}
+78
View File
@@ -0,0 +1,78 @@
<?php
namespace app\core;
class Database{
public \PDO $pdo;
public function __construct(array $config){
$dsn = $config['dsn'] ?? '';
$user = $config['user'] ?? '';
$password = $config['password'] ?? '';
$this->pdo = new \PDO($dsn, $user, $password);
$this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
}
public function applyMigrations(){
$this->createMigrationsTable();
$appliedMigrations = $this->getAppliedMigrations();
$newMigrations = [];
$files = scandir(Application::$ROOT_DIR.'/migrations');
$toApplyMigrations = array_diff($files, $appliedMigrations);
foreach($toApplyMigrations as $migration){
if($migration === '.' || $migration === '..'){
continue;
}
require_once Application::$ROOT_DIR.'/migrations/'.$migration;
$className = pathinfo($migration, PATHINFO_FILENAME);
$instance = new $className();
$this->log("Applying migration $migration" . PHP_EOL);
$instance->up();
$this->log("Applied migration $migration" . PHP_EOL);
$newMigrations[] = $migration;
}
if(!empty($newMigrations)){
$this->saveMigrations($newMigrations);
}else{
$this->log('All migrations are applied');
}
}
public function createMigrationsTable(){
$this->pdo->exec("
CREATE TABLE IF NOT EXISTS migrations (
id INT AUTO_INCREMENT,
migration varchar(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
)
ENGINE=INNODB;");
}
public function getAppliedMigrations(){
$statement = $this->pdo->prepare("SELECT migration FROM migrations");
$statement->execute();
return $statement->fetchAll(\PDO::FETCH_COLUMN);
}
public function saveMigrations(array $migrations){
$str = implode(",", array_map(fn($m) => "('$m')", $migrations));
$statement = $this->pdo->prepare("INSERT INTO migrations (migration) VALUES $str");
$statement->execute();
}
public function prepare($sql){
return $this->pdo->prepare($sql);
}
protected function log($message){
echo '['.date('Y-m-d H:i:s').'] - '.$message.PHP_EOL;
}
}
+51
View File
@@ -0,0 +1,51 @@
<?php
namespace app\core;
abstract class DbModel extends Model{
abstract public function tableName(): string;
abstract public function attributes(): array;
abstract public function primaryKey(): string;
public function save(){
$tableName = $this->tableName();
$attributes = $this->attributes();
$params = array_map(fn($attr) => ":$attr", $attributes);
$statement = self::prepare("INSERT INTO $tableName ("
.implode(',', $attributes)
.") VALUES ("
.implode(',', $params)
.")"
);
foreach($attributes as $attribute){
$statement->bindValue(":$attribute", $this->{$attribute});
}
$statement->execute();
return true;
}
public static function prepare($sql){
return Application::$app->db->pdo->prepare($sql);
}
public function findOne($where){
$tableName = static::tableName();
$attributes = array_keys($where);
$sql = implode("AND ", array_map(fn($attr) => "$attr = :$attr", $attributes));
$statement = self::prepare("SELECT * from $tableName WHERE $sql");
foreach($where as $key => $item){
$statement->bindValue(":$key", $item);
}
$statement->execute();
return $statement->fetchObject(static::class);
}
}
+104
View File
@@ -0,0 +1,104 @@
<?php
namespace app\core;
abstract class Model{
public const RULE_REQUIRED = 'required';
public const RULE_EMAIL = 'email';
public const RULE_MIN = 'min';
public const RULE_MAX = 'max';
public const RULE_UNIQUE = 'unique';
public function loadData($data){
foreach ($data as $key => $value){
if(property_exists($this, $key)){
$this->{$key} = $value;
}
}
}
abstract public function rules():array;
public function labels(): array{
return [];
}
public array $errors = [];
private function addErrorForRule(string $attribute, string $rule, $params =[]){
$message = $this->errorMessages()[$rule] ?? '';
foreach($params as $key => $value){
$message = str_replace("{{$key}}", $value, $message);
}
$this->errors[$attribute][] = $message;
}
public function addError(string $attribute, string $message){
$this->errors[$attribute][] = $message;
}
public function errorMessages(){
return [
self::RULE_REQUIRED => 'This field is required',
self::RULE_EMAIL => 'This field must be valid email address',
self::RULE_MIN => 'Min length of this field must be {min}',
self::RULE_MAX => 'Max length of this field must be {max}',
self::RULE_UNIQUE => 'Email already exists'
];
}
public function validate(){
foreach($this->rules() as $attribute => $rules){
$value = $this->{$attribute};
foreach($rules as $rule){
$ruleName = $rule;
if(!is_string($ruleName)){
$ruleName = $rule[0];
}
if($ruleName === self::RULE_REQUIRED && !$value){
$this->addErrorForRule($attribute, self::RULE_REQUIRED);
}
if($ruleName === self::RULE_EMAIL && !filter_var($value, FILTER_VALIDATE_EMAIL)){
$this->addErrorForRule($attribute, self::RULE_EMAIL);
}
if($ruleName === self::RULE_MIN && strlen($value) < $rule['min']){
$this->addErrorForRule($attribute, self::RULE_MIN, $rule);
}
if($ruleName === self::RULE_MAX && strlen($value) > $rule['max']){
$this->addErrorForRule($attribute, self::RULE_MAX, $rule);
}
if($ruleName === self::RULE_UNIQUE){
$className = $rule['class'];
$uniqueAttr = $rule['attirubute'] ?? $attribute;
$tableName = $className::tableName();
$statement = Application::$app->db->prepare(("SELECT * FROM $tableName WHERE $uniqueAttr = :attr"));
$statement->bindValue(":attr", $value);
$statement->execute();
$record = $statement->fetchObject();
if($record){
$this->addErrorForRule($attribute, SELF::RULE_UNIQUE);
}
}
}
}
return empty($this->errors);
}
public function hasError($attribute){
return $this->errors[$attribute] ?? false;
}
public function getFirstError($attribute){
return $this->errors[$attribute][0] ?? '';
}
}
+54
View File
@@ -0,0 +1,54 @@
<?php
namespace app\core;
class Request{
private int $lengthOfRoot;
public function __construct(){
$this->lengthOfRoot = strlen('knowyourfood') + 1;
}
public function getPath(){//identificam din URL pe pagina trebuie accesata
$path = $_SERVER['REQUEST_URI'];
$path = substr($path, $this->lengthOfRoot);
$position = strpos($path, '?');
if(!$position){
return $path;
}
return substr($path, 0, $position);
}
public function method(){
return strtolower($_SERVER['REQUEST_METHOD']);
}
public function isGet(){
return $this->method() === 'get';
}
public function isPost(){
return $this->method() === 'post';
}
//bazat pe tipul de request, accesam tuplele key/value pentru a identifica body ul requestului
public function getBody(){
$body = [];
if($this->method() === 'get'){
foreach($_GET as $key => $value){
$body[$key] = filter_input(INPUT_GET, $key, FILTER_SANITIZE_SPECIAL_CHARS);
}
}
if($this->method() === 'post'){
foreach($_POST as $key => $value){
$body[$key] = filter_input(INPUT_POST, $key, FILTER_SANITIZE_SPECIAL_CHARS);
}
}
return $body;
}
}
+13
View File
@@ -0,0 +1,13 @@
<?php
namespace app\core;
class Response{
public function setStatusCode(int $code){
http_response_code($code);
}
public function redirect(string $url){
header("Location: ".$url);
}
}
+100
View File
@@ -0,0 +1,100 @@
<?php
namespace app\core;
use app\core\exceptions\NotFoundException;
class Router
{
public Request $request;
public Response $response;
protected array $routes = [];
public function __construct(Request $request, Response $response)
{
$this->request = $request;
$this->response = $response;
}
public function get($path, $callback)
{
$this->routes['get'][$path] = $callback;
}
public function post($path, $callback)
{
$this->routes['post'][$path] = $callback;
}
/*
in aceasta functie se identifica path ul catre fisier, metoda (GET/POST) si se identifica un "routes" callback ul (in general controller ul) pe care trebuie sa l returneze
astfel, in call_user_function, se ofera clasa de tip constructor si request ul de la user ca dupa controller ul sa se ocupe de restul
*/
public function resolve()
{
$path = $this->request->getPath();
$method = $this->request->method();
$callback = $this->routes[$method][$path] ?? false;
if (!$callback) {
throw new NotFoundException();
}
if (is_string($callback)) {
return $this->renderView($callback);
}
if (is_array($callback)) {
$controller = new $callback[0]();
Application::$app->controller = $controller;
$controller->action = $callback[1];
$callback[0] = $controller;
foreach($controller->getMiddlewares() as $middleware){
$middleware->execute();
}
}
return call_user_func($callback, $this->request, $this->response);
}
//layout ne va ajuta sa identificam ce fel de layout trebuie sa aiba pagina pe care o trimitem (elemente extra in afara de continut: navbar, elemente din baza de date etc)
protected function layoutContent()
{
$layout = Application::$app->layout;
if (Application::$app->controller) {
$layout = Application::$app->controller->layout;
}
ob_start();
include_once Application::$ROOT_DIR . "/views/layouts/$layout.php";
return ob_get_clean();
}
public function renderView($view, $params = [])
{
$layoutContent = $this->layoutContent();
$viewContent = $this->renderOnlyView($view, $params);
return str_replace('{{content}}', $viewContent, $layoutContent);
}
public function renderContent($viewContent)
{
$layoutContent = $this->layoutContent();
return str_replace('{{content}}', $viewContent, $layoutContent);
}
protected function renderOnlyView($view, $params)
{
foreach ($params as $key => $value) {
$$key = $value;
}
ob_start();
include_once Application::$ROOT_DIR . "/views/$view.php";
return ob_get_clean();
}
}
+50
View File
@@ -0,0 +1,50 @@
<?php
namespace app\core;
class Session{
protected const FLASH_KEY = 'flash_messages';
public function __construct(){
session_start();
$flashMessages = $_SESSION[self::FLASH_KEY] ?? [];
foreach($flashMessages as $key => &$flashMessage){
$flashMessage['remove'] = 'true';
}
$_SESSION[self::FLASH_KEY] = $flashMessages;
}
public function setFlash($key, $message){
$_SESSION[self::FLASH_KEY][$key] = [
'value' => $message,
'remove' => 'false'
];
}
public function getFlash($key){
return $_SESSION[self::FLASH_KEY][$key]['value'] ?? false;
}
public function set($key, $value){
$_SESSION[$key] = $value;
}
public function get($key){
return $_SESSION[$key] ?? false;
}
public function remove($key){
unset($_SESSION[$key]);
}
public function __destruct(){
$flashMessages = $_SESSION[self::FLASH_KEY] ?? [];
foreach($flashMessages as $key => &$flashMessage){
if($flashMessage['remove']){
unset($flashMessages[$key]);
}
}
$_SESSION[self::FLASH_KEY] = $flashMessages;
}
}
@@ -0,0 +1,8 @@
<?php
namespace app\core\exceptions;
class ForbiddenException extends \Exception{
protected $code = 403;
protected $message = 'You do not have permission to access this page';
}
@@ -0,0 +1,8 @@
<?php
namespace app\core\exceptions;
class NotFoundException extends \Exception{
protected $code = 404;
protected $message = 'Page not found';
}
+12
View File
@@ -0,0 +1,12 @@
<?php
namespace app\core\form;
class Button{
public function __toString(){
return sprintf('
<button type="submit" class="button">Submit</button>
',
);
}
}
+43
View File
@@ -0,0 +1,43 @@
<?php
namespace app\core\form;
use app\core\Model;
class Field{
public const TYPE_TEXT = 'text';
public const TYPE_PASSWORD = 'password';
public string $type;
public Model $model;
public string $attribute;
public function __construct(Model $model, string $attribute){
$this->type = self::TYPE_TEXT;
$this->model = $model;
$this->attribute = $attribute;
}
public function __toString(){
return sprintf('
<div class="mb-3">
<label>%s</label>
<input type="%s" name="%s" value="%s" class="form-control%s">
<div class="invalid-feedback">
%s
</div>
</div>
',
$this->model->labels()[$this->attribute] ?? $this->attribute,
$this->type,
$this->attribute,
$this->model->{$this->attribute},
$this->model->hasError($this->attribute) ? ' is invalid' : '',
$this->model->getFirstError($this->attribute)
);
}
public function passwordField(){
$this->type = self::TYPE_PASSWORD;
return $this;
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
namespace app\core\form;
use app\core\Model;
class Form{
public static function begin($action, $method){
echo sprintf('<form action ="%s" method ="%s">', $action, $method);
return new Form();
}
public static function end(){
echo '</form>';
}
public function field(Model $model, $attribute){
return new Field($model, $attribute);
}
public function button(){
return new Button();
}
}
@@ -0,0 +1,21 @@
<?php
namespace app\core\middlewares;
use app\core\Application;
use app\core\exceptions\ForbiddenException;
class AuthMiddleware extends BaseMiddleware{
public array $actions = [];
public function __construct(array $actions = []){
$this->actions = $actions;
}
public function execute(){
if(Application::isLoggedIn()){
if(empty($this->actions) || in_array(Application::$app->controller->action, $this->actions)){
throw new ForbiddenException;
}
}
}
}
@@ -0,0 +1,7 @@
<?php
namespace app\core\middlewares;
abstract class BaseMiddleware{
abstract public function execute();
}