Cakephp 3.4 - How to validated associated relation data? - validation

For Example I have Club table and Club detail tables. Club table has many details.
And My form control is like below:
$this->Form->control('name');
$this->Form->control('club_payment_settings.0.no_of_account');
$this->Form->control('club_payment_settings.0.actual_rate');
$this->Form->control('club_payment_settings.1.no_of_account');
$this->Form->control('club_payment_settings.1.actual_rate');
$this->Form->control('club_payment_settings.2.no_of_account');
$this->Form->control('club_payment_settings.2.actual_rate');
And in controller written code as bellow:
$club = $this->Clubs->patchEntity($club, $this->request->getData(), [
'associated' => [
'ClubPaymentSettings' => [
'validate' => 'default'
],
]
]);
How can I validate in Model validationDefault()? I am trying to do as follow in my ClubsTable.php file:
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('clubs');
$this->setPrimaryKey('id');
$this->addBehavior('Timestamp');
$this->hasMany('ClubPaymentSettings', [
'foreignKey' => 'club_id',
'joinType' => 'LEFT'
]);
}
public function validationDefault(Validator $validator)
{
$validator
->integer('id')
->allowEmpty('id', 'create');
$validator
->requirePresence('name', true)
->notEmpty('name');
$settingValidator = new Validator();
$settingValidator->requirePresence('no_of_account', true);
$settingValidator->requirePresence('actual_rate', true);
$validator->addNestedMany('club_payment_settings', $settingValidator);
}

Related

how to update key/value database with laravel?

I'm just learning laravel. I want update key / value in database with laravel api but not work.
My products model is one to many with ProductMeta and many to many with contents model.
My Models
class Product extends Model
{
use HasFactory;
protected $guarded = [];
public function productMeta()
{
return $this->hasMany(ProductMeta::class);
}
public function content()
{
return $this->belongsToMany(Content::class, 'product_contents')->withTimestamps();
}
}
class ProductMeta extends Model
{
use HasFactory;
protected $guarded = [];
public function products()
{
return $this->belongsTo(Product::class);
}
}
class Content extends Model
{
use HasFactory;
protected $guarded= [];
public function product()
{
return $this->belongsToMany(Product::class, 'product_contents');
}
Controller
public function update(Request $request, $id)
{
$product = Product::findOrFail($id);
DB::table('product_metas')
->upsert(
[
[
'product_id' => $product->id,
'key' => 'name',
'value' => $request->name,
],
[
'product_id' => $product->id,
'key' => 'price',
'value' => $request->name,
],
[
'product_id' => $product->id,
'key' => 'amount',
'value' => $request->name,
],
],
['product_id','key'],
['value']
);
return \response()->json([], 204);
}
Table Structure
API parameter
I tried with update and updateOrcreate and updateOrInsert and upsert methods.
just in upsert method writed database but inserted new data.not updated.
In your case, you should use updateOrCreate() instead of upsert.
Product::updateOrCreate([
'product_id' => $id,
'name' => $request->name,
'price' => $request->price,
'amount' => $request->amount
]);
or
Product::upsert([
[
'product_id' => $id,
'name' => $request->name,
'price' => $request->price,
'amount' => $request->amount
]
], ['product_id'], ['name', 'price', 'amount']);
In addition your problem is your table name is not matching with your structure table name. In your controller DB::table('product_metas') should be DB::table('products_meta').
my problem solved this way:
ProductMeta::query()->where('product_id', $id)->upsert([
['product_id' => $id, 'key' => 'name', 'value' => $request->name],
['product_id' => $id, 'key' => 'price', 'value' => $request->price],
['product_id' => $id, 'key' => 'amount', 'value' => $request->amount]],
['product_id'], ['value']);
$contentRecord = Product::find($id);
$contentRecord->content()->update(['path'=>$request->path]);
return response()->json([], 204);
I forget use query() method for ProductMeta and added $table->unique(['product_id', 'key']); to product meta migration.
**products relation one to many with product Meta
And Many to many with content.

Laravel Resource collection showing null field

I'm developing an API with Laravel. In one of the endpoint I'm accessing, some fields are showing a null value, but it should have some information.
Note the "addicionais_descricao" and "valor" fields, both always come with null values when I include them in the attributeitems array, but if I leave it at the initial level, the data is presented, but it doesn't solve my case, because I need this information with the attribute items:
enter image description here
This is where the endpoint calls, I make the query in the "Attribute" table, which has a relationship with the "Attributeitems" table, while the "attributeitems" table is linked to "Attribute" and "product".
public function show($id)
{
$atributos = Atributo::query('atributo')
->select(
'atributo.id',
'atributo.atrdescricao',
'atributoitens.atributo_id',
'atributoitens.produto_id',
'produto.prodescricao',
'produto.provalor'
)
->leftJoin('atributoitens', 'atributo.id', '=', 'atributoitens.atributo_id')
->leftJoin('produto', 'produto.id', '=', 'atributoitens.produto_id')
->where('atributo.id', '=', $id)
->get()->unique('id');
return AtributoResource::collection($atributos);
}
Resource Atributo:
public function toArray($request)
{
return [
'id' => $this->id,
'descricao' => $this->atrdescricao,
'atributoitens' => AtributoitensResource::collection($this->atributoitens),
];
}
Resource Atributo Itens:
public function toArray($request)
{
return [
'id' => $this->id,
'atributo' => $this->atributo_id,
'produtos' => $this->produto_id,
'adicionais_descricao' => $this->prodescricao,
'valor' => $this->provalor
];
}
What is the correct procedure for this situation?
Take this example as a reference :
Controller
$data = $shop->products()
->whereStatus(true)
->where('product_shop.active', true)
->where('product_shop.quantity', '>=', $this->min_product_qty)
->paginate(50);
return (new ProductCollection($data))
->response()
->setStatusCode(200);
ProductCollection
public function toArray($request)
{
return [
'data' => $this->collection
->map(function($product) use ($request) {
return (new ProductResource($product))->toArray($request);
}),
'brand' => $this->when($request->brand, $request->brand)
];
}
ProductResource
public function toArray($request)
{
return [
'type' => 'product',
'id' => (string) $this->id,
'attributes' => [
'uuid' => $this->uuid,
'name' => $this->name,
'slug' => $this->slug,
'description' => $this->description,
'thumb_path' => $this->thumb_path,
'cover_path' => $this->cover_path,
],
'relationships' => [
'brand' => $this->brand
]
];
}
Something like this should help you do what you want. I cant exactly do it for you. by the way why you are not using Eloquent, something like
Attribute::where(...)->with(['relation_1', 'products'])->get();
public function toArray($request)
{
return [
'id' => $this->id,
'attributes' => [...],
'products' => $this->collection
->map(function($this->product) use ($request) {
return (new ProductResource($product))->toArray($request);
}),
];
}

Laravel validate if same item is selected more than once

In my application I have add more functionality for selected multiple devices. I need to show an error message if the user has selected the same device two or more times.
Here is my code.
class ValidateDeviceRequest extends FormRequest
{
private $data = [];
public function __construct()
{
$this->data = request()->all();
}
public function rules()
{
$rules = [
'devices' => ['required', 'array'],
'devices.*.device_company_id' => [
'required',
'integer',
'exists:device_companies,id,company_id,' . session()->get('COMPANY_ID')
],
];
foreach($this->data['devices'] as $key => $array)
{
$rules["devices.{$key}.device_id"] = [
'required',
'integer',
"exists:devices,id,device_company_id," . #$array["device_company_id"]
];
}
# returning
return $rules;
}
}
If you're just checking duplicated values inside the array. You can use the validation rule distinct for this.
'devices.*.device_company_id' => [
'distinct',
// ...
]
If you wish to validate duplicates within a model you can use the rule unique:model
Thanks to #Tommie. Here is my final code.
class ValidateDeviceRequest extends FormRequest
{
private $data = [];
public function __construct()
{
$this->data = request()->all();
}
public function rules()
{
$rules = [
'devices' => ['required', 'array'],
'devices.*.device_company_id' => [
'required',
'integer',
'exists:device_companies,id,company_id,' . session()->get('COMPANY_ID')
],
'devices.*.device_company_id' => [
'required',
'distinct',
]
];
foreach($this->data['devices'] as $key => $array)
{
$rules["devices.{$key}.device_id"] = [
'required',
'distinct',
'integer',
"exists:devices,id,device_company_id," . #$array["device_company_id"]
];
}
# returning
return $rules;
}
}
I also found this Laravel array validation for unique attribute in array but not required to be unique in table

Test the exist validator in Yii2 without database with mockeryBuilder()

I want to test my AR model without connect to database in Yii 2 so I use mockBuilder() but I dont know how can I pass the mock object to the model exist validator, for example:
class Comment extends ActiveRecord
{
public function rules()
{
return [
[['id', 'user_id', 'post_id'], 'comment'],
['comment', 'string',
'max' => 200
],
['user_id', 'exist',
'targetClass' => User::className(),
'targetAttribute' => 'id'
],
['post_id', 'exist',
'targetClass' => Post::className(),
'targetAttribute' => 'id'
]
];
}
}
class CommentTest extends TestCase
{
public function testValidateCorrectData()
{
$user = $this->getMockBuilder(User::className())
->setMethods(['find'])
->getMock();
$user->method('find')->willReturn(new User([
'id' => 1
]));
$post = $this->getMockBuilder(Post::className())
->setMethods(['find'])
->getMock();
$post->method('find')->willReturn(new Post([
'id' => 1
]));
// How can I pass to $user and $post to exist validator in Comment model?
$comment = new Comment([
'user_id' => 1,
'post_id' => 1,
'comment' => 'test...'
]);
expect_that($comment->validate());
}
}
ok, It's not a best code just I'd like to introduce what I want to do.
Yii2 ExistValidator uses ActiveQuery::exists() for check existence and you should replace generated validator to mockobject where the method createQuery returns mockobject of ActiveQuery where ::exists() return something you want (true/false) e.g.
$activeQueryMock = $this->getMockBuilder(ActiveQuery::className())
->disableOriginalConstructor()
->setMethods(['exists'])
->getMock();
$activeQueryMock->expects($this->any())
->method('exists')
->willReturn($value); //Your value here true/false
$model = $this->getMockBuilder(Comment::className())
->setMethods(['getActiveValidators'])
->getMock();
$model->expects($this->any())
->method('getActiveValidators')
->willReturnCallback(function () use ($activeQueryMock) {
$validators = (new Comment())->activeValidators;
foreach ($validators as $key => $validator) {
if (($validator instanceof ExistValidator) && ($validator->attributes = ['user_id'])) {
$mock = $this->getMockBuilder(ExistValidator::className())
->setConstructorArgs(['config' => get_object_vars($validator)])
->setMethods(['createQuery'])
->getMock();
$mock->expects($this->any())
->method('createQuery')
->willReturn($activeQueryMock);
$validators[$key] = $mock;
break;
}
}
return $validators;
});
$model->validate();

Store belongsToMany relationships w/ Conditions - Laravel

I have the following tables for my many to many relationship: soldhomestests, tasks and soldhomestest_task (as the pivot).
My soldhomestests table has already been populated with data. How do I get my soldhomestest_task pivot table to populate with data upon the creation of a new task that meets conditions in my soldhomestest table? In my example, I want to store the relationship data when the following conditions are met:
'tasks.city' = 'soldhomestests.city'
'tasks.address' = 'soldhomestests.address'
I can't seem to find any documentation on how to proceed with this?
MODELS:
class Task extends Model
{
protected $fillable = [
'address', 'city', 'state',
];
public function user()
{
return $this->belongsTo(User::class);
}
public function soldhomestests()
{
return $this->belongsToMany('App\Soldhomestest');
}
}
class Soldhomestest extends Model
{
public function tasks()
{
return $this->belongsToMany('App\Task');
}
}
CONTROLLER:
public function store(Request $request)
{
$this->validate($request, [
'address' => 'required|max:255',
'city' => 'required|max:255',
'state' => 'required|max:255',
]);
$request->user()->tasks()->create([
'address' => $request->address,
'city' => $request->city,
'state' => $request->state,
]);
return redirect()->route('settings.index');
}
Don't believe this is the Laravel way but I modified my controller to create an array of IDs using the where condition:
public function store(Request $request)
{
$this->validate($request, [
'address' => 'required|max:255',
'city' => 'required|max:255',
'state' => 'required|max:255',
]);
$newtask = $request->user()->tasks()->create([
'address' => $request->address,
'city' => $request->city,
'state' => $request->state,
]);
$condition = DB::table('soldhomestests')->where([
['soldhomestests.address', '=', $request->address],
['soldhomestests.city', '=', $request->city],
])->pluck('id');
$lastid = $newtask->id;
$tasksoldhome = Task::find($lastid);
$tasksoldhome->soldhomestests()->sync($condition);
return redirect()->route('settings.index');
}
Using eloquent, you can do this way.
$task=new Task();
$task->city=$request->city;
$task->address=$request->address;
$task->save();
$soldhometests=Soldhometest::all();
foreach($soldhometests as $soldhometest)
{
if($task->city==$soldhometest->city && $task->address==$soldhometest->address)
{
$soldhometest_task=new SoldhometestTask(); // pivot model
$soldhometest_task->task_id=$task->id;
$soldhometest_task->soldhometest_id=$soldhometest->id;
$soldhometest_task->save();
}
}

Resources