Laravel 5.1 Reusable Slug - laravel-5

Hello how can I make the unique slug logic reusable in my project. I have a method for creating a unique slug in my Product model and I want to use that same logic in my other model here:
/**
* Set the name attribute and automatically the slug
*
* #param string $name
*/
public function setNameAttribute($name)
{
$this->attributes['name'] = $name;
if(! $this->exists)
{
$this->setUniqueSlug($name, '');
}
}
/**
* Recursive routine to set a unique slug
*
* #param string $name
* #param mixed $extra
*/
public function setUniqueSlug($name, $extra)
{
$slug = str_slug($name . '-' . $extra);
if (static::whereSlug($slug)->exists())
{
$this->setUniqueSlug($name, $extra + 1);
return;
}
$this->attributes['slug'] = $slug;
}

Martin Beans comments would be the best way to go about this.
There is nothing wrong with Paul Vidal's way, however, I wouldn't use the Base Model approach unless you're going to use it with every model.
As long as you're not using different field names for the slug and you're using something like a parent slug (i.e. product/{slug}) you should be able to put the above code straight into a trait and then use in on the models you want to.
Hope this helps!

There are many ways, you can create a Base model class that extends from eloquent, and then extends the rest of your models from that base model. Create the method "setUniqueSlug" in that base model to be accesible from any other.
Or you can create a Helper class, so you can call "setUniqueSlug" from anywhere.

Related

How to change the using of the pattern repository loaded using interfaces for initializing the model? Laravel, Laracom

Good day.
A store was created based on https://github.com/Laracommerce/laracom on Laravel.
In the process, it was noticed that, along with pulling up the implementation for the interface with a call like:
use App\Products\Repositories\Interfaces\ProductRepositoryInterface;
the binding of which is declared in RepositoryServiceProvider (app \ Providers \ RepositoryServiceProvider.php),
direct calls like use App\Shop\Products\Repositories\ProductRepository are used;
(e.g. here app/Shop/Orders/Repositories/OrderRepository.php)
You can find several similar examples in the code, and most often a direct address is required to invoke
$repositoryWithModel = new Repository($modelObject).
I did not find a definite way out of this situation, I ask the advice of those who came across an example of quality implementation.
The implementation of your ProductRepository expects a Product as constructor parameter. A respository should not do that. Instead if a repository has to handle a product model, it should be passed as a parameter to a function.
For example this:
/**
* #param Brand $brand
*/
public function saveBrand(Brand $brand)
{
$this->model->brand()->associate($brand);
}
Can be rewritten to:
/**
* #param Product $product
* #param Brand $brand
*/
public function saveBrand(Product $product, Brand $brand)
{
$product->brand()->associate($brand);
}
If you remove the Product parameter from the constructor, then you can use the repository without creating it using the new keyword every time:
class BrandController extends Controller {
public function __construct(ProductRepositoryInterface $repository) {
$this->repository = $repository;
}
public function linkBrandToProduct(Request $request): void {
$product = $this->repository->findProductById($request->productId);
$brand = $this->repository->findBrandById($request->brandId);
$this->repository->saveBrand($product, $brand);
}
}

Laravel Eloquent collection of mixed models

I have a database with a table called files. Within that, we have the following structure -
- id
- parent_id (nullable)
- name
- type (enum: File or Folder)
- created_at
- updated_at
I then have two models, one called File and one called Folder. Folder extends File. Is there a way that when I call File::all(), for example, I can utilize Eloquent to map the respective models based on the databases type field?
Eloquent returns collection instances, so one way would be to call map() and have that return the appropriate objects for each item, eg, if it's a file just return the file, whereas if it's a folder populate a new Folder instance and return it.
Or you could have File and Folder be models that work off the same table, with global scopes used to limit the query set by type, then call all() on both of them and merge them.
But I think the best thing to do with them is make them a single model, that behaves differently based on the type. Put any differing functionality in methods on the model so it can be treated the same regardless of type by calling those methods. I think that's the safer option in that you're making your models polymorphic - they can be treated the same regardless of type.
I've been able to work out the answer by extending Laravel Models newFromBuilder method.
Here is my class -
class File {
public static $types = ['File', 'Folder'];
/**
* Create a new model instance that is existing.
*
* #param array $attributes
* #param null $connection
*
* #return Model|string
*/
public function newFromBuilder($attributes = [], $connection = null)
{
$model = $this->newInstanceFromType($attributes->type);
$model->exists = true;
$model->setRawAttributes((array) $attributes, true);
$model->setConnection($connection ?: $this->getConnectionName());
$model->fireModelEvent('retrieved', false);
return $model;
}
/**
* Determine our model instance based on the type field.
*
* #param string $type
*
* #return mixed
*/
private function newInstanceFromType(string $type)
{
if (!in_array($type, static::$types)) {
throw new InvalidArgumentException('$type must be one of static::$types');
}
$model = 'App\Models\\' . $type;
return new $model;
}
}
This will then return either a File or Folder model instance depending on what the type enum is from the database.
Thanks all for the input!

Loading model objects in Laravel when passing primarykey in new()

I'm beginning to play around with Eloquent models. I've got the following model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
/**
* Class Character
* #package App
*
* #property string $name
* #property int $id
* #property int $score
*/
class Character extends Model
{
protected $table = 'characters';
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
}
}
My question is, is it possible when I do the following:
$character = new Character( 1 ); // 1 is the primary key
I'd like it to go ahead and load the model with the info in the database. If it doesn't exist, I'd like to set 1 as $character->id. Is this possible? Or does Eloquent already do this and I'm just missing something?
Theoretically you can do it by this:
public function __construct($primaryKey)
{
parent::__construct($attributes);
$character = self::find($primaryKey);
if(is_null($character)){
$character = self::find(1);
}
return $character;
}
But, ideally you should not do this because Laravel is not designed this way. You better do the same thing outside of the model. Besides, there is another problem in this code, we are hard coding the $primaryKey = 1 if the object is not found. Such hard coded value can be risky sometimes.
Don't do this. Eloquent is not designed to work this way. Overriding the constructor like this is bound to cause plenty of issues down the road.
Consider adding a static method for your use-case - much like Imran's solution above. That way you can enjoy the odd behaviour that you're after without risking breaking anything else.
The closest solution you can get is using findOrNew() function https://laravel.com/api/5.2/Illuminate/Database/Eloquent/Builder.html#method_findOrNew.
You can use it as
$character = Character::findOrNew(1);
This will query the characters table for id 1 and will return an instance of the Character model, if the id is not found in the database a new instance of Character model will be instantiated and returned. However there is no option for setting the id of the model.

How to create a universal getter/mutator for datetimes in Laravel?

I have created one and I thought it works:
<?php
namespace App\Traits;
use Carbon\Carbon;
trait FormatDates
{
public function setAttribute($key, $value)
{
parent::setAttribute($key, $value);
if (strtotime($value))
$this->attributes[$key] = Carbon::parse($value);
}
}
But there is a problem when calling related models. For example if you have an Article and Tag model and you want to get all tags like this:
$article->tags
it returns null because of that getter mutator.
How to fix this?
update 17.11.2017
I have found a solution to my problem. The best way to present the date in locale is to use this function:
\Carbon\Carbon::setToStringFormat("d.m.Y H:i");
simply create a service provider or a middleware and it will show all $dates in format you want. There is no need to make a getter.
Based from this: https://laravel.com/api/5.5/Illuminate/Database/Eloquent/Concerns/HasAttributes.html#method_getAttribute
The description says:
Get a plain attribute (not a relationship).
Luckily there are another two methods below it called getRelationValue and getRelationshipFromMethod, and it reads:
Get a relationship.
Get a relationship value from a method.
respectively.
And in your example, it looks like you're calling a relation.
I think you should consider it when doing your universal getter/mutator.
UPDATE:
If you inspect the code, the getAttribute also calls the getRelationValue method. But it is the last resort of the function; if the key is neither an attribute or has a mutator or is a method of the class.
Here is the stub: https://github.com/laravel/framework/blob/5.5/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php#L302
/**
* Get an attribute from the model.
*
* #param string $key
* #return mixed
*/
public function getAttribute($key)
{
if (! $key) {
return;
}
// If the attribute exists in the attribute array or has a "get" mutator we will
// get the attribute's value. Otherwise, we will proceed as if the developers
// are asking for a relationship's value. This covers both types of values.
if (array_key_exists($key, $this->attributes) ||
$this->hasGetMutator($key)) {
return $this->getAttributeValue($key);
}
// Here we will determine if the model base class itself contains this given key
// since we don't want to treat any of those methods as relationships because
// they are all intended as helper methods and none of these are relations.
if (method_exists(self::class, $key)) {
return;
}
return $this->getRelationValue($key);
}
ANOTHER UPDATE
Since you've changed your question:
You can just put the attribute name to $casts or $dates array (in your Model) so Laravel will automatically transform it into a Carbon instance when accessing it, like this:
class Article extends Model {
...
protected $dates = ['some_date_attribute`];
or with $casts
...
protected $casts = ['some_date_attributes' => 'date'];
You really can avoid this, it's already there!
on the model Class you can do:
protected $dates = ['nameOfTheDateOrTimestampTypeField','nameOfAnotherOne'];

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