Store config in database in Laravel - laravel-5

I am currently using Laravel 5.2. I want to be able to store config properties (key-value pairs) in database, that I am willing to access from both my application on runtime and the console (either php artisan command or Tinker).
What is my best option?
.env is one way, but it is not stored in the database, but in a file.
Config::get is another way, but it also writes in files. Can it be configured to write in database?
Cache::get is setup to work with the database, but is temporary, not permanent, so it is out of question.
The reason I am interested in database config, is because we often replace/delete files during deployment. Also it would be nice to store values encrypted. Also important feature here is to be able to easily get values via either php artisan or tinker

Make a migration: php artisan make:migration CreateSettingsTable
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateSettingsTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('settings', function (Blueprint $table) {
$table->id();
$table->string('key');
$table->string('value');
$table->timestamps();
$table->unique([
'key', //I add a unique to prevent double keys
]);
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::dropIfExists('settings');
}
}
Make the model: php artisan make:model Setting
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Setting extends Model
{
protected $fillable = [
'key',
'value'
];
//I would normally do this in a repository,
// but for simplicity sake, i've put it in here :)
static public $settings = null;
static function get($key, $default = null)
{
if (empty(self::$settings)) {
self::$settings = self::all();
}
$model = self
::$settings
->where('key', $key)
->first();
if (empty($model)) {
if (empty($default)) {
//Throw an exception, you cannot resume without the setting.
throw new \Exception('Cannot find setting: '.$key);
}
else {
return $default;
}
}
else {
return $model->value;
}
}
static function set(string $key, $value)
{
if (empty(self::$settings)) {
self::$settings = self::all();
}
if (is_string($value) || is_int($value)) {
$model = self
::$settings
->where('key', $key)
->first();
if (empty($model)) {
$model = self::create([
'key' => $key,
'value' => $value
]);
self::$settings->push($model);
}
else {
$model->update(compact('value'));
}
return true;
}
else {
return false;
}
}
}
Please note here, that I added the get and set functions, together with a static $settings variable directly to the model, to keep the example small. Usually I would opt to making a repository or service(not serviceprovider) to handle these functions. This way you only query db once(per request) for all the settings. You could stick this in cache, but that is not part of this answer of now.
Run php artisan migrate to ge the table in the db.
Run composer dump-autoload to make sure tinker can find the Setting class.
Use someting like php artisan tinker(https://laravel.com/docs/7.x/artisan#tinker) to test it, in this case you can do:
Setting::set('someKey', 'someValue'); //Set someKey to someValue
Setting::get('someKey'); //Get someKey, throws exception if not found
Setting::get('somekey2', 'someDefault'); //Shows someDefault because somekey2 is not set yet.
I hope it helps! :)

I extended Rob Biermann approach to handling json data
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
class Setting extends Model
{
use HasFactory;
protected $casts = [
'value' => 'array'
];
protected $fillable = [
'key',
'value'
];
/**
* #var Setting[]|\Illuminate\Database\Eloquent\Collection|null
*/
static public $settings = null;
static function getAll(string $key, $default = null){
if (empty(self::$settings)) {
self::$settings = self::all();
}
$keys = explode('.', $key);
$databaseKey = $keys[0];
unset($keys[0]);
$model = self
::$settings
->where('key', $databaseKey)
->first();
if (empty($model)) {
if (empty($default)) {
//Throw an exception, you cannot resume without the setting.
throw new \Exception('Cannot find setting: ' . $key);
} else {
return $default;
}
} else {
return $model->value;
}
}
static function get(string $key, $default = null)
{
if (empty(self::$settings)) {
self::$settings = self::all();
}
$keys = explode('.', $key);
$databaseKey = $keys[0];
unset($keys[0]);
$model = self
::$settings
->where('key', $databaseKey)
->first();
if (empty($model)) {
if (empty($default)) {
//Throw an exception, you cannot resume without the setting.
throw new \Exception('Cannot find setting: ' . $key);
} else {
return $default;
}
} else {
if(!empty( $keys)){
return Arr::get($model->value, implode('.',$keys));
}
if(is_string( $model->value)){
return $model->value;
}
if(Arr::has($model->value, 'default')){
return $model->value['default'];
}
return $model->value;
}
}
static function set(string $key, $value)
{
if (empty(self::$settings)) {
self::$settings = self::all();
}
$keys = explode('.', $key);
$databaseKey = $keys[0];
unset($keys[0]);
$model = self
::$settings
->where('key', $databaseKey)
->first();
if (empty($model)) {
if(!empty($keys)){
$array = [];
$model = self::create([
'key' => $key,
'value' => Arr::set($array, implode('.',$keys), $value)
]);
}
else{
$model = self::create([
'key' => $key,
'value' => $value
]);
}
self::$settings->push($model);
} else {
if(!empty($keys)){
$old = $model->value;
if(is_string($old)){
$old = ["default" => $old] ;
}
if(Arr::has($old, implode('.',$keys))){
$old = Arr::set($old, implode('.',$keys), $value);
}
else{
$old = Arr::add($old, implode('.',$keys), $value);
}
$model->update(['value' => $old]);
}
else{
if(is_array($model->value)){
$new = $model->value;
$new['default'] = $value;
$value = $new;
}
$model->update(['value' => $value]);
}
}
return true;
}
}
now u can use
Setting::get('someKey.key');
Setting::get('someKey.key.key1');
Setting::set('someKey.key', 'test');
Setting::set('someKey.key.key1', 'test');

I'm using laravel 9, and using package from spatie: spatie/laravel-settings.
If you follow the docs you may set the setting class, for example I want to store payment gateway settings into the database, namely Securepay in Malaysia.
In settings folder, App\Settings will have a new file PaymentGatewaySettings.php:
<?php
namespace App\Settings;
use Spatie\LaravelSettings\Settings;
class PaymentGatewaySettings extends Settings
{
public string $env;
public string $uid;
public string $auth_token;
public string $checksum_token;
public static function group() : string
{
return 'payment_gateway';
}
}
In AppSeviceProvider.php we add new line under boot method:
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
/**
* Payment Gateway settings
*
*/
if(DB::table('settings')->where('group', 'payment_gateway')->exists()) {
config()->set('services', array_merge(config('services'), [
'securepay' => [
'env' => app(SecurepaySettings::class)->env,
'uid' => app(SecurepaySettings::class)->uid,
'auth_token' => app(SecurepaySettings::class)->auth_token,
'checksum_token' => app(SecurepaySettings::class)->checksum_token,
]
]));
}
}
If we do not put the if statement, it would be an error while want to run php artisan command.
In other cases you may extend the Illuminate\Foundation\Application class, and you may use something like this app()->getSecurePayEnv() in everywhere in you application, but to set the config I'm still using boot method in AppSeviceProvider.php.
Hope it helps.

Related

Import [insert or update] Excel/CSV to MySQL database using maatwebsite in laravel 7

While importing/uploading an excel file, if the data is already present in the excel file then update it in the Database or else insert it. This means before inserting should check with the database. So, anyone please help to solve with this issue:
This is the Import class for customers:
<?php
namespace App\Imports;
use App\Customer;
use Illuminate\Validation\Rule;
use Maatwebsite\Excel\Concerns\ToModel;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
use Maatwebsite\Excel\Concerns\WithValidation;
use Maatwebsite\Excel\Concerns\Importable;
class ImportCustomers implements ToModel, WithHeadingRow, WithValidation
{
use Importable;
/**
* #param array $row
*
* #return \Illuminate\Database\Eloquent\Model|null
*/
public function model(array $row)
{
// Check mobile already exists
/* $count = Customer::where('mobile',$row['mobile'])->count();
dd($count);
if($count > 0){
return null;
} */
return new Customer([
'customer_name' => $row['customer_name'],
'mobile' => $row['mobile'],
'email' => $row['email']
]);
}
public function rules(): array
{
return [
'*.customer_name' => 'required',
'*.mobile' => 'required|unique:customers',
'*.email' => 'required',
];
}
}
/* This is Controller:*/
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Requests\CustomerImportRequest;
use App\Imports\ImportCustomers;
use App\Exports\ExportCustomers;
use Maatwebsite\Excel\Facades\Excel;
use DB;
use App\Customer;
use Illuminate\Support\Arr;
class ImportExportExcelController extends Controller
{
protected $customers;
public function __construct(Customer $customers){
$this->customers = $customers;
}
public function index()
{
$customers = $this->customers->orderBy('id', 'desc')->get();
return view('ImportExportExcel', compact('customers'));
}
public function importExcel(CustomerImportRequest $request)
{
try {
if ($request->hasFile('import_file'))
{
$file = $request->file('import_file');
$columnRead = (new ImportCustomers)->toArray($file);
$customerCheck = $this->customers->where('mobile',$columnRead[0][1]["mobile"])->first(); //**here not getting result, rather shows null**
//dd($customerCheck);
if($customerCheck)
{
$customers = $customerCheck;
/*
**How to update if duplicates are found and display old values updated. How to achieve this?**
*/
}else{
$customers = new $this->customers;
Excel::import(new ImportCustomers, $file);
return redirect()->back()->with('success','Data imported successfully.');
}
}
} catch (\Maatwebsite\Excel\Validators\ValidationException $e) {
$failures = $e->failures();
//dd($failures);
return redirect()->back()->with('import_errors', $failures);
}
}
public function exportExcel()
{
$customers = Customer::select(["customer_name", "mobile", "email"])->get();
return Excel::download(new ExportCustomers($customers), 'customers.xlsx');
}
}
/This is the database migration schema:/
public function up()
{
Schema::create('customers', function (Blueprint $table) {
$table->id();
$table->string('customer_name');
$table->string('mobile', 13)->unique();
$table->string('email')->nullable();
$table->timestamps();
});
}
Here "mobile" is unique, So if values like customer_name, and email are with modified values in an excel sheet with the same mobile no. then while importing, values should be updated.
excel sheet
I have used maatwebsite with Laravel 6
Controller :
Excel::import(new ImportCustomers(), $file);
then you could apply your logic at the Import class for customers:
public function model(array $row)
{
try {
$mobile = $row[1]; // referenced by row
$customer_name = $row[0];
$email = $row[1];
$customer = Customer::where('mobile', $mobile)->first();
//apply your logic
if (!$customer) { // you may not need if else, if no customer exists then create a new record and assign mobile
$customer = new Customer();
$customer->mobile = $mobile;
}
$customer->customer_name = $customer_name;
$customer->email = $email;
$customer->save();
return $customer;
} catch (\Exception $ex) {
dd($ex);
return;
}
}
Also please remove the rule about mobile, I think this should work
"*.mobile' => 'required',"
because your logic handles that mobile is unique.
//Check for the existing value in database and if result is found do this.
public function model(array $row)
{
// Check mobile already exists
$count = Customer::where('mobile',$row['mobile'])->first();
if($count){
return;
}
else{
return new Customer([
'customer_name' => $row['customer_name'],
'mobile' => $row['mobile'],
'email' => $row['email']
]);
}
}

Log failed queue jobs to file fallback

If MySQL go down, there is any fallback options to log Failed Queue Jobs to file?
I try
namespace App\Providers\AppServiceProvider;
function register()
Queue::failing(function (JobFailed $event) {
if($event->exception instanceof \PDOException){
$data = [
'code' => $event->exception->getCode(),
'connectionName' => $event->connectionName,
'getQueue' => $event->job->getQueue(),
'getRawBody' => $event->job->getRawBody(),
'exception' => (string)$event->exception,
];
\App\Repositories\FailedJobMysqlDown::set($data);
}
});
but this check the reasons of jobs go down,
i wanna catch inserting into failed_jobs exception
[2002] No such file or directory (SQL: insert into `failed_jobs` (`connection`, `queue`, `payload`, `exception`, `failed_at`) values (redis, superhigh, {"ty................
Any Ideas?
Thanks
found solutions
Create class
<?php
namespace App\Override;
use Illuminate\Queue\Failed\FailedJobProviderInterface;
use Illuminate\Queue\Failed\DatabaseFailedJobProvider ;
use Illuminate\Support\Facades\Date;
class FallbackDatabaseFailedJobProvider extends DatabaseFailedJobProvider implements FailedJobProviderInterface
{
public function log($connection, $queue, $payload, $exception)
{
try{
return parent::log(...func_get_args());
}catch (\Exception $e) {
$failed_at = Date::now();
$exception = (string) $exception;
$data = [
'connectionName' => $connection,
'getQueue' => $queue,
'getRawBody' => $payload,
'exception' => $exception,
'failed_at' => $failed_at,
];
\App\Repositories\FailedJobMysqlDown::set($data);
}
}
}
and register it in serviceprovider
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Override\FallbackDatabaseFailedJobProvider;
class FailedLogServiceProvider extends ServiceProvider
{
public function boot()
{
// Get a default implementation to trigger a deferred binding
$_ = $this->app['queue.failer'];
// Swap the implementation
$this->app->singleton('queue.failer', function ($app) {
$config = $this->app['config']['queue.failed'];
return new FallbackDatabaseFailedJobProvider($this->app['db'], $config['database'], $config['table']);
});
}
}
add to condig/app.php in providers
'providers' => [
..............
App\Providers\FailedLogServiceProvider::class,
]
use current or create your own implementation log function
<?php
namespace App\Repositories;
/**
* Log failed job to file fallback
*/
class FailedJobMysqlDown
{
private static $file = '_xlogJobFailedMysqlDown'; //set full path
public static function get(){
$x = file(self::$file);
$data = [];
foreach($x as $line){
$data[] = json_decode($line);
}
return $data;
}
public static function set($message){
$message = json_encode($message);
file_put_contents(self::$file,$message.PHP_EOL , FILE_APPEND | LOCK_EX );
}
}
voilĂ 

Should I define model class functions for repeated controller codes in Laravel 5?

I use the code like that repeatedly in my controllers:
...
if (Cache::has('user_' . $id)) {
$user = Cache::get('user_' . $id);
} else {
$user = User::with('location')->find($id);
if(!$user) {
return Response::view('errors/404', array());
}
Cache::put('user_' . $id, $user, 15);
}
...
Is it best practice to place them in Model classes as a class function? So that I would write just one line:
$user = User::getUserDetails($id);
And in model classes:
public function getUserDetails($id)
{
if(Cache::has('user_' . $id)) {
$user = Cache::get('user_' . $id);
} else {
$user = User::with('location')->find($id);
if(!$user) {
return Response::view('errors/404', array());
}
Cache::put('user_' . $id, $user, 15);
}
return $user;
}
What do you suggest?
You have two options, either use Helpers or just put it in one controller.
You just need to set it as static function.
public static function getUserDetails($id)
{
if(Cache::has('user_' . $id)) {
$user = Cache::get('user_' . $id);
} else {
$user = User::with('location')->find($id);
if(!$user) {
return Response::view('errors/404', array());
}
Cache::put('user_' . $id, $user, 15);
}
return $user;
}
If you use Helpers.
use Helpers;
Helpers::getUserDetails($id);
If you use Controller.
use App\Http\Controllers\TheControllerName;
TheControllerName::getUserDetails($id)
You can make a repository for reusable code.
Make a repository in app/Repositories/UserRepository.php
<?php
namespace App\Repositories;
use App/User;
/**
* Class UserRepository
*
* #package App\Repositories
*/
class UserRepository
{
public static function getUserDetails($id)
{
if(Cache::has('user_' . $id)) {
$user = Cache::get('user_' . $id);
} else {
$user = User::with('location')->find($id);
if(!$user) {
return Response::view('errors/404', array());
}
Cache::put('user_' . $id, $user, 15);
}
return $user;
}
}
In your controller, You can use like this:
<?php
namespace App\Http\Controllers;
use App\Repositories\UserRepository;
class UserController extends Controller
{
protected $user_repo;
public function __construct(UserRepository $user)
{
..
$this->user_repo = $seo;
..
}
public function YourFunctionName()
{
..
$this->user_repo->getUserDetails($id);
..
}
}
Please let me know for any confusion!

Save all values in Uppercase Laravel 5+

What would be the best way to save all values in the database as uppercase. So before saving convert all strings to uppercase. I see options to use Events or a trait would probably be best but not quite sure how to achieve this. I do not want to create accessors & mutators for each of my fields.
Got this from : https://laracasts.com/discuss/channels/eloquent/listen-to-any-saveupdatecreate-event-for-any-model
trait Trackable {
public static function bootTrackable()
{
static::creating(function ($model) {
// blah blah
});
static::updating(function ($model) {
// bleh bleh
});
static::deleting(function ($model) {
// bluh bluh
});
}
}
Im not sure how I would be able to get the actual request values to convert them to uppercase?
As #Louwki said, you can use a Trait to do that, in my case I did something like this:
trait SaveToUpper
{
/**
* Default params that will be saved on lowercase
* #var array No Uppercase keys
*/
protected $no_uppercase = [
'password',
'username',
'email',
'remember_token',
'slug',
];
public function setAttribute($key, $value)
{
parent::setAttribute($key, $value);
if (is_string($value)) {
if($this->no_upper !== null){
if (!in_array($key, $this->no_uppercase)) {
if(!in_array($key, $this->no_upper)){
$this->attributes[$key] = trim(strtoupper($value));
}
}
}else{
if (!in_array($key, $this->no_uppercase)) {
$this->attributes[$key] = trim(strtoupper($value));
}
}
}
}
}
And in your model, you can specify other keys using the 'no_upper' variable. Like this:
// YouModel.php
protected $no_upper = ['your','keys','here'];
Was a lot easier than I through. Solution that is working for me using traits, posting it if anyone also run into something like this.
<?php
namespace App\Traits;
trait SaveToUpper
{
public function setAttribute($key, $value)
{
parent::setAttribute($key, $value);
if (is_string($value))
$this->attributes[$key] = trim(strtoupper($value));
}
}
}
UPDATE:
For Getting values as upper case you can add this to the trait or just add it as a function in the model:
public function __get($key)
{
if (is_string($this->getAttribute($key))) {
return strtoupper( $this->getAttribute($key) );
} else {
return $this->getAttribute($key);
}
}

Reusing an accessors & mutators

In several of my models I have code like this
public function setTotalAttribute($value)
{
return $this->attributes['total'] = $value * 100;
}
public function getTotalAttribute($value)
{
return $value * 0.01;
}
Sometimes the field that I am mutating is called purchase or price, but the code is the same (changing 7.99 to 799 to store in the DB, and change it back on return).
If all the fields were named the same I could use a trait, however they are slightly different.... is there a way I can setup something similar to the date fields which auto-mutate to Carbon instances?
One solution is to define the fields that deal with dollars/cents conversion in the models that have such fields, and then use a trait to override the global mutators/accessors.
class Model
{
use HasMoneyFields;
protected $moneyFields = ['purchase', 'price', 'total'];
}
trait HasMoneyFields
{
public function getAttributeValue($key)
{
$value = parent::getAttributeValue($key);
if (property_exists($this, 'moneyFields')) {
if (in_array($key, $this->moneyFields)) {
$value /= 100;
}
}
return $value;
}
public function setAttribute($key, $value)
{
parent::setAttribute($key, $value);
if (property_exists($this, 'moneyFields')) {
if (in_array($key, $this->moneyFields)) {
$this->attributes[$key] = $value * 100;
}
}
}
}
You might be interested in https://github.com/topclaudy/eloquent-mutators
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
use \Awobaz\Mutator\Mutable;
protected $accessors = [
'title' => 'trim_whitespace',
'content' => 'trim_whitespace',
];
}
The package allows you to create custom accessors/mutators extensions.

Resources