Custom query parameter for api platform - api-platform.com

I'm using api-platform with Elasticsearch as the datasource. Using the filters you specify the object property that you want to filter. This is then exposed as a query parameter on the api.
Is there a way to map a different name for the query parameter?
So instead of exposing the model property name of "updatedDate" you could instead just use "date"?
e.g. GET /products?updatedDate=today becomes GET /products?date=today but underneath it still filters on updatedDate?

You can extend a filter that you use. For example, if you use DateFilter for updatedDate property, your class can look like this:
namespace App\Filter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\DateFilter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
final class UpdatedDateFilter extends DateFilter
{
/** #param mixed $values */
protected function filterProperty(string $property, $values, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null): void
{
if ($property === 'date') {
$property = 'updatedDate';
}
parent::filterProperty($property, $values, $queryBuilder, $queryNameGenerator, $resourceClass, $operationName)
}
}
And use it for your entity:
use App\Filter\UpdatedDateFilter;
/**
* #ApiFilter(UpdatedDateFilter::class, properties={"date"})
*/
class Product
{ .... }

Related

lararvel uuid as primary key

I'm trying to set an uuid as primary key in a Laravel Model. I've done it setting a boot method in my model as stablished here so I don't have to manually create it everytime I want to create and save the model. I have a controller that just creates the model and saves it in database.
It is saved correctly in database but when controller returns the value of the id is always returned with 0. How can I make it to actually return the value that it is creating in database?
Model
class UserPersona extends Model
{
protected $guarded = [];
protected $casts = [
'id' => 'string'
];
/**
* Setup model event hooks
*/
public static function boot()
{
parent::boot();
self::creating(function ($model) {
$uuid = Uuid::uuid4();
$model->id = $uuid->toString();
});
}
}
Controller
class UserPersonaController extends Controller
{
public function new(Request $request)
{
return UserPersona::create();
}
}
You need to change the keyType to string and incrementing to false. Since it's not incrementing.
public $incrementing = false;
protected $keyType = 'string';
Additionally I have an trait which I simply add to those models which have UUID keys. Which is pretty flexible. This comes originally from https://garrettstjohn.com/articles/using-uuid-laravel-eloquent-orm/ and I added some small adjustments to it for issues which I have discovered while using it intensively.
use Illuminate\Database\Eloquent\Model;
use Ramsey\Uuid\Uuid;
/**
* Class Uuid.
* Manages the usage of creating UUID values for primary keys. Drop into your models as
* per normal to use this functionality. Works right out of the box.
* Taken from: http://garrettstjohn.com/entry/using-uuids-laravel-eloquent-orm/
*/
trait UuidForKey
{
/**
* The "booting" method of the model.
*/
public static function bootUuidForKey()
{
static::retrieved(function (Model $model) {
$model->incrementing = false; // this is used after instance is loaded from DB
});
static::creating(function (Model $model) {
$model->incrementing = false; // this is used for new instances
if (empty($model->{$model->getKeyName()})) { // if it's not empty, then we want to use a specific id
$model->{$model->getKeyName()} = (string)Uuid::uuid4();
}
});
}
public function initializeUuidForKey()
{
$this->keyType = 'string';
}
}
Hope this helps.
Accepted answer not worked for me on Laravel 9, but this way worked perfect, you can try it:
1- Create new Trait Class in project path app/Traits/IdAsUuidTrait.php (if you not found Traits folder create it, this is full code of this Class:
<?php
namespace App\Traits;
use Illuminate\Support\Str;
trait IdAsUuidTrait
{
public function initializeIdAsUuidTrait(): void
{
$this->keyType = 'string';
$this->id = Str::orderedUuid()->toString();
}
}
2- In any model you want to make id as UUID just call trait like this:
use App\Traits\IdAsUuidTrait;
class YourModelName extends Model
{
use IdAsUuidTrait;
...
That is it, now try to create, select, update any row in database by this model...

Laravel - Eloquent override get method when using where

I override eloquent get() method in one of my models OrderStatus
public static function get()
{
return "hit";
}
when I call it without where it's working fine
>>> $order_statuses = OrderStatus::get();
=> "hit"
But when I call it with where it uses the parent get method again:
>>> $order_statuses = OrderStatus::where('order_id', 24)->get();
=> Wilgucki\Csv\CsvCollection {#4434
all: [],
}
Is there a way to override it anyway?
you can do that by overriding the get() method inside the query builder in \Illuminate\Database\Query\Builder, an example of this override is provided in this medium post. But in your case it seems you want to override it only when used against the OrderStatuses model.
The good news is that the Builder class has a reference to the table:
/**
* The table which the query is targeting.
*
* #var string
*/
public $from;
it is set here:
/**
* Set the table which the query is targeting.
*
* #param string $table
* #return $this
*/
public function from($table)
{
$this->from = $table;
return $this;
}
so you can do something like this:
namespace App\Override;
class QueryBuilder extends \Illuminate\Database\Query\Builder {
//#Override
public function get($columns = ['*']) {
if ($this->from == 'OrderStatus') {
// customize the call
} else {
//Return default
return parent::get($columns);
}
}
}
The get() function is not defined on the Model class but it is called as a dynamic method on the Eloquent QueryBuilder, that is is handled by calling on the Model class this function:
public static function __callStatic($method, $parameters)
At the end when you call the get() function on Model you are instead calling it dynamically on the Illuminate\Database\Query\Builder class.
So you can't really override the chainable method get() on a class derived from Model without messing up things.

Laravel: override eloquent function for get results?

I can override function before save :
public function save(array $options = [])
{
if(isset($this->datesConvert)){
foreach($this->datesConvert as $date){
$this->attributes[$date] = Carbon::createFromFormat('d/m/Y', $this->attributes[$date])->format('Y-m-d');
}
}
parent::save($options);
}
But which method I can use for get result ? and where is documentation for this. I am looking for something like :
public function get()
{
parent::get();
if(isset($this->datesConvert)){
foreach($this->datesConvert as $date){
$this->attributes[$date] = Carbon::createFromFormat('Y-m-d', $this->attributes[$date])->format('d/m/Y');
}
}
}
With that I can convert 10 date rows without need of 20 mutators..
It seems that Attribute casting fits your needs or use Date mutators
You may customize which fields are automatically mutated, and even completely disable this mutation, by overriding the $dates property of your model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* The attributes that should be mutated to dates.
*
* #var array
*/
protected $dates = [
'created_at',
'updated_at',
'deleted_at',
// more dates
];
}
EDIT
Another way, you can override getAttribute method in Model
<?php
namespace App;
use Carbon\Carbon;
trait DateFormatting
{
protected function dateFields()
{
return [];
}
public function getAttribute($key)
{
if ( array_key_exists( $key, $this->dateFields() ) ) {
return Carbon::createFromFormat('d/m/Y', $this->attributes[$key])->format('Y-m-d');
}
return parent::getAttribute($key);
}
}
then you can use this trait in any your model, just don't forget override dateFields in it
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use App\DateFormatting;
class User extends Model
{
use DateFormatting;
protected function dateFields()
{
return [
'finished_at',
// other field names that you need to format
];
}
after all you can access to this fields as usual(using magic __get())
$model->finished_at;
I find a solution, My solution is :
public function save(array $options = [])
{
if(isset($this->datesConvert)){
foreach($this->datesConvert as $date){
$this->attributes[$date] = \Carbon\Carbon::createFromFormat('d/m/Y', $this->attributes[$date])->format('Y-m-d');
}
}
parent::save($options);
}
public function getAttribute($key)
{
$value = parent::getAttribute($key);
if(isset($this->attributes[$key])){
if(isset($this->datesConvert) && in_array($key, $this->datesConvert)){
$value = \Carbon\Carbon::createFromFormat('Y-m-d', $value)->format('d/m/Y');
}
}
return $value;
}
Laravel comes with something very useful for this problem. I'm not sure what it's called, but you can modify attributes or even add new attributes like this:
class YourModel extends Model
{
...
public function getDateAttribute()
{
return Carbon::createFromFormat('Y-m-d', $this->attributes[$date])->format('d/m/Y');
}
...
}
You can retrieve this attribute like:
$yourModel->date;
Edit after comment #fico7489
You can't ignore the fact you always have to modify things. However there are still some solutions to make it easier.
For example you can change your date column to a string and just store your date with the desired date format.
Other solution should be modifying the date through sql. FORMAT(Now(),'YYYY-MM-DD').
Example in laravel would look like (not tested):
YourModel::select([
'*',
DB::raw('
FORMAT(yourDateColumn,'YYYY-MM-DD')
')
])->get();

How to set multiple boolean mutators

I'm using Laravel 4 and I have a model with a lot of boolean attributes.
For each of them I'm setting a setter like this
public function setIsRemoteAttribute($value){
$this->attributes['isRemote'] = !!$value;
}
and a getter like this
public function getIsRemoteAttribute($value){
return !! $this->attributes['isRemote'];
}
Is there any way to abstract that out so I'm not individually setting 12+ mutators?
I guess you can override setAttribute method like:
public function setAttribute($key, $value){
if(in_array($key, 'abstract_keys')){
$this->attributes[$key] = !!$value;
}
else{
parent::setAttribute($key, $value);
}
}
Same would go for getAttribute.
I have L5 installation but I'm pretty sure this will apply to L4.2 as well.
If you look in the code for Eloquent's Model class you will find the following method:
/**
* Set a given attribute on the model.
*
* #param string $key
* #param mixed $value
* #return void
*/
public function setAttribute($key, $value)
{
// First we will check for the presence of a mutator for the set operation
// which simply lets the developers tweak the attribute as it is set on
// the model, such as "json_encoding" an listing of data for storage.
if ($this->hasSetMutator($key))
{
$method = 'set'.studly_case($key).'Attribute';
return $this->{$method}($value);
}
// If an attribute is listed as a "date", we'll convert it from a DateTime
// instance into a form proper for storage on the database tables using
// the connection grammar's date format. We will auto set the values.
elseif (in_array($key, $this->getDates()) && $value)
{
$value = $this->fromDateTime($value);
}
if ($this->isJsonCastable($key))
{
$value = json_encode($value);
}
$this->attributes[$key] = $value;
}
You could potentially, override this function in your own model:
Store a list of attributes that should get the boolean mutator
Check if $key is within this list of elements
If it is - do something
If it's not, default to the parent implementation (This method)
Example:
public function setAttribute($key, $value)
{
if (in_array($key, $this->booleans))
{
// Do your stuff here - make sure to return it
}
return parent::setAttribute($key, $value);
}
You can do the same thing for the getAttribute method.
With this approach, all you need to do is add the names of the attributes to the list of booleans for them to work.
protected $booleans = array('attr1', 'attr2');

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