I am using Policys and want to be sure that I am prevent data to be shown of other users.
In every Table I have the column 'user_id' and check if the current logged in user with his id the same with the data and his user_id.
In this specific case I have a table of Objects and Objektverwaltung where the objekt_id is given as foreign key.
I want to use my policy to be sure that just the data for the given object was shown in objektverwaltung where the foreign key 'objekt_id' is given.
ObjektVerwaltung Controller with the show method:
public function show($objektvwId) {
$objektId = ObjektVerwaltung::with('Objekt')->find($objektvwId);
$this->authorize('view', $objektId);
$objekte = ObjektVerwaltung::where('objekt_id',$objektvwId)->get();
return view('objekte.verwaltung', compact('objekte'));
}
Policy:
public function view(User $user, ObjektVerwaltung $objektVerwaltung)
{
return $objektVerwaltung->user_id === $user->id;
}
Models:
class ObjektVerwaltung extends Model
{
use HasFactory;
protected $table = 'objekte_verwaltungens';
protected $fillable = ['user_id','objekt_id','key', 'value'];
public function Objekt() {
return $this->belongsTo(Objekt::class);
}
}
class Objekt extends Model
{
use HasFactory;
protected $table = 'objekts';
protected $fillable = ['name','strasse', 'hausnummer', 'plz', 'ort', 'user_id'];
public function Mieter() {
return $this->hasMany(Mieter::class);
}
public function Company() {
return $this->belongTo(Company::class);
}
public function Objektverwaltung() {
return $this->hasMany(ObjektVerwaltung::class);
}
}
I learned that I can easily use find() as method for the Models to validate data. But in this specific case I have to check for the objekt_id (foreign key in objektverwaltung) and not for the ID and because of that I cant use find(). But if I use where or another method I cant use my policy and always getting unauthorized.
I tried to use the with method on the model but maybe there is a better way to my problem. I strongly believe.
Thanks!
This could be solution, but I am getting always "Unauthorized" and do not get to the policy: $objekt= ObjektVerwaltung::where('objekt_id', $objektId)->get(); $this->authorize('view', $objekt);
I solved this issue. I had to use my ObjektPolicy, because I am using the objekt_id Key.
Related
I'm new to Laravel (but not PHP).
I have two database files (which cannot be modified).
One file (salesorderlines) has two keys ordn55 and ordl55 which are an Id and a sequence number. The other file (certificate) combines these two keys with a '-' between them and the leading zeros on the sequence number.
I can construct the key to this file using the following code:
$this->ordn55.'-'. sprintf('%03s', $this->ordl55);
which works fine.
However I can't do this with a hasone() function call.
Is this possible to do with Laravel Eloquent? If not, what if the best way to do it.
Here is more detailed code for this:
SalesOrderLine model
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class SalesOrderLine extends Model
{
protected $connection = 'ibmi';
public $incrementing = false;
protected $table = 'ault2f1.oep55';
protected $primaryKey = 'ordn55';
protected $appends = ['combinedkey'];
public $timestamps = false;
public function salesorder()
{
return $this->hasOne(SalesOrderHeader::class, 'ordn40', 'ordn55')->where('cono40', 'AM');
}
public function item()
{
return $this->hasOne(ItemMaster::class, 'pnum35', 'catn55')->where('cono35', 'AM');
}
public function certificate()
{
return $this->hasOne(Certificate::class, 'order_no', 'combinedkey')->where('cono70', 'AM');
}
public function getCombinedkeyAttribute()
{
return $this->ordn55.'-'. sprintf('%03s', $this->ordl55);
}
}
You can see that I have tried to achieve this using a combinedkey property that I have added to the $appends array but this doesn't seem to work.
Here is the code to run the query for this
$salesorder = SalesOrderHeader::query()
->with('salesorderlines', 'salesorderlines.item')
->with('salesorderlines', 'salesorderlines.certificate')
->join('ault1f1.slp05', 'ault1f1.slp05.cusn05', 'cusn40')
->where('cono40', 'AM')
->where('cono05', 'AM')
->where('ordn40', $id)
->first();
The item section works correctly as it doesn't need a constructed key for the foreign key like the Certificate does.
Hopefully this makes sense.
Thanks in advance for any help
I have a polymorphic relation where class (Request) can have a relationship with either class (Leave) or class (Overtime).
Each object of class (Request) belongs to a user.
I would like to setup a relationship in the User class to directly get all of their Leave or Overtime objects.
Here is how the code looks:
Class Request with:
user_id
requestable_id
requestable_type can be App\Leave or App\Overtime
class Request extends Model
{
public function requestable() {
return $this->morphTo();
}
public function user() {
return $this->belongsTo('App\User');
}
}
Class Leave
class Leave extends Model
{
public function request() {
return $this->morphOne('App\Request', 'requestable');
}
}
Class Overtime
class Overtime extends Model
{
public function request() {
return $this->morphOne('App\Request', 'requestable');
}
}
Class User
class User extends Authenticatable
{
public function requests() {
return $this->hasMany(Request::class);
}
public function leaves() {
// Need help here
}
public function overtimes() {
// And here
}
}
What I would like to do is get all leaves and overtimes a user has, so ultimately I should be able to do this:
$userLeaves = $user->leaves;
$userOvertimes = $user->overtimes;
Seems that you need a combination of polymorphic relation (that you've already defined) and hasManyThrough.
return $this->hasManyThrough(Leave::class, Request::class);
and
return $this->hasManyThrough(Overtime::class, Request::class);
respectively. But check foreign and local keys (see more info here).
you can fetch the user leaves and overtimes through the user requests by using
$requests = $user->requests->with('requestable');
but this will fetch all user requests not depending on the type, however you can fetch them depending on the type by using the leaves and overtimes function if you want and specifying the type there
User Class
public function leaves()
{
return $this->requests->where('requestable_type', 'App\Leave');
}
public function overTimes()
{
return $this->requests->where('requestable_type', 'App\OverTime');
}
Answering my own question.
Using hasManyThrough:
public function leaves() {
return $this->hasManyThrough(
Leave::class, // the class that we want objects from
Request::class, // the class sitting between this one and the target
'user_id', // this class's foreign key in the request class
'id', // foreign key in leave class
'id', // local key in this class
'requestable_id' // key of the leave in the request class
)
// we have to limit it to only Leave class
->where('requestable_type', array_search(Leave::class, Relation::morphMap()) ?: Leave::class);
}
public function overtimes() {
return $this->hasManyThrough(
Overtime::class, // the class that we want objects from
Request::class, // the class sitting between this one and the target
'user_id', // this class's foreign key in the request class
'id', // foreign key in overtime class
'id', // local key in this class
'requestable_id' // key of the overtime in the request class
)
// we have to limit it to only overtime class
->where('requestable_type', array_search(Overtime::class, Relation::morphMap()) ?: Overtime::class);
}
So I have the following models:
class TemplateEntity extends Model {
protected $table = "TemplateEntities";
const UPDATED_AT = null;
const CREATED_AT = null;
public function element() {
return $this->morphTo("element", "entity_type", "id_Entity");
}
public function getEntityTypeAttribute($entity_type) {
return 'App\\' . $entity_type;
}
}
class Template extends Model {
protected $table = "Template";
const UPDATED_AT = null;
const CREATED_AT = null;
public function entities() {
return $this->hasMany("App\TemplateEntity", "id_Template");
}
}
class TemplateEntity extends Model {
protected $table = "TemplateEntities";
const UPDATED_AT = null;
const CREATED_AT = null;
public function element() {
return $this->morphTo("element", "entity_type", "id_Entity");
}
public function getEntityTypeAttribute($entity_type) {
return 'App\\' . $entity_type;
}
}
I want to eager load template entity elements using Eloquent ORM's ::with() method, however whenever I do this I get an error:
//$template_id is defined as a controller param
$template = Template::with("entities", "entities.element")->where("id", "=", $template_id)->get()
"Class 'App\' not found"
I did some debugging and when I echo $entity_type in TemplateEntity's GetEntityTypeAttribute() method I get an empty value. However, my models generally work fine if I don't use eager loading, but I would like to add it to my application if possible to make it more efficient.
Any help you all can provide would help!
edit: fixed a typo, should have been Template::with instead of $template::with
Part of the problem might be a blank class in that variable. Suggest you use the class name when calling get(). So \App\Template:: instead of $template::.
Another item to help may be the way you are calling the relationship's eager load. Perhaps try to call through the function. This might work better for you:
\App\Template::with(['entities' => function($query){
$query->with('element');
}])->get();
The accessor function might be interfering with the Laravel morph function. I realise you want to use the shortened name of the class in the DB. To do this without the use of the getter (and globally), I suggest using a morphMap.
In AppServiceProvider inside the boot() method:
\Illuminate\Database\Eloquent\Relations\Relation::morphMap([
'MyTemplate' => \App\MyTemplate::class,
'Section' => \App\Section::class,
// etc.
]);
This will allow you to add only 'Section' to the DB and remove the accessor function from your class.
I have a table accounts:
act_id,
act_name,
act_address
And I have a table addresses:
add_id,
add_street1,
<other fields you'd expect in an address table>
accounts.act_address is a foreign key to addresses.add_id. In Laravel, I have my Account model:
use LaravelBook\Ardent\Ardent;
use Illuminate\Database\Eloquent\SoftDeletingTrait;
class Account extends Ardent
{
use SoftDeletingTrait;
protected $table = 'accounts';
protected $primaryKey = 'act_id';
public static $rules = array
(
'act_name' => 'required|unique:accounts'
);
protected $fillable = array
(
'act_name'
);
public function address()
{
return $this->hasOne('Address', 'add_id', 'act_address');
}
}
As you can see, I have my one-to-one relationship setup here. (Of course, the Address model has a 'belongsTo' as well). This all works.
The thing is, the address foreign key is nullable, as accounts don't require addresses. So, if I try to access the Account->address when it doesn't have one, I'll get a 'trying to access property of non-object' error.
What I'd like to do is set Account->address to a new Address object (all fields empty), if the account record doesn't have one set.
What I've been able to do is either create a second method in the model:
public function getAddress()
{
return empty($this->address) ? new Address() : $this->address;
}
Or, add it on the fly:
if (empty($account->address))
$account->address = new Address();
The first solution is really close, but I'd really like to keep the functionality of accessing address as a property instead of a method.
So, my question is:
How can I have Account->address return new Address() if Account->address is empty/null?
Oh, and I tried overriding the $attributes like so:
protected $attributes = array
(
'address' => new Address()
);
But that throws an error.
Use accessor:
Edit: Since it is belongsTo not hasOne relation, it is a bit tricky - you can't associate a model to non-existing one, for the latter has no id:
public function getAddressAttribute()
{
if ( ! array_key_exists('address', $this->relations)) $this->load('address');
$address = ($this->getRelation('address')) ?: $this->getNewAddress();
return $address;
}
protected function getNewAddress()
{
$address = $this->address()->getRelated();
$this->setRelation('address', $address);
return $address;
}
However, now you need this:
$account->address->save();
$account->address()->associate($account->address);
which is not very convenient. You can alternatively save newly instantiated address in getNewAddress method, or override Account save method, to do the association automatically. Anyway for this relation I'm not sure if it makes sense to do it.. For hasOne it would play nice.
Below is the way how it should look like for hasOne relation:
public function getAddressAttribute()
{
if ( ! array_key_exists('address', $this->relations)) $this->load('address');
$address = ($this->getRelation('address')) ?: $this->getNewAddress();
return $address;
}
protected function getNewAddress()
{
$address = $this->address()->getRelated();
$this->associateNewAddress($address);
return $address;
}
protected function associateNewAddress($address)
{
$foreignKey = $this->address()->getPlainForeignKey();
$address->{$foreignKey} = $this->getKey();
$this->setRelation('address', $address);
}
You could do all this in single accessor, but this is the way it 'should' look like.
I'd like to be able to add a custom attribute/property to an Laravel/Eloquent model when it is loaded, similar to how that might be achieved with RedBean's $model->open() method.
For instance, at the moment, in my controller I have:
public function index()
{
$sessions = EventSession::all();
foreach ($sessions as $i => $session) {
$sessions[$i]->available = $session->getAvailability();
}
return $sessions;
}
It would be nice to be able to omit the loop and have the 'available' attribute already set and populated.
I've tried using some of the model events described in the documentation to attach this property when the object loads, but without success so far.
Notes:
'available' is not a field in the underlying table.
$sessions is being returned as a JSON object as part of an API, and therefore calling something like $session->available() in a template isn't an option
The problem is caused by the fact that the Model's toArray() method ignores any accessors which do not directly relate to a column in the underlying table.
As Taylor Otwell mentioned here, "This is intentional and for performance reasons." However there is an easy way to achieve this:
class EventSession extends Eloquent {
protected $table = 'sessions';
protected $appends = array('availability');
public function getAvailabilityAttribute()
{
return $this->calculateAvailability();
}
}
Any attributes listed in the $appends property will automatically be included in the array or JSON form of the model, provided that you've added the appropriate accessor.
Old answer (for Laravel versions < 4.08):
The best solution that I've found is to override the toArray() method and either explicity set the attribute:
class Book extends Eloquent {
protected $table = 'books';
public function toArray()
{
$array = parent::toArray();
$array['upper'] = $this->upper;
return $array;
}
public function getUpperAttribute()
{
return strtoupper($this->title);
}
}
or, if you have lots of custom accessors, loop through them all and apply them:
class Book extends Eloquent {
protected $table = 'books';
public function toArray()
{
$array = parent::toArray();
foreach ($this->getMutatedAttributes() as $key)
{
if ( ! array_key_exists($key, $array)) {
$array[$key] = $this->{$key};
}
}
return $array;
}
public function getUpperAttribute()
{
return strtoupper($this->title);
}
}
The last thing on the Laravel Eloquent doc page is:
protected $appends = array('is_admin');
That can be used automatically to add new accessors to the model without any additional work like modifying methods like ::toArray().
Just create getFooBarAttribute(...) accessor and add the foo_bar to $appends array.
If you rename your getAvailability() method to getAvailableAttribute() your method becomes an accessor and you'll be able to read it using ->available straight on your model.
Docs: https://laravel.com/docs/5.4/eloquent-mutators#accessors-and-mutators
EDIT: Since your attribute is "virtual", it is not included by default in the JSON representation of your object.
But I found this: Custom model accessors not processed when ->toJson() called?
In order to force your attribute to be returned in the array, add it as a key to the $attributes array.
class User extends Eloquent {
protected $attributes = array(
'ZipCode' => '',
);
public function getZipCodeAttribute()
{
return ....
}
}
I didn't test it, but should be pretty trivial for you to try in your current setup.
I had something simular:
I have an attribute picture in my model, this contains the location of the file in the Storage folder.
The image must be returned base64 encoded
//Add extra attribute
protected $attributes = ['picture_data'];
//Make it available in the json response
protected $appends = ['picture_data'];
//implement the attribute
public function getPictureDataAttribute()
{
$file = Storage::get($this->picture);
$type = Storage::mimeType($this->picture);
return "data:" . $type . ";base64," . base64_encode($file);
}
Step 1: Define attributes in $appends
Step 2: Define accessor for that attributes.
Example:
<?php
...
class Movie extends Model{
protected $appends = ['cover'];
//define accessor
public function getCoverAttribute()
{
return json_decode($this->InJson)->cover;
}
you can use setAttribute function in Model to add a custom attribute
Let say you have 2 columns named first_name and last_name in your users table and you want to retrieve full name. you can achieve with the following code :
class User extends Eloquent {
public function getFullNameAttribute()
{
return $this->first_name.' '.$this->last_name;
}
}
now you can get full name as:
$user = User::find(1);
$user->full_name;
In my subscription model, I need to know the subscription is paused or not.
here is how I did it
public function getIsPausedAttribute() {
$isPaused = false;
if (!$this->is_active) {
$isPaused = true;
}
}
then in the view template,I can use
$subscription->is_paused to get the result.
The getIsPausedAttribute is the format to set a custom attribute,
and uses is_paused to get or use the attribute in your view.
in my case, creating an empty column and setting its accessor worked fine.
my accessor filling user's age from dob column. toArray() function worked too.
public function getAgeAttribute()
{
return Carbon::createFromFormat('Y-m-d', $this->attributes['dateofbirth'])->age;
}