How to use laravel accessor in trait? - laravel-5

I would like to put accessor in trait, and for some reason this is not working (I have current applocale in session):
Trait:
namespace App\Traits;
trait TranslateEntities
{
public function getNameAttribute($value)
{
if (session('applocale')=='en')
{
return $value;
} else {
return trans("entities.".$this->code);
}
}
}
Model:
namespace App\Models;
use App\Traits\TranslateEntities;
class Repairstatus extends \Eloquent {
use TranslateEntities;
(...)
}
This way I'm not getting translated entity, but if I put this public function getNameAttribute($value) inside model, it works ok.
Any idea?

Okay, I found a solution here:
Laravel pluck but combining first name + last name for select
Problem is in "pluck" method, not having "code" attribute...

Related

Laravel: immutable Trait

I need that when a document is considered closed, you won't be able to modify, update or delete it anymore.
I was thinking to use a trait like an "ImmutableTrait".
I've done this:
<?php
namespace App\Traits;
use Illuminate\Database\Eloquent\Model;
trait ImmutableTrait
{
protected $isImmutable = false;
public function setAttribute($key, $value)
{
if ($this->isImmutable) {
return $this;
}
return parent::setAttribute($key, $value);
}
}
Then in my model:
use Illuminate\Database\Eloquent\Model;
use App\Traits\ImmutableTrait;
class MedicalRecord extends Model
{
use ImmutableTrait;
public function closeDocument()
{
$this->isImmutable = true;
}
}
Finally the controller:
public function closeDocument(Document $document)
{
.....
$document->closeDocument();
$document->saveorfail();
}
Then, if I try to retrieve the closed model and update a field, I shouldn't be able to do it:
Route::put('{document}/updateStatus', 'DocumentController#updateStatus');
class DocumentController extends Controller
{
....
public function updateStatus(Document $document)
{
$document->status= "TEST";
$document->saveorfail();
}
}
Calling the API with the id of a closed document, should fail the update, but this is not happening. The field is updated normally.
Obviously I'm missing something. But what?
Thank you all!
Just for reference if anyone needs this.
I ended up creating the following trait:
<?php
namespace App\Traits;
use App\Exceptions\ImmutableModelException;
trait ImmutableModelTrait {
public function __set($key, $value)
{
if ($this->isClosed)
{
throw new ImmutableModelException();
}
else {
//do what Laravel normally does
$this->setAttribute($key, $value);
}
}
}
The problem with my first solution, as #mrhn stated in one comment, was that I was searching for the "isImmutable" variable on a new model instance but I wasn't persisting that variable in the DB table.
So now my "Document" table has a field "isClosed" that becomes true when the document is considered closed.

Laravel: Class not found if it is called from a Trait

After creating several Apps with Laravel and using softDelete properties I realized that methods like destroy(), restore() and kill() are exactly the same among several controllers. Therefore I am trying to put themn in a trait and use it from diferent Controllers.
My code is as follows:
ProfilesController.php
<?php
namespace App\Http\Controllers;
use App\Profile;
class ProfilesController extends Controller
{
public function destroy(Profile $profile)
{
Profile::del($profile, 'profiles');
return redirect()->route('profiles.index');
}
public function trashed()
{
Profile::trash('Profile');
}
}
Profile.php (model)
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Profile extends Model
{
protected $fillable = ['user_id', 'role_id', 'title', 'subtitle', 'slug', 'birthday', 'about'];
use SoftDeletes, Helpers, commonMethods;
public function getRouteKeyName()
{
return 'slug';
}
// ... more code here
}
trait file: commonMethods.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;
use App\Profile;
use Session;
trait commonMethods
{
public static function del($element, $page_name)
{
$element->delete();
Session::flash('success', $element . ' successfully deleted!');
}
public static function trash($model)
{
$total = $model::onlyTrashed()->get();
$total_tr = count($total);
$all_tr = $model::all();
return view('partials.templates.trashed', compact('total', 'total_tr', 'all_tr'));
}
// ...more code here
}
The problem:
I try to visit the view "Trashed" that will list all elements "softdeleted" but not "killed", the method.
I pass the $model variable with the method trash($model)
I get the following error:
Class App/Profile does not found. Try to call App/Profile
I have debugged and the $model variable contains exactly what I need, the string 'Profile' which is what I need to build the Query:
$total = Profile::onlyTrashed()->get();
This query works while in the ProfilesController, but does not work while in a trait, since the model class is not found.
Any idea how could I make it work?
I am using Laravel 6.
If you need to use a class as a string you will want to use its full name. 'App\Profile' instead of 'Profile'.
$model = 'Profile';
new $model; // will use `\Profile`
$model = 'App\Profile';
new $model; // will use '\App\Profile';
In your controller( ProfilesController ) write :
use App\Profile;
In your model write :
use App\commonMethods;

Laravel 5 Global Mutator to escape all html chars?

Building an API but because I am dynamically creating tables etc in Vue.js from the API response I can't make use of blades html escaping.
I know in my model I can use a mutator:
public function getNameAttribute($value) {
return strtolower($value); // example
}
But we have a lot fields that can be edited across many models. Is there a way I can automatically return all values with htmlspecialchars()?
Or is the only option to change the API responses to run htmlspecialchars() on every field?
Thanks.
EDIT: Using Laravel Spark. Suggested answer was to create a new model and extend that on our models but the Spark models already have a long list of extended classes.
You can create a class which extends Model class and make all you models extend this class instead of Model. In the class override getAttributeValue method:
protected function getAttributeValue($key)
{
$value = $this->getAttributeFromArray($key);
if ($this->hasGetMutator($key)) {
return $this->mutateAttribute($key, $value);
}
if ($this->hasCast($key)) {
return $this->castAttribute($key, $value);
}
if (in_array($key, $this->getDates()) && ! is_null($value)) {
return $this->asDateTime($value);
}
return is_string($value) ? htmlspecialchars($value) : $value;
}
Apart from extending classes. Alternate solution is to use traits.
Create a new trait
namespace App\Traits;
trait ExtendedModel {
public function getNameAttribute($value)
{
return strtolower($value); // example
}
}
Use traits in required models:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Traits\ExtendedModel;
class MyTable extends Model
{
use ExtendedModel;
}
This is how I would do this.
I would make a trait and then attach this trait to all models where you want it.
The trait would overrider getAttributeValue($key) method with your own logic. Like this:
trait CastAttributes {
public function getAttributeValue($value)
{
$value = $this->getAttributeValue($value);
return is_string($value) ? strtolower($this->getAttributeValue($value)) : $val;
}
}
That beign said, you will most likely not want to cast absolutely everything. In that case I would do this:
trait CastAttributes {
protected $toLower = [];
public function getAttributeValue($key)
{
$value = $this->getAttributeValue($key);
return in_array($key, $this->toLower) ? strtolower($value) : $value;
}
}
And then override the $toLower array with all the attributes that you actually want cast to lower case.

Laravel Pass Authenticated to Every View

I need to pass a collection to every view; the collection contains the IDs of the items in the user's shopping cart. I've tried Service Providers and a BaseClass but neither worked as (apparently) Auth hasn't been registered at those points and only returns null.
What's the best way get records from an authenticated user and pass it to every view?
Edit: here's the relevant code
User.php
public static function getCart()
{
if (Auth::guest()) {
return [];
}
$collection = new \Illuminate\Database\Eloquent\Collection();
$collection = Auth::user()->cart()->pluck('post_id');
return $collection;
}
CartServiceProvider.php
namespace App\Providers;
use View;
use App\User;
use Illuminate\Support\ServiceProvider;
class CartServiceProvider extends ServiceProvider
{
public function boot()
{
View::share('cart', User::getCart());
}
public function register()
{
//
}
}
In any view...
<?php dd($cart); ?>
returns [] because Auth hasn't been registered yet, so the empty array is returned.
Found the answer on Laracasts and it seems to work quite well.
https://laracasts.com/discuss/channels/general-discussion/l5-service-provider-for-sharing-view-variables
From the OP #imJohnBon: "I managed to solve this issue by creating 2 files. First a ComposerServiceProvider which uses a wildcard to be applied to every view and not just particular views:"
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Contracts\View\Factory as ViewFactory;
class ComposerServiceProvider extends ServiceProvider {
public function boot(ViewFactory $view)
{
$view->composer('*', 'App\Http\ViewComposers\GlobalComposer');
}
public function register()
{
//
}
}
"And then the corresponding GlobalComposer where I share variables that should be available in all views:"
namespace App\Http\ViewComposers;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Facades\Auth;
class GlobalComposer {
public function compose(View $view)
{
$view->with('currentUser', Auth::user());
}
}

Polymorphic relations in upgraded Laravel app from 4.2 to 5

Short: some related models are returning instances correctly, but some aren't (the polymorphic ones).
I have those three models:
app/Models/User.php
<?php namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
public function company()
{
return $this->hasOne('App\Company');
}
}
app/Models/Company.php
<?php namespace App;
use Illuminate\Database\Eloquent\Model;
class Company extends Model {
public function user()
{
return $this->belongsTo('App\User');
}
public function address()
{
// Also tested with morphMany, without success
return $this->morphOne('App\Address', 'addressable');
}
}
app/Models/Address.php
<?php namespace App;
use Illuminate\Database\Eloquent\Model;
class Address extends Model {
public function addressable()
{
return $this->morphTo();
}
}
And the controller:
app/Http/Controllers/MyController.php
<?php namespace App\Http\Controllers;
// ... many "use" clauses not relevant to the question
use Auth;
// ...
use App\Address;
use App\Company;
use App\User;
class MyController extends Controller {
// Ok here
$user = Auth::user();
// Ok here, too
$company = $user->company()->first();
// Here is the problem; $address is null
$address = $company->address()->first();
}
The line $company->address()->first(); is always returning null to $address in Laravel 5, but it worked well in Laravel 4.2
In L4 models were not namespaced by default, so they were saved as ModelName in your table, while now in L5 they are rather Namespace\ModelName and are retrieved the same way.
That said, your data saved in L4 needs to be adjusted so it matches your current models, or you can use protected $morphClass on the models.
However take this into consideration for the latter solution.
If you open your database - you'll see the relationship in your old L4 data stored as: User or Company
You need to run a script that updates the columns to the new namespace names - such as App\User or App\Company
This is because you are now namespacing your models - so Laravel needs to know which namespace to call.
Along with #The Shift Exchange's answer and following my question's example, you can follow this approach:
Instead of adding the namespace in addressable_type column values from address table (and this is a valid solution), you can use $morphClass:
<?php namespace App;
use Illuminate\Database\Eloquent\Model;
class Company extends Model {
protected $morphClass = 'Company';
public function user()
{
return $this->belongsTo('App\User');
}
public function address()
{
// Also tested with morphMany, without success
return $this->morphOne('App\Address', 'addressable');
}

Resources