Laravel cache::remember - cannot serialize closure - laravel

I am getting the following exception when trying to use cache remember:
But in the Laravel docs (https://laravel.com/docs/5.7/cache) they are using a closure in their example:
Any help much appreciated ... have googled it and people seem to be having issue with forever() closures not being serializable (but suggested solutions not working for me)
/**
* #param string $guid
* #return Account
*/
public function getAccount(string $guid): Account
{
$key = md5(sprintf('xero/accounts[guid="%s"]', $guid));
return $this->cache->remember($key, Carbon::now()->addHour(), function () use ($guid) {
return $this->xero->loadByGUID(Account::class, $guid);
});
}
I've now also tried doing this instead (to get around passing a closure to cache::remember fxn):
public function getAccount(string $guid): Account
{
$key = md5(sprintf('xero/accounts[guid="%s"]', $guid));
$account = $this->cache->get($key);
if ($account === null) {
//dump('account not found, storing in cache...');
/** #var Account $account */
$account = $this->xero->loadByGUID(Account::class, $guid);
$this->cache->put($key, $account, Carbon::now()->addHour());
}
}
But still getting same error (cannot serialize Closure) at the line '$this->cache->put($key, $account, Carbon::now()->addHour());'
The $account object is of type: use XeroPHP\Models\Accounting\Account;
(from https://github.com/calcinai/xero-php)

Models contain a reference to the Xero Application which contain an instance of a Guzzle client, which itself has properties that contains closures.
PHPs serialize function is not able to serialize Closures: https://3v4l.org/1MIpd
A possibility would be calling toStringArray and fromStringArray when you store and retrieve a model.
(full credit to Josh-G: https://github.com/calcinai/xero-php/issues/734)

Related

How does Laravel scope chaining call scopes from the base class not the Builer?

Let there is a model named User with two scopes ScopeF1() and ScopeF2(). We can write
User::f1()->f2()->get();
and it performs the scopes and returns the result. From what I know, the scopes return an instance of Builder because we for example call query->where(...,...) inside it. When in code
User::f1()->f2()->get();
It calls f1(), an instance of Builder is returned and based of chaining rule, it must call f2() from Builder but in Laravel, it calls f2()of class User not Builder and I don't know the logic behind it. I would be appreciated if anyone can explain how it works.
Update
What I am trying to do is to implement a default scope for every field of a model in my project. For example, if model User contains mobile field, then I can use the following scope without actually writing its function
User::mobileScope($value)->...
To do so every model extends an abstract class EntityAbstract which in turn extends Model class. Every model in my project contains a static function mappedFields() which returns a key-value array of camel case-actual field names. for example
return [
'firstName' => 'first_name',
'lastName' => 'last_name',
'mobile' => 'mobile'
];
A function fn($value) also exists which returns field name of a given camel case value. As an example User::fn('firstName') returns first_name.
In EntityAbstract I have the following functions
public function __call($name, $arguments)
{
if (str_ends_with($name, "Scope")) {
$scopeField = substr($name, 0, -5);
$mappedFields = static::mappedFields();
if (array_key_exists($scopeField, $mappedFields)) {
return $this->where(static::fn($scopeField), $arguments[0]);
} else
return parent::__call($name, $arguments);
} else
return parent::__call($name, $arguments);
}
public static function __callStatic($name, $arguments)
{
if (str_ends_with($name, "Scope")) {
$scopeField = substr($name, 0, -5);
$mappedFields = static::mappedFields();
if (array_key_exists($scopeField, $mappedFields)) {
$model = new static();
return $model->where(static::fn($scopeField), $arguments[0]);
}
else
return parent::__callStatic($name, $arguments);
} else
return parent::__callStatic($name, $arguments);
}
I called parent::__callStatic($name, $arguments) in order to make sure the work flow of Laravel is not interrupted when the conditions of my scope is not satisfied.
Now the problem is when I write
User::mobileScope('123456')->first()
it works perfectly but when I write
User::mobileScope('123456')->nameScope('xyz')->first()
it throws the error
Call to undefined method Illuminate\Database\Eloquent\Builder::nameScope()
As you see, the mobileScope returns as instance of Builder not the User class and nameScope() is undefined in Builder.
Thanks in advance.
I think that you are complicating things with those calls and static functions as almost practically you are overriding scopes behaviour of yours models.
It wouldn't be better to declare dynamic scopes as declared in the Laravel documentation.
/**
* Scope a query to only include users with a given field value.
*
* #param Builder $query
* #param $field
* #param $value
* #return Builder
*/
public function scopeWithField(Builder $query, $field, $value): Builder
{
return $query->where($field, $value);
}
Using as
$user = User::withField('name','Manuel Glez')->withField('email','manuel#mydomain.com')->get();
or even you can improve that and pass another param which specify the operator to use and make it default to '='
public function scopeWithField(Builder $query, $field, $value,$operator='='): Builder
{
return $query->where($field,$operator ,$value);
}
So using as before continues working, but now you can query with operators like
<,>,>=
etc...
Hope this help

How to change the using of the pattern repository loaded using interfaces for initializing the model? Laravel, Laracom

Good day.
A store was created based on https://github.com/Laracommerce/laracom on Laravel.
In the process, it was noticed that, along with pulling up the implementation for the interface with a call like:
use App\Products\Repositories\Interfaces\ProductRepositoryInterface;
the binding of which is declared in RepositoryServiceProvider (app \ Providers \ RepositoryServiceProvider.php),
direct calls like use App\Shop\Products\Repositories\ProductRepository are used;
(e.g. here app/Shop/Orders/Repositories/OrderRepository.php)
You can find several similar examples in the code, and most often a direct address is required to invoke
$repositoryWithModel = new Repository($modelObject).
I did not find a definite way out of this situation, I ask the advice of those who came across an example of quality implementation.
The implementation of your ProductRepository expects a Product as constructor parameter. A respository should not do that. Instead if a repository has to handle a product model, it should be passed as a parameter to a function.
For example this:
/**
* #param Brand $brand
*/
public function saveBrand(Brand $brand)
{
$this->model->brand()->associate($brand);
}
Can be rewritten to:
/**
* #param Product $product
* #param Brand $brand
*/
public function saveBrand(Product $product, Brand $brand)
{
$product->brand()->associate($brand);
}
If you remove the Product parameter from the constructor, then you can use the repository without creating it using the new keyword every time:
class BrandController extends Controller {
public function __construct(ProductRepositoryInterface $repository) {
$this->repository = $repository;
}
public function linkBrandToProduct(Request $request): void {
$product = $this->repository->findProductById($request->productId);
$brand = $this->repository->findBrandById($request->brandId);
$this->repository->saveBrand($product, $brand);
}
}

Cache Eloquent query for response

In one of my applications I have a property that is needed throughout the app.
Multiple different parts of the application need access such as requests, local and global scopes but also commands.
I would like to "cache" this property for the duration of a request.
My current solution in my Game class looks like this:
/**
* Get current game set in the .env file.
* #return Game
*/
public static function current()
{
return Cache::remember('current_game', 1, function () {
static $game = null;
$id = config('app.current_game_id');
if ($game === null || $game->id !== $id) {
$game = Game::find($id);
}
return $game;
});
}
I can successfully call this using Game::current() but this solutions feels "hacky" and it will stay cached over the course of multiple requests.
I tried placing a property on the current request object but this won't be usable for the commands and seems inaccessible in the blade views and the objects (without passing the $request variable.
Another example of its usage is described below:
class Job extends Model
{
/**
* The "booting" method of the model.
*
* #return void
*/
protected static function boot()
{
parent::boot();
static::addGlobalScope('game_scope', function (Builder $builder) {
$builder->whereHas('post', function ($query) {
$query->where('game_id', Game::current()->id);
});
});
}
}
I do not believe I could easily access a request property in this boot method.
Another idea of mine would be to store the variable on a Game Facade but I failed to find any documentation on this practice.
Could you help me find a method of "caching" the Game::current() property accessible in most if not all of these cases without using a "hacky" method.
Use the global session helper like this:
// Retrieve a piece of data from the session...
$value = session('key');
// Store a piece of data in the session...
session(['key' => 'value']);
For configuration info and more options: https://laravel.com/docs/5.7/session

Using an ID and username for slugs (route model binding)

So by default I do not require a user to create a username on registration. So their username won't be set, and instead their UID will be their id rather than a username like so:
/**
* Get the Username
*/
public function getUsernameAttribute()
{
if($this->attributes['username']){
return $this->attributes['username'];
}
return $this->id;
}
So whenever they visit a URL that is has route model binding, it will get their information (the ID)
But now let's say I want to use a username for the route model biding as well in case they have updated and set a username I can do that like so:
/**
* Get the route key for the model.
*
* #return string
*/
public function getRouteKeyName()
{
return 'username';
}
Which works for usernames. Now my problem is, it no longer works for those without usernames (ids) and instead is looking for usernames:
where username = ?
Due to the way route model binding works, and the load order, I can not do an if statement to check if their username exists, because, well, how would laravel know to get either an ID or username.
Is there a way around this without creating a pull-request? Have any of you experienced something like this?
If you scroll past the getRouteKeyName section in the docs, you'll find a section on customising the resolution logic.
https://laravel.com/docs/5.5/routing#explicit-binding
This allows you to specify the query that is run to find a model. E.g.
// RouteServiceProvider.php
public function boot()
{
parent::boot();
Route::bind('user', function ($value) {
return User::where(function ($query) use ($value) {
return $query->where('id', $value)->orWhere('username', $value);
})
->firstOrFail();
});
}

Checking Model Relationships From Nested Resources

A common setup in Laravel routing is to use nested resources with route model binding. This allows great logical urls that represent the actual relationships that the models have with each other in the database. An example of this might be /library/section/book/. The book is owned by the section, the section is owned by the library. But when using route model binding, the ids of these resources are turned into models without any knowledge of each other. /1/7/234 would return the models of these resources but there is no guarantee that they are properly related. book 234 might not be owned by section 7 and section 7 might not be owned by library 1. I often have a method at the top of each controller that handles checking what I call relationship tests. This function would be found in the Book controller.
private function relationshipCheck($library, $section, $book)
{
if(library->id == $section->library_id) {
if($book != false) {
if($section->id == $book->section_id) {
return true;
} else {
return response()->json(["code" => 401], 401);
}
} else {
return true;
}
} else {
return response()->json(["code" => 401, 401);
}
}
What is the proper way to handle using these sorts of routes that represent relationships? Is there a more automated way to do this? Is there a good reason to just ignore everything but the last resource when the relationships are all one to many?
It's an old question, but still relevant today. There is a good answer here, which suggests explicitly binding the models in question. It's similar to another answer here, but with less abstraction.
Route::bind('section', function ($section, $route) {
return Section::where('library_id', $route->parameter('library'))->findOrFail($section);
});
Route::bind('book', function ($book, $route) {
return Book::where('Section_id', $route->parameter('section'))->findOrFail($book);
});
This will automatically work everywhere. If required, you could test for the upstream parameter to be found, and only perform the test in those cases (e.g. to cater for routes where only a book is specified).
Route::bind('book', function ($book, $route) {
$section = $route->parameter('section');
return $section ? Book::where('Section_id', $route->parameter('section'))->findOrFail($book) : $book;
});
...when using route model binding, the ids of these resources are turned into models without any knowledge of each other.
I am just starting to deal with this and here is how I've decided to make the approach.
Make it easier to check a model's relations
Laravel 5.3 has a method to determine if two models have the same ID and belong to the same table. is()
I submitted a pull request that would add relationship tools. You can see the changes to Illuminate\Database\Eloquent\Model that I am using in my project.
Create a middleware for nested routes with model binding.
Middleware
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Exception\HttpResponseException;
/**
* Class EntityChain
*
* Determine if bound models for the route are related to
* each other in the order they are nested.
*
* #package App\Http\Middleware
*/
class EntityChain
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
// Array of the bound models for the route.
$parameters = array_filter($request->route()->parameters(),
function ($v) {
if ($v instanceof Model) return true;
return false;
});
// When there are two or more bound models.
if (count($parameters) > 1) {
// The first model is the parent.
$parent = array_shift($parameters);
while (count($parameters) > 0) {
// Assume the models are not related.
$pass = false;
// Set the child model.
$child = array_shift($parameters);
// Check if the parent model is related to the child.
if ($parent->is_related($child)) {
$pass = true;
}
$parent = $child;
// Fail on no relation.
if (!$pass) {
throw new HttpResponseException(response()->json('Invalid resource relation chain given.', 406));
}
}
}
return $next($request);
}
}
I've come across the need to do this before. This is how I've done it:
In my RouteServiceProvider.php file I have the following method:
private function addSlugBindingWithDependency(Router $router, $binding, $className, $dependency, $dependencyClassName, $dependencyField)
{
$router->bind($binding, function($slug, $route) use($className, $dependency, $dependencyClassName, $dependencyField) {
if (!is_string($slug)) {
throw new NotFoundHttpException;
}
$params = $route->parameters();
if (!$params || !isset($params[$dependency]) || get_class($params[$dependency]) != $dependencyClassName) {
throw new NotFoundHttpException;
}
$dependencyInstance = $params[$dependency];
$item = $className::where('slug', $slug)->where($dependencyField, $dependencyInstance->id)->first();
if (!$item) {
throw new NotFoundHttpException;
}
return $item;
});
}
It's a function that helps me set up a route/model binding for a slug, where that slug depends on another part of the URL/path. It works by taking a look at the already bound parts of the route and grabbing the instance of the model it had previously bound and uses it to check that the two are linked together.
I also have another, more basic helper function, addSlugBinding that I use to bind a slug to an object too.
You would use it in the boot method of the RouteServiceProvider class like this:
public function boot(Router $router)
{
parent::boot($router);
$this->addSlugBinding($router, 'librarySlug', 'App\Library');
$this->addSlugBindingWithDependency($router, 'sectionSlug', 'App\Section', 'librarySlug', 'App\Library', 'library_id');
$this->addSlugBindingWithDependency($router, 'bookSlug', 'App\Book', 'sectionSlug', 'App\Section', 'section_id');
}
Then in my routes file I might have the following:
Route::get('{librarySlug}/{sectionSlug}/{bookSlug}', function($librarySlug, $sectionSlug, $bookSlug) {
});
Note: I've done this when I've wanted nested URLs by slug rather than ID, but it can easily be adapted to use IDs.

Resources