Why \Cache::remember and Cache::put do not write data into redis? - laravel

Checking which methods can be used with writing data in redis(it is supposed that item data would be often read and not often writte)
I found that \Cache::remember and Cache::put do not work at all !
<?php
namespace App\Jobs;
use App\Library\Services\Interfaces\LogInterface;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Foundation\Bus\Dispatchable;
use App\Models\Item;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Redis;
/*
php artisan job:dispatchNow ItemsCachingJob
*/
class ItemsCachingJob
{
// Interface for logging messages
protected $logger;
use Dispatchable;
/**
* Create a new job instance.
*
* #return void
*/
public function __construct()
{
$this->logger = app(LogInterface::class);
}
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
...
$ret = \Cache::remember($redisUniqueKey.'aaa', $itemsCachingHours, function() use($item) {
return serialize( $item->toArray());
});
\Log::info($ret); // I see no data in redis, but I see packed string in log file
$ret2 = Cache::put($redisUniqueKey.'bbb', serialize( $item->toArray()), $itemsCachingHours);
\Log::info($ret2); // I see no data in redis, but I see boolean value "1" in log file
$ret3 = Redis::set($redisUniqueKey, serialize( $item->toArray()));
\Log::info($ret3); // I SEE Valid data in redis with valid key and valid TTL, and I see array => Array (
[ Predis\Response\Status payload] => OK)
Redis::expire($redisUniqueKey, $itemsCachingHours);
I used
use Illuminate\Support\Facades\Cache;
Is it correct file to import ?
Why so ?
"laravel/framework": "^9.47",
"predis/predis": "^2.1",
Thanks in advance!

Related

How to execute save in a Laravel Listener

I try to import a file with Laravel Command. The import works well. After the import, Laravel triggers an event. This works also well.
With a listener, I catch the event. It works well when I do as example dd('it works')!
<?php
namespace App\Listeners;
use App\EventsNew\Notifiables\ConsignmentCreatedViaApi;
class ConsignmentCreatedListener
{
public function __construct()
{
}
public function handle($event)
{
dd('it works');
}
}
But if I try to save some values into the db, it doesn't work, neighter it returns an error:
<?php
namespace App\Listeners;
use App\Models\Settlement;
class ConsignmentCreatedListener
{
/**
* Create the event listener.
*
* #return void
*/
public function __construct()
{
}
/**
* Handle the event.
*
* #param object $event
* #return void
*/
public function handle( $event)
{
Settlement::create([
'consignment_id'=> 1
]);
}
}
I tried to execute Settlement::create in the console command and it works.
Isn't it possible to insert something into the db after an avent? Or is there another way to do it?

Laravel 9 - How to get resolved instance of task in Queue::before event?

I have a multi-tenant project with multiple databases and a single queue. I need to switch between databases before running the job.
Here's the code I have:
Queue::before(function (JobProcessing $event) {
$costumer = DB::table('costumers')
->select('db_password', 'id')
->where('id', 11)
->first();
DB::disconnect('mysql');
config(
[
'database.connections.mysql.database' => 'costumer_'.$costumer->id.'_db',
'database.connections.mysql.username' => 'costumer_'.$costumer->id,
'database.connections.mysql.password' => Crypt::decryptString($costumer->db_password),
'costumer.code' => $costumer->id,
]
);
DB::reconnect('mysql');
});
It's working, but in the where clause, the id must be dynamically set.
So I pass the id in the Job::dispatch() method, but here's the problem: how do I get the job instance to return the data inside it?
I saw in another question the $event->job->instance and $event->job->getResolvedJob().
The first option is a protected property, so it doesn't work (it worked in Laravel 5). The second returns null.
You can set public property or getter in your job, so you can retrieve your id from the job instance, like here in getPodcastId:
<?php
namespace App\Jobs;
use App\Models\Podcast;
use App\Services\AudioProcessor;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ProcessPodcast implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* The podcast instance.
*
* #var \App\Models\Podcast
*/
protected $podcast;
/**
* Create a new job instance.
*
* #param App\Models\Podcast $podcast
* #return void
*/
public function __construct(Podcast $podcast)
{
$this->podcast = $podcast;
}
public function getPodcastId()
{
return $this->podcast?->id;
}
/**
* Execute the job.
*
* #param App\Services\AudioProcessor $processor
* #return void
*/
public function handle(AudioProcessor $processor)
{
// Process uploaded podcast...
}
}
But, to be honest, I think this is not really safe to change config on the go. The better solution would be to initialize another database connection inside your job and use it in your job dirrectly:
use Illuminate\Database\Connectors\ConnectionFactory;
// ...
public function __construct()
{
$factory = app(ConnectionFactory::class);
return $this->db = $factory->make(/* Config */);
}

Laravel 6 Use a custom Hasher during migration from CakePHP 2.x

We are migrating applications from CakePHP 2.X but we need to implement our mobile API's before the migration. I have followed all the items I could find but they all seem to be for v5 or less. No matter what I do Hash::make() still results in a Bcrypt password.
I really want to 2 birds one stone with having this allow sha1() login and update to Bcrypt upon login but we havent implemented on CakePHP 2.x successfully. So I need to get the Hasher working or a workaround. I know I can just Hash manually in the model but that doesnt allow Auth to work.
Any help would be appreciated
app.php config file
Illuminate\Foundation\Providers\FoundationServiceProvider::class,
//Illuminate\Hashing\HashServiceProvider::class,
App\Providers\CustomHashServiceProvider::class,
Illuminate\Mail\MailServiceProvider::class,
CustomHashServiceProvider.php
<?php
namespace App\Providers;
use Illuminate\Hashing\HashServiceProvider;
use App\Libs\CustomHash\CustomHasher as CustomHasher;
class CustomHashServiceProvider extends HashServiceProvider
{
public function register()
{
$this->app->singleton('hash', function () {
return new CustomHasher;
});
}
}
CustomHasher.php
<?php
namespace App\Lib\CustomHash;
use Illuminate\Contracts\Hashing\Hasher as HasherContract;
class CustomHasher implements HasherContract {
/**
* Hash the given value.
*
* #param string $value
* #return array $options
* #return string
*/
public function make($value, array $options = array()) {
//I have custom encoding / encryption here//
//Define your custom hashing logic here//
return sha1(env('SEC_SALT').$value);
}
/**
* Check the given plain value against a hash.
*
* #param string $value
* #param string $hashedValue
* #param array $options
* #return bool
*/
public function check($value, $hashedValue, array $options = array()) {
return $this->make($value) === $hashedValue;
}
/**
* Check if the given hash has been hashed using the given options.
*
* #param string $hashedValue
* #param array $options
* #return bool
*/
public function needsRehash($hashedValue, array $options = array()) {
return false;
}
public function info($hashedValue): array {
return $hashedValue;
}
}
UPDATE
I refactored based on #Mdexp answer to this .... but I found out the Configs are ignored unless added in app.php on Lumen
New app.php
/*
* Application Service Providers...
*/
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class,
App\Providers\Sha1HashServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
Sha1HashServiceProvider.php
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class Sha1HashServiceProvider extends ServiceProvider {
public function register() {
//
}
public function boot() {
$this->app->make('hash')->extend('sha1', function () {
// Just create the driver instance of the class you created in the step 1
return new \App\Lib\Sha1Hash\Sha1Hasher;
});
}
}
Sha1Hasher.php
<?php
namespace App\Lib\Sha1Hash;
use Illuminate\Hashing\AbstractHasher;
use Illuminate\Contracts\Hashing\Hasher as HasherContract;
use RuntimeException;
class Sha1Hasher extends AbstractHasher implements HasherContract {
public function __construct(array $options = []) {
}
public function make($value, array $options = []) {
$hash = sha1(env('SEC_SALT').$value);
if ($hash === false) {
throw new RuntimeException('Sha1 hashing not supported.');
}
return $hash;
}
public function check($value, $hashedValue, array $options = []) {
return ($this->make($value) == $hashedValue)?true:false;
}
public function needsRehash($hashedValue, array $options = array()): bool {
return false;
}
}
I would use the default HashServiceProvider and register a new driver into it. It would also make the switch back from sha1 to bcrypt even quicker once you completed the transitioning phase.
1) You have have to create a class which extends the Illuminate\Hashing\AbstractHasher or at least implements the Illuminate\Contracts\Hashing\Hasher. Take a look at the current Bcrypt driver implementation as a reference on GitHub.
The CustomHasher class you provided should work just fine as a driver, I would just rename it to avoid confusion with naming.
2) Now you can register the hash drivers in a service provider like:
public function boot()
{
$this->app->make('hash')->extend('sha1', function () {
// Just create the driver instance of the class you created in the step 1
return new YourCustomSha1Hasher();
});
}
3) Then in your config/hashing.php file, set the driver to 'sha1' (must be equal to the first parameter of the extend function call.
4) It should work straight out of the box, and to choose a different hashing driver, just change the config/hashing.php configuration file with the driver that you want to use for hashing.
Note: the whole code hasn't been tested, but I looked through the source code to come up with this solution that should work. Just comment anything isn't working as expected so I can fix my answer.

Call to a member function expectsOutput() on integer when testing artisan console command

I have a very simple example to show the problem:
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class VendorCounts extends Command
{
/**
* The name and signature of the console command.
*
* #var string
*/
protected $signature = 'vendor:counts
{year : The year of vendor counts}';
/**
* The console command description.
*
* #var string
*/
protected $description = 'Runs vendor counts';
/**
* Create a new command instance.
*
* #return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* #return mixed
*/
public function handle()
{
$this->info('Starting Vendor Counts');
}
}
<?php
namespace Tests\Feature\Console\Vendor;
use Tests\TestCase;
class VendorCountsTest extends TestCase {
public function testVendorCounts()
{
$this->artisan('vendor:counts', ['year' => 2019])
->expectsOutput('Starting Vendor Counts')
->assertExitCode(0);
}
}
I get the following error:
1) Tests\Feature\Console\Vendor\VendorCountsTest::testVendorCounts
Error: Call to a member function expectsOutput() on integer
/Users/albertski/Sites/vrs/tests/Feature/Console/Vendor/VendorCountsTest.php:12
I know the command definitely runs because if I put a dump statement in it shows the debug output.
I am using Laravel 6.3. Is there a different way to test this?
The problem I was using was that TestCase was using Laravel\BrowserKitTesting\TestCase as BaseTestCase. I ended up creating another Base just for console commands.
<?php
namespace Tests;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
abstract class ConsoleTestCase extends BaseTestCase
{
use CreatesApplication;
}
Can you add this to your VendorCountsTest class:
public $mockConsoleOutput = true;
This is set by a trait but just making sure something hasn't changed the value. When $mockConsoleOutput is false it will directly run the artisan commmand. When it is true it will wrap it in a PendingCommand object that has those methods you are trying to call.
I had an issue where the use of expectedOutput() on my Artisan class would fail all the time, which turned out to be because I had used exit() and/or die() in a method, which really did not work well with phpunit test methods.
So if you want to stop processing the "script" at some point, just use an empty return and not exit() or die() if you want to utilize the built-in ->artisan() testing in Laravel.
Working example:
<?php
// app/Console/Commands/FooCommand.php
public function handle()
{
$file = $this->argument('file');
if (! file_exists($file)) {
$this->line('Error! File does not exist!');
return;
}
}
// tests/Feature/FooCommandTest.php
public function testFoo() {
$this->artisan('foo', ['file' => 'foo.txt'])->expectsOutput('Something');
}
Non-working example:
<?php
// app/Console/Commands/FooCommand.php
public function handle()
{
$file = $this->argument('file');
if (! file_exists($file)) {
$this->line('Error! File does not exist!');
exit;
}
}
// tests/Feature/FooCommandTest.php
public function testFoo() {
$this->artisan('foo', ['file' => 'foo.txt'])->expectsOutput('Something');
}

Put function in Queue but not loaded relation data | Laravel Queue

I need to generate a pdf of user with their related data. I have created a function in controller to generate pdf(using snappy-pdf package). Data is loaded properly, pdf generated and uploaded on S3 successfully. Now I am trying to put that function in queue with user data. But data is not loaded for generation pdf. Instead it only laod the user model data but not the related data (like property and contact).
$user = User::where("id", 83)->with(['property', 'contacts'])->first();
$contact_list = [];
if(count($user->contacts)){
foreach ($user->contacts as $contact) {
array_push($contact_list, $contact->contactDetails->name);
}
}
$user->contact_list = $contact_list;
return view('pdf_view', ["user" => $user]);
if($request->has('download')) {
// dispatch the event
/*$pdf = PDF::loadView('pdf_view', ["user" => $user]);
// upload pdf
Storage::disk('s3')->put("user-invoice/user.pdf", $pdf->output(), 'public');*/
dispatch(new GenerateSignedDocument($user));
return "done";
}
And here is my job file.
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Log;
use PDF;
use Storage;
class GenerateSignedDocument implements ShouldQueue
{
use InteractsWithQueue, Queueable, SerializesModels;
/**
* Create a new job instance.
*
* #return void
*/
private $user;
public function __construct($user)
{
$this->user = $user;
}
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
// Log::info('Showing user: '.$this->user);
$pdf = PDF::loadView('pdf_view', ["user" => $this->user]);
// upload pdf
Storage::disk('s3')->put("user-invoice/user.pdf", $pdf->output(), 'public');
}
}
The SerializesModels trait only stores the model's key and reloads the model from the database when it is unserialized, so this is likely not going to load your relationships.
This is usually a big benefit as you'll receive fresh data when your queued job actually runs, not the data from when the job was scheduled.
In any case, it's probably best to either:
Load the relationships the job requires in the job itself and not rely on them being preloaded.
or
Pass the property and contacts as separate dependencies to your job class.
I don't know this is the perfect solution or not but I do this and work fine.
I called the required function(uploadUserDocument) for performing pdf generation in the handler function.
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
app('App\Http\Controllers\UserController')->uploadUserDocument($this->user_id);
}
and dispatch the event as it is in the controller
dispatch(new GenerateSignedDocument(83));
and put the required code for generating pdf in the uploadUserDocument function like this.
/**
* Function is used to Save document to s3 cloud
* #param $user_id
* #return Response
*/
public function uploadUserDocument($user_id){
$pdf = PDF::loadView('pdf_view', ["user" => $user]);
// upload pdf
Storage::disk('s3')->put("user-invoice/user.pdf", $pdf->output(), 'public');
}

Resources