refactor code to be database-agnostic - laravel

Actually I have two questions:
1) Is it possible to configure Laravel temporarily for a call to be database-agnostic and work on models instantiated in code only? In other words to prevent any database access when working with models and relations.
2) I tried to refactor this code to allow 1 but I'm hitting problems
/** #var Collection $collection */
$collection = $model->relation()->whereIn('MY_ID', [
RelationModel::IDENTIFIER_FOO,
RelationModel::IDENTIFIER_BAR,
// ...
])->get();
if (0 === $collection->count()) {
throw new \InvalidArgumentException('no entry found but mandatory');
}
Which I tried to change to this:
/** #var Collection $collection */
// note the missing parentheses
$collection = $model->relation->whereIn('MY_ID', [
RelationModel::IDENTIFIER_FOO,
RelationModel::IDENTIFIER_BAR,
// ...
])->get();
if (0 === $collection->count()) {
throw new \InvalidArgumentException('no entry found but mandatory');
}
But now I'm getting the error
Type error: Too few arguments to function Illuminate\Support\Collection::get(), 0 passed
It seems omitting the parentheses there is a different type of collection returned which requires to pass a parameter instead of none for the other type of collection.
Is it possible to refactor this to allow both kinds of requests, with and without database access?

Related

Error: Multiple databases for CI4 and its uses default database as object validation in inserting data

By the way, this is not really a question. I just want really to share how to validate using a specific database in multiple database connections. Since I got this error and it took almost 2 days and thank you to the members of the Codeigniter 4 community who help me how to solve it.
I would like to share it also here for future references if someone encounters this error. Shout out to kenjis for the help!
At first, I thought it was just a bug in the codeigniter4 that if I have a multiple database connections the validation rules only use the default DB group. But unfortunately, No. I just don't specify what DB group I should verify.
In case you encounter this error, just add this on your controller:
protected function validate($rules, array $messages = []): bool
{
$this->validator = \Config\Services::validation();
// If you replace the $rules array with the name of the group
if (is_string($rules)) {
$validation = config('Validation');
// If the rule wasn't found in the \Config\Validation, we
// should throw an exception so the developer can find it.
if (! isset($validation->{$rules})) {
throw ValidationException::forRuleNotFound($rules);
}
// If no error message is defined, use the error message in the Config\Validation file
if (! $messages) {
$errorName = $rules . '_errors';
$messages = $validation->{$errorName} ?? [];
}
$rules = $validation->{$rules};
}
return $this->validator->withRequest($this->request)->setRules($rules, $messages)->run(null, null, 'other-db-group-name');
}
reference: https://github.com/codeigniter4/CodeIgniter4/issues/5654

Why pagination request raise error adding calculating in map?

In laravel 8 app when in pagination request I use mapping with calculated new field :
$hostelDataRows = Hostel
::orderBy('created_at', 'desc')
->where('hostels.id', 41) // debug
->with('state')
->with('region')
->with('user')
->withCount('hostelRooms')
->withCount('hostelInquiries')
->withCount('hostelImages')
->getByName($this->filters['name'], true)
->getByStatus($this->filters['status'], true)
->getByStateId($this->filters['state_id'], true)
->getByFeature($this->filters['feature'], true)
->addSelect([
'min_hostel_rooms_price' =>
HostelRoom::query()
->selectRaw('MIN(price)')
->whereColumn('hostel_id', 'hostels.id'),
])
->paginate($backend_per_page)
->map(function ($hostelItem) {
if ($hostelItem->hostel_rooms_count > 0 and $hostelItem->hostel_inquiries_count > 0 and $hostelItem->hostel_images_count > 0 ) {
$hostelItem->info_text = 'Has ' . ($hostelItem->hostel_rooms_count . pluralize3( $hostelItem->hostel_rooms_count, 'no rooms', 'room', 'rooms' ) );
}
return $hostelItem;
})
;
I got error :
Method Illuminate\Database\Eloquent\Collection::links does not exist. (
it is clear that with mappimg $hostelDataRows is not pagination object anyway...
How that can be fixed ?
Thanks!
When you use map() on a Paginator instance, you will get a Collection instance back (I think it is Illuminate\Database\Eloquent\Collection here). So you can't put map() in front of it since that would require you to fetch the query (which is dependent on the paginator page). However there are some other solutions:
Solution 1: use the tap() helper
The tap() helper always returns the argument to the function. Since Collection map() (and transform() which is very similar) modify the instance Collection itself, this works as intended and you now get your original paginator instance back (with the extra properties from your map-function).
return tap($query->paginate())->map(...);
Solution 2: use a getInfoTextAttribute() accessor
In your specific usecase you might just as well make a property for your $hostelItem if you are going to use this property more often, like so:
// Models/HostelItem.php
/**
* #property string $info_text
*/
class HostelItem extends Model
{
public function getInfoTextAttribute()
{
return 'Has ' . ($this->hostel_rooms_count . pluralize3( $this->hostel_rooms_count, 'no rooms', 'room', 'rooms' ) );
}
}
I've added a #property annotation so you can have code assist in most of your files. Now, simply call $hostelItem->info_text and you're done. no need for a map.

Laravel filtered collection is no longer filtered after json encoding

I have an Eloquent collection with an eager loaded relationship. When I filter this eager loaded relationship, it works fine, but if I encode it as JSON (to pass it to my front end Javascript framework), the collection is no longer filtered.
Simplified example:
$questions = Question::with('variables')->get();
foreach($questions as $key => $q) {
$questions[$key]->variables = $q->variables->reject(function($v) {
return $v->type == 3;
});
}
dd($questions);
If I look at the $questions variable at this point, my collection is correctly filtered. If, however, I add json_decode(json_encode($questions)) following line before dumping, the collection is no longer filtered.
Note that in my real application, I have to do some things with the rejected variables before throwing them out of the collection, so I cannot simply filter them out during the query.
My workaround right now is to json encode and decode the collection, then do an array filter to get rid of the variables I do not want to pass to the front end. This works, but seems like a terribly inelegant and unnecessary solution. Am I doing something wrong or is this the expected behavior?
I'm still running Laravel 5.8 on this application in the event that this behavior has changed on newer versions.
Why not just load the variables twice then?
$questions = Question::with(['variables' => fn($v) => $v->where('type', '!=', 3)])->get();
// do whatever you need to do with the filtered collection
// reload variables relationship
$questions->load('variables');
// do whatever you need to do with the question and all its variables.
You can try
$questionsWithFilteredVariables = $questions->map(function($question) {
$variables = $question->variables->reject(fn($var) => $var->type === 3);
unset($question->variables);
$question->variables = $variables;
return $question;
});
//Now do json_decode(json_encode(...)), it will still contain filtered variables
$questionsWithFilteredVariables = json_decode(
json_encode($questionsWithFilteredVariables)
);

Handling Failed eloquent methods

I'm calling a product's information field, as you can see below:
$product->attributes->first()->attributeValues->where('locale',$iso);
Basically in the $product variable already have information regarding the product.
I use $product->attributes->first() to get his attributes, and after getting them I go get his values with ->attributeValues->where('locale',$iso) with the specific language.
The data it outputs is good, but only if attributes exist, because in case there isn't any it doesn't and because of the attributeValues method the page fails.
How can I handle in this situation?
You may check it with a simple empty() or even count() if you prefer.
$attributes = $product->attributes->first()->attributeValues->where('locale',$iso);
if (count($attributes) == 0) {
// There is no attribute, do something
}
Split up your line
$attributes = $product->attributes->first(); // placeholder
if(isset($attributes) { // check if we have one
$attributes->attributeValues->where('locale',$iso); // if so.. do the dance
} else {// go home }

How to acces is_* property in laravel model?

I am working with laravel 4.2 and have table in db with property is_active.
When I try to access this model property:
$model->is_active
I am getting following error:
Relationship method must return an object of type Illuminate\Database\Eloquent\Relations\Relation
So question is how to access this property?
Please do not recommend to rename this field in the database if possible because this is already existing database in production.
Here is my model class:
class Position extends \Eloquent {
protected $table = "hr_positions";
protected $fillable = ['slug', 'info_small', 'info_full', 'is_active', 'start_date', 'end_date', 'tags', 'user_create_id', 'user_update_id'];
use \MyApp\Core\StartEndDateTrait;
public function postulations(){
return $this->hasMany('Postulation', 'position_id', 'id');
}
}
Latest notice:
All this error ocurrs on a page where I am creating my entity. In the controller before forwarding to the page I am doing:
$position = new \Position();
and then, for example, following code produce error as well:
dd(($position->getAttribute('is_active')));
but if I replace $position = new \Position(); with
$position = \Position::first();
error is gone?
What is going on here?????
Laravel does a lot of magic behind the scenes, as in, calls a lot of php magic methods.
If a called property is not defined, __call is invoked which in Eloquent calls getAttribute().
Steps taken by getAttribute($key) are
Is there a database field by this key? If so, return it.
Is there a loaded relationship by this key? If so, return it.
Is there a camelCase method of this key? If so, return it. (is_active looks for isActive method)
Returns null.
The only time that exception is thrown is in step 3.
When you create a new instance, eloquent has no idea what kind of fields it has, so if you have a method by the same name, it will always throw a relation error, this seems to be the case in both Laravel4 and Laravel5.
How to avoid it? Use the getAttributeValue($key) method. It has no relation checks and returns null by default.
Alternatively you can also add a get mutator for your field.
I have found a hack for this. Still not ideal but at least I have some solution. Better any than none.
So This code produce problem:
$position = new \Position();
if($position->is_active){
//
}
and this one works fine, this is solution even hacky but solution:
$position = new \Position(['is_active' => 0]);
if($position->is_active){
//
}
I will wait if someone give better, cleaner solution. If no one comes in next few days I will accept mine.

Resources