CakePHP 4 - Validate password when not empty - validation

While editing a user in CakePHP 4, I want to validate the fields 'password' and 'password_check' only when the 'password' field is not empty.
When 'password' is not empty, those validation rules should be active:
'password' should count at least 8 characters.
'password' should count at most 60 characters.
'password_check' should be required.
'password_check' should count at least 8 characters.
'password_check' should count at most 60 characters.
'password_check should be identical to 'password'.
In UsersController.php, I tried to remove the 'password' value out of the request data so it's not validated when the entity is patched, but apperently that's not possible:
if (empty($this->request->getData('password'))) {
unset($this->request->getData('password')); // error: can't use method return value in write context
unset($this->request->getData('password_check')); // error: can't use method return value in write context
};
$user = $this->Users->patchEntity($user, $this->request->getData()); // validator is called here
Can somebody guide me the way so my preferred validation happens in an efficient way?
Thanks a lot!
Oh yes, here's my UsersTable.php code:
<?php
declare(strict_types=1);
namespace App\Model\Table;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
/**
* Users Model
*
* #property \App\Model\Table\LanguagesTable&\Cake\ORM\Association\BelongsTo $Languages
* #property \App\Model\Table\RolesTable&\Cake\ORM\Association\BelongsTo $Roles
* #property \App\Model\Table\ArticleCategoriesTable&\Cake\ORM\Association\HasMany $ArticleCategories
* #property \App\Model\Table\ArticleImagesTable&\Cake\ORM\Association\HasMany $ArticleImages
* #property \App\Model\Table\ArticleTagsTable&\Cake\ORM\Association\HasMany $ArticleTags
* #property \App\Model\Table\ArticlesTable&\Cake\ORM\Association\HasMany $Articles
* #property \App\Model\Table\CategoriesTable&\Cake\ORM\Association\HasMany $Categories
* #property \App\Model\Table\LinksTable&\Cake\ORM\Association\HasMany $Links
* #property \App\Model\Table\MenusTable&\Cake\ORM\Association\HasMany $Menus
* #property \App\Model\Table\ModulePartsTable&\Cake\ORM\Association\HasMany $ModuleParts
* #property \App\Model\Table\ModulesTable&\Cake\ORM\Association\HasMany $Modules
* #property \App\Model\Table\PagesTable&\Cake\ORM\Association\HasMany $Pages
* #property \App\Model\Table\PartsTable&\Cake\ORM\Association\HasMany $Parts
* #property \App\Model\Table\TagsTable&\Cake\ORM\Association\HasMany $Tags
*
* #method \App\Model\Entity\User newEmptyEntity()
* #method \App\Model\Entity\User newEntity(array $data, array $options = [])
* #method \App\Model\Entity\User[] newEntities(array $data, array $options = [])
* #method \App\Model\Entity\User get($primaryKey, $options = [])
* #method \App\Model\Entity\User findOrCreate($search, ?callable $callback = null, $options = [])
* #method \App\Model\Entity\User patchEntity(\Cake\Datasource\EntityInterface $entity, array $data, array $options = [])
* #method \App\Model\Entity\User[] patchEntities(iterable $entities, array $data, array $options = [])
* #method \App\Model\Entity\User|false save(\Cake\Datasource\EntityInterface $entity, $options = [])
* #method \App\Model\Entity\User saveOrFail(\Cake\Datasource\EntityInterface $entity, $options = [])
* #method \App\Model\Entity\User[]|\Cake\Datasource\ResultSetInterface|false saveMany(iterable $entities, $options = [])
* #method \App\Model\Entity\User[]|\Cake\Datasource\ResultSetInterface saveManyOrFail(iterable $entities, $options = [])
* #method \App\Model\Entity\User[]|\Cake\Datasource\ResultSetInterface|false deleteMany(iterable $entities, $options = [])
* #method \App\Model\Entity\User[]|\Cake\Datasource\ResultSetInterface deleteManyOrFail(iterable $entities, $options = [])
*
* #mixin \Cake\ORM\Behavior\TimestampBehavior
*/
class UsersTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config): void
{
parent::initialize($config);
$this->setTable('users');
$this->setDisplayField('username');
$this->setPrimaryKey('id');
$this->addBehavior('Timestamp');
$this->belongsTo('Languages', [
'foreignKey' => 'language_id',
'joinType' => 'INNER',
]);
$this->belongsTo('Roles', [
'foreignKey' => 'role_id',
'joinType' => 'INNER',
]);
$this->hasMany('ArticleCategories', [
'foreignKey' => 'user_id',
]);
$this->hasMany('ArticleImages', [
'foreignKey' => 'user_id',
]);
$this->hasMany('ArticleTags', [
'foreignKey' => 'user_id',
]);
$this->hasMany('Articles', [
'foreignKey' => 'user_id',
]);
$this->hasMany('Categories', [
'foreignKey' => 'user_id',
]);
$this->hasMany('Links', [
'foreignKey' => 'user_id',
]);
$this->hasMany('Menus', [
'foreignKey' => 'user_id',
]);
$this->hasMany('ModuleParts', [
'foreignKey' => 'user_id',
]);
$this->hasMany('Modules', [
'foreignKey' => 'user_id',
]);
$this->hasMany('Pages', [
'foreignKey' => 'user_id',
]);
$this->hasMany('Parts', [
'foreignKey' => 'user_id',
]);
$this->hasMany('Tags', [
'foreignKey' => 'user_id',
]);
}
/**
* Default validation rules.
*
* #param \Cake\Validation\Validator $validator Validator instance.
* #return \Cake\Validation\Validator
*/
public function validationDefault(Validator $validator): Validator
{
$validator
->scalar('first_name', __('Valid first_name is required.'))
->maxLength('first_name', 30, __('First name should count at most 30 characters.'))
->requirePresence('first_name', 'create')
->notEmptyString('first_name', __('First name is required.'));
$validator
->scalar('last_name', __('Valid last name is required.'))
->maxLength('last_name', 30, __('Last name should count at most 30 characters.'))
->requirePresence('last_name', 'create')
->notEmptyString('last_name', __('Last name is required.'));
$validator
->scalar('username', __('Valid username is required.'))
->maxLength('username', 30, __('Username should count at most 30 characters.'))
->requirePresence('username', 'create')
->notEmptyString('username', __('Username is required.'));
$validator
->email('email', true, __('Valid email is required.'))
->requirePresence('email', 'create')
->notEmptyString('email', __('Email is required.'));
$validator
->scalar('password', __('Valid password is required.'))
->minLength('password', 8, __('Password should count at least 8 characters.'))
->maxLength('password', 60, __('Password should count at most 60 characters.'))
->requirePresence('password', 'create')
->notEmptyString('password', __('Password is required.'));
$validator
->scalar('password_check', __('Password check is required.'))
->minLength('password_check', 8, __('Password check should count at least 8 characters.'))
->maxLength('password_check', 60, __('Password check should count at most 60 characters.'))
->requirePresence('password_check', 'create')
->notEmptyString('password_check', __('Password check is required.'))
->sameAs('password_check', 'password', __('Password check and password should be identical.'));
$validator
->integer('role_id', __('Valid role is required.'))
->notEmptyString('role_id', __('Role is required.'));
$validator
->boolean('is_active', __('Valid is active is required.'))
->notEmptyString('is_active', __('Is active is required.'));
$validator
->integer('language_id', __('Valid language is required.'))
->notEmptyString('language_id', __('Language is required.'));
return $validator;
}
/**
* Returns a rules checker object that will be used for validating
* application integrity.
*
* #param \Cake\ORM\RulesChecker $rules The rules object to be modified.
* #return \Cake\ORM\RulesChecker
*/
public function buildRules(RulesChecker $rules): RulesChecker
{
$rules->add($rules->isUnique(['username']), ['errorField' => 'username']);
$rules->add($rules->isUnique(['email']), ['errorField' => 'email']);
$rules->add($rules->existsIn('role_id', 'Roles'), ['errorField' => 'role_id']);
$rules->add($rules->existsIn('language_id', 'Languages'), ['errorField' => 'language_id']);
return $rules;
}
}

There are exceptions, but usually when you feel the need to modify the data in the actual request object, you're most likely doing something wrong.
The intended way to modify (request) data before marshalling when creating/patching entities, is the beforeMarshal event/callback.
// in `UsersTable` class
public function beforeMarshal(
\Cake\Event\EventInterface $event,
\ArrayAccess $data,
\ArrayObject $options
): void {
if (empty($data['password'])) {
unset($data['password']);
}
if (empty($data['password_check'])) {
unset($data['password_check']);
}
}
See also
Cookbook > Database Access & ORM > Saving Data > Modifying Request Data Before Building Entities

Related

Laravel form returns "The given data is invalid" while I send alternative error messages

I am using Laravel 6 on a project (included with this chat system / project).
First time so searching for some things.
Using the debugging fucntion of xdebug w/ PHPStorm I can follow it perfectly.
This is what I see:
RegisterController:
protected function validator(array $data)
{
$data['name'] = htmlspecialchars($data['name']);
return Validator::make($data, [
'name' => ['required', 'string', 'max:100'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users', 'regex:/^[\w\-\.\+]+\#[a-zA-Z0-9\.\-]+\.[a-zA-z0-9]{2,4}$/'],
'password' => ['required', 'string', 'min:3', 'max:30', 'confirmed', new NoSpaceContaine()],
'min18years' => ['accepted'],
'accept_all_agreements' => ['accepted'],
'medical1' => ['accepted'],
'medical2' => ['accepted']
], [
'name.required' => 'A name is required',
'email.required' => 'An email is required',
'password.required' => 'A password is required',
'min18years.accepted' => 'An approval for min 18 years is required',
'accept_all_agreements.accepted' => 'An approval for agreements is required',
'medical1.accepted' => 'An approval for medical1 is required',
'medical2.accepted' => 'An approval for medical2 is required'
]);
}
$Data (from debugger, On purpose not selected the min18years checkbox):
$data = {array} [9]
_token = "oEMQFjasGoex4MDiThonh8Vw0e5UbyQ5o7GTRAi8"
name = "Peter7"
email = "my7#email.here"
password = "123"
password_confirmation = "123"
min18years = "0"
accept_all_agreements = "yes"
medical1 = "yes"
medical2 = "yes"
Illuminate\Validation\Validator.php:"
public function validate()
{
if ($this->fails()) {
throw new ValidationException($this);
}
return $this->validated();
}
Error get's thrown, the current data when calling "ValidationException":
$this:
$this = {Illuminate\Validation\Validator} [27]
translator = {Illuminate\Translation\Translator} [7]
container = {Illuminate\Foundation\Application} [33]
presenceVerifier = {Illuminate\Validation\DatabasePresenceVerifier} [2]
failedRules = {array} [1]
min18years = {array} [1]
Accepted = {array} [0]
excludeAttributes = {array} [0]
messages = {Illuminate\Support\MessageBag} [2]
messages = {array} [1]
min18years = {array} [1]
0 = "An approval for min 18 years is required"
format = ":message"
data = {array} [9]
_token = "oEMQFjasGoex4MDiThonh8Vw0e5UbyQ5o7GTRAi8"
name = "Peter7"
email = "my7#email.here"
password = "123"
password_confirmation = "123"
min18years = "0"
accept_all_agreements = "yes"
medical1 = "yes"
medical2 = "yes"
initialRules = {array} [7]
rules = {array} [7]
currentRule = "accepted"
implicitAttributes = {array} [0]
implicitAttributesFormatter = null
distinctValues = {array} [0]
after = {array} [0]
customMessages = {array} [7]
name.required = "A name is required"
email.required = "An email is required"
password.required = "A password is required"
min18years.accepted = "An approval for min 18 years is required"
accept_all_agreements.accepted = "An approval for agreements is required"
medical1.accepted = "An approval for medical1 is required"
medical2.accepted = "An approval for medical2 is required"
But when I follow the call to \Illuminate\Validation\ValidationException.php:
public function __construct($validator, $response = null, $errorBag = 'default')
{
parent::__construct('The given data was invalid.');
$this->response = $response;
$this->errorBag = $errorBag;
$this->validator = $validator;
}
and the $this didn't receive the messages that was passed along to this function.
$this:
$this = {Illuminate\Validation\ValidationException} [12]
validator = null
response = null
status = {int} 422
errorBag = null
redirectTo = null
message = ""
*Exception*string = ""
code = {int} 0
file = "C:\Sources\wachtweken.nl\vendor\laravel\framework\src\Illuminate\Validation\Validator.php"
line = {int} 386
*Exception*trace = {array} [44]
*Exception*previous = null
I hope this is clear when I go throgh te debug steps...
Added validator function as requested (but it's the default :
protected function validator(array $data)
{
$data['name'] = htmlspecialchars($data['name']);
return Validator::make($data, [
'name' => ['required', 'string', 'max:100'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users', 'regex:/^[\w\-\.\+]+\#[a-zA-Z0-9\.\-]+\.[a-zA-z0-9]{2,4}$/'],
'password' => ['required', 'string', 'min:3', 'max:30', 'confirmed', new NoSpaceContaine()],
'min18years' => ['accepted'],
'accept_all_agreements' => ['accepted'],
'medical1' => ['accepted'],
'medical2' => ['accepted']
], [
'name.required' => 'A name is required',
'email.required' => 'An email is required',
'password.required' => 'A password is required',
'min18years.accepted' => 'An approval for min 18 years is required',
'accept_all_agreements.accepted' => 'An approval for agreements is required',
'medical1.accepted' => 'An approval for medical1 is required',
'medical2.accepted' => 'An approval for medical2 is required'
]);
}
Full code from controller deeper down:
Post function of the forum goes here:
RegisterController.php:register
/**
* #param Request $request
*
* #return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function register(Request $request)
{
try {
$this->validator($request->all())->validate();
event(new Registered($user = $this->create($request->all())));
$this->guard()->login($user);
return $this->registered($request, $user)
?: redirect($this->redirectPath());
} catch (Exception $e) {
return Redirect::back()->withInput()->withErrors(['error' => $e->getMessage()]);
}
}
Illuminate\Validation\Factory
class Validator {
/**
* Create a new Validator instance.
*
* #param array $data
* #param array $rules
* #param array $messages
* #param array $customAttributes
* #return \Illuminate\Validation\Validator
* #static
*/
public static function make($data, $rules, $messages = [], $customAttributes = [])
{
/** #var \Illuminate\Validation\Factory $instance */
return $instance->make($data, $rules, $messages, $customAttributes);
}
/**
* Validate the given data against the provided rules.
*
* #param array $data
* #param array $rules
* #param array $messages
* #param array $customAttributes
* #return array
* #throws \Illuminate\Validation\ValidationException
* #static
*/
public static function validate($data, $rules, $messages = [], $customAttributes = [])
{
/** #var \Illuminate\Validation\Factory $instance */
return $instance->validate($data, $rules, $messages, $customAttributes);
}
/**
* Register a custom validator extension.
*
* #param string $rule
* #param \Closure|string $extension
* #param string|null $message
* #return void
* #static
*/
There the exception is trigger and it goes to the
Illuminate\Validation\ValidationException:
<?php
namespace Illuminate\Validation;
use Exception;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Validator as ValidatorFacade;
class ValidationException extends Exception
{
/**
* The validator instance.
*
* #var \Illuminate\Contracts\Validation\Validator
*/
public $validator;
/**
* The recommended response to send to the client.
*
* #var \Symfony\Component\HttpFoundation\Response|null
*/
public $response;
/**
* The status code to use for the response.
*
* #var int
*/
public $status = 422;
/**
* The name of the error bag.
*
* #var string
*/
public $errorBag;
/**
* The path the client should be redirected to.
*
* #var string
*/
public $redirectTo;
/**
* Create a new exception instance.
*
* #param \Illuminate\Contracts\Validation\Validator $validator
* #param \Symfony\Component\HttpFoundation\Response|null $response
* #param string $errorBag
* #return void
*/
public function __construct($validator, $response = null, $errorBag = 'default')
{
parent::__construct('The given data was invalid.');
$this->response = $response;
$this->errorBag = $errorBag;
$this->validator = $validator;
}
/**
* Create a new validation exception from a plain array of messages.
*
* #param array $messages
* #return static
*/
public static function withMessages(array $messages)
{
return new static(tap(ValidatorFacade::make([], []), function ($validator) use ($messages) {
foreach ($messages as $key => $value) {
foreach (Arr::wrap($value) as $message) {
$validator->errors()->add($key, $message);
}
}
}));
}
/**
* Get all of the validation error messages.
*
* #return array
*/
public function errors()
{
return $this->validator->errors()->messages();
}
/**
* Set the HTTP status code to be used for the response.
*
* #param int $status
* #return $this
*/
public function status($status)
{
$this->status = $status;
return $this;
}
/**
* Set the error bag on the exception.
*
* #param string $errorBag
* #return $this
*/
public function errorBag($errorBag)
{
$this->errorBag = $errorBag;
return $this;
}
/**
* Set the URL to redirect to on a validation error.
*
* #param string $url
* #return $this
*/
public function redirectTo($url)
{
$this->redirectTo = $url;
return $this;
}
/**
* Get the underlying response instance.
*
* #return \Symfony\Component\HttpFoundation\Response|null
*/
public function getResponse()
{
return $this->response;
}
}
Your problem is the try/catch. You don't have to use it, if it throws an error, it will by default return back to the page where it came from (response()->back()) with input (->withInput()) and with the validation errors (errors will be populated for you to use in blade).
So, change your code to:
public function register(Request $request)
{
$this->validator($request->all())->validate();
event(new Registered($user = $this->create($request->all())));
$this->guard()->login($user);
return $this->registered($request, $user)
?: redirect($this->redirectPath());
}
Read this part of the documentation again, so you can see that this will work.

Laravel 7 - Pagination on Collection

I need to get my data with pagination when I use collection.
Couldn't find any way, and nothing works that written on documents.
Here's my controller;
...
$data = $process->paginate(30);
$data = OrderResource::collection($data);
And here's my resource:
<?php
namespace App\Http\Resources;
use Carbon\Carbon;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Resources\Json\JsonResource;
class OrderResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
$user = Auth::user();
return [
"id" => $this->id,
"customer" => $this->customer,
"vehicle" => $this->vehicle,
"basket" => $this->basket,
"total" => money_formatter($this->total),
"discount" => $this->discount,
"net_total" => money_formatter($this->net_total),
"status" => $this->status,
"payment_type" => $this->payment_type,
"main_name" => $this->vehicle->fleet_id ? $this->vehicle->fleet->title : ($this->customer->company_id ? $this->customer->company->title : $this->customer->fullname),
"sub_name" => $this->vehicle->fleet_id ? ($this->customer->company_id ? $this->customer->company->title : $this->customer->fullname) : '',
"created_at" => Carbon::parse($this->created_at)->formatLocalized('%a, %d %B %Y'),
];
}
}
You can add a macro inside your AppServiceProvider.php for this, inside the boot method.
/**
* Paginate a standard Laravel Collection.
*
* #param int $perPage
* #param int $total
* #param int $page
* #param string $pageName
* #return array
*/
Collection::macro('paginate', function ($perPage = 15, $total = null, $page = null, $pageName = 'page') {
$page = $page ?: LengthAwarePaginator::resolveCurrentPage($pageName);
return new LengthAwarePaginator(
$this->forPage($page, $perPage),
$total ?: $this->count(),
$perPage,
$page,
[
'path' => LengthAwarePaginator::resolveCurrentPath(),
'pageName' => $pageName,
]
);
});
And then you can use it like this (assuming $data is a regular laravel collection)
$data = $data->paginate(50);
dd($data);
You can't add any metadata (pagination links) with the collection method. First create a ResourceCollection with php artisan make:resource -c OrderCollection.
Then, in that newly created file, you can do the following.
class OrderCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
$collection = [
'data' => OrderResource::collection($this->collection)
];
if ($this->resource instanceof \Illuminate\Pagination\LengthAwarePaginator) {
$collection['pagination'] = [
'current_page' => $this->resource->currentPage(),
'last_page' => $this->resource->lastPage(),
'first_page_url' => $this->resource->url(1),
'last_page_url' => $this->resource->url($this->resource->lastPage()),
'prev_page_url' => $this->resource->previousPageUrl(),
'next_page_url' => $this->resource->nextPageUrl(),
'from' => $this->resource->firstItem(),
'to' => $this->resource->lastItem(),
'total' => $this->resource->total(),
'per_page' => $this->resource->perPage(),
'path' => $this->resource->path(),
];
}
return $collection;
}
}
dd(json_encode(new OrderCollection(Order::paginate(3)), JSON_PRETTY_PRINT));

Illuminate\Database\QueryException SQLSTATE[22007]: Invalid datetime format: 1366 Incorrect double value: 'required'

I got the following error when I try to write $fillable in Model.
Illuminate\Database\QueryException SQLSTATE[22007]: Invalid datetime format: 1366 Incorrect double value: 'required' for column db_productmanagementsystem.products.price at row 1 (SQL: insert into products (title, type, firstname, surname, price, papl, updated_at, created_at)
Model: Product.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
protected $fillable = ['title', 'type', 'firstname', 'surname', 'price', 'papl'];
//use HasFactory;
}
ProductController.php
<?php
namespace App\Http\Controllers;
use App\Models\Product;
use Illuminate\Http\Request;
class ProductController extends Controller
{
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index()
{
$products=product::all();
return view('products', ['products'=>$products]);
}
/**
* Show the form for creating a new resource.
*
* #return \Illuminate\Http\Response
*/
public function create()
{
return view('createProduct');
}
/**
* Store a newly created resource in storage.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function store(Request $request)
{
request()->validate([
'title' => 'required',
'type' => 'required',
'firstname' => 'required',
'surname' => 'required',
'price' => 'required',
'papl' => 'required'
]);
Product::create([
'title' => 'required',
'type' => 'required',
'firstname' => 'required',
'surname' => 'required',
'price' => 'required',
'papl' => 'required'
]);
return redirect('/products');
}
/**
* Display the specified resource.
*
* #param \App\Models\Product $product
* #return \Illuminate\Http\Response
*/
public function show(Product $id)
{
return view('singleProduct', ['product'=>$id]);
}
/**
* Show the form for editing the specified resource.
*
* #param \App\Models\Product $product
* #return \Illuminate\Http\Response
*/
public function edit(Product $id)
{
return view('editProduct', ['product'=>$id]);
}
/**
* Update the specified resource in storage.
*
* #param \Illuminate\Http\Request $request
* #param \App\Models\Product $product
* #return \Illuminate\Http\Response
*/
public function update($id)
{
request()->validate([
'title' => 'required',
'type' => 'required',
'firstname' => 'required',
'surname' => 'required',
'price' => 'required',
'papl' => 'required'
]);
$product=product::findOrFail($id);
$product->title = request('title');
$product->type = request('type');
$product->firstname = request('firstname');
$product->surname = request('surname');
$product->price = request('price');
$product->papl = request('papl');
$product->save();
return redirect('/products');
}
/**
* Remove the specified resource from storage.
*
* #param \App\Models\Product $product
* #return \Illuminate\Http\Response
*/
public function destroy($id)
{
$product=product::find($id)->delete();
return redirect('/products');
}
}
Database:
enter image description here
You didn't provide the right value to your product model.
Recheck your store method
Product::create([
'title' => 'required',
'type' => 'required',
'firstname' => 'required',
'surname' => 'required',
'price' => 'required', // here you passed a string. but this field type is double in the database schema
'papl' => 'required'
]);
you should get the Product values from the request:
Product::create([
'title' => $request->input( 'title'),
'type' => $request->input('type'),
'firstname' => $request->input('firstname'),
'surname' => $request->input('surname'),
'price' => $request->input('price'),
'papl' => $request->input('papl')
]);

Laravel - Name instead of ID

Somehow I can't trace what is wrong with my codes. I'm trying to display a typ_name instead of id, so I defined the following relationship in both type and infrastructure models but I can't able to display the name.
Models codes shown below
type.php
<?php
namespace App\Models;
use Eloquent as Model;
use Illuminate\Database\Eloquent\SoftDeletes;
/**
* Class type
* #package App\Models
* #version January 16, 2020, 4:01 am UTC
*
* #property string typ_name
*/
class type extends Model
{
use SoftDeletes;
public $table = 'types';
protected $dates = ['deleted_at'];
public $fillable = [
'typ_name'
];
public function type()
{
return $this->hasMany('infrastructure');
}
/**
* The attributes that should be casted to native types.
*
* #var array
*/
protected $casts = [
'id' => 'integer',
'typ_name' => 'string'
];
/**
* Validation rules
*
* #var array
*/
public static $rules = [
'typ_name' => 'required'
];
}
infrastructure.php
<?php
namespace App\Models;
use Eloquent as Model;
use Illuminate\Database\Eloquent\SoftDeletes;
/**
* Class infrastructure
* #package App\Models
* #version January 23, 2020, 1:44 am UTC
*
* #property string inf_name
* #property integer inf_height
* #property integer inf_width
* #property integer typ_id
* #property integer island_id
* #property integer engnr_id
* #property integer designer_id
* #property integer foreman_id
* #property string start_date
* #property string cmplt_date
* #property string inf_lifspan
* #property string inf_file
* #property integer inf_lat
* #property integer inf_long
* #property string inf_comment
*/
class infrastructure extends Model
{
use SoftDeletes;
public $table = 'infrastructures';
protected $dates = ['deleted_at'];
public $fillable = [
'inf_name',
'inf_height',
'inf_width',
'inf_length',
'typ_id',
'island_id',
'engnr_id',
'designer_id',
'foreman_id',
'start_date',
'cmplt_date',
'inf_lifspan',
'inf_file',
'inf_lat',
'inf_long',
'inf_comment'
];
/**
* Get the type record associated with the Infrastructure.
*/
public function type()
{
return $this->belongsTo('type', 'typ_id', 'id');
}
/**
* The attributes that should be casted to native types.
*
* #var array
*/
protected $casts = [
'id' => 'integer',
'inf_name' => 'string',
'inf_height' => 'integer',
'inf_width' => 'integer',
'inf_length' => 'integer',
'typ_id' => 'integer',
'island_id' => 'integer',
'engnr_id' => 'integer',
'designer_id' => 'integer',
'foreman_id' => 'integer',
'start_date' => 'date',
'cmplt_date' => 'date',
'inf_lifspan' => 'date',
'inf_lat' => 'double',
'inf_long' => 'double',
'inf_comment' => 'string'
];
/**
* Validation rules
*
* #var array
*/
public static $rules = [
'inf_name' => 'required',
'inf_height' => 'required',
'inf_width' => 'required',
'inf_length' => 'required',
'typ_id' => 'required',
'island_id' => 'required',
'engnr_id' => 'required',
'designer_id' => 'required',
'foreman_id' => 'required',
'start_date' => 'required',
'cmplt_date' => 'required',
'inf_lifspan' => 'required',
'inf_file' => 'required',
'inf_lat' => 'required',
'inf_long' => 'required',
'inf_comment' => 'required'
];
}
infrastructureController.php
public function show($id)
{
$infrastructure = $this->infrastructureRepository->find($id);
if (empty($infrastructure)) {
Flash::error('Infrastructure not found');
return redirect(route('infrastructures.index'));
}
return view('infrastructures.show')->with('infrastructure', $infrastructure);
}
In my blade.php
<!-- Typ Id Field -->
<div class="form-group">
{!! Form::label('typ_id', 'Type:') !!}
{{ $infrastructure->type->typ_name }}
</div>
I think you defined your relation wrong in your infrastructure.php.
Should be
public function type()
{
return $this->belongsTo(type::class, 'typ_id');
}
typ_id being your foreign key in your infrastructures (?) table
Also, don't forget to adjust your type.php
public function type()
{
return $this->hasMany(infrastructure::class);
}

How to build rule exist in or equal to a number in cakephp 3?

I have table comments with column parent_id.
And this is content of CommentsTable.php:
namespace App\Model\Table;
use App\Model\Entity\Comment;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
/**
* Comments Model
*/
class CommentsTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config)
{
$this->table('comments');
$this->displayField('id');
$this->primaryKey('id');
$this->belongsTo('Users', [
'foreignKey' => 'user_id',
'joinType' => 'INNER'
]);
$this->belongsTo('Posts', [
'foreignKey' => 'post_id',
'joinType' => 'INNER'
]);
$this->belongsTo('ParentComments', [
'className' => 'Comments',
'foreignKey' => 'parent_id'
]);
$this->hasMany('ChildComments', [
'className' => 'Comments',
'foreignKey' => 'parent_id'
]);
}
/**
* Default validation rules.
*
* #param \Cake\Validation\Validator $validator Validator instance.
* #return \Cake\Validation\Validator
*/
public function validationDefault(Validator $validator)
{
$validator
->add('id', 'valid', ['rule' => 'numeric'])
->allowEmpty('id', 'create')
->requirePresence('body', 'create')
->notEmpty('body')
->requirePresence('path', 'create')
->notEmpty('path')
->add('status', 'valid', ['rule' => 'numeric'])
->requirePresence('status', 'create')
->notEmpty('status')
->add('created_at', 'valid', ['rule' => 'datetime'])
->requirePresence('created_at', 'create')
->notEmpty('created_at')
->add('updated_at', 'valid', ['rule' => 'datetime'])
->requirePresence('updated_at', 'create')
->notEmpty('updated_at');
return $validator;
}
/**
* Returns a rules checker object that will be used for validating
* application integrity.
*
* #param \Cake\ORM\RulesChecker $rules The rules object to be modified.
* #return \Cake\ORM\RulesChecker
*/
public function buildRules(RulesChecker $rules)
{
$rules->add($rules->existsIn(['user_id'], 'Users'));
$rules->add($rules->existsIn(['post_id'], 'Posts'));
$rules->add($rules->existsIn(['parent_id'], 'ParentComments'));
return $rules;
}
}
I want to build rule for field parent_id: exist in ParentComments or equal to 0.
Can you help me?
Thank you very much.
Rules are just callable functions or callable classes. The existsIn() function is just an alias for the ExistsIn class. We can use the to our advantage:
...
use Cake\ORM\Rule\ExistsIn;
class CommentsTable extends Table
{
...
public function buildRules(RulesChecker $rules)
{
...
$rules->add(
function ($entity, $options) {
$rule = new ExistsIn(['parent_id'], 'ParentComments');
return $entity->parent_id === 1 || $rule($entity, $options);
},
['errorField' => 'parent_id', 'message' => 'Wrong Parent']
);
return $rules;
}
}

Resources