Skip to content

Instantly share code, notes, and snippets.

@jeff-silva
Last active April 4, 2024 20:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jeff-silva/c3c82b72ff1fcd5816c82b8326164844 to your computer and use it in GitHub Desktop.
Save jeff-silva/c3c82b72ff1fcd5816c82b8326164844 to your computer and use it in GitHub Desktop.
Laravel
<?php
/*
1) Insert method above in Controller.php
public function generateRoutes()
{
$refClass = new \ReflectionClass(get_called_class());
// Methods Attributes
foreach(get_class_methods($this) as $method) {
foreach($refClass->getMethod($method)->getAttributes() as $attr) {
$args = $attr->getArguments() ?? [];
$args['controller'] = get_called_class();
$args['controllerMethod'] = $method;
(new \ReflectionClass($attr->getName()))->newInstanceArgs($args);
}
}
// Class Attributes
foreach($refClass->getAttributes() as $attr) {
$args = $attr->getArguments() ?? [];
$args['controller'] = get_called_class();
(new \ReflectionClass($attr->getName()))->newInstanceArgs($args);
}
}
2) In the controller constructor, call $this->generateRoutes()
3) Create app/Helper/Router folder and then inside files above
*/
// To create route, put this on methods
// #[Create(method: 'get', path: '/app/test', name: 'app.test', middleware: 'api')]
namespace App\Helper\Router;
use Illuminate\Support\Facades\Route;
class Create
{
public function __construct($controller, $controllerMethod, $method, $path, $name = null, $middleware = null)
{
$method = is_array($method) ? $method : [$method];
$r = Route::match($method, $path, [$controller, $controllerMethod])->name($name);
if ($middleware)
$r->middleware($middleware);
}
}
// To create apiResource, put this on class
// #[Resource(name: 'get', middleware: 'api')]
namespace App\Helper\Router;
use Illuminate\Support\Facades\Route;
class Resource
{
public function __construct($name, $controller, $middleware = null)
{
$r = Route::apiResource($name, $controller);
if ($middleware)
$r->middleware($middleware);
}
}
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class AppMake extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'app:make';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Generate models, controllers, routes and stores from database schema';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
public function getDatabaseSchema() {
$env_database = env('DB_DATABASE');
$fks = \DB::select("SELECT * FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE CONSTRAINT_SCHEMA='{$env_database}' ");
$tables = [];
foreach(\DB::select('SHOW TABLE STATUS') as $table) {
$table = (object) array_merge([
'Singular' => \Str::singular($table->Name),
'Plural' => \Str::plural($table->Name),
'Route' => str_replace('_', '-', \Str::kebab($table->Name)),
], (array) $table);
$table->Model = new \stdClass;
$table->Model->Name = \Str::studly($table->Singular);
$table->Model->Namespace = "\App\Models\\{$table->Model->Name}";
$table->Model->File = "app/Models/{$table->Model->Name}.php";
$table->Model->FileExists = file_exists(base_path($table->Model->File));
$table->Controller = new \stdClass;
$table->Controller->Name = \Str::studly($table->Singular).'Controller';
$table->Controller->Namespace = "\App\Http\Controllers\\{$table->Controller->Name}";
$table->Controller->File = "app/Http/Controllers/{$table->Controller->Name}.php";
$table->Controller->FileExists = file_exists(base_path($table->Controller->File));
$table->FieldNames = [];
$table->Fields = [];
$table->ForeignKeys = [];
foreach($fks as $fk) {
if ($fk->TABLE_NAME==$table->Name AND $fk->REFERENCED_TABLE_NAME) {
$table->ForeignKeys[] = $fk;
}
}
foreach(\DB::select("SHOW FULL COLUMNS FROM `{$table->Name}` ") as $col) {
$table->FieldNames[] = $col->Field;
$table->Fields[ $col->Field ] = $col;
}
$table->Fields = (object) $table->Fields;
$tables[ $table->Name ] = $table;
}
foreach($tables as $table_name=>$table) {
foreach($table->ForeignKeys as $i => $fk) {
$fk->Model = new \stdClass;
if ($fk->REFERENCED_TABLE_NAME AND isset($tables[ $fk->REFERENCED_TABLE_NAME ])) {
$fk->Model = $tables[ $fk->REFERENCED_TABLE_NAME ]->Model;
}
}
}
return $tables;
}
public function bladeCompile($data=[], $html='') {
$_generated = \Illuminate\View\Compilers\BladeCompiler::compileString($html);
ob_start() and extract($data, EXTR_SKIP);
try { eval('?> '.$_generated); }
catch (\Exception $e) { ob_get_clean(); throw $e; }
return ob_get_clean();
}
public function generateFiles() {
// dd( $this->getDatabaseSchema() );
$signature = sprintf("/*\n * %s\n * Generated at %s\n * To generate again, run \"php artisan {$this->signature}\".\n */", env('APP_NAME'), date('d/m/Y H:i'));
// dd($signature);
$routes = ['<?php', '', $signature, ''];
$tables = $this->getDatabaseSchema();
foreach($tables as $table) {
$modelFillable = "[\n\t\t'". implode("',\n\t\t'", $table->FieldNames) ."'\n\t]";
$foreignKeysMethods = [];
foreach($table->ForeignKeys as $fk) {
// $methodName = \Str::studly($fk->CONSTRAINT_NAME);
$methodName = \Str::of($fk->CONSTRAINT_NAME)->lower()->camel();
$foreignKeysMethods[] = "public function {$methodName}() {\n\t\treturn \$this->hasOne({$fk->Model->Name}::class);\n\t}";
}
$foreignKeysMethods = implode("\n\n\t", $foreignKeysMethods);
$files[] = ['path' => $table->Model->File, 'content' => <<<EOF
<?php
{$signature}
namespace App\Models;
class {$table->Model->Name} extends \Illuminate\Database\Eloquent\Model {
use \App\Traits\Model;
protected \$fillable = {$modelFillable};
public function validate(\$data=[]) {
return \Validator::make(\$data, [
'name' => ['required'],
]);
}
{$foreignKeysMethods}
}
EOF];
$files[] = ['path' => $table->Controller->File, 'content' => <<<EOF
<?php
{$signature}
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class {$table->Controller->Name} extends Controller
{
/**
* Buscar {$table->Controller->Name}
*
* Parâmetros aceitos:
* search: Termo de busca
* page: Página
* orderby: Nome da coluna de parâmetro para ordenação
* order: asc ou desc
*/
public function search(Request \$request) {
return (new {$table->Model->Namespace})->search(\$request->all());
}
/**
* Retornar {$table->Controller->Name} por id
*/
public function find(\$id) {
return {$table->Model->Namespace}::find(\$id);
}
/**
* Salvar dados de {$table->Controller->Name}
*/
public function save(Request \$request) {
return (new {$table->Model->Namespace})->store(\$request->all());
}
/**
* Deletar {$table->Controller->Name}
*/
public function delete(\$id) {
return {$table->Model->Namespace}::find(\$id)->remove();
}
}
EOF];
$files[] = ['path' => "resources/nuxt/store/{$table->Model->Name}.js", 'content' => <<<EOF
{$signature}
export const state = () => ({
model: {},
search: {
search: "",
page: 1,
perpage: 10,
orderby: "id",
order: "desc",
},
result: {
loading: false,
current_page: 1,
from: 1,
per_page: 10,
to: 1,
total: 0,
data: [],
},
});
export const mutations = {
set(state, tevep) {
state.tevep = tevep;
},
};
export const actions = {
find(id) {
this.\$axios.get(`/api/{$table->Route}/find/\${id}`).then(resp => {
// this.set(aa);
});
},
search() {
this.\$axios.get(`/api/{$table->Route}/search`).then(resp => {
// this.set(aa);
});
},
save() {
this.\$axios.post(`/api/{$table->Route}/save`).then(resp => {
// this.set(aa);
});
},
delete(id) {
this.\$axios.post(`/api/{$table->Route}/delete/\${id}`).then(resp => {
// this.set(aa);
});
},
};
EOF];
$routes[] = "/* {$table->Model->Namespace} routes */";
$routes[] = "Route::get('{$table->Route}/search', '{$table->Controller->Namespace}@search')->name('{$table->Singular}.search');";
$routes[] = "Route::get('{$table->Route}/find/{id}', '{$table->Controller->Namespace}@find')->name('{$table->Singular}.find');";
$routes[] = "Route::post('{$table->Route}/save/', '{$table->Controller->Namespace}@save')->name('{$table->Singular}.save');";
$routes[] = "Route::post('{$table->Route}/delete/{id}', '{$table->Controller->Namespace}@delete')->name('{$table->Singular}.delete');";
$routes[] = '';
}
foreach($files as $file) {
$this->comment($file['path']);
$file['path'] = base_path(str_replace('/', DIRECTORY_SEPARATOR, $file['path']));
$info = pathinfo($file['path']);
if (! file_exists($info['dirname'])) {
mkdir($info['dirname'], 0755, true);
}
file_put_contents($file['path'], $file['content']);
}
file_put_contents(base_path('routes/api_generated.php'), implode("\n", $routes));
// dd($tables);
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$this->comment('Generating files');
$this->generateFiles();
}
}
<?php
Artisan::command('app:hash', function () {
$pass = $this->ask('Senha');
$this->comment(\Illuminate\Support\Facades\Hash::make($pass));
})->describe('Generate password to set in database');
<?php
/**
* Gerando rotas com PHP8 metadata
* https://samirmhsnv.medium.com/laravel-routing-with-controller-only-using-php-8-attributes-on-laravel-routing-d6c22cd43140
*/
namespace App\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
function __construct()
{
$this->onInit();
}
public function onInit()
{
$this->autoRoutes();
}
public function route($methods, $path, $callback)
{
$methods = is_array($methods)? $methods: [ $methods ];
$prefix = (new \ReflectionClass($this))->getShortName();
$prefix = (string) \Str::of(str_replace('Controller', '', $prefix))->studly()->kebab();
if (! $prefix) return;
$path = $prefix .'/'. trim($path, '/');
$call = [get_class($this), $callback];
$name = $prefix .'-'. (string) \Str::of($callback)->kebab();
return Route::match($methods, $path, $call)->name($name);
}
public function autoRoutes($params = [])
{
$params = array_merge([
'except' => [],
], $params);
if (!in_array('find', $params['except'])) {
$this->route('get', '/find/:id', 'find');
}
if (!in_array('search', $params['except'])) {
$this->route('get', '/search', 'search');
}
if (!in_array('save', $params['except'])) {
$this->route('post', '/save', 'save');
}
if (!in_array('delete', $params['except'])) {
$this->route('post', '/delete', 'delete');
}
}
public function find($id)
{
return func_get_args();
}
public function search()
{
return func_get_args();
}
public function save()
{
return func_get_args();
}
public function delete()
{
return func_get_args();
}
}
// app/Http/Controllers/TestController.php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
class TestController extends Controller
{
public function onInit()
{
// api/test/random
// $this->route($method, $path, $function)
$this->route('get', 'random', 'random');
}
public function random(Request $request)
{
$return['start'] = rand(0, 99);
$return['final'] = rand(0, 99);
$return['items'] = array_map(function($number) {
$return['number'] = $number;
return $return;
}, range($return['start'], $return['final']));
return $return;
}
}
// routes/api.php
new \App\Http\Controllers\TestController;
<?php
// https://stackoverflow.com/questions/50639622/set-default-host-value-for-php-artisan-serve
// 1) php artisan make:command CustomServeCommand
// 2) customize this file if needed
// 3) php artisan serve
namespace App\Console\Commands;
use Illuminate\Foundation\Console\ServeCommand;
use Symfony\Component\Console\Input\InputOption;
class CustomServeCommand extends ServeCommand
{
/**
* Get the console command options.
*
* @return array
*/
protected function getOptions()
{
return [
['host', null, InputOption::VALUE_OPTIONAL, 'The host address to serve the application on.', '0.0.0.0'],//default 127.0.0.1
['port', null, InputOption::VALUE_OPTIONAL, 'The port to serve the application on.', 8000],
];
}
}
<?php
function ddd() {
call_user_func_array('dump', func_get_args());
$lines[] = 'document.querySelectorAll(".sf-dump-str-collapse").forEach(item => { console.log(item); });';
$lines[] = 'document.querySelectorAll(".sf-dump-compact").forEach(item => { item.className = "sf-dump-expanded"; });';
$lines[] = 'document.querySelectorAll(".sf-dump-str").forEach(item => { item.className = "sf-dump-str sf-dump-str-expand"; });';
echo "<script>\n" . implode("\n", $lines) . "\n</script>"; die;
}
public function validate() {
$rules = [
'name' => 'required',
'email' => "email:rfc,dns|unique:users,email,{$this->id}",
];
$messages = [
'name.required' => 'Informe o nome',
'email.email' => 'E-mail inválido',
'email.unique' => 'E-mail já utilizado',
];
$validator = \Validator::make($this->toArray(), $rules, $messages);
if ($validator->fails()) {
throw new \Exception(json_encode($validator->errors()));
}
return false;
}
<?php
// Filename: app/Providers/AppServiceProvider.php
/*
* Ex: module Test with routes:
* 1) Create dir app/Mods/Test
* 2) Create file app/Mods/Test/Http/routes.php
*/
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public $dirmods = 'Mods';
public $modules = array();
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
foreach($this->modules as $mod) {
// Views
if ($mod['views']) {
$this->loadViewsFrom($mod['views'], $mod['name']);
}
// Lang
if ($mod['lang']) {
$this->loadTranslationsFrom($mod['views'], $mod['name']);
}
// Routes
if ($mod['routes']) {
\Route::group(['middleware'=>['web']], function() use($mod) {
include $mod['routes'];
});
}
}
// dd($this);
}
/**
* Register any application services.
*
* @return void
*/
public function register()
{
foreach(glob(__DIR__ . "/../{$this->dirmods}/*") as $dir) {
$dir = realpath($dir);
if (is_dir($dir)) {
$namespace = basename($dir);
if ($namespace[0]=='_') continue;
$this->modules[] = [
'dir' => $dir,
'namespace' => $namespace,
'name' => strtolower($namespace),
'url' => asset("/{$this->dirmods}/{$namespace}"),
'serviceProvider' => realpath(app_path("/{$this->dirmods}/{$namespace}/Providers/{$namespace}ServiceProvider.php"))? "\\App\\{$this->dirmods}\\{$namespace}\\Providers\\{$namespace}ServiceProvider": null,
'routes' => realpath(app_path("/{$this->dirmods}/{$namespace}/Http/routes.php")),
'views' => realpath(app_path("/{$this->dirmods}/{$namespace}/Resources/views")),
'lang' => realpath(app_path("/{$this->dirmods}/{$namespace}/Resources/lang")),
];
}
}
foreach($this->modules as $mod) {
if ($mod['serviceProvider']) {
$this->app->register($mod['serviceProvider']);
}
}
}
}
<?php
// Baseado em https://github.com/williamoliveira/eloquent-array-query-builder
class Model extends Eloquent {
public function scopeQuerySearch($query) {
return $query;
}
}
Model::querySearch([
// Predefined fields
'select' => 'id,name',
'orderby' => 'id:desc', // order by id desc
'orderby' => ['id:desc', 'name:asc'], // order by id desc, name asc
'page' => 1,
'perpage' => 10,
'q' => 'test', // where any field like '%{$q}%'
// if field is a $fillable item:
'age' => 35, // where age=35
'age' => [35,36], // where age in (35, 36)
'age' => ['op' => '=', 'value' => 35], // where age=35
'age' => ['op' => '>', 'value' => 35], // where age > 35
'age' => ['op' => '>=', 'value' => 35], // where age >= 35
'age' => ['op' => '<', 'value' => 35], // where age < 35
'age' => ['op' => '<=', 'value' => 35], // where age <= 35
'age' => ['op' => 'in', 'value' => [35, 36]], // where age in (35, 36)
'age' => ['op' => 'between', 'value' => [35, 36]], // where age between (35, 36)
'age' => ['op' => 'empty'], // where (age is null or age='')
'age' => [
'relation' => 'or',
['field'=>'age', 'op'=>'=', 'value'=>35],
['field'=>'age', 'op'=>'=', 'value'=>36],
], // where ((age = 35) or (age = 36))
'name' => ['op' => 'like', 'value' => 'john'], // where name like '%john%'
]);
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Example extends Model
{
// Retorna SQL final
// $model->where('active', 1)->toRawSql();
public function scopeToRawSql($query) {
return call_user_func(function($query) {
$sql = $query->toSql();
$bindings = $query->getBindings();
$needle = '?';
foreach ($bindings as $replace){
$pos = strpos($sql, $needle);
if ($pos !== false) {
if (gettype($replace) === "string") {
$replace = ' "'.addslashes($replace).'" ';
}
$sql = substr_replace($sql, $replace, $pos, strlen($needle));
}
}
return $sql;
}, $query);
}
}
<?php
/*
Works like ->with() method, but using joins instead subqueries
*/
class Product extends Eloquent {
public function scopeWithJoin($query, $joins=[], $side='left') {
foreach($joins as $join) {
$e = explode(':', $join);
$method = $e[0];
$fields = isset($e[1])? $e[1]: '';
$fields = array_filter(explode(',', $fields), 'strlen');
$build = call_user_func([$this, $method]);
if (! is_a($build, 'Illuminate\Database\Eloquent\Relations\BelongsTo')) {
continue;
}
$related = $build->getRelated();
$parent = $build->getParent();
$related_field = implode('.', [$related->getTable(), $related->getKeyName()]);
$there_field = implode('.', [$parent->getTable(), $build->getForeignKey()]);
$query->leftJoin($related->getTable(), $related_field, '=', $there_field);
$fields = empty($fields)? $related->getFillable(): $fields;
$selectRaw = array_map(function($field) use($related) {
if (strpos($field, ' as ') !== false) {
return $related->getTable() .'.'. $field;
}
return implode('.', [$related->getTable(), $field]) .' as '. implode('_', [$related->getTable(), $field]);
}, $fields);
$query->selectRaw(implode(', ', $selectRaw));
}
return $query;
}
}
// How to use
Product::withJoin([
'category:id,slug,name',
'image',
]);
<?php
namespace App\Search;
use Illuminate\Support\Str;
class Search
{
public $model = null;
public $params = null;
public $builder = null;
public function __construct($params = [], $model = null)
{
$this->model = $model ? $model : $this->model;
$this->params = $this->paramsDefault($params);
$this->builder = $this->builder();
}
public function params()
{
return [];
}
public function search($query, $params)
{
return $query;
}
public function options($query, $params, $options)
{
return $options;
}
public function builder()
{
$query = app($this->model)->query();
$model = $query->getModel();
$params = $this->params;
$query = $this->search($query, $params);
// ?q=the+terms
if ($params->q) {
$query->where(function ($q) use ($params, $model) {
$terms = array_values(array_filter(preg_split('/[^a-zA-Z0-9]/', $params->q)));
foreach ($model->getFillable() as $field) {
foreach ($terms as $term) {
if (empty ($q->getQuery()->wheres)) {
$q->where($field, 'like', "%{$term}%");
continue;
}
$q->orWhere($field, 'like', "%{$term}%");
}
}
});
}
// ?find=123
if ($params->find) {
$query->where(function ($q) use ($params, $model) {
$q->where('id', $params->find);
if (in_array('slug', $model->getFillable())) {
$q->orWhere('slug', $params->find);
}
});
}
// ?hidden=field1,field2
// ?hidden[]=field1&hidden[]=field2
if ($params->hidden) {
$hidden = is_array($params->hidden) ? $params->hidden : explode(',', $params->hidden);
$query->exclude($hidden);
}
// ?with=relation1,relation2
// ?with[]=relation1&with[]=relation2
if ($params->with) {
$withs = is_array($params->with) ? $params->with : explode(',', $params->with);
$query->with($withs);
}
// ?limit=10
if ($params->limit) {
$query->take($params->limit);
}
// ?order=id:desc,name:asc
// ?order[]=id:desc&order[]=name:asc
if ($params->order) {
$orders = is_array($params->order) ? $params->order : explode(',', $params->order);
foreach ($orders as $order) {
if (in_array($order, ['rand', 'random'])) {
$query->orderByRaw('RAND()');
continue;
}
list($field, $type) = explode(':', $order);
$query->orderBy($field, $type);
}
}
return $query;
}
protected function paramsDefault($merge = [])
{
return (object) array_merge(
[
'q' => null,
'find' => null,
'hidden' => null,
'page' => 1,
'per_page' => 10,
'limit' => null,
'order' => 'updated_at:desc',
'with' => null,
],
$this->params(),
$merge
);
}
protected function optionsDefault($query, $params)
{
$options = [];
$options['withs'] = [];
$relationTypes = [
'Illuminate\Database\Eloquent\Relations\HasOne',
'Illuminate\Database\Eloquent\Relations\HasMany',
'Illuminate\Database\Eloquent\Relations\BelongsTo',
'Illuminate\Database\Eloquent\Relations\BelongsToMany',
];
foreach ((new \ReflectionClass($query->getModel()))->getMethods() as $refMethod) {
if ($returnType = $refMethod->getReturnType()) {
if (in_array($returnType->getName(), $relationTypes)) {
$options['withs'][] = $refMethod->getName();
}
}
}
return $this->options($query, $params, $options);
}
public function first()
{
return $this->builder->first();
}
public function get()
{
return $this->builder->get();
}
public function paginate()
{
$query = $this->builder;
$params = $this->params;
$pagination = $query->paginate($params->per_page, ['*'], 'page', $params->page)->toArray();
return [
'pagination' => [
'current_page' => $pagination['current_page'],
'last_page' => $pagination['last_page'],
'total' => $pagination['total'],
'from' => $pagination['from'],
'to' => $pagination['to'],
],
'data' => $pagination['data'],
'params' => $params,
'options' => $this->optionsDefault($query, $params),
];
}
public function sql()
{
return Str::replaceArray('?', collect($this->builder->getBindings())
->map(function ($i) {
if (is_object($i)) {
$i = (string) $i;
}
return (is_string($i)) ? "'$i'" : $i;
})->all(), $this->builder->toSql());
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment