Log failed queue jobs to file fallback - laravel

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Ă 

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']
]);
}
}

How to modify fortify CreatesNewUsers.php interface?

I need to modify /vendor/laravel/fortify/src/Contracts/CreatesNewUsers.php interface
and to add 1 more bool parameter, as using CreateNewUser in different places of the app
validations rules are different, say in some places password is not filled on user creation, but must be separate function.
So I copied file /project/resources/fortify/CreatesNewUsers.php with content :
<?php
namespace Laravel\Fortify\Contracts;
interface CreatesNewUsers
{
public function create(array $input, bool $makeValidation);
}
and in app/Actions/Fortify/CreateNewUser.php I modified :
<?php
namespace App\Actions\Fortify;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
//use Laravel\Fortify\Contracts\CreatesNewUsers;
use Resources\Fortify\CreatesNewUsers; // Reference to my interface
use Laravel\Jetstream\Jetstream;
class CreateNewUser implements CreatesNewUsers
{
use PasswordValidationRules;
public function create(array $input, bool $makeValidation)
{
...
But trying to use this class I got error
Interface "Resources\Fortify\CreatesNewUsers" not found
Which is the valid way ?
Thanks!
I moved interface at file app/Actions/Fortify/CreatesNewUsers.php :
<?php
namespace App\Actions\Fortify;
interface CreatesNewUsers
{
public function create(array $input, bool $make_validation, array $hasPermissions);
}
and modified app/Actions/Fortify/CreateNewUser.php :
<?php
namespace App\Actions\Fortify;
use App\Models\User;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use DB;
use App\Actions\Fortify\CreatesNewUsers;
use Laravel\Jetstream\Jetstream;
use Spatie\Permission\Models\Permission;
class CreateNewUser implements CreatesNewUsers
{
use PasswordValidationRules;
/**
* Validate and create a newly registered user.
*
* #param array $input
*
* #return \App\Models\User
*/
public function create(array $input, bool $make_validation, array $hasPermissions)
{
if ($make_validation) {
$userValidationRulesArray = User::getUserValidationRulesArray(null, '', []);
if (\App::runningInConsole()) {
unset($userValidationRulesArray['password_2']);
}
$validator = Validator::make($input, $userValidationRulesArray);//->validate();
if ($validator->fails()) {
$errorMsg = $validator->getMessageBag();
if (\App::runningInConsole()) {
echo '::$errorMsg::' . print_r($errorMsg, true) . '</pre>';
}
return $errorMsg;
}
} // if($make_validation) {
$newUserData = [
'name' => $input['name'],
'email' => $input['email'],
'account_type' => $input['account_type'],
'phone' => $input['phone'],
'website' => $input['website'],
'notes' => $input['notes'],
'first_name' => $input['first_name'],
'last_name' => $input['last_name'],
];
if (isset($input['password'])) {
$newUserData['password'] = Hash::make($input['password']);
}
if (isset($input['status'])) {
$newUserData['status'] = $input['status'];
}
if (isset($input['activated_at'])) {
$newUserData['activated_at'] = $input['activated_at'];
}
if (isset($input['avatar'])) {
$newUserData['avatar'] = $input['avatar'];
}
try {
DB::beginTransaction();
$newUser = User::create($newUserData);
foreach ($hasPermissions as $nextHasPermission) {
$appAdminPermission = Permission::findByName($nextHasPermission);
if ($appAdminPermission) {
$newUser->givePermissionTo($appAdminPermission);
}
}
DB::commit();
return $newUser;
} catch (QueryException $e) {
DB::rollBack();
if (\App::runningInConsole()) {
echo '::$e->getMessage()::' . print_r($e->getMessage(), true) . '</pre>';
}
}
return false;
}
}
It allows me to use CreateNewUser from different parts of app, like seeders, adminarea, user registration
with different behaviour. For me it seems good way of using fortify and CreateNewUser...

Laravel php testing, called undefined function?

I write a code using laravel 8 and i want to create CRUD Testing for all model so i can called it in every test case, for example I Have Operator Test that extends TestCase (crud testing master) ref : crud test, this is my Operator Test looks like,..
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
class OperatorTest extends TestCase
{
use RefreshDatabase, WithFaker;
public function test_user_can_update_an_operator()
{
$this->setBaseRoute('master.operator');
$this->setBaseModel('App\Models\Operator');
$this->signIn();
$this->attributes = [
'username' => 'test update',
'level' => 1,
'category_id' => 1,
'password' => 'password'
];
$this->update($this->attributes);
}
}
and this is my TestCase.php looks like,...
<?php
namespace Tests;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use App\Models\Operator;
use Illuminate\Foundation\Testing\WithFaker;
abstract class TestCase extends BaseTestCase
{
use CreatesApplication;
use RefreshDatabase;
protected $base_route = null;
protected $base_model = null;
protected function signIn($user = null)
{
$user = $user ?? Operator::factory()->create();
$this->actingAs($user);
return $this;
}
protected function setBaseRoute($route)
{
$this->base_route = $route;
}
protected function setBaseModel($model)
{
$this->base_model = $model;
}
protected function update($attributes = [], $model = '', $route = '')
{
$this->withoutExceptionHandling();
$route = $this->base_route ? "{$this->base_route}.update" : $route;
$model = $this->base_model ?? $model;
$model = create($model);
if (! auth()->user()) {
$this->expectException(\Illuminate\Auth\AuthenticationException::class);
}
$response = $this->patchJson(route($route, $model->id), $attributes);
tap($model->fresh(), function ($model) use ($attributes) {
collect($attributes)->each(function($value, $key) use ($model) {
$this->assertEquals($value, $model[$key]);
});
});
return $response;
}
}
after that when I tun php artisan test, i got an error like this :
anything worng in my codes ? i used laravel 8.
You need to initialize the model first and then call the model factory.
The create function is undefined at line 64.
Instead of
$model = create($model);
Use bellow code
$model = app()->make($model)
$model = $model::factory()->create();
More information on app()->make() and factory.

Store config in database in Laravel

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.

"message": "Call to a member function warning() on null",

This is my laravel model
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Library\Log as MailLog;
class SendingServerPhpMail extends SendingServer
{
protected $table = 'sending_servers';
public function send($message, $params = array())
{
try {
$transport = \Swift_MailTransport::newInstance();
// Create the Mailer using your created Transport
$mailer = \Swift_Mailer::newInstance($transport);
// Actually send
$sent = $mailer->send($message);
if ($sent) {
MailLog::info('Sent!');
return array(
'status' => self::DELIVERY_STATUS_SENT,
);
} else {
**MailLog::warning('Sending failed');**
return array(
'status' => self::DELIVERY_STATUS_FAILED,
'error' => 'Unknown SMTP error',
);
}
} catch (\Exception $e) {
MailLog::warning('Sending failed');
MailLog::warning($e->getMessage());
return array(
'status' => self::DELIVERY_STATUS_FAILED,
'error' => $e->getMessage(),
'message_id' => null,
);
}
}
}
?>
This is the library which i am using as mail log
<?php
namespace App\Library;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Formatter\LineFormatter;
class Log
{
public static $logger;
public static $path;
public static function warning($message)
{
self::$logger->warning($message);
}
}
?>
I am getting this error while calling warning function which is declared in mail log library. you can see modal where I am calling warning function
"message": "Call to a member function warning() on null"
In modal, I have used the maillog library and after that, I am calling warning and other function in the model, which is defined in the mail log library.
but for warning function, I am getting error Call to a member function warning() on null
I think this is because the $logger inside App\Library\Log class is never set
public static function warning($message)
{
if (! self::$logger) {
self::$logger = new Logger('name');
self::$logger->pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING));
}
self::$logger->warning($message);
}
Hope this can help

Resources