Yii2- get from related table name to add to Sluggable - activerecord

Yii2. How can i get data from the related table and add it to Sluggable behavior.
In the example below, I want for every book add slug-title like "Book House, author Greenberg".
class Books extends \yii\db\ActiveRecord
{
public function behaviors()
{
return [
[
'class' => SluggableBehavior::className(),
'attribute' => "Book" . $this->name . ", author " . $this->getAuthor->name,
//'slugAttribute' => 'slug',
],
];
}
public function getAuthor()
{
return $this->hasOne(Author::className(), ['id' => 'author_id']);
}
}

Simply use a callback for the sluggable behavior like so:
public function behaviors()
{
return [
[
'class'=>SluggableBehavior::className(),
'value'=>function ($event) {
$parts = ['Book', $this->name, 'author', $this->author->name];
return Inflector::slug(implode('-', $parts));
},
],
];
}
My example will output a slug like this: book-house-author-greenberg which is more best practice for slugs than your version. Anyway...if you still prefer the way you described above, simply change the return value.
The doc for the feature used in my example is here.

Related

Merge FormRequest rules

This question is based on this thread: Merge 2 rules FormFequest for validate an update and store action in laravel5.5
Context: Let's suppose I have these 2 requests and I want to merge the SocialMediaFormRequest rules in ReadersFormRequest rules.
ReadersFormRequest
class ReadersFormRequest extends FormRequest
{
public function rules(SocialMediaFormRequest $social)
{
$mediaRules = $social->rules();
$rules = [
'first_name'=>'required',
'last_name'=>'required',
'birthday'=>'required',
'region'=>'required',
'photo_url'=>'required',
'support'=>'required',
'riwayas_id'=>'required',
'description'=>'required',
];
return array_merge($rules,$mediaRules);
}
}
SocialMediaFormRequest
class SocialMediaFormRequest extends FormRequest
{
public function rules()
{
return [
'url'=>'required|url',
'title'=>'required'
];
}
}
Form that I received
first_name: "example"
last_name: "example"
birthday: 2022-06-13
region: somewhere
photo_url: "https:XXX"
support: false
riwayas_id: 1
description: ""
media.url: "https:YYY"
media.title: "stackoverflow"
Question: How can I only pass the argument media.XXX in my form SocialMediaFormRequest?
You can use prepareForValidation() method in the form request to sanitize the inputs : https://laravel.com/docs/9.x/validation#preparing-input-for-validation
So, if in SocialMediaFormRequest you receive the full request you can only get the required fields like that:
public function prepareForValidation()
{
$this->replace([
'url' => $this->url ?? ($this->media['url'] ?? null),
'title' => $this->title ?? ($this->media['title'] ?? null),
]);
}
Also, in ReadersFormRequest when you inject the other request or resolve it from the container it doesn't work correctly, so it is better to get the rules like that:
public function rules()
{
$mediaRules = (new SocialMediaFormRequest())->rules();
and in order to access the media.* attributes in ReadersFormRequest you can again use prepareForValidation:
public function prepareForValidation()
{
$this->merge([
'url' => $this->media['url'] ?? null,
'title' => $this->media['title'] ?? null,
]);
}

Laravel / OctoberCMS frontend filter

I am using OctoberCMS and I have created a custom component. I am trying to create a frontend filter to filter Packages by the Tour they are assigned to.
This is what I have so far. The issue is that the code is looking for a tour field within the packages table rather than using the tour relationship. Does anyone have any ideas?
<?php namespace Jakefeeley\Sghsportingevents\Components;
use Cms\Classes\ComponentBase;
use JakeFeeley\SghSportingEvents\Models\Package;
use Illuminate\Support\Facades\Input;
class FilterPackages extends ComponentBase
{
public function componentDetails()
{
return [
'name' => 'Filter Packages',
'description' => 'Displays filters for packages'
];
}
public function onRun() {
$this->packages = $this->filterPackages();
}
protected function filterPackages() {
$tour = Input::get('tour');
$query = Package::all();
if($tour){
$query = Package::where('tour', '=', $tour)->get();
}
return $query;
}
public $packages;
}
I really appreciate any help you can provide.
Try to query the relationship when the filter input is provided.
This is one way to do it;
public $packages;
protected $tourCode;
public function init()
{
$this->tourCode = trim(post('tour', '')); // or input()
$this->packages = $this->loadPackages();
}
private function loadPackages()
{
$query = PackagesModel::query();
// Run your query only when the input 'tour' is present.
// This assumes the 'tours' db table has a column named 'code'
$query->when(!empty($this->tourCode), function ($q){
return $q->whereHas('tour', function ($qq) {
$qq->whereCode($this->tourCode);
});
});
return $query->get();
}
If you need to support pagination, sorting and any additional filters you can just add their properties like above. e.g;
protected $sortOrder;
public function defineProperties(): array
{
return [
'sortOrder' => [
'title' => 'Sort by',
'type' => 'dropdown',
'default' => 'id asc',
'options' => [...], // allowed sorting options
],
];
}
public function init()
{
$filters = (array) post();
$this->tourCode = isset($filters['tour']) ? trim($filters['tour']) : '';
$this->sortOrder = isset($filters['sortOrder']) ? $filters['sortOrder'] : $this->property('sortOrder');
$this->packages = $this->loadPackages();
}
If you have a more complex situation like ajax filter forms or dynamic partials then you can organize it in a way to load the records on demand vs on every request.e.g;
public function onRun()
{
$this->packages = $this->loadPackages();
}
public function onFilter()
{
if (request()->ajax()) {
try {
return [
"#target-container" => $this->renderPartial("#packages",
[
'packages' => $this->loadPackages()
]
),
];
} catch (Exception $ex) {
throw $ex;
}
}
return false;
}
// call component-name::onFilter from your partials..
You are looking for the whereHas method. You can find about here in the docs. I am not sure what your input is getting. This will also return a collection and not singular record. Use ->first() instead of ->get() if you are only expecting one result.
$package = Package::whereHas('tour', function ($query) {
$query->where('id', $tour);
})->get();

Laravel Map DB Column Names Using Proper Convention to Actual DB Column Names in Model

We're building a portal to replace part of an existing application as step one, but the DB schema holds to absolutely no conventions. Aside from the lack of any constraints, indexes, etc the names of columns are not descriptive and not snake-cased.
Is it possible to map DB table column names so that the portal uses proper descriptive and snake-cased column names like first_name but writes to the actual database column first to at least have the portal be a first step towards cleaning up the tech debt?
For example, similar to how the table name (Model::table) can be set if the table name doesn't follow convention:
Example
private $columns = [
// convention => actual
'first_name' => 'first',
'last_name' => 'last',
'mobile_phone' => 'phone',
'home_phone' => 'otherPhone', // seriously!?
];
I've looked through Model and the HasAttributes trait, but I'm still hoping that this might exist, or someone has found a way to do this as a temporary solution.
You can create a parent class for all your models:
abstract class Model extends \Illuminate\Database\Eloquent\Model {
protected $columns = [];
public function attributesToArray()
{
$attributes = parent::attributesToArray();
foreach ($this->columns as $convention => $actual) {
if (array_key_exists($actual, $attributes)) {
$attributes[$convention] = $attributes[$actual];
unset($attributes[$actual]);
}
}
return $attributes;
}
public function getAttribute($key)
{
if (array_key_exists($key, $this->columns)) {
$key = $this->columns[$key];
}
return parent::getAttributeValue($key);
}
public function setAttribute($key, $value)
{
if (array_key_exists($key, $this->columns)) {
$key = $this->columns[$key];
}
return parent::setAttribute($key, $value);
}
}
Then override $columns in your models:
protected $columns = [
'first_name' => 'first',
'last_name' => 'last',
'mobile_phone' => 'phone',
'home_phone' => 'otherPhone',
];
The proper way is to use accessors and mutators.
Defining An Accessor
public function getFirstNameAttribute() {
return $this->first;
}
Then, you can access the value by $model->first_name.
Defining A Mutator
public function setFirstNameAttribute($value) {
$this->attributes['first'] = $value;
}
Then, you can mutate the value for example:
$model->first_name = 'first_name';
$model->save();

Eloquent does not add foreign key from relation

I could not find an answer to my problem and I hope I can describe it properly. I do hope to be able to provide all necessary information.
Bottom line: Why does the relations parent ID not get injected on creating a new database entry through the parent model.
I have an Occasions model which holds a collection of pictures. Within the addPicture ($name, $filepath) method the exception is thrown. As pointed out rhough code-comments
Occasion.php
// namespace + use directives omitted
class Occasion extends Model
{
use Sluggable;
protected $fillable = [ 'name', 'root-folder', 'path' ];
public function sluggable ()
{
return [
'slug' => [
'source' => [ 'root-folder', 'name', ],
],
];
}
public function pictures ()
{
return $this->hasMany(picture::class);
}
public function addPicture ($name, $filepath)
{
$thumbname = $this->getThumbFilename($filepath);
dump($this,$this->pictures()); // dump to check my data
$pic = Picture::create(compact('name', 'thumbname'));
// this line is never reached
$pic->createThumb($filepath);
}
...
}
Picture.php:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Spatie\Glide\GlideImage;
class Picture extends Model
{
protected $fillable = [ 'name', 'thumbname' ];
public function createThumb ($filepath)
{
$this->ppath = storage_path('app') . "/" . $filepath;
$this->tpath = storage_path('app/public/thumbs') . "/" . $this->getfilename($filepath);
GlideImage::create($this->ppath)->modify([ 'w' => 100, 'h' => 100, 'fit' => 'max' ])->save($this->tpath);
$this->save();
}
public function occasion ()
{
return $this->belongsTo(Occasion::class);
}
/* public function slideshow ()
{
return $this->belongsToMany(Slideshow::class);
}*/
private function getfilename ($path)
{
$tmp = array_slice(explode('/', $path), -3);
return str_replace(" ", "-", implode("-", $tmp));
}
}
The result of dump($this->pictures()); shows the relation and the columns used:
HasMany {#206 ▼
#foreignKey: "pictures.occasion_id"
#localKey: "id"
#query: Builder {#205 ▶}
#parent: Occasion {#215 ▶}
#related: Picture {#210 ▶}
}
But I'm getting an error message telling me that my occasion_id (in pictures table) is missing a default value. Looking at the built query the occasion_id is indeed missing. What I can't figure out is why said ID does not get injected as I am creating the new picture instance through an occasion-object.
QueryException
SQLSTATE[HY000]: General error: 1364 Field 'occasion_id' doesn't have a default value (SQL: insert into `pictures` (`name`, `thumbname`, `updated_at`, `created_at`) values (IMG_0015.JPG, 2006-PVanlage-06-IMG_0015.JPG, 2017-09-12 19:34:07, 2017-09-12 19:34:07))
I hope that all necessary information is provided.
First you need to add "occasion_id" to fillable array in App\Picture model. Secondly, you need to create occasion object first, then pass the ID addPicture to create picture object, see below
public Picture extends Model{
public $fillable = ['name', 'filepath', 'occasion_id'];
public function occasion(){
$this->belongsTo(App\Occassion::class);
}
}
public function addPicture ($name, $filepath, $occasion_id)
{
$thumbname = $this->getThumbFilename($filepath);
dump($this,$this->pictures()); // dump to check my data
$pic = Picture::create(compact('name', 'thumbname', 'occasion_id'));
// this line is never reached
$pic->createThumb($filepath);
}
There's a smarter way to do this, but this should work.

Laravel Dingo nested transformers

I'm trying to get one to many relationship objects with transformers. I want to get include metas but i only get just regular transform fields.
my transformer:
class AssistantTransformer extends TransformerAbstract
{
protected $availableIncludes = [
'assistantmetas'
];
public function transform(User $user)
{
return [
'id' => (int) $user->id,
'firstname' => ucfirst($user->first_name),
'lastname' => ucfirst($user->last_name),
];
}
public function includeMetas(User $user)
{
$assistantmetas = $user->userMetas;
return $this->item($assistantmetas, new AssistantsMetaTransformer);
}
}
Just use defaultIncludes not available includes, because it needs to send request via url? include=assistantmetas to get result like this.

Resources