A tiny PHP Framework
Clone this repository to your system and start using it �
git clone https://github.com/mahamadali/jolly.git
php commander set:config
In settings/app.php, set your config variables. Do not forget to set SUB_DIR if you are not running your project on root of the server
// Set GET route for /home
Router::get(['/home'], [ WelcomeController::class, 'index' ]);
# Set GET route for multiple prefixes. Below route will be accessible for both yourproject.com/home and yourproject.com/
Router::get(['/home', '/'], [ WelcomeController::class, 'index' ]);
# Set name to route
Router::get(['/home'], [ WelcomeController::class, 'index' ])->name('homepage');
## Add barrier to the route.
## Note: Barrier is like check/middleware before route is accessible
Router::get(['/home', '/'], [ WelcomeController::class, 'index' ])->name('landing')->barrier('verify-token');
// OR
Router::get(['/home', '/'], [ WelcomeController::class, 'index' ])->name('landing')->barrier(VerifyToken::class);
# Note : VerifyToken Barrier class will return true or false for proceeding next
// Multiple Barriers
Router::get(['/home', '/'], [ WelcomeController::class, 'index' ])->name('landing')->barrier([VerifyToken::class, VerifyAPIKey::class]);
// OR
Router::get(['/home', '/'], [ WelcomeController::class, 'index' ])->name('landing')->barrier([VerifyToken::class, 'verify-api-key']);
# Barrier aliases can be set in settings/aliases.php for Barriers array
return [
'Barriers' => [
'verify-token' => VerifyToken::class,
]
];
### Route Bunch
Router::bunch('/user', ['barrier' => ['verify-token'], 'as' => 'user.'], function() {
Router::get('/list', [ UserController::class, 'index' ])->name('list')->withoutBarrier('verify-token');
Router::get('/create', [ UserController::class, 'create' ])->name('create');
Router::post('/store', [ UserController::class, 'store' ])->name('store');
});
# Note : withoutBarrier can be applied on specific route to skip sepecific barrier check on route. withoutBarrier() accepts string (alias of barrier), array (alias or class of barriers)
### Dynamic parameters binding
Router::bunch('/post', ['as' => 'post.'], function() {
Router::get('/{posted-by}/{?title}', [ WelcomeController::class, 'page' ])->name('details')->where('po
9E88
sted-by', '[0-9]+');
});
# In above route {posted-by} is mendatory attribute but title is optional attribute
# Regix can be set with ->where($dynamic_attr, 'REGEX EXPRESSION') on any dynamic attribute to validate dynamic urls
Note : You can create seperate files (Ex - user.php, admin.php, api.php etc.) as per your need in segments/routes to formalize your project routes and it will be automatically loaded in the project
## SUPOPRTED METHODS ARE 'ANY', 'GET', 'PUT', 'POST', 'DELETE', 'PATCH', 'UPDATE', 'OPTIONS'
class UserController {
public function index()
{
// It will load segments/views/user/home.jly.php
return render('user/home');
}
}
# Passing additional data to view
return render('user/home')->with(['greet' => 'Welcome to Jolly World!']);
return redirect(route('user.home'))->go();
# Redirect with flash data
return redirect(route('user.home'))->withUser($user)->go();
# Redirect with status
return redirect(route('user.home'), 301)->withUser($user)->go();
# Redirect back
return redirect()->withUser($user)->back();
# Set flash
Session::setFlash('type', 'text');
// OR
session()->setFlash('type');
# Get flash
Session::flash('type');
// OR
session()->flash('type');
#Set session data
Session::set('key', 'value'); OR session()->set('key', 'value');
#Get session data
Session::get('key'); OR session()->get('key');
# Set collection in session
Session::appendSet('key', 'array|object'); OR session()->appendSet('key', 'array|object');
# Remove session variable
Session::remove('key'); OR session()->remove('key');
# Check session has key
Session::has('key'); OR session()->has('key');
#Check session has flash
Session::hasFlash('type'); OR session()->hasFlash('type');
# Clear session
Session::clear(); OR session()->clear();
Session::setLanguage('en'); or session()->setLanguage('en');
# Usage:
session()->setLanguage('en');
$translated = trans('built_n_managing'); #Here translated word will be returned from translations/en.php
# Dynamic translations
--locker
--translations
--en.php
return [
'welcome_user_with_role' => 'Welcome {{user}}, You entered with {{role}} role',
];
# Usage
$trans = trans('welcome_user_with_role', [
'user' => 'Akbarhusen G. Maknojiya',
'role' => 'Visitor',
]);
# Default platform language will be taken from 'default_lang' attribute from settings/app.php
Bones\Request will be accessible on each route with request data
public function save(Request $request)
{
$formData = $request->all();
}
// Set custom msgs for validation rules. If not provided then default system messages will be returned on error
$errorMessages = [
'name.required' => 'Name must not be empty',
'email.unique' => 'Email already exists in the system',
];
# Request can be validated with validate method
$validate = $request->validate([
'name' => ['required', 'max:10'],
'user_name' => ['required', 'max:10'],
'email' => 'required|max:10|email|unique:users,email',
'password' => ['required', 'min:6', 'max:12', 'eqt:confirm_password'],
], $errorMessages);
if ($validate->hasError()) {
return redirect(route('user.home'))->with(['errors' => $validate->errors()])->go();
}
// Check for the files
if ($file = $request->hasFile('fileinput') {
$fileMimeType = $request->files('fileinput')->mimeType;
$fileFileSize = $request->files('fileinput')->fileSize;
$fileOrigName = $request->files('fileinput')->origName;
$fileExtension = $request->files('fileinput')->extension;
if ($request->files('fileinput')->isImage()) {
$fileDimensions = $request->files('fileinput')->dimensions;
}
// Saving file
$request->files('fileinput')->save($destination);
// Saving file with custom name
$request->files('fileinput')->save($destination, $uploadAs);
}
# Get current page in request
request()->currentPage();
# Retrieve request parameter
request()->get('formElement');
# Retrieve specific parameter(s) [Returns Array]
request()->getOnly(string|array);
>> request()->getOnly('username'); OR request()->getOnly(['first_name', 'last_name', 'username', 'password']);
# Retrieve parameters but exclude specific parameter(s) [Returns Array]
request()->except(string|array);
>> request()->except('username'); OR request()->except(['first_name', 'last_name']);
namespace Models;
use Models\Base;
use Models\Post;
class User extends Base
{
protected $table = 'users';
protected $primaryKey = 'id';
protected $elements = [
'name',
'email',
'password',
'is_deleted'
];
protected $defaults = [
'is_deleted' => 0,
];
public function getAvatarProperty()
{
return 'https://i2.wp.com/ui-avatars.com/api/'.urlencode($this->name).'/64?ssl=1';
}
public function posts()
{
return $this->hasMany(Post::class, 'user_id', 'id');
}
}
// Database table to bind with. If not provided then from model name with snake-case and pluralize format $table would be default value
$table = '';
// Primary key of table. If not provided then `id` would be default column for primaryKey
$primaryKey = '';
// Elements (Columns) which needs to be used as a set while adding directly from array in create() method
protected $elements = [];
// Set default values to specific columns
protected $defaults = [];
// Attach relational data with the object itself
protected $with = [];
// Attach properties the object itself
protected $attaches = [];
// Hide properies on the object
protected $hidden = [];
### Create dynamic properties for model object
public function getDynamicProperty() {
return 'I am ' . $this->dynamic;
}
# And above will be accessible on model object like $user->dynamic;
public function posts() {
return Post::where('posted_by', $this->id)->get();
}
# And above will be accessible on model object like $user->posts();
public function posts() {
return $this->hasMany(Post::class, 'user_id', 'id');
}
# And above will be accessible on model object like $user->posts();
# Usage of above relational function
User::with('posts')->orderBy('id')->get(); // This will return all users in descending order with associated posts for each user object matching columns given in then defination of relation
parallelTo() - // Returns one result if matches with the columns given in the defination
hasMany() - // Returns multiple results if matches with the columns given in the defination
hasOne() - // Returns one result if matches with the columns given in the defination
hasManyVia() - // Returns multiple results if matches with the columns given in the defination
hasOneVia() - // Returns one result if matches with the columns
F438
given in the defination
# Retrieve row by primary key
$model->find('id'); // It will try to find row as per the primary key set for model
# Retrieve one row
// If no column provided then it will return all columns
$model->getOne();
// OR
$model->where('column', 'value')->getOne('column_1, column_2');
// OR
$model->where('column', 'value')->orWhere('column', 'value')->getOne();
# Retrieve results
$model->get(); // Returns all rows
$model->get('number_of_rows', 'columns');
> $model->get(10, 'name, email'); // This will return 10 rows with name and email columns only
# Retrieve value of column from one row
$model->getValue('column');
> $model->where('email', 'test@example.com')->getValue('username'); // This will return username from first row table where email will be test@example.com
> $model->where('is_active', 1)->getAsJson();
> $model->where('is_active', 1)->getAsObject();
> $model->where('is_active', 1)->getAsArray();
> $model->where('is_active', 1)->getOneAsJson();
> $model->where('is_active', 1)->getOneAsObject();
> $model->where('is_active', 1)->getOneAsArray();
# Where conditions
$model->where('column', 'value', 'operator', 'cond'); // Default operator is = and default cond is AND
> $model->where('name', 'jolly%', 'LIKE'); // Like
> $model->whereLike('name', 'value');
> $model->where('name', 'jolly%', 'NOT LIKE'); // NOT Like
> $model->whereNotLike('name', 'value');
> $model->where('points', '200', '>='); // Conditional
> $model->where('points', [200, 100], 'IN'); // Where in
> $model->where('points', [200, 100], 'NOT IN'); // Where not in
> $model->where('points', [200, 100], '!='); // Where not equalt to
> $model->where('points', 10, '>=')->where('points', 20, '<='); // Where >=10 and <=20
> $model->where('id', [4, 20], 'BETWEEN'); // Where between 4 and 20
> $model->where('id', [4, 20], 'NOT BETWEEN'); // Where not between 4 and 20
# pluck specific columns
> $model->where('cat_id', '2')->pluck(['id', 'name']);
# create entry
> $model->create([
'title' => 'Jolly',
'description' => 'Tiny PHP Framework'
]);
> $model->create(request()->all());
# create entry with insert()
> $model->insert([
'title' => 'Jolly',
'description' => 'Tiny PHP Framework'
]);
# create multiple entries with insertMulti()
> $model->insertMulti([
[
'title' => 'Jolly',
'description' => 'Tiny PHP Framework'
],
[
'title' => 'Addiotional Jolly',
'description' => 'Additional Tiny PHP Framework'
],
]);
# Update entry
> $model->where('id', 22)->update([
'title' => 'Uograded Jolly'
]);
# Delete entry
$model->where('id', 22)->delete();
@extends('app')
@block("title"){{ 'User Home' }}@endblock
@block("styles")
<style type="text/css">
div {
margin: 0 auto;
justify-content: center;
text-align: center;
}
a {
background-color: blue;
color:#fff;
text-decoration:none;
padding: 10px;
margin-top: 2em;
display: inline-block;
}
</style>
@endblock
@block("content")
<div>
<a href="{{ route('user.create') }}">Create User</a>
@if (session()->has('user')):
{{ session()->user()->name.' is just registered!' }}
@endif
</div>
@endblock
@block("scripts")
<script src="{{ url('assets/js/app.js') }}" type="text/javascript"></script>
@endblock
<!-- Above view is extending app.jly.php -->
# app.jly.php (It will serve as master file to plot and plant the view files)
<html>
<head>
<title>@plot('title')</title>
<meta charset="utf-8">
@plot('styles')
</head>
<body>
@plot('content')
@plot('scripts')
</body>
</html>
// Foreach loop
@foreach ($collections as $collection):
// Do your jolly stuff
@endforeach
// For loop
@for ($i = 0; $i <10; $i++):
// Do your jolly stuff
@endfor
// While loop
$count = 10;
@while($count <= 0): {
$count--;
}
// Echoing
<h1>How are your {{ $user->name }}</h1>
// Echoing htmlstrings
{{{ $pages->contact_us }}}
echo setting('{filename}.{variable}');
Example
// Below statement will try to get the variable from settings/app.php.
echo setting('app.title');
// Get setting variable with default value if given variable not found in setting file
echo setting('app.title', 'Welcome to Jolly Framework!');
# List routes
php commander list:routes
# Clear views builds (compiler/builds) directory
php commander clear:builds
# It will create model file(s) in segments/Models directory
php commander create:model 'ModelName'
# Note : create:model will auto populate {$table} name with camel-case and pluralize pattern if no table given in command
php commander create:model 'Movie'
php commander create:model 'Entertainment/Movie'
# creates model file with attributes auto loaded
php commander create:model 'Entertainment/Movie' --table='movies' --primaryKey='id'
# It will create controller file(s) in segments/Controllers directory
php commander create:controller 'MovieController'
php commander create:controller 'Entertainment/MovieController'
# It will create view file(s) in segments/views directory
php commander create:view 'welcome'
# to create view file in specific directory
php commander create:view 'movie/list.jly.php'
# It will create barrier files in segments/Barriers directory
php commander create:barrier 'VerifyToken'
# It will create dbgram file(s) in locker/dbgrams directory
# create dbgram file to create movies table
# Here table and its operation will be auto adjusted with camel-case and pluralize format
php commander create:dbgram create_users_table
# Manually pass the table name and its operation like create | modify | drop | truncate
php commander create:dbgram create_users_table --table='movies' --action='create'
# It will create dbfiller file(s) in locker/dbfillers directory
php commander create:dbfiller MovieFiller
# Create dbfiller file(s) in specific directory inside locker/dbfillers
php commander create:dbfiller entertainment/MovieFiller
php commander run:dbgram
php commander run:dbgram --refresh
php commander run:dbgram --force
php commander run:dbgram --refresh
php commander run:dbgram --setup
php commander run:dbgram --reset
php commander run:dbgram create_movies_table_11_02_2021_128976,add_column_to_movies_table_create_movies_table_12_02_2021_321234
php commander rollback:dbgram
php commander rollback:dbgram --stack=2
# This will rollback last 3 stack dbgrams
php commander rollback:dbgram --limit=3
php commander run:dbfiller
php commander run:dbfiller Entertainment/MovieFiller
php commander run:dbgram --refresh --fill
## Sample code
use Bones\DataWing;
use Bones\Skeletons\DataWing\Skeleton;
return new class
{
protected $table = 'posts';
public function arise()
{
DataWing::create($this->table, function (Skeleton $table) {
$table->id()->primaryKey();
$table->string('title')->nullable(false);
$table->text('description')->nullable();
$table->unsignedBigInteger('posted_by');
return $table;
});
}
public function fall()
{
DataWing::drop($this->table);
}
};
> arise() method will be called when dbgram is executed
> fall() method will be called when rollback is executed
# to create table
DataWing::create($this->table, function (Skeleton $table) {
return $table;
});
# to modfiy table
DataWing::modify($this->table, function (Skeleton $table) {
return $table;
});
# to truncate table
DataWing::truncate();
# to drop table
DataWing::drop();
$table->engine(); // set table engine
$table->prefix(); // set table prefix
$table->collation(); // set table collation
$table->charSet(); // set table character set
$table->autoIncrement('column'); // Add auto increment integer column
$table->autoIncrementBig('column'); // Add auto increment big integer column
$table->integer('column');
$table->tinyInteger('column');
$table->smallInteger('column');
$table->mediumInteger('column');
$table->bigInteger('column');
$table->unsignedInteger('column');
$table->unsignedTinyInteger('column');
$table->unsignedSmallInteger('column');
$table->unsignedMediumInteger('column');
$table->unsignedBigInteger('column');
$table->float('column');
$table->floatAuto('column');
$table->real('column');
$table->serial('column');
$table->bit('column');
$table->double('column');
$table->decimal('column');
$table->unsignedFloat('column');
$table->unsignedFloatAuto('column');
$table->unsignedDouble('column');
$table->unsignedDecimal('column');
$table->char('column');
$table->varchar('column');
$table->string('column');
$table->text('column');
$table->mediumText('column');
$table->longText('column');
$table->boolean('column');
$table->enum('column', Array of Allowed Set);
$table->set('column', Array of Allowed Set);
$table->json('column');
$table->jsonb('column');
$table->date('column');
$table->dateTime('column');
$table->dateTimeTz('column');
$table->time('column');
$table->timeTz('column');
$table->year('column');
$table->geometry('column');
$table->point('column');
$table->linestring('column');
$table->polygon('column');
$table->geometryCollection('column');
$table->multiPoint('column');
$table->multiLineString('column');
$table->multiPolygon('column');
$table->multiPolygonZ('column');
$table->autoCalculateAsInt('column', 'expression');
$table->autoCalculateAsDouble('column', 'expression');
$table->autoCalculateAsFloat('column', 'expression');
$table->dropColumn('column'); // Drop column
$table->id(); // Add autoincrement big integer column as id
$table->id('tid'); // id() also accepts column name to add as autoincrement big integer
$table->timestamps(); // Adds created_at and updated_at columns in table with default values CURRENT_TIMESTAMP and CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP respectively
$table->rememberToken(); // Add column remember_token of string type
$table->setIndex(Array of columns); // Add index on columns
$table->setUnique(Array of columns); // Add unique index on columns
$table->setFullText(Array of columns); // Add fulltext index on columns
$table->setSpatialIndex(Array of columns); // Add spatial index on columns
$table->dropIndex(string index|Array of indexes); // Drop given index(s)
$table->dropForeign(string foregin_constraint|Array of foregin_constraints); // Drop foreign key constraint(s)
$table->renameTable('new_table_name'); // Rename table name
$table->id()->primaryKey(); // Set primary key to id column
$table->string('title')->nullable();
$table->string('title')->nullable(false)->default('Jolly!');
$table->boolean('email')->unique();
$table->bigInteger('followers')->unsigned();
$table->text('description')->comment('This explains the description');
$table->string('title')->after('id');
$table->string('title')->after('id')->modify(); // modify() used when need to alter existing column
$table->autoCalculateAsInt('virtual_column', 'buyprice * quantityinstock');
$table->autoCalculateAsInt('virtual_column', 'buyprice * quantityinstock')-virtual(); //
$table->autoCalculateAsInt('virtual_column', 'buyprice * quantityinstock')->stored();
# Note : As per MySQL the virtual columns are calculated on the fly each time data is read whereas the stored column are calculated and stored physically when the data is updated.
namespace Bones\Skeletons\DBFiller;
use Bones\Database;
return new class
{
protected $table = 'users';
public function fill()
{
Database::insertMulti([
[
'name' => 'User ' . uniqid(),
'email' => 'user' . generateOTP(8) . '@gmail.com',
'is_active' => 1
],
[
'name' => 'User ' . uniqid(),
'email' => 'user' . generateOTP(8) . '@gmail.com',
'is_active' => 1
],
], null, $this->table);
}
};
# This will load default template of 503 (settings/templates/). You can set your custom view in that file to load customized 503 page
php commander stop
# This will load default template of 503 (settings/templates/) with message
php commander stop --message='App is down for certain time...'
# This will load default template of 503 (settings/ templates/) with message
php commander start
Note : In case if start or stop command does not work then manually you can add `stop` file without extension and put (string) message inside the file to get message on your 503 template or you can simply leave it blank if no message needed.