laravel algolia send only specific fields - laravel-4

Is there a way with laravel and algolia package to update to the index not ALL the fields but only the ones i need?

You can use getAlgoliaRecord() method in your model and return array from it with attributes you want to index.
Example:
use Illuminate\Database\Eloquent\Model;
class Contact extends Model
{
use AlgoliaEloquentTrait;
public function getAlgoliaRecord()
{
return [
'indexedAttribute' => $this->indexedAttribute,
'otherIindexedAttribute' => $this->otherIindexedAttribute,
'nextIndexedAttribute' => $this->nextIndexedAttribute,
];
}
}

#JanPetr answer is correct but only for laravel 4.
For Laravel 5.3 and above,
As mentioned in docs
By default, the entire toArray form of a given model will be persisted to its search index. If you would like to customize the data that is synchronized to the search index, you may override the toSearchableArray method on the model.
<?php
namespace App;
use Laravel\Scout\Searchable;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
use Searchable;
/**
* Get the indexable data array for the model.
*
* #return array
*/
public function toSearchableArray()
{
$array = $this->toArray();
// Customize array...
$array = [
'post_name' => $this->post_name,
'post_author' => $this->author,
'publisher' => $this->publisher,
'publishing_date' => $this->published_at
];
return $array;
}
}

Related

Laravel getAttribute() on eloquent?

so i just wondered, if something like this is possible, since my code does not work.
protected $appends = ['position_name'];
public function getPositionNameAttribute()
{
return $this->belongsTo('App\EmployeePosition', 'employee_position_id')->name;
}
Can I append the name of Eloquen relationship model?
edit: so far, i am using this:
foreach ($employees as $e) {
$e->position_name = $e->position->name;
}
So, I needed to use the relation defined before.
protected $appends = ['position_name'];
public function position()
{
return $this->belongsTo('App\EmployeePosition', 'employee_position_id');
}
public function getPositionNameAttribute()
{
return $this->position->name;
}
Based on your comments i'd suggest to use the laravel default solution for your problems API resrouces
eg
class EmployeeResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'position_name' => $this->position->name,
];
}
}
note: using the with as other people suggested to preload information can increase performance by reducing the amount of queries, if you are returning a collection of employees.
Creating an accessor that looks up a value in another model and appending this field by using $appends is bad practice and will lead to n+1 queries whenever you fetch your Employee model. You should avoid doing this and just use $employee->position->name.
You should also make sure to use Employee::with('position') when you need to show the position name, so that the position model is fetched in a single query.
If the position name is something that you need in all your Employee queries, then you can set the Employee to always eager load the position by defining the following inside your Employee model:
/**
* The relationships that should always be loaded.
*
* #var array
*/
protected $with = ['position'];
I think you can just create a model with position names and reference it to the position id in the other mode by using eloquent relationships.

Eloquent's fillable not working with mutators

I have a following model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
class PaymentOption extends Model
{
protected $table = 'payment_option';
public $timestamps = false;
protected $fillable = ['payment_option_code', 'payment_option_name'];
public function setCodeAttribute($value)
{
$this->attributes['payment_option_code'] = $value;
}
public function getCodeAttribute()
{
return $this->payment_option_code;
}
public function setNameAttribute($value)
{
$this->attributes['payment_option_name'] = $value;
}
public function getNameAttribute($value)
{
$this->payment_option_name;
}
}
As you can see, I have weird column names. I need mutators cause I will be accepting variables without the prefix payment_option.
Now, I do it in my controller like so:
<?php
namespace App\Http\Controllers;
use App\PaymentOption;
use App\Http\Requests\PaymentOptionRequest;
class PaymentOptionController extends Controller
{
private $paymentOption;
public function __construct(PaymentOption $paymentOption)
{
$this->paymentOption = $paymentOption;
}
public function create(PaymentOptionRequest $request)
{
$paymentOption = $this->paymentOption->fill($request->validated());
dump($paymentOption);
return response()->json([]);
}
}
When I tried to use the fill() it doesn't call the mutators. But when I tried to set it 1 by 1 it worked like so:
$paymentOption->code = $validated['code'];
Why is that so?
Thanks!
fill does loop on the input data that are in your $fillable array
public function fill(array $attributes)
{
$totallyGuarded = $this->totallyGuarded();
foreach ($this->fillableFromArray($attributes) as $key => $value) {
$key = $this->removeTableFromKey($key);
// The developers may choose to place some attributes in the "fillable" array
// which means only those attributes may be set through mass assignment to
// the model, and all others will just get ignored for security reasons.
if ($this->isFillable($key)) {
$this->setAttribute($key, $value);
} elseif ($totallyGuarded) {
throw new MassAssignmentException(sprintf(
'Add [%s] to fillable property to allow mass assignment on [%s].',
$key, get_class($this)
));
}
}
return $this;
}
hence, you need to add 'code' and 'name' to the fillable array to have them assigned by fill
With latest realese of laravel 8 there is a change in implementation of fill. In laravel older version fill method does work for mutator and database column both. But in laravel 8 implementation of fill method is changed now fill also check if the key given in an array to fill method is a database column or not.
For example if you have database column name user_id and mutator name is user it will not work with laravel 8. It does work for the older version of laravel.
If the $guarded array contains at least one column/key, then that Eloquent Model goes into a strict mode where it can only be filled with values for valid database columns. here is the test is written in the official codebase for that(link to test).
To solve the problem use forceFill. That will solve your problem.

Laravel - Collection::delete method does not exist

I am trying to test the boot() static::deleting method, which should fire when a model is deleted through Eloquent.
The command in tinker App\User::find(6)->delete(); returns a 'method [...]Collection::delete does not exist'.
If I try to use App\User::where('id', 6)->delete(); then the static::deleting method does not get triggered since Eloquent is not loaded. If I load Eloquent with ->first() then I get the same error that states method does not exist.
Here is the entire user model
<?php
namespace App;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use Notifiable;
public function profile() {
return $this->hasOne(Profile::class);
}
public function posts() {
return $this->hasMany(Post::class);
}
public function tempUploads() {
return $this->hasMany(TempUploads::class);
}
protected static function boot() {
parent::boot();
static::created(function ($user) {
$user->profile()->create(['id' => $user->username, 'avatar' => '/storage/avatars/edit-profile.png']);
mkdir(public_path() . "/storage/images/" . $user->username , 0755);
// $data = [
// 'user_id' => $user->username
// ];
// Mail::to($user->email)->send(new WelcomeMail($data));
});
static::deleting(function ($user) {
$user->posts->delete();
if ($user->profile->avatar != '/storage/avatars/edit-profile.png') {
if ($user->profile->cover != NULL && $user->profile->cover != '') {
$oldAvatar = $_SERVER['DOCUMENT_ROOT'] . $user->profile->avatar;
$oldCover = $_SERVER['DOCUMENT_ROOT'] . $user->profile->cover;
if (is_file($oldAvatar) && is_file($oldCover)) {
unlink($oldAvatar);
unlink($oldCover);
} else {
die("Грешка при изтриване на стария файл. File does not exist in profile deleting method.");
}
}
}
$user->profile->delete();
});
}
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'username', 'email', 'password',
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token',
];
/**
* The attributes that should be cast to native types.
*
* #var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
}
I have spent hours now looking through google for possible solutions but nothing has yet.
How should I properly delete a User model while triggering the boot deleting method ?
In your deleting listener you are trying to delete something else, which is a Collection which is causing the error.
$user->posts is a relationship to Posts which is a plural which is a hasMany relationship (most likely) so it returns a Collection always. Collections do not have a delete method. You will have to iterate through the collection and call delete on each Post
// calling `delete()` on a Collection not a Model
// will throw the error you see
$user->posts->delete();
// iterate through the Collection
foreach ($user->posts as $post) {
$post->delete();
}
Side Note: you can not do any action in bulk with Models and queries and have the events be fired. All Model events are based on single instances of the Models. A direct query bypasses the Model.
You can optimise lagbox's answer by using only one query to delete all of the posts. In his example he's executing a delete query for every post attached to the user.
For a single delete query either use the query builder of the relationship directly:
$user->posts()->delete();
or use the pluck method of the collection and a separate query:
Post::where('id', $user->posts->pluck('id'))->delete();
You can use higher order messages as well:
$user->posts->each->delete();
$user->posts->map->delete()
I used this in my Controller File to delete the Database Entry:
public function destroy(Item $id) {
$id->destroy($id->id);
//return view('inv.delete', compact('id'));
return redirect('/inv');
}
$user->posts()->delete() will work
$user->posts->delete() will not work
Because $user->posts() is a query , not a collection

Get data from different tables using inner joins

I have column in results as 'user_id','test','subject' and in datatable i want to get the 'test_name' which is saved in 'tests' table, Student name 'name' saved in 'users' and 'subject name' in table 'subjects' in column 'subjects' now tell me what is the best way to get this data.
So far i had tried this but getting null while dumping.
$result = DB::table('results')
->where([
['results.subject',$request->subject],
['test',$request->test],
['user_id',$request->name]
])
->join('users','results.user_id','=','users.name')
->join('tests','tests.id','=','results.test')
->join('subjects','subjects.id','=','results.subject')
->select('results.*','users.name','tests.test_name','subjects.subjects As s_subject')
->first();
dd($result);
anyone who can guide me the best possible solution.
You should use the power of the Eloquent model of Laravel. You have to create Results model class as follow(and obviously need to create User, Test and Subject model class)
Results.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Carbon\Carbon;
class Results extends Model
{
/**
* The attributes that aren't mass assignable.
*
* #var array
*/
protected $guarded = ['id'];
/**
* Get the user profile associated with the results.
*/
public function user()
{
return $this->hasOne('App\User', 'user_id');
}
/**
* Get the test details associated with the results.
*/
public function test()
{
return $this->hasOne('App\Test', 'test');
}
/**
* Get the subject associated with the results.
*/
public function subject()
{
return $this->hasOne('App\Subject', 'subject');
}
}
Controller.php
$results = App\Results::where(['subject' => $request->subject, 'test' => $request->test, 'user_id' => $request->name])->first();
Now you will get the Result object with the test, subject, and test property.
Note that:- You should provide the output when you are asking any question.

Dynamically hide certain columns when returning an Eloquent object as JSON?

How do dynamically hide certain columns when returning an Eloquent object as JSON? E.g. to hide the 'password' column:
$users = User::all();
return Response::json($users);
I'm aware I can set protected properties in the model ($hidden or $visible), but how do I set these dynamically? I might want to hide or show different columns in different contexts.
$model->getHidden();
$model->setHidden(array $columns);
$model->setVisible(array $columns);
From Lavarel 5.3 Documentation :
Temporarily Modifying Attribute Visibility
If you would like to make some typically hidden attributes visible on a given model instance, you may use the makeVisible method. The makeVisible method returns the model instance for convenient method chaining:
return $user->makeVisible('attribute')->toArray();
Likewise, if you would like to make some typically visible attributes hidden on a given model instance, you may use the makeHidden method.
return $user->makeHidden('attribute')->toArray();
I've found a complete solution around the problem with using $model->setHidden(array $columns);
Lets say, for example, that you would like to decide in the controller exactly which fields to return. Updating only the model's hidden forces you to go over each model before you return an array of models for example. The problem becomes even worse when those models have relationships that you would also like to change. You have to loop over each model, set the hidden attribute, and then for each also set the relationships hidden. What a mess.
My solution involves creating a static member for each model that when present, updates the visible/hidden attribute just before the call to "toArray":
<?php
trait DynamicHiddenVisible {
public static $_hidden = null;
public static $_visible = null;
public static function setStaticHidden(array $value) {
self::$_hidden = $value;
return self::$_hidden;
}
public static function getStaticHidden() {
return self::$_hidden;
}
public static function setStaticVisible(array $value) {
self::$_visible = $value;
return self::$_visible;
}
public static function getStaticVisible() {
return self::$_visible;
}
public static function getDefaultHidden() {
return with(new static)->getHidden();
}
public static function geDefaultVisible() {
return with(new static)->getVisible();
}
public function toArray() {
if (self::getStaticVisible())
$this->visible = self::getStaticVisible();
else if (self::getStaticHidden())
$this->hidden = self::getStaticHidden();
return parent::toArray();
}
}
As an added bonus, I expose a way to the model's default hidden/visible that you may have set in your model's class.
Don't to forget to add the trait
class Client extends Eloquent {
use DynamicHiddenVisible;
}
Finally, in the controller, before returning your model, decide on visible/hidden attributes:
public function getIndex($clientId) {
// in this specific call, I would like to hide the "special_type" field of my Client model
$hiddenFields = Client::getDefaultHidden();
array_push($hiddenFields, "special_type");
Client::setStaticHidden($hiddenFields);
return Client::find($clientId)->toJson();
}
I don't believe it is the job of the ORM to worry about presentation logic, and that is what JSON is. You'll aways need to cast data to various types as well as hide things and sometimes create a buffer zone to rename things safely.
You can do all of that with Fractal which I built for exactly this reason.
<?php namespace App\Transformer;
use Acme\Model\Book;
use League\Fractal\TransformerAbstract;
class BookTransformer extends TransformerAbstract
{
/**
* List of resources possible to include
*
* #var array
*/
protected $availableIncludes = [
'author'
];
/**
* Turn this item object into a generic array
*
* #return array
*/
public function transform(Book $book)
{
return [
'id' => (int) $book->id,
'title' => $book->title,
'year' => (int) $book->yr,
'links' => [
[
'rel' => 'self',
'uri' => '/books/'.$book->id,
]
],
];
}
/**
* Include Author
*
* #return League\Fractal\ItemResource
*/
public function includeAuthor(Book $book)
{
$author = $book->author;
return $this->item($author, new AuthorTransformer);
}
}
Embedding (including) stuff might be a bit more than you need right now, but it can be very handy too.
In 5.4 you can hide and show attributes dinamically:
$model->makeVisible('attribute');
$model->makeHidden('attribute');
Laravel docs
In addition to #deczo's answer - I feel the $hidden variable is not really designed to be used dynamically. It is more to protect specific data from ever been incorrectly displayed (such as 'password').
If you want specific columns - you should probably just be using a select statement and just get the specific columns you want.
For Laravel 5.3 or greater version,
If you want to make multiple attributes temporary hidden or visible using single statement, you may use model->makeVisible() and model->makeHidden() methods with passing array of attributes.
For example, to hide multiple attributes,
$user->makeHidden(["attribute1", "attribute2", "attribute3"]);
And to make visible multiple attributes,
$user->makeVisible(["otherAttribute1", "otherAttribute2", "otherAttribute3"]);
In the Model:
protected $hidden = [
'your_field_1',
'your_field_2',
];
You can override the getHidden method in order to hide certain columns dynamically:
class FooModel extends Model
{
public function getHidden()
{
// do here your validations and return
// the columns names with the specific criteria
// you need
return ['columnName1', 'columnName2'];
}
}
Made a package for this that uses Model Policies.
https://github.com/salomoni/authorized-attributes
Use the Salomoni\AuthorizedAttributes trait
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Salomoni\AuthorizedAttributes;
class Post extends Model
{
use AuthorizedAttributes;
/**
* The attributes that should be hidden for serialization.
*
* #var array
*/
protected $hidden = ['author_comments'];
}
Create and register a model policy. Add methods for the hidden attributes in camel-case prefixed with see.
namespace App\Policies;
use App\User;
class PostPolicy
{
/**
* Determine if a post author_comments-atrribute can be seen by the user.
*
* #param \App\User $user
* #return bool
*/
public function seeAuthorComments(User $user)
{
return $user->isAuthor();
}
}

Resources