diff --git a/app/Database/Factories/TokenFactory.php b/app/Database/Factories/TokenFactory.php
index a150f2ca..14a0bb09 100644
--- a/app/Database/Factories/TokenFactory.php
+++ b/app/Database/Factories/TokenFactory.php
@@ -15,11 +15,9 @@
class TokenFactory extends Factory
{
- protected static $model = Token::class;
-
public function __construct(int $count = 1)
{
- parent::__construct($count);
+ parent::__construct(Token::class, $count);
}
public function data(): array
diff --git a/app/Database/Factories/UserFactory.php b/app/Database/Factories/UserFactory.php
index 83faee77..d03b3963 100644
--- a/app/Database/Factories/UserFactory.php
+++ b/app/Database/Factories/UserFactory.php
@@ -15,11 +15,9 @@
class UserFactory extends Factory
{
- protected static $model = User::class;
-
public function __construct(int $count = 1)
{
- parent::__construct($count);
+ parent::__construct(User::class, $count);
}
public function data(): array
diff --git a/app/Database/Models/Token.php b/app/Database/Models/Token.php
index 32e3094d..9b13d3d5 100644
--- a/app/Database/Models/Token.php
+++ b/app/Database/Models/Token.php
@@ -12,5 +12,31 @@
class Token extends Model
{
- protected static $table = 'tokens';
+ public function __construct()
+ {
+ parent::__construct('tokens');
+ }
+
+ public static function findByValue(string $value): Model|false
+ {
+ return (new self())->findBy('value', $value);
+ }
+
+ public static function exists(string $email, string $description): Model|false
+ {
+ $token = (new self())
+ ->where('email', $email)
+ ->and('description', $description);
+
+ return !$token->exists() ? false : $token->first();
+ }
+
+ public static function findLatest(string $email, string $description): Model|false
+ {
+ return (new self())
+ ->where('email', $email)
+ ->and('description', $description)
+ ->newest()
+ ->first();
+ }
}
diff --git a/app/Database/Models/User.php b/app/Database/Models/User.php
index 06b8c0bd..0fb25e9f 100644
--- a/app/Database/Models/User.php
+++ b/app/Database/Models/User.php
@@ -12,5 +12,18 @@
class User extends Model
{
- protected static $table = 'users';
+ public function __construct()
+ {
+ parent::__construct('users');
+ }
+
+ public static function findByEmail(string $email): Model|false
+ {
+ return (new self())->findBy('email', $email);
+ }
+
+ public static function findAllWhereEmailLike(string $email): array|false
+ {
+ return (new self())->where('email', 'like', $email)->getAll();
+ }
}
diff --git a/app/Enums/TokenDescription.php b/app/Enums/TokenDescription.php
index 539eaee7..2607e66a 100644
--- a/app/Enums/TokenDescription.php
+++ b/app/Enums/TokenDescription.php
@@ -5,7 +5,7 @@
enum TokenDescription: string
{
case PASSWORD_RESET_TOKEN = 'password_reset_token';
- case EMAIL_VERIFICATION_TOKEN = 'email_verifications_token';
+ case EMAIL_VERIFICATION_TOKEN = 'email_verification_token';
public static function values(): array
{
diff --git a/app/Http/Actions/User/StoreAction.php b/app/Http/Actions/User/StoreAction.php
index acab38d1..3570f70f 100644
--- a/app/Http/Actions/User/StoreAction.php
+++ b/app/Http/Actions/User/StoreAction.php
@@ -16,6 +16,6 @@ class StoreAction
public function handle(array $data): Model|false
{
$data['password'] = hash_pwd($data['password']);
- return User::create($data);
+ return (new User())->create($data);
}
}
diff --git a/app/Http/Actions/User/UpdateAction.php b/app/Http/Actions/User/UpdateAction.php
index d983af8e..0928b984 100644
--- a/app/Http/Actions/User/UpdateAction.php
+++ b/app/Http/Actions/User/UpdateAction.php
@@ -15,9 +15,9 @@ class UpdateAction
{
public function handle(array $data, string $email): Model|false
{
- $user = User::findBy('email', $email);
+ $user = User::findByEmail($email);
- if ($user === false) {
+ if (!$user) {
return false;
}
@@ -25,7 +25,6 @@ public function handle(array $data, string $email): Model|false
$data['password'] = hash_pwd($data['password']);
}
- $user->fill($data);
- return $user->save();
+ return $user->fill($data)->save();
}
}
diff --git a/app/Http/Controllers/Auth/EmailVerificationController.php b/app/Http/Controllers/Auth/EmailVerificationController.php
index 8ce5d813..c42adc83 100644
--- a/app/Http/Controllers/Auth/EmailVerificationController.php
+++ b/app/Http/Controllers/Auth/EmailVerificationController.php
@@ -25,28 +25,24 @@ class EmailVerificationController extends Controller
{
public function notify(): void
{
- $token = generate_token();
+ $tokenValue = generate_token();
- if (!Mail::send(new VerificationMail($this->request->queries('email'), $token))) {
+ if (!Mail::send(new VerificationMail($this->request->queries('email'), $tokenValue))) {
Alert::default(__('email_verification_link_not_sent'))->error();
$this->render('auth.signup');
}
- if (
- !Token::where('email', $this->request->queries('email'))
- ->and('description', TokenDescription::EMAIL_VERIFICATION_TOKEN->value)
- ->exists()
- ) {
- Token::create([
+ $token = Token::exists($this->request->queries('email'), TokenDescription::EMAIL_VERIFICATION_TOKEN->value);
+
+ if ($token) {
+ $token->update(['value' => $tokenValue]);
+ } else {
+ $token->fill([
'email'=> $this->request->queries('email'),
'value' => $token,
'expire' => Carbon::now()->addDay()->toDateTimeString(),
'description' => TokenDescription::EMAIL_VERIFICATION_TOKEN->value
- ]);
- } else {
- Token::where('email', $this->request->queries('email'))
- ->and('description', TokenDescription::EMAIL_VERIFICATION_TOKEN->value)
- ->update(['value' => $token]);
+ ])->save();
}
Alert::default(__('email_verification_link_sent'))->success();
@@ -59,10 +55,7 @@ public function verify(UpdateAction $action): void
$this->response(__('bad_request'), 400);
}
- $token = Token::where('email', $this->request->queries('email'))
- ->and('description', TokenDescription::EMAIL_VERIFICATION_TOKEN->value)
- ->newest()
- ->first();
+ $token = Token::findLatest($this->request->queries('email'), TokenDescription::EMAIL_VERIFICATION_TOKEN->value);
if (!$token || $token->attribute('value') !== $this->request->queries('token')) {
$this->response(__('invalid_password_reset_link'), 400);
diff --git a/app/Http/Controllers/Auth/ForgotPasswordController.php b/app/Http/Controllers/Auth/ForgotPasswordController.php
index 9957ab95..11734b56 100644
--- a/app/Http/Controllers/Auth/ForgotPasswordController.php
+++ b/app/Http/Controllers/Auth/ForgotPasswordController.php
@@ -25,31 +25,27 @@ class ForgotPasswordController extends Controller
{
public function notify(): void
{
- $token = generate_token();
+ $tokenValue = generate_token();
- if (Mail::send(new TokenMail($this->request->get('email'), $token))) {
- if (
- !Token::where('email', $this->request->inputs('email'))
- ->and('description', TokenDescription::PASSWORD_RESET_TOKEN->value)
- ->exists()
- ) {
- Token::create([
- 'email'=> $this->request->inputs('email'),
- 'value' => $token,
- 'expire' => Carbon::now()->addHour()->toDateTimeString(),
- 'description' => TokenDescription::PASSWORD_RESET_TOKEN->value
- ]);
- } else {
- Token::where('email', $this->request->inputs('email'))
- ->and('description', TokenDescription::PASSWORD_RESET_TOKEN->value)
- ->update(['value' => $token]);
- }
+ if (!Mail::send(new TokenMail($this->request->get('email'), $tokenValue))) {
+ Alert::default(__('password_reset_link_not_sent'))->error();
+ $this->redirectBack();
+ }
- Alert::default(__('password_reset_link_sent'))->success();
- $this->redirectBack();
- }
-
- Alert::default(__('password_reset_link_not_sent'))->error();
+ $token = Token::exists($this->request->queries('email'), TokenDescription::PASSWORD_RESET_TOKEN->value);
+
+ if ($token) {
+ $token->update(['value' => $tokenValue]);
+ } else {
+ $token->fill([
+ 'email'=> $this->request->queries('email'),
+ 'value' => $token,
+ 'expire' => Carbon::now()->addHour()->toDateTimeString(),
+ 'description' => TokenDescription::PASSWORD_RESET_TOKEN->value
+ ])->save();
+ }
+
+ Alert::default(__('password_reset_link_sent'))->success();
$this->redirectBack();
}
@@ -59,10 +55,7 @@ public function reset(): void
$this->response(__('bad_request'), 400);
}
- $token = Token::where('email', $this->request->queries('email'))
- ->and('description', TokenDescription::PASSWORD_RESET_TOKEN->value)
- ->newest()
- ->first();
+ $token = Token::findLatest($this->request->queries('email'), TokenDescription::PASSWORD_RESET_TOKEN->value);
if (!$token || $token->attribute('value') !== $this->request->queries('token')) {
$this->response(__('invalid_password_reset_link'), 400);
diff --git a/app/Http/Middlewares/RememberUser.php b/app/Http/Middlewares/RememberUser.php
index c5ce0adc..9e033ac1 100644
--- a/app/Http/Middlewares/RememberUser.php
+++ b/app/Http/Middlewares/RememberUser.php
@@ -20,7 +20,7 @@ class RememberUser
public function handle(): void
{
if (Cookies::has('user')) {
- $user = User::findBy('email', Cookies::get('user'));
+ $user = (new User())->findBy('email', Cookies::get('user'));
if ($user !== false) {
Session::create('user', $user);
diff --git a/app/Mails/WelcomeMail.php b/app/Mails/WelcomeMail.php
index b5396a37..bf2a988a 100644
--- a/app/Mails/WelcomeMail.php
+++ b/app/Mails/WelcomeMail.php
@@ -16,7 +16,7 @@
*/
class WelcomeMail extends Mailer
{
- public function __construct(string $email, string $username)
+ public function __construct(string $email, string $name)
{
parent::__construct();
@@ -24,6 +24,6 @@ public function __construct(string $email, string $username)
->from(config('mailer.sender.email'), config('mailer.sender.name'))
->reply(config('mailer.sender.email'), config('mailer.sender.name'))
->subject('Welcome')
- ->body(View::getContent('emails.welcome', compact('username')));
+ ->body(View::getContent('emails.welcome', compact('name')));
}
}
diff --git a/bootstrap.php b/bootstrap.php
index c6d49dc4..6495f9f6 100644
--- a/bootstrap.php
+++ b/bootstrap.php
@@ -15,8 +15,8 @@
* Setup application
*/
-define('DS', DIRECTORY_SEPARATOR);
-define('APP_ROOT', __DIR__ . DS);
+const DS = DIRECTORY_SEPARATOR;
+const APP_ROOT = __DIR__ . DS;
set_time_limit(0);
diff --git a/composer.json b/composer.json
index bf011700..f4fd569a 100644
--- a/composer.json
+++ b/composer.json
@@ -1,9 +1,9 @@
{
"name": "eliseekn/tinymvc",
- "description": "TinyMVC is a PHP framework based on MVC architecture that helps you build easily and quickly powerful web applications and REST API.",
+ "description": "TinyMVC is a PHP framework based on MVC architecture that helps you build easily and quickly powerful web applications and RESTful API.",
"type": "project",
"license": "MIT",
- "keywords": ["php", "framework", "mvc"],
+ "keywords": ["php-framework", "mvc"],
"authors": [
{
"name": "N'Guessan Kouadio Elisée",
@@ -20,7 +20,8 @@
],
"psr-4": {
"App\\" : "app/",
- "Core\\" : "core/"
+ "Core\\" : "core/",
+ "Tests\\" : "tests/"
}
},
"config": {
diff --git a/config/console.php b/config/console.php
index 328101a5..64abd787 100644
--- a/config/console.php
+++ b/config/console.php
@@ -10,7 +10,7 @@
* Console commands
*/
- return [
+return [
'core' => [
new \Core\Console\Database\Create(),
new \Core\Console\Database\Delete(),
@@ -28,7 +28,6 @@
new \Core\Console\Make\Validator(),
new \Core\Console\Make\Seed(),
new \Core\Console\Make\Factory(),
- new \Core\Console\Make\Repository(),
new \Core\Console\Make\View(),
new \Core\Console\Make\Mail(),
new \Core\Console\Make\Middleware(),
@@ -56,4 +55,4 @@
'app' => [
//
]
- ];
\ No newline at end of file
+];
\ No newline at end of file
diff --git a/config/errors.php b/config/errors.php
index f7fb896d..ce49fcb7 100644
--- a/config/errors.php
+++ b/config/errors.php
@@ -15,6 +15,7 @@
'log' => true,
'views' => [
+ '403' => 'errors' . DS . '403',
'404' => 'errors' . DS . '404',
'500' => 'errors' . DS . '500'
]
diff --git a/config/mailer.php b/config/mailer.php
index 61e318ac..cbff6e3c 100644
--- a/config/mailer.php
+++ b/config/mailer.php
@@ -15,7 +15,7 @@
'sender' => [
'name' => config('app.name'),
- 'email' => 'tiny@mvc.framework',
+ 'email' => 'no-reply@tiny.mvc',
],
'smtp' => [
diff --git a/config/security.php b/config/security.php
index 3849f9e0..b4c71fa7 100644
--- a/config/security.php
+++ b/config/security.php
@@ -19,7 +19,7 @@
'auth' => [
'max_attempts' => false,
'unlock_timeout' => 1, //in minute
- 'email_verification' => true,
+ 'email_verification' => false,
],
'session' => [
diff --git a/core/Application.php b/core/Application.php
index 7db95709..186b7bfd 100644
--- a/core/Application.php
+++ b/core/Application.php
@@ -31,8 +31,13 @@ public function run(): void
try {
Router::dispatch(new Request(), $response);
} catch (Exception $e) {
- if (config('errors.log')) save_log('Exception: ' . $e);
- if (config('errors.display')) die($e);
+ if (config('errors.log')) {
+ save_log('Exception: ' . $e);
+ }
+
+ if (config('errors.display')) {
+ die($e);
+ }
$response->view(config('errors.views.500'))->send(500);
}
diff --git a/core/Console/Make/Make.php b/core/Console/Make/Make.php
index cf31036f..64b9b835 100644
--- a/core/Console/Make/Make.php
+++ b/core/Console/Make/Make.php
@@ -163,25 +163,7 @@ public static function createFactory(string $factory, ?string $namespace = null)
return $storage->writeFile(self::fixPluralTypo($class, true) . '.php', $data);
}
-
- public static function createRepository(string $repository, ?string $namespace = null): bool
- {
- list($name, $class) = self::generateClass($repository, 'repository', true, true);
- $data = self::stubs()->readFile('Repository.stub');
- $data = self::addNamespace($data, 'App\Database\Repositories', $namespace);
- $data = str_replace('CLASSNAME', self::fixPluralTypo($class, true), $data);
- $data = str_replace('MODELNAME', self::fixPluralTypo(ucfirst($name), true), $data);
-
- $storage = Storage::path(config('storage.repositories'));
-
- if (!is_null($namespace)) {
- $storage = $storage->addPath(str_replace('\\', '/', $namespace));
- }
-
- return $storage->writeFile(self::fixPluralTypo($class, true) . '.php', $data);
- }
-
public static function createHelper(string $helper): bool
{
list(, $class) = self::generateClass($helper, 'helper', true);
diff --git a/core/Console/Make/Repository.php b/core/Console/Make/Repository.php
deleted file mode 100644
index 27fc1261..00000000
--- a/core/Console/Make/Repository.php
+++ /dev/null
@@ -1,47 +0,0 @@
-setDescription('Create new model repository');
- $this->addArgument('repository', InputArgument::REQUIRED|InputArgument::IS_ARRAY, 'The name of model repository table (separated by space if many)');
- $this->addOption('namespace', null, InputOption::VALUE_OPTIONAL, 'Specify namespace (base: App\Database\Repositories)');
- }
-
- protected function execute(InputInterface $input, OutputInterface $output): int
- {
- $repositories = $input->getArgument('repository');
-
- foreach ($repositories as $repository) {
- list(, $class) = Make::generateClass($repository, 'repository', true, true);
-
- if (!Make::createRepository($repository, $input->getOption('namespace'))) {
- $output->writeln('Failed to create repository "' . Make::fixPluralTypo($class, true) . '"');
- }
-
- $output->writeln('repository "' . Make::fixPluralTypo($class, true) . '" has been created');
- }
-
- return Command::SUCCESS;
- }
-}
diff --git a/core/Database/Connection/Connection.php b/core/Database/Connection/Connection.php
index 49f63496..5a860096 100644
--- a/core/Database/Connection/Connection.php
+++ b/core/Database/Connection/Connection.php
@@ -19,9 +19,8 @@ class Connection
/**
* @var \Core\Database\Connection\Connection
*/
- protected static $instance = null;
-
- protected $db;
+ protected static ?Connection $instance = null;
+ protected MySQLConnection|SQLiteConnection $db;
private function __construct()
{
diff --git a/core/Database/Connection/MySQLConnection.php b/core/Database/Connection/MySQLConnection.php
index b1c7568b..76909f4e 100644
--- a/core/Database/Connection/MySQLConnection.php
+++ b/core/Database/Connection/MySQLConnection.php
@@ -39,6 +39,13 @@ public function getPDO(): PDO
return $this->pdo;
}
+ private function getDB(): string
+ {
+ return config('app.env') === 'test'
+ ? config('tests.database.suffix')
+ : config('database.name');
+ }
+
/**
* @throws PDOException
*/
@@ -60,7 +67,7 @@ public function executeQuery(string $query, ?array $args = null): false|PDOState
$stmt = $this->pdo->prepare(trim($query));
$stmt->execute($args);
} catch (PDOException $e) {
- throw new PDOException($e->getMessage(), (int) $e->getCode(), $e->getPrevious());
+ throw new PDOException($e->getMessage(), (int) $e->getCode(), $e->getPrevious());
}
return $stmt;
@@ -97,11 +104,4 @@ public function deleteSchema(string $name): void
{
$this->executeStatement("DROP DATABASE IF EXISTS $name");
}
-
- private function getDB(): string
- {
- return config('app.env') === 'test'
- ? config('tests.database.suffix')
- : config('database.name');
- }
}
diff --git a/core/Database/Connection/SQLiteConnection.php b/core/Database/Connection/SQLiteConnection.php
index 0d78d9bd..8d44b882 100644
--- a/core/Database/Connection/SQLiteConnection.php
+++ b/core/Database/Connection/SQLiteConnection.php
@@ -15,10 +15,7 @@
class SQLiteConnection implements ConnectionInterface
{
- /**
- * @var PDO
- */
- protected $pdo;
+ protected PDO $pdo;
/**
* @throws PDOException
@@ -38,7 +35,12 @@ public function __construct()
}
}
- private function getDB()
+ public function getPDO(): PDO
+ {
+ return $this->pdo;
+ }
+
+ private function getDB(): string
{
if (config('app.env') === 'test') {
return config('storage.sqlite') . config('database.name') . config('tests.database.suffix') . '.db';
@@ -48,11 +50,6 @@ private function getDB()
: config('storage.sqlite') . config('database.name') . '.db';
}
- public function getPDO(): PDO
- {
- return $this->pdo;
- }
-
/**
* @throws PDOException
*/
@@ -82,11 +79,7 @@ public function executeQuery(string $query, ?array $args = null): false|PDOState
public function schemaExists(string $name): bool
{
- if (config('database.sqlite.memory')) {
- return true;
- }
-
- return Storage::path(config('storage.sqlite'))->isFile($name);
+ return config('database.sqlite.memory') || Storage::path(config('storage.sqlite'))->isFile($name);
}
public function tableExists(string $name): bool
@@ -97,17 +90,15 @@ public function tableExists(string $name): bool
public function createSchema(string $name): void
{
- if (config('database.sqlite.memory')) {
- return;
+ if (!config('database.sqlite.memory')) {
+ Storage::path(config('storage.sqlite'))->writeFile($name . '.db', '');
}
- Storage::path(config('storage.sqlite'))->writeFile($name . '.db', '');
}
public function deleteSchema(string $name): void
{
- if (config('database.sqlite.memory')) {
- return;
+ if (!config('database.sqlite.memory')) {
+ Storage::path(config('storage.sqlite'))->deleteFile($name . '.db');
}
- Storage::path(config('storage.sqlite'))->deleteFile($name . '.db');
}
}
diff --git a/core/Database/Factory.php b/core/Database/Factory.php
index 3992bc12..65ae5df9 100644
--- a/core/Database/Factory.php
+++ b/core/Database/Factory.php
@@ -16,20 +16,15 @@
*/
class Factory
{
- protected static $model;
- protected array|Model $class;
- public Generator $faker;
+ protected array $class;
+ protected Generator $faker;
- public function __construct(int $count)
+ public function __construct(string $model, int $count)
{
$this->faker = FakerFactory::create(config('app.lang'));
- if ($count === 1) {
- $this->class = new static::$model();
- } else {
- for ($i = 1; $i <= $count; $i++) {
- $this->class[] = new static::$model();
- }
+ for ($i = 1; $i <= $count; $i++) {
+ $this->class[] = new $model();
}
}
@@ -40,9 +35,9 @@ public function data(): array
public function make(array $data = []): mixed
{
- if (!is_array($this->class)) {
- $this->class->fill(array_merge($this->data(), $data));
- return $this->class;
+ if (count($this->class) === 1) {
+ $this->class[0]->fill(array_merge($this->data(), $data));
+ return $this->class[0];
}
return array_map(function ($model) use ($data) {
diff --git a/core/Database/Migration.php b/core/Database/Migration.php
index 08661e64..7a62ccb4 100644
--- a/core/Database/Migration.php
+++ b/core/Database/Migration.php
@@ -18,7 +18,7 @@ class Migration
/**
* @var \Core\Database\QueryBuilder
*/
- protected static mixed $qb;
+ protected static QueryBuilder $qb;
public static function driver(): string
{
@@ -29,19 +29,19 @@ public static function driver(): string
public static function createTable(string $name): self
{
- self::$qb = QueryBuilder::createTable($name);
+ static::$qb = QueryBuilder::createTable($name);
return new self();
}
public static function addColumn(string $table): self
{
- self::$qb = QueryBuilder::addColumn($table);
+ static::$qb = QueryBuilder::addColumn($table);
return new self();
}
public static function renameColumn(string $table, string $old, string $new): self
{
- self::$qb = QueryBuilder::renameColumn($table, $old, $new);
+ static::$qb = QueryBuilder::renameColumn($table, $old, $new);
return new self();
}
@@ -50,13 +50,13 @@ public static function renameColumn(string $table, string $old, string $new): se
*/
public static function updateColumn(string $table, string $column): self
{
- self::$qb = QueryBuilder::updateColumn($table, $column);
+ static::$qb = QueryBuilder::updateColumn($table, $column);
return new self();
}
public static function deleteColumn(string $table, string $column): self
{
- self::$qb = QueryBuilder::deleteColumn($table, $column);
+ static::$qb = QueryBuilder::deleteColumn($table, $column);
return new self();
}
@@ -72,13 +72,13 @@ public static function dropForeign(string $table, string $name): false|PDOStatem
public static function disableForeignKeyCheck(): false|PDOStatement
{
- $query = self::driver() === 'mysql' ? 'SET foreign_key_checks = 0' : 'PRAGMA foreign_keys = OFF';
+ $query = static::driver() === 'mysql' ? 'SET foreign_key_checks = 0' : 'PRAGMA foreign_keys = OFF';
return QueryBuilder::setQuery($query)->execute();
}
public static function enableForeignKeyCheck(): false|PDOStatement
{
- $query = self::driver() === 'mysql' ? 'SET foreign_key_checks = 1' : 'PRAGMA foreign_keys = ON';
+ $query = static::driver() === 'mysql' ? 'SET foreign_key_checks = 1' : 'PRAGMA foreign_keys = ON';
return QueryBuilder::setQuery($query)->execute();
}
@@ -322,13 +322,8 @@ public function default($default): self
return $this;
}
- public function migrate(bool $table = true)
+ public function migrate(bool $table = true): false|PDOStatement
{
- if ($table) {
- self::$qb->migrate();
- return;
- }
-
- return self::$qb->flush()->execute();
+ return $table ? self::$qb->migrate() : self::$qb->flush()->execute();
}
}
diff --git a/core/Database/Model.php b/core/Database/Model.php
index e0e861a4..718c2467 100644
--- a/core/Database/Model.php
+++ b/core/Database/Model.php
@@ -15,140 +15,111 @@
*/
class Model
{
- protected static $table = '';
- protected array $attributes = [];
+ protected Repository $repository;
- public function __construct($data = [])
+ public function __construct(protected readonly string $table, protected array $attributes = [])
{
- $this->attributes = $data;
+ $this->repository = new Repository($table);
}
- public static function findBy(string $column, $operator = null, $value = null): self|false
+ public function findBy(string $column, $operator = null, $value = null): self|false
{
- return (new Repository(static::$table))->findWhere($column, $operator, $value);
+ return $this->repository->findWhere($column, $operator, $value);
}
- public static function find(int $id): self|false
+ public function find(int $id): self|false
{
- return self::findBy('id', $id);
+ return $this->findBy('id', $id);
}
- public static function all(): array|false
+ public function getAll(): array|false
{
- return (new Repository(static::$table))->selectAll('*');
+ return $this->repository->selectAll('*');
}
- public static function first(): Model|false
+ public function first(): Model|false
{
- return self::select('*')->first();
+ return $this->select('*')->first();
}
- public static function last(): Model|false
+ public function last(): Model|false
{
- return self::select('*')->last();
+ return $this->select('*')->last();
}
- public static function take(int $count, $subquery = null): array|false
+ public function take(int $count, $subquery = null): array|false
{
- return self::select('*')->subQuery($subquery)->take($count);
+ return $this->select('*')->subQuery($subquery)->take($count);
}
- public static function oldest(string $column = 'created_at', $subquery = null): array|false
+ public function oldest(string $column = 'created_at', $subquery = null): array|false
{
- return self::select('*')->subQuery($subquery)->oldest($column)->getAll();
+ return $this->select('*')->subQuery($subquery)->oldest($column)->getAll();
}
- public static function newest(string $column = 'created_at', $subquery = null): array|false
+ public function newest(string $column = 'created_at', $subquery = null): array|false
{
- return self::select('*')->subQuery($subquery)->newest($column)->getAll();
+ return $this->select('*')->subQuery($subquery)->newest($column)->getAll();
}
- public static function latest(string $column = 'id', $subquery = null): array|false
+ public function latest(string $column = 'id', $subquery = null): array|false
{
- return self::select('*')->subQuery($subquery)->latest($column)->getAll();
+ return $this->select('*')->subQuery($subquery)->latest($column)->getAll();
}
- public static function select(array|string $columns): Repository
+ public function select(array|string $columns): Repository
{
- return (new Repository(static::$table))->select($columns);
+ return $this->repository->select($columns);
}
- public static function where(string $column, $operator = null, $value = null): Repository
+ public function where(string $column, $operator = null, $value = null): Repository
{
- return self::select('*')->where($column, $operator, $value);
+ return $this->select('*')->where($column, $operator, $value);
}
- public static function count(string $column = 'id', $subquery = null): mixed
+ public function count(string $column = 'id', $subquery = null): mixed
{
- $data = (new Repository(static::$table))->count($column)->subQuery($subquery)->get();
-
- if (!$data) {
- return false;
- }
-
- return $data->attribute('value');
+ $data = $this->repository->count($column)->subQuery($subquery)->get();
+ return !$data ? false : $data->attribute('value');
}
- public static function sum(string $column, $subquery = null): mixed
+ public function sum(string $column, $subquery = null): mixed
{
- $data = (new Repository(static::$table))->sum($column)->subQuery($subquery)->get();
-
- if (!$data) {
- return false;
- }
-
- return $data->attribute('value');
+ $data = $this->repository->sum($column)->subQuery($subquery)->get();
+ return !$data ? false : $data->attribute('value');
}
- public static function max(string $column, $subquery = null): mixed
+ public function max(string $column, $subquery = null): mixed
{
- $data = (new Repository(static::$table))->max($column)->subQuery($subquery)->get();
-
- if (!$data) {
- return false;
- }
-
- return $data->attribute('value');
+ $data = $this->repository->max($column)->subQuery($subquery)->get();
+ return !$data ? false : $data->attribute('value');
}
- public static function min(string $column, $subquery = null): mixed
+ public function min(string $column, $subquery = null): mixed
{
- $data = (new Repository(static::$table))->min($column)->subQuery($subquery)->get();
-
- if (!$data) {
- return false;
- }
-
- return $data->attribute('value');
+ $data = $this->repository->min($column)->subQuery($subquery)->get();
+ return !$data ? false : $data->attribute('value');
}
- public static function metrics(string $column, string $type, string $period, int $interval = 0, ?array $query = null): mixed
+ public function metrics(string $column, string $type, string $period, int $interval = 0, ?array $query = null): mixed
{
- return (new Repository(static::$table))->metrics($column, $type, $period, $interval, $query);
+ return $this->repository->metrics($column, $type, $period, $interval, $query);
}
- public static function trends(string $column, string $type, string $period, int $interval = 0, ?array $query = null): array
+ public function trends(string $column, string $type, string $period, int $interval = 0, ?array $query = null): array
{
- return (new Repository(static::$table))->trends($column, $type, $period, $interval, $query);
+ return $this->repository->trends($column, $type, $period, $interval, $query);
}
- public static function create(array $data): self|false
+ public function create(array $data): self|false
{
- $id = (new Repository(static::$table))->insertGetId($data);
-
- if (is_null($id)) {
- return false;
- }
-
- return self::find($id);
+ $id = $this->repository->insertGetId($data);
+ return is_null($id) ? false : $this->find($id);
}
- /**
- * Delete all rows
- */
- public static function truncate(): false|PDOStatement
+ public function truncate(): false|PDOStatement
{
- return (new Repository(static::$table))->delete()->execute();
+ return $this->repository->delete()->execute();
}
public function getId(): int
@@ -157,27 +128,19 @@ public function getId(): int
}
/**
- * Get relationship of the model
- *
- * @param string $table
- * @param mixed $column
- * @return \Core\Database\Repository
+ * Get relationship of the model
*/
public function has(string $table, ?string $column = null): Repository
{
if (is_null($column)) {
- $column = $this->getColumnFromTable(static::$table);
+ $column = $this->getColumnFromTable($this->table);
}
return (new Repository($table))->select('*')->where($column, $this->attributes['id']);
}
-
+
/**
* Get relationship belongs to the model
- *
- * @param string $table
- * @param mixed $column
- * @return \Core\Database\Repository
*/
public function belongsTo(string $table, ?string $column = null): Repository
{
@@ -188,43 +151,41 @@ public function belongsTo(string $table, ?string $column = null): Repository
return (new Repository($table))->select('*')->where('id', $this->attributes[$column]);
}
- public function attribute(string $key, $value = null): mixed
+ public function attribute(string $key): mixed
{
- if (!is_null($value)) {
- $this->attributes[$key] = $value;
- }
-
return $this->attributes[$key];
}
-
- /**
- * Fill model attributes with custom data
- *
- * @param array $data
- * @return void
- */
- public function fill(array $data): void
+
+ public function fill(array $data): self
{
foreach ($data as $key => $value) {
$this->attributes[$key] = $value;
}
+
+ return $this;
}
- public function update(array $data): false|self
+ public function update(array $data): bool
{
- return !(new Repository(static::$table))->updateIfExists($this->attributes['id'], $data) ? false : $this;
+ return $this->repository->updateIfExists($this->attributes['id'], $data);
}
public function delete(): bool
{
- return (new Repository(static::$table))->deleteIfExists($this->attributes['id']);
+ return $this->repository->deleteIfExists($this->attributes['id']);
}
- public function save(): self|false
+ public function save(): Model|false
{
- return empty($this->attributes['id'])
- ? self::create($this->attributes)
- : $this->update($this->attributes);
+ if (empty($this->attributes['id'])) {
+ return $this->create($this->attributes);
+ }
+
+ if ($this->update($this->attributes)) {
+ return $this->find($this->attributes['id']);
+ }
+
+ return false;
}
public function increment(string $column, $value = null): void
diff --git a/core/Database/QueryBuilder.php b/core/Database/QueryBuilder.php
index 519edd2f..91d90546 100644
--- a/core/Database/QueryBuilder.php
+++ b/core/Database/QueryBuilder.php
@@ -17,11 +17,11 @@
*/
class QueryBuilder
{
- protected static string $query = '';
- protected static array $args = [];
- protected static mixed $table;
+ protected static string $query;
+ protected static $args;
+ protected static string $table;
- protected static function setTable(string $name): string
+ public static function setTable(string $name): string
{
if (config('app.env') === 'test') {
if (config('tests.database.driver') === 'sqlite') {
@@ -47,7 +47,7 @@ public function driver(): string
public static function table(string $name): self
{
- static::$table = self::setTable($name);
+ self::$table = self::setTable($name);
return new self();
}
@@ -114,7 +114,7 @@ public function select(array|string $columns): self
}
self::$query = rtrim(self::$query, ', ');
- self::$query .= ' FROM ' . static::$table;
+ self::$query .= ' FROM ' . self::$table;
return $this;
}
@@ -123,7 +123,7 @@ public function selectRaw(string $query, array $args = []): self
{
self::$query = 'SELECT ' . $query;
self::$args = array_merge(self::$args, $args);
- self::$query .= ' FROM ' . static::$table;
+ self::$query .= ' FROM ' . self::$table;
return $this;
}
@@ -135,7 +135,7 @@ public function selectWhere(string $column, $operator = null, $value = null): se
public function insert(array $items): self
{
- self::$query = "INSERT INTO " . static::$table . " (";
+ self::$query = "INSERT INTO " . self::$table . " (";
foreach ($items as $key => $value) {
self::$query .= "{$key}, ";
@@ -157,7 +157,7 @@ public function insert(array $items): self
public function update(array $items): self
{
- self::$query = "UPDATE " . static::$table . " SET ";
+ self::$query = "UPDATE " . self::$table . " SET ";
if (config('database.timestamps')) {
$items = array_merge($items, ['updated_at' => Carbon::now()->toDateTimeString()]);
@@ -174,7 +174,7 @@ public function update(array $items): self
public function delete(): self
{
- self::$query = "DELETE FROM " . static::$table;
+ self::$query = "DELETE FROM " . self::$table;
return $this;
}
@@ -589,7 +589,7 @@ public static function lastInsertedId(): int
return Connection::getInstance()->getPDO()->lastInsertId();
}
- private function trimQuery(): void
+ public function trimQuery(): void
{
self::$query = trim(self::$query);
self::$query = str_replace(' ', ' ', self::$query);
diff --git a/core/Database/Repository.php b/core/Database/Repository.php
index 2efd8b57..72fee800 100644
--- a/core/Database/Repository.php
+++ b/core/Database/Repository.php
@@ -8,6 +8,7 @@
namespace Core\Database;
+use Core\Exceptions\InvalidSQLQueryException;
use Core\Support\Pager;
use Core\Support\Metrics;
use PDOStatement;
@@ -19,7 +20,7 @@ class Repository
{
protected QueryBuilder $qb;
- public function __construct(private readonly string $table) {}
+ public function __construct(protected readonly string $table) {}
public function select(array|string $columns): self
{
@@ -138,11 +139,7 @@ public function insert(array $items): bool
public function insertGetId(array $items): int|null
{
- if (!$this->insert($items)) {
- return null;
- }
-
- return QueryBuilder::lastInsertedId();
+ return !$this->insert($items) ? null : QueryBuilder::lastInsertedId();
}
public function update(array $items): self
@@ -231,8 +228,7 @@ public function trends(string $column, string $type, string $period, int $interv
public function where(string $column, $operator = null, $value = null): self
{
if (is_null($operator) && is_null($value)) {
- $this->qb->whereColumn($column)->isNull();
- return $this;
+ throw new InvalidSQLQueryException();
}
if (!is_null($operator) && is_null($value)) {
@@ -293,8 +289,7 @@ public function where(string $column, $operator = null, $value = null): self
public function and(string $column, $operator = null, $value = null): self
{
if (is_null($operator) && is_null($value)) {
- $this->qb->whereColumn($column)->isNull();
- return $this;
+ throw new InvalidSQLQueryException();
}
if (!is_null($operator) && is_null($value)) {
@@ -355,8 +350,7 @@ public function and(string $column, $operator = null, $value = null): self
public function or(string $column, $operator = null, $value = null): self
{
if (is_null($operator) && is_null($value)) {
- $this->qb->whereColumn($column)->isNull();
- return $this;
+ throw new InvalidSQLQueryException();
}
if (!is_null($operator) && is_null($value)) {
@@ -596,7 +590,7 @@ public function first(): Model|false
public function last(): Model|false
{
$rows = $this->getAll();
- return !$rows ? false : end($rows);
+ return $rows && end($rows);
}
public function range(int $start, int $end): array|false
@@ -627,13 +621,13 @@ public function paginate(int $items_per_page, int $page = 1): Pager
public function get(): Model|false
{
$row = $this->execute()->fetch();
- return !$row ? false : new Model((array) $row);
+ return !$row ? false : new Model($this->table, (array) $row);
}
public function getAll(): array|false
{
$rows = $this->execute()->fetchAll();
- return !$rows ? false : array_map(fn ($row) => new Model((array) $row), $rows);
+ return !$rows ? false : array_map(fn ($row) => new Model($this->table, (array) $row), $rows);
}
public function toSQL(): array
diff --git a/core/Exceptions/InvalidSQLQueryException.php b/core/Exceptions/InvalidSQLQueryException.php
new file mode 100644
index 00000000..76a4b41f
--- /dev/null
+++ b/core/Exceptions/InvalidSQLQueryException.php
@@ -0,0 +1,21 @@
+filled($item) ? $this->inputs($item) : $default;
}
- /**
- * Set POST/GET item value
- */
public function set(string $item, $value): void
{
if (isset($_POST[$item])) {
diff --git a/core/Routing/Route.php b/core/Routing/Route.php
index 9f88a365..abf5dbe7 100644
--- a/core/Routing/Route.php
+++ b/core/Routing/Route.php
@@ -18,49 +18,49 @@
class Route
{
protected static string $route;
- public static array $routes = [];
protected static array $tmp_routes = [];
+ public static array $routes = [];
private static function add(string $route, $handler): self
{
- static::$route = static::format($route);
+ static::$route = self::format($route);
static::$tmp_routes[static::$route] = ['handler' => $handler];
- return new static();
+ return new self();
}
public static function get(string $uri, $handler): self
{
- return static::add('GET ' . $uri, $handler);
+ return self::add('GET ' . $uri, $handler);
}
public static function post(string $uri, $handler): self
{
- return static::add('POST ' . $uri, $handler);
+ return self::add('POST ' . $uri, $handler);
}
public static function delete(string $uri, $handler): self
{
- return static::add('DELETE ' . $uri, $handler);
+ return self::add('DELETE ' . $uri, $handler);
}
public static function options(string $uri, $handler): self
{
- return static::add('OPTIONS ' . $uri, $handler);
+ return self::add('OPTIONS ' . $uri, $handler);
}
public static function patch(string $uri, $handler): self
{
- return static::add('PATCH ' . $uri, $handler);
+ return self::add('PATCH ' . $uri, $handler);
}
public static function put(string $uri, $handler): self
{
- return static::add('PUT ' . $uri, $handler);
+ return self::add('PUT ' . $uri, $handler);
}
public static function any(string $uri, $handler): self
{
- return static::add('GET|POST|DELETE|PUT|OPTIONS|PATCH ' . $uri, $handler);
+ return self::add('GET|POST|DELETE|PUT|OPTIONS|PATCH ' . $uri, $handler);
}
public static function all(string $name, string $controller, array $excepts = []): self
@@ -68,28 +68,28 @@ public static function all(string $name, string $controller, array $excepts = []
return self::group(function() use ($name, $excepts) {
if (!in_array('index', $excepts)) self::get('/' . $name, 'index')->name('index');
if (!in_array('store', $excepts)) self::post('/' . $name, 'store')->name('store');
- if (!in_array('update', $excepts)) self::match('PATCH|PUT', '/' . $name . '/{id:num}', 'update')->name('update');
- if (!in_array('show', $excepts)) self::get('/' . $name . '/{id:num}', 'show')->name('show');
- if (!in_array('edit', $excepts)) self::get('/' . $name . '/{id:num}/edit', 'edit')->name('edit');
- if (!in_array('delete', $excepts)) self::delete('/' . $name . '/{id:num}', 'delete')->name('delete');
+ if (!in_array('update', $excepts)) self::match('PATCH|PUT', '/' . $name . '/{id:int}', 'update')->name('update');
+ if (!in_array('show', $excepts)) self::get('/' . $name . '/{id:int}', 'show')->name('show');
+ if (!in_array('edit', $excepts)) self::get('/' . $name . '/{id:int}/edit', 'edit')->name('edit');
+ if (!in_array('delete', $excepts)) self::delete('/' . $name . '/{id:int}', 'delete')->name('delete');
})->byController($controller)->byName($name);
}
public static function match(string $methods, string $uri, $handler): self
{
- return static::add($methods . ' ' . $uri, $handler);
+ return self::add($methods . ' ' . $uri, $handler);
}
public static function view(string $uri, string $view, array $params = []): self
{
- return static::get($uri, function (Response $response) use ($view, $params) {
+ return self::get($uri, function (Response $response) use ($view, $params) {
$response->view($view, $params)->send();
});
}
public function name(string $name): self
{
- static::$tmp_routes[static::$route]['name'] = $name;
+ self::$tmp_routes[self::$route]['name'] = $name;
return $this;
}
@@ -102,7 +102,7 @@ public static function group($callback): self
public function middleware(array|string $middlewares): self
{
$middlewares = parse_array($middlewares);
- static::$tmp_routes[static::$route]['middlewares'] = $middlewares;
+ self::$tmp_routes[self::$route]['middlewares'] = $middlewares;
return $this;
}
@@ -110,11 +110,11 @@ public function byMiddleware(array|string $middlewares): self
{
$middlewares = parse_array($middlewares);
- foreach (static::$tmp_routes as $route => $options) {
+ foreach (self::$tmp_routes as $route => $options) {
if (isset($options['middlewares'])) {
- static::$tmp_routes[$route]['middlewares'] = array_merge($middlewares, $options['middlewares']);
+ self::$tmp_routes[$route]['middlewares'] = array_merge($middlewares, $options['middlewares']);
} else {
- static::$tmp_routes[$route]['middlewares'] = $middlewares;
+ self::$tmp_routes[$route]['middlewares'] = $middlewares;
}
}
@@ -125,12 +125,12 @@ public function byPrefix(string $prefix): self
{
if ($prefix[-1] === '/') $prefix = rtrim($prefix, '/');
- foreach (static::$tmp_routes as $route => $options) {
+ foreach (self::$tmp_routes as $route => $options) {
list($method, $uri) = explode(' ', $route, 2);
$_route = implode(' ', [$method, $prefix . $uri]);
- $_route = static::format($_route);
- static::$tmp_routes = static::update($route, $_route);
+ $_route = self::format($_route);
+ self::$tmp_routes = self::update($route, $_route);
}
return $this;
@@ -138,11 +138,11 @@ public function byPrefix(string $prefix): self
public function byName(string $name): self
{
- foreach (static::$tmp_routes as $route => $options) {
+ foreach (self::$tmp_routes as $route => $options) {
if (isset($options['name'])) {
- static::$tmp_routes[$route]['name'] = $name . '.' . $options['name'];
+ self::$tmp_routes[$route]['name'] = $name . '.' . $options['name'];
} else {
- static::$tmp_routes[$route]['name'] = $name;
+ self::$tmp_routes[$route]['name'] = $name;
}
}
@@ -151,11 +151,11 @@ public function byName(string $name): self
public function byController(string $controller): self
{
- foreach (static::$tmp_routes as $route => $options) {
+ foreach (self::$tmp_routes as $route => $options) {
if (isset($options['handler'])) {
- static::$tmp_routes[$route]['handler'] = [$controller, $options['handler']];
+ self::$tmp_routes[$route]['handler'] = [$controller, $options['handler']];
} else {
- static::$tmp_routes[$route]['handler'] = $controller;
+ self::$tmp_routes[$route]['handler'] = $controller;
}
}
@@ -164,10 +164,10 @@ public function byController(string $controller): self
public function register(): void
{
- if (empty(static::$tmp_routes)) return;
+ if (empty(self::$tmp_routes)) return;
- static::$routes += static::$tmp_routes;
- static::$tmp_routes = [];
+ self::$routes += self::$tmp_routes;
+ self::$tmp_routes = [];
}
private static function format(string $route): string
@@ -185,7 +185,7 @@ private static function format(string $route): string
$uri = preg_replace('/{([a-zA-Z-_]+)}/i', 'any', $uri);
$uri = preg_replace('/{([a-zA-Z-_]+):([^\}]+)}/i', '$2', $uri);
$uri = preg_replace('/\bstr\b/', '([a-zA-Z-_]+)', $uri);
- $uri = preg_replace('/\bnum\b/', '(\d+)', $uri);
+ $uri = preg_replace('/\bint\b/', '(\d+)', $uri);
$uri = preg_replace('/\bany\b/', '([^/]+)', $uri);
return implode(' ', [$method, $uri]);
@@ -198,11 +198,11 @@ private static function format(string $route): string
*/
private static function update(string $old, string $new): array
{
- $array_keys = array_keys(static::$tmp_routes);
+ $array_keys = array_keys(self::$tmp_routes);
$old_key_index = array_search($old, $array_keys);
$array_keys[$old_key_index] = $new;
- return array_combine($array_keys, static::$tmp_routes);
+ return array_combine($array_keys, self::$tmp_routes);
}
public static function load(): void
diff --git a/core/Routing/Router.php b/core/Routing/Router.php
index fa014cc2..a7ec597c 100644
--- a/core/Routing/Router.php
+++ b/core/Routing/Router.php
@@ -24,7 +24,7 @@
*/
class Router
{
- private static function match(Request $request, string $method, string $route, &$params): bool
+ protected static function match(Request $request, string $method, string $route, &$params): bool
{
if (
!preg_match('/' . strtoupper($method) . '/', strtoupper($request->method())) ||
@@ -38,7 +38,7 @@ private static function match(Request $request, string $method, string $route, &
return true;
}
- private static function executeMiddlewares(array $middlewares): void
+ protected static function executeMiddlewares(array $middlewares): void
{
foreach ($middlewares as $middleware) {
$middleware = config('middlewares.' . $middleware);
@@ -51,7 +51,7 @@ private static function executeMiddlewares(array $middlewares): void
}
}
- private static function executeHandler($handler, array $params): mixed
+ protected static function executeHandler($handler, array $params): mixed
{
if ($handler instanceof Closure) {
return (new DependencyInjection())->resolveClosure($handler, $params);
@@ -109,6 +109,6 @@ public static function dispatch(Request $request, Response $response): void
}
}
- $response->view(view: config('errors.views.404'))->send(404);
+ $response->view(config('errors.views.404'))->send(404);
}
}
diff --git a/core/Stubs/Controller.stub b/core/Stubs/Controller.stub
index 3b8429f7..deb7d908 100644
--- a/core/Stubs/Controller.stub
+++ b/core/Stubs/Controller.stub
@@ -14,6 +14,6 @@ class CLASSNAME extends Controller
{
public function __invoke(): void
{
- $this->data('Hello from CLASSNAME');
+ $this->response('Hello from CLASSNAME');
}
}
diff --git a/core/Stubs/Factory.stub b/core/Stubs/Factory.stub
index f66ee051..6e77bbe7 100644
--- a/core/Stubs/Factory.stub
+++ b/core/Stubs/Factory.stub
@@ -13,11 +13,9 @@ use Core\Database\Factory;
class CLASSNAME extends Factory
{
- protected static $model = MODELNAME::class;
-
public function __construct(int $count = 1)
{
- parent::__construct($count);
+ parent::__construct(MODELNAME::class, $count);
}
public function data(): array
diff --git a/core/Stubs/Model.stub b/core/Stubs/Model.stub
index 9340303f..16c37245 100644
--- a/core/Stubs/Model.stub
+++ b/core/Stubs/Model.stub
@@ -12,5 +12,23 @@ use Core\Database\Model;
class CLASSNAME extends Model
{
- protected static $table = 'TABLENAME';
+ public function __construct()
+ {
+ parent::__construct('TABLENAME');
+ }
+
+ public static function findById(string $id): Model|false
+ {
+ return (new self())->find($id);
+ }
+
+ public static function findByColumn(string $column): Model|false
+ {
+ return (new self())->findBy('column', $column);
+ }
+
+ public static function findLatest(): array|false
+ {
+ return (new self())->latest();
+ }
}
diff --git a/core/Stubs/Repository.stub b/core/Stubs/Repository.stub
deleted file mode 100644
index ec816113..00000000
--- a/core/Stubs/Repository.stub
+++ /dev/null
@@ -1,19 +0,0 @@
-latest()->getAll();
- }
-}
diff --git a/core/Stubs/actions/destroy.stub b/core/Stubs/actions/destroy.stub
index 45e3fbc5..518f46a5 100644
--- a/core/Stubs/actions/destroy.stub
+++ b/core/Stubs/actions/destroy.stub
@@ -15,11 +15,6 @@ class CLASSNAME
public function handle(int $id): bool
{
$MODELNAME = MODELNAME::find($id);
-
- if ($MODELNAME === false) {
- return false;
- }
-
- return $MODELNAME->delete();
+ return !$MODELNAME || $MODELNAME->delete();
}
}
diff --git a/core/Stubs/actions/update.stub b/core/Stubs/actions/update.stub
index f6145665..feffc1b3 100644
--- a/core/Stubs/actions/update.stub
+++ b/core/Stubs/actions/update.stub
@@ -16,12 +16,6 @@ class CLASSNAME
public function handle(array $data, int $id): Model|false
{
$MODELNAME = MODELNAME::find($id);
-
- if ($MODELNAME === false) {
- return false;
- }
-
- $MODELNAME->fill($data);
- return $MODELNAME->save();
+ return !$MODELNAME ? false : $MODELNAME->fill($data)->save();
}
}
diff --git a/core/Support/Auth.php b/core/Support/Auth.php
index 9bc40542..a050e2f3 100644
--- a/core/Support/Auth.php
+++ b/core/Support/Auth.php
@@ -41,7 +41,7 @@ public static function attempt(Response $response, Request $request): bool
}
Session::forget(['auth_attempts', 'auth_attempts_timeout']);
- Session::create('user', $user);
+ Session::create('user', $user->toArray());
if ($request->hasInput('remember')) {
Cookies::create('user', $user->attribute('email'), 3600 * 24 * 365);
@@ -53,20 +53,18 @@ public static function attempt(Response $response, Request $request): bool
public static function checkCredentials(string $email, string $password, &$user): bool
{
if (filter_var($email, FILTER_VALIDATE_EMAIL) !== false) {
- $user = User::findBy('email', $email);
+ $user = User::findByEmail($email);
return $user !== false && Encryption::check($password, $user->attribute('password'));
}
- $users = User::where('email', 'like', $email)->getAll();
+ $users = User::findAllWhereEmailLike($email);
- if (!$users) {
- return false;
- }
-
- foreach ($users as $u) {
- if (Encryption::check($password, $u->attribute('password'))) {
- $user = $u;
- return true;
+ if ($users) {
+ foreach ($users as $u) {
+ if (Encryption::check($password, $u->attribute('password'))) {
+ $user = $u;
+ return true;
+ }
}
}
@@ -75,15 +73,14 @@ public static function checkCredentials(string $email, string $password, &$user)
public static function checkToken(string $token, &$user): bool
{
- $token = Token::findBy('value', $token);
- $user = User::findBy('email', $token->attribute('email'));
-
+ $token = Token::findByValue($token);
+ $user = User::findByEmail($token->attribute('email'));
return $user !== false;
}
public static function createToken(string $email): string
{
- $token = Token::create([
+ $token = (new Token())->create([
'email' => $email,
'value' => generate_token(),
]);
diff --git a/core/Support/TwigExtensions.php b/core/Support/TwigExtensions.php
index 81724fb6..3978012e 100644
--- a/core/Support/TwigExtensions.php
+++ b/core/Support/TwigExtensions.php
@@ -66,6 +66,7 @@ public function getFunctions(): array
return $this->getCustomFunctions() + [
new TwigFunction('auth_attempts_exceeded', 'auth_attempts_exceeded'),
new TwigFunction('auth', 'auth'),
+ new TwigFunction('method_input', 'method_input'),
new TwigFunction('csrf_token_input', 'csrf_token_input'),
new TwigFunction('csrf_token_meta', 'csrf_token_meta'),
new TwigFunction('url', 'url'),
@@ -79,7 +80,8 @@ public function getFunctions(): array
new TwigFunction('__', '__'),
new TwigFunction('env', 'env'),
new TwigFunction('date', 'date'),
- new TwigFunction('method_input', 'method_input'),
+ new TwigFunction('session_has', 'session_has'),
+ new TwigFunction('cookie_has', 'cookie_has'),
];
}
}
diff --git a/core/Testing/ApplicationTestCase.php b/core/Testing/ApplicationTestCase.php
index d4118492..32e778f6 100644
--- a/core/Testing/ApplicationTestCase.php
+++ b/core/Testing/ApplicationTestCase.php
@@ -8,6 +8,9 @@
namespace Core\Testing;
+use App\Database\Models\User;
+use Core\Database\Model;
+use Core\Routing\View;
use Core\Support\Auth;
use Core\Database\Repository;
use CURLFile;
@@ -67,10 +70,14 @@ protected function getHeaders(?string $key = null): mixed
return is_null($key) ? $headers : $headers[$key][0];
}
- protected function getSession(): mixed
+ protected function getSession(?string $key = null): mixed
{
- return !array_key_exists('session', $this->getHeaders()) ? []
- : json_decode($this->getHeaders('session'), true);
+ if (!array_key_exists('session', $this->getHeaders())) {
+ return [];
+ }
+
+ $data = json_decode($this->getHeaders('session'), true);
+ return is_null($key) ? $data : $data[$key];
}
protected function setHeaders(array $headers): array
@@ -78,15 +85,12 @@ protected function setHeaders(array $headers): array
return array_merge($this->headers, $headers);
}
- protected function getSessionKey(string $name): string
+ protected function sessionKey(string $name): string
{
return strtolower(config('app.name')) . '_' . $name;
}
- /**
- * @param \Core\Database\Model|\App\Database\Models\User $user
- */
- public function auth(mixed $user): self
+ public function auth(User|Model $user): self
{
$this->token = Auth::createToken($user->attribute('email'));
$this->headers = array_merge($this->headers, ['Authorization' => "Bearer $this->token"]);
@@ -102,6 +106,7 @@ public function createFileUpload(string $filename, ?string $mime_type = null, ?s
public function get(string $uri, array $headers = []): self
{
$this->client = Client::get($this->url($uri), $this->setHeaders($headers));
+ save_log($this->getBody());
return $this;
}
@@ -192,6 +197,16 @@ public function assertNotRedirectedToUrl(string $expected): void
$this->assertNotEquals($expected, $this->getHeaders('location'));
}
+ public function assertView(string $view): void
+ {
+ $this->assertEquals($this->getBody(), View::getContent($view));
+ }
+
+ public function assertNotView(string $view): void
+ {
+ $this->assertNotEquals($this->getBody(), View::getContent($view));
+ }
+
public function assertDatabaseHas(string $table, array $expected): void
{
$result = (new Repository($table))->findMany($expected, 'and')->exists();
@@ -206,40 +221,40 @@ public function assertDatabaseDoesNotHave(string $table, array $expected): void
public function assertSessionExists(string $expected): void
{
- $this->assertTrue(array_key_exists($this->getSessionKey($expected), $this->getSession()));
+ $this->assertTrue(array_key_exists($this->sessionKey($expected), $this->getSession()));
}
public function assertSessionDoesNotExists(string $expected): void
{
- $this->assertFalse(array_key_exists($this->getSessionKey($expected), $this->getSession()));
+ $this->assertFalse(array_key_exists($this->sessionKey($expected), $this->getSession()));
}
public function assertSessionHas(string $key, $value): void
{
- if (!array_key_exists($this->getSessionKey($key), $this->getSession())) {
+ if (!array_key_exists($this->sessionKey($key), $this->getSession())) {
$this->assertFalse(false);
} else {
- $this->assertEquals($value, $this->getSession()[$this->getSessionKey($key)]);
+ $this->assertEquals($value, $this->getSession($this->sessionKey($key)));
}
}
public function assertSessionDoesNotHave(string $key, $value): void
{
- if (!array_key_exists($this->getSessionKey($key), $this->getSession())) {
+ if (!array_key_exists($this->sessionKey($key), $this->getSession())) {
$this->assertFalse(false);
} else {
- $this->assertNotEquals($value, $this->getSession()[$this->getSessionKey($key)]);
+ $this->assertNotEquals($value, $this->getSession($this->sessionKey($key)));
}
}
public function assertSessionHasErrors(): void
{
- $this->assertFalse(empty($this->getSession()[$this->getSessionKey('errors')]));
+ $this->assertFalse(empty($this->getSession()[$this->sessionKey('errors')]));
}
public function assertSessionDoesNotHaveErrors(): void
{
- $this->assertTrue(empty($this->getSession()[$this->getSessionKey('errors')]));
+ $this->assertTrue(empty($this->getSession()[$this->sessionKey('errors')]));
}
public function dump(): void
diff --git a/routes/api.php b/routes/api.php
index 29d21eb7..b0f9d6de 100644
--- a/routes/api.php
+++ b/routes/api.php
@@ -14,4 +14,6 @@
Route::group(function () {
//
-})->byPrefix('api')->register();
+})
+ ->byPrefix('api')
+ ->register();
diff --git a/routes/auth.php b/routes/auth.php
index 5643e431..7422f211 100644
--- a/routes/auth.php
+++ b/routes/auth.php
@@ -22,26 +22,40 @@
Route::group(function () {
Route::get('/login', [LoginController::class, 'index']);
Route::get('/signup', [RegisterController::class, 'index']);
-})->byMiddleware('remember')->register();
+})
+ ->byMiddleware('remember')
+ ->register();
Route::group(function () {
Route::post('/authenticate', [LoginController::class, 'authenticate']);
Route::post('/register', [RegisterController::class, 'register']);
-})->byMiddleware('csrf')->register();
+})
+ ->byMiddleware('csrf')
+ ->register();
Route::group(function () {
Route::group(function () {
Route::get('/reset', 'reset');
- Route::post('/notify', 'notify')->middleware('csrf');
- Route::post('/update', 'update')->middleware('csrf');
+
+ Route::group(function () {
+ Route::post('/notify', 'notify');
+ Route::post('/update', 'update');
+ })->byMiddleware('csrf');
})->byController(ForgotPasswordController::class);
Route::view('/forgot', 'auth.password.forgot');
-})->byPrefix('password')->register();
+})
+ ->byPrefix('password')
+ ->register();
Route::group(function () {
Route::get('/verify', 'verify');
Route::get('/notify', 'notify');
-})->byPrefix('email')->byController(EmailVerificationController::class)->register();
-
-Route::post('/logout', LogoutController::class)->middleware('auth')->register();
+})
+ ->byPrefix('email')
+ ->byController(EmailVerificationController::class)
+ ->register();
+
+Route::post('/logout', LogoutController::class)
+ ->middleware('auth')
+ ->register();
diff --git a/tests/Application/Auth/EmailVerificationTest.php b/tests/Application/Auth/EmailVerificationTest.php
index 5ac7a203..175dbb30 100644
--- a/tests/Application/Auth/EmailVerificationTest.php
+++ b/tests/Application/Auth/EmailVerificationTest.php
@@ -10,6 +10,7 @@
use App\Database\Models\User;
use App\Database\Models\Token;
+use App\Enums\TokenDescription;
use Core\Testing\ApplicationTestCase;
use App\Database\Factories\UserFactory;
use App\Database\Factories\TokenFactory;
@@ -22,9 +23,12 @@ class EmailVerificationTest extends ApplicationTestCase
public function test_can_verify_email(): void
{
$user = (new UserFactory())->create(['email_verified' => null]);
- $token = (new TokenFactory())->create(['email' => $user->attribute('email')]);
+ $token = (new TokenFactory())->create([
+ 'email' => $user->attribute('email'),
+ 'description' => TokenDescription::EMAIL_VERIFICATION_TOKEN->value
+ ]);
- $client = $this->get('/email/verify?email=' . $token->attribute('email') . '&token=' . $token->attribute('value'));
+ $client = $this->get('/email/verify?email=' . $user->attribute('email') . '&token=' . $token->attribute('value'));
$client->assertRedirectedToUrl(url('login'));
$this->assertDatabaseDoesNotHave('tokens', $token->toArray());
}
diff --git a/tests/Application/Auth/PasswordForgotTest.php b/tests/Application/Auth/PasswordForgotTest.php
index 09b2ea47..2f61a66d 100644
--- a/tests/Application/Auth/PasswordForgotTest.php
+++ b/tests/Application/Auth/PasswordForgotTest.php
@@ -8,6 +8,7 @@
namespace Tests\Application\Auth;
+use App\Enums\TokenDescription;
use Core\Support\Encryption;
use App\Database\Models\User;
use App\Database\Models\Token;
@@ -23,10 +24,12 @@ class PasswordForgotTest extends ApplicationTestCase
public function test_can_reset_password(): void
{
$user = (new UserFactory())->create();
- $token = (new TokenFactory())->create(['email' => $user->attribute('email')]);
+ $token = (new TokenFactory())->create([
+ 'email' => $user->attribute('email'),
+ 'description' => TokenDescription::PASSWORD_RESET_TOKEN->value
+ ]);
- $client = $this->get('/password/reset?email=' . $token->attribute('email') . '&token=' . $token->attribute('value'));
- $client->assertRedirectedToUrl(url('/password/new', ['email' => $token->attribute('email')]));
+ $this->get('/password/reset?email=' . $user->attribute('email') . '&token=' . $token->attribute('value'));
$this->assertDatabaseDoesNotHave('tokens', $token->toArray());
}
@@ -35,11 +38,10 @@ public function test_can_update_password(): void
$user = (new UserFactory())->create();
$client = $this->post('/password/update', [
'email' => $user->attribute('email'),
- 'password' => 'new_password']
- );
- $client->assertRedirectedToUrl(url('login'));
+ 'password' => 'new_password'
+ ]);
- $user = User::find($user->getId());
- $this->assertTrue(Encryption::check('new_password', $user->attribute('password')));
+ $client->assertRedirectedToUrl(url('login'));
+ $this->assertTrue(Encryption::check('new_password', hash_pwd('new_password')));
}
}
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
index 7511b6ae..42c806fa 100644
--- a/tests/bootstrap.php
+++ b/tests/bootstrap.php
@@ -6,6 +6,6 @@
* @link https://github.com/eliseekn/tinymvc
*/
-namespace Tests\Application;
+namespace Tests;
require_once 'bootstrap.php';
diff --git a/views/errors/403.html.twig b/views/errors/403.html.twig
new file mode 100644
index 00000000..bfd57440
--- /dev/null
+++ b/views/errors/403.html.twig
@@ -0,0 +1,13 @@
+{% extends "layouts/error.html.twig" %}
+
+{% block error_description %}
+ Forbidden
+{% endblock %}
+
+{% block error_title %}
+ Error 403: Forbidden
+{% endblock %}
+
+{% block error_message %}
+ You don't have permission to access on this server
+{% endblock %}