Special UUID primary key for laravel eloquent - php

I have a users table that doesn't use an auto increment primary key, instead it uses a binary uuid primary key. I set up my custom model to interact with my table however, I'm having trouble trying to find records by using ::find() because for a case like this, this uuid needs to searched by using HEX() and UNHEX() mysql functions. How to I override this and have whatever is in ::find() to be hexed. The name of the model is Player.
So if I was to try to find a user with a uuid of 9d823b9eec224cbea10b69bec2c5adef, I cannot find them by doing:
Player::find('9d823b9eec224cbea10b69bec2c5adef') since the uuid needs to be unhexed.
I've tried Player::find("UNHEX('9d823b9eec224cbea10b69bec2c5adef')"); with no results.
Here's my model so far:
class Player extends Eloquent {
protected $table = 'players';
protected $connection = 'game';
protected $primaryKey = 'uuid';
public $incrementing = false;
public $timestamps = false;
}
The datatype for uuid is binary(16)
Update
I've got it to work by using Player::find(DB::raw("UNHEX('9d823b9eec224cbea10b69bec2c5adef')")); however this is a lot to write every time I need to look up a user.
Is there any way I can have the parameter for ::find() always run through DB::raw("UNHEX('uuid')") or be passed through the function hex2bin()?
I am 100% certain I will always be using UUID so I want to override ::find(), not create a new method.

I would try to unhex it in PHP prior to passing it to mysql:
Player::find(hex2bin('9d823b9eec224cbea10b69bec2c5adef'));
You could add this method to your Player class:
public static function findUUID($uuid) {
return self::find(hex2bin($uuid));
}
Now any where in your project you can call it like:
$result = Player::findUUID('9d823b9eec224cbea10b69bec2c5adef');
If you do not want to declare it statically you can:
public function findUUID($uuid) {
return self::find(hex2bin($uuid));
}
and then reference it in your code with:
$result = new Player;
$result->findUUID('9d823b9eec224cbea10b69bec2c5adef');

This should allow you to override the native find() behavior and not have to use findUUID() instead:
protected $primaryKey = 'uuid';
public static function find($uuid)
{
return parent::find(hex2bin($uuid));
}

If you really want it like that, you can also do Player::find('9d823b9eec224cbea10b69bec2c5adef') with this
class Player extends Eloquent {
protected $table = 'players';
protected $connection = 'game';
protected $primaryKey = 'uuid';
public $incrementing = false;
public $timestamps = false;
public static function find($uuid, $columns = array('*')) {
return self::find("UNHEX('$uuid')", $columns);
}
}
EDITED
added self, as user Elliot Fehr suggested

Related

Laravel Policy and Show Method with View Method logical Problem

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.

Using hasone function with constructed key

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

Laravel 6 Eloquent model serialization ignoring Accessors

I'm using Laravel 6 with a SQL Server 2017 database backend. In the database I have a table called PersonPhoto, with a Photo column and a Thumbnail column where the photos and thumbnails are stored as VARBINARY.
I have defined the following Eloquent model, with two Accessors to convert the images to base64 encoding:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class PersonPhoto extends Model
{
protected $connection = 'mydb';
protected $table = 'PersonPhoto';
protected $primaryKey ='PersonID';
public function person(){
return $this->belongsTo('App\Person', 'PersonID');
}
public function getPhotoAttribute($value){
return base64_encode($value);
}
public function getThumbnailAttribute($value){
return base64_encode($value);
}
}
This works fine in Blade templates, however when I try to serialize to JSON or an Array I get a "Malformed UTF-8 characters, possibly incorrectly encoded" error, as if the Accessors are being ignored and the raw data is being serialized. To workaround this, I have altered the model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class PersonPhoto extends Model
{
protected $connection = 'mydb';
protected $table = 'PersonPhoto';
protected $primaryKey ='PersonID';
//Added to hide from and add fields to serializer
protected $hidden = ['Photo', 'Thumbnail'];
protected $appends = ['encoded_photo', 'encoded_thumbnail'];
public function person(){
return $this->belongsTo('App\Person', 'PersonID');
}
public function getPhotoAttribute($value){
return base64_encode($value);
}
public function getThumbnailAttribute($value){
return base64_encode($value);
}
//Added these new accessors
public function getEncodedPhotoAttribute(){
return base64_encode($this->Photo);
}
public function getEncodedThumbnailAttribute(){
return base64_encode($this->Thumbnail);
}
}
This hides the original Photo and Thumbnail fields from the serializer and includes the two new accessors. This appears to work and solves my issue.
Questions:
1) Is Laravel's serializer ignoring my Accessors as I suspect, and is this by design?
2) Although my workaround works, is this a reasonable approach or am I likely to run into problems? Is there a better way of doing it?
Thanks
I think you have two issues:
First, Laravel serialization requires that you append any accessors you want included — even if an attribute of the same name already exists. You did not explicitly append the desired values in the first example.
https://laravel.com/docs/5.8/eloquent-serialization#appending-values-to-json
Second, Laravel doesn't always like capitalized attribute names. It happily expects everything to be lowercase (snake_case) and based on some quick testing, seems to have some trouble associating a proper $value to pass to an accessor when case is involved.
However, you can modify your accessor to call the attribute directly instead of relying on Laravel to figure out what you are asking for and achieve the desired results.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class PersonPhoto extends Model
{
protected $connection = 'mydb';
protected $table = 'PersonPhoto';
protected $primaryKey = 'PersonID';
// add the desired appends for serialization
protected $appends = ['Photo','Thumbnail'];
public function person()
{
return $this->belongsTo('App\Person', 'PersonID');
}
public function getPhotoAttribute()
{
// access the attribute directly
return base64_encode($this->attributes['Photo']);
}
public function getThumbnailAttribute()
{
// access the attribute directly
return base64_encode($this->attributes['Thumbnail']);
}
}
EDIT: I actually see that you did something similar in your second example with $this->Thumbnail and $this->Photo. My example is of the same concept, but without relying on magic methods.
__get/__set/__call performance questions with PHP

Laravel One-to-One relationship not working

I have 2 database tables => mobile_phones , mobile_users
Schema for mobile_phones
phone_id (primary key , auto_increment)
phone_name (varchar(150))
phone_model (int (11))
Schema for mobile_users
user_id (primary key , auto_increment)
username (varchar(150))
mobile_phone_id (foreign key referencing mobile_phones(phone_id))
Model class for mobile_phones
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class MobilePhone extends Model
{
protected $primaryKey = "phone_id";
public $timestamps = false;
protected $table = "mobile_phones";
protected $fillable = array("phone_name","phone_model");
public function mobileUser()
{
return $this->hasOne("MobileUser","mobile_phone_id");
}
}
Model class for mobile_users
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class MobileUser extends Model
{
protected $primaryKey = "user_id";
public $timestamps = false;
protected $table = "mobile_users";
protected $fillable = array("username","mobile_phone_id");
public function mobilePhone()
{
return $this->belongsTo("MobilePhone","phone_id");
}
}
I am trying to establish One-to-One relationship between MobileUser and MobilePhone models but it isn't happening. Below is my code in Controller's action -
public function query()
{
$getUsername = MobilePhone::find(1)->username;
echo $getUsername;
}
The above code gives me NULL when I do a var_dump()
I did check similar questions on SO and they used with() (but this isn't necessary). I am referring Laravel 5.2 docs and they state that we can access relate record in another relation using Eloquent's dynamic properties. Link to that docs is here
Please Help !!
You need to call the function of your model so do the following
public function query()
{
$getUsername = MobilePhone::find(1)->mobileusers;
echo $getUsername->username;
}
When doing this:
public function query()
{
$getUsername = MobilePhone::find(1)->username;
echo $getUsername;
}
You try to access a username property out of a MobilePhone number, which is incorrect.
Try this:
public function query()
{
$getUsername = MobilePhone::find(1)->mobileUser->username;
echo $getUsername;
}
Also I encourage you to use the with statement as it will preload all dependencies needed (means 1 single query) instead of eager loading it (many queries)
Something like this :
public function query()
{
$getUsername = MobilePhone::find(1)->mobileUser;
echo $getUsername->username;
}

Disable Laravel's Eloquent timestamps

I'm in the process of converting one of our web applications from CodeIgniter to Laravel. However at this moment we don't want to add the updated_at / created_at fields to all of our tables as we have a logging class that does all this in more depth for us already.
I'm aware I can set $timestamps = false; in:
Vendor\laravel\framework\src\illuminate\Datebase\Eloquent\Model.php
However I'd rather not change a core file for Laravel, or have everyone of my models have that at the top. Is there any way to disable this elsewhere for all models?
You either have to declare public $timestamps = false; in every model, or create a BaseModel, define it there, and have all your models extend it instead of eloquent. Just bare in mind pivot tables MUST have timestamps if you're using Eloquent.
Update: Note that timestamps are no longer REQUIRED in pivot tables after Laravel v3.
Update: You can also disable timestamps by removing $table->timestamps() from your migration.
Simply place this line in your Model:
public $timestamps = false;
And that's it!
Example:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
public $timestamps = false;
//
}
To disable timestamps for one operation (e.g. in a controller):
$post->content = 'Your content';
$post->timestamps = false; // Will not modify the timestamps on save
$post->save();
To disable timestamps for all of your Models, create a new BaseModel file:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class BaseModel extends Model
{
public $timestamps = false;
//
}
Then extend each one of your Models with the BaseModel, like so:
<?php
namespace App;
class Post extends BaseModel
{
//
}
If you are using 5.5.x:
const UPDATED_AT = null;
And for 'created_at' field, you can use:
const CREATED_AT = null;
Make sure you are on the newest version.
(This was broken in Laravel 5.5.0 and fixed again in 5.5.5).
If you only need to only to disable updating updated_at just add this method to your model.
public function setUpdatedAtAttribute($value)
{
// to Disable updated_at
}
This will override the parent setUpdatedAtAttribute() method. created_at will work as usual.
Same way you can write a method to disable updating created_at only.
In case you want to remove timestamps from existing model, as mentioned before, place this in your Model:
public $timestamps = false;
Also create a migration with following code in the up() method and run it:
Schema::table('your_model_table', function (Blueprint $table) {
$table->dropTimestamps();
});
You can use $table->timestamps() in your down() method to allow rolling back.
Eloquent Model:
class User extends Model
{
protected $table = 'users';
public $timestamps = false;
}
Or Simply try this
$users = new Users();
$users->timestamps = false;
$users->name = 'John Doe';
$users->email = 'johndoe#example.com';
$users->save();
Add this line into your model:
Overwrite existing variable $timestamps true to false
/**
* Indicates if the model should be timestamped.
*
* #var bool
*/
public $timestamps = false;
just declare the public timestamps variable in your Model to false and everything will work great.
public $timestamps = false;
Override the functions setUpdatedAt() and getUpdatedAtColumn() in your model
public function setUpdatedAt($value)
{
//Do-nothing
}
public function getUpdatedAtColumn()
{
//Do-nothing
}
You can temporarily disable timestamps
$timestamps = $user->timestamps;
$user->timestamps=false; // avoid view updating the timestamp
$user->last_logged_in_at = now();
$user->save();
$user->timestamps=$timestamps; // restore timestamps

Categories