Items of different types: how well organized my code

Hello everyone,

I need your help because I’m not sure of the best way to develop part of my site.

I need to be able to add Items which can have different types but which have attributes in common (name, date, type, id etc.).

When creating I have to choose between 3 different types and depending on this type they have specific attributes (an image field, a link field, nothing).

During the modification I must be able to change the type of the item they have and therefore the fields that go with this type.

Example :
Each item has a title, a date and therefore a type.
A post type item has only text,
A video type item with only a video field and the text field in addition.
I can first create a post type item and then change it to a video.

Any ideas on the best way to do this?
Obviously, it’s the same controller for everyone, but in terms of form validation, it might be complicated?

Sounds like you want to use a different validation set based on which type was selected.

I just looked quickly but I understand that you have to choose the validator before displaying the form except I would like to use the same form for all my type of items and specify the item in a select of the form.

I don’t know if it’s well explained? haha

I may have found a solution with the on arrays of the validator in my model:

	$validator->add('script', 
				'valid-script', [
	    			'rule' => function ($value, $context){
						return !empty(trim($value));
				 	},
				 	'message' => "Il faut remplir la description.",
				 	'on' => function ($context) {
						return in_array($context['data']['type'],['reportage','extrait']);
					},
				]
			);


			$validator->add('UrlYoutube', 
				'valid-yt-url', [
	    			'rule' => function ($value, $context){
						if (preg_match('/(.+)youtube\.com\/watch\?v=([\w-]+)/',$value)) {
							return true;
						}else{return false;}
				 	},
				 	'message' => "Il faut une URL Youtube valide pour un type reportage.",
				 	'on' => function ($context) {
						return in_array($context['data']['type'],['reportage']);
					},
				]
			);

You don’t need to choose a validator before creating the form. Validation is done when creating the entity

$article = $articles->newEntity(
    $this->request->getData(),
    ['validate' => 'update']
);

you can have different custom-validation in the Model.

// in Model/Table/ArticlesTable.php
class ArticlesTable extends Table
{
    public function validationCustomType1Update($validator)
    {
        // todo, own validation here
        return $validator;
    }
   public function validationCustomType2Update($validator)
    {
        // todo, own validation here
        return $validator;
    }
   public function validationCustomType3Update($validator)
    {
        // todo, own validation here
        return $validator;
    }

}

Then you can access them easily in the Controller

// in /Controller/ArticlesController.php
$article = $articlesTable->newEntity(
    $this->request->getData(),
    ['validate' => 'customType1Update']
);

// or you can use another way
$type2Validator = $articlesTable->getValidator('customType2Update');
$errors = $type2Validator->validate( $this->request->getData() );
$article->setErrors($errors);

Is there something I don’t understand but I think it’s a mistake in my code?

I create an empty entity as soon as the user arrives on the form page (first line of the function) so I don’t know what he will choose as a post because he decides via a select in the form.



	function add($file = null){

		$post = $this->Posts->newEmptyEntity();
	
		if ($this->request->is('post'))
		{
			$data = $this->request->getData();
			$video->user_id = $this->request->getAttribute('identity')->id; 
			$video = $this->Posts->patchEntity($video, $data, [
				'accessibleFields' => ['user_id' => false]
			]);

			if ($this->Posts->save($video)) {

				$this->Flash->success(__('Succès.'));
				return $this->redirect(['action' => 'index']);
			}else{
				$this->Flash->error(__('Erreur.'));
			}
		}

		$this->set('post', $post);
	}

I have to choose the validator once there is a post request?

Thanks for your help

I found a solution ! For those who have the same problem, here is the code:

For CREATE :

<?php
// PostsController.php
	function add($file = null){

		$video = $this->Posts->newEmptyEntity();
		$this->Authorization->authorize($post);

		if ($this->request->is('post'))
		{
			$data = $this->request->getData();
			$video->user_id = $this->request->getAttribute('identity')->id; 
			$video = $this-> Posts->patchEntity($post, $data, [
				'accessibleFields' => ['user_id' => false],
				'validate' => $data['type'], // HERE ! 
			]);


			if ($this->Videos->save($video)) {
 			     // ... 
			}

FOR UPDATE:

<?php
// PostsController.php
	function edit (int $id){
		$post = $this->Posts->get($id);

		if ($this->request->is(['post','put'])) {
			$data = $this->request->getData();

			$video = $this-> Posts->patchEntity($post, $data,[
				'accessibleFields' => ['user_id' => false],
				'validate' => $data['type'],
			]);

			if ($this->Videos->save($video)) {
 			     // ... 
			}
			

IN THE MODEL :

<?php
// PostsTable.php
	 public function validationDefault(Validator $validator): Validator
	{	
		$validator
			->requirePresence('name',true)
			->requirePresence('type',true);

		$validator
			->notEmptyString('type','Il faut remplir ce champ.')
			->notEmptyString('name','Il faut remplir ce champ.')
			->add('type',"valid-type",['rule'=>['inList',['type1', 'type2']],'message'=>'Il faut un type valide.'])
		
	public function validationType1($validator)
	{	
		$validator = $this->validationDefault($validator);
		$validator
			->rulehere()

		return $validator;
	}

	public function validationType2 ($validator)
	{	
		$validator = $this->validationDefault($validator);
		$validator
			->rulehere()

		return $validator;
	}

However, I don’t quite understand how to use the
src/Model/Validation/PostValidator.php which I created while reading the documentation?

Yes, this is exactly what @yousuo and I were suggesting.

Why do you have a PostValidator if you don’t know what it’s for? What’s it trying to accomplish for you?

I was trying to experiment and understand the doc. I thought I could put all my validation rules there.

I come from CakePHP 2 and I took over the code so a lot of luck has changed since!

I think that’s meant for situations where you need the same validation rule across multiple models, e.g. validating phone numbers based on international standards.

Maybe you can look into conditional validation?

https://book.cakephp.org/4/en/core-libraries/validation.html#conditional-validation

$validator->requirePresence(‘UrlYoutube’, function ($context) {
if (isset($context[‘data’][‘type’])) {
return $context[‘data’][‘type’] === ‘video’;
}
return false;
});

That’s what I started out doing. But I find that separating validators is more clean.