Как и куда размещать код прообразования данных, приходящих с формы?

Здравствуйте.

У нас такая ситуация, что с формы создания элемента (объекта недвижемости) приходят данные, которые ещё надо преобразовывать.

Мы делаем так.

В контроллере




$model=new Object;

if(isset($_POST['Object']))

{

  $model->attributes=$_POST['Object'];

  $this->saveModel($model);

}



вызывается метод saveModel, в котором описаны все дотолнительные преобразования:




	public function saveModel($model, $modelName='')

	{

		$model->city_name = '';

		if($model->city>0){

			$t_City = City::model()->findByPk($model->city);

			$model->city_name = $t_City->name.' '.$t_City->Short.'.';

		}elseif($model->settlement>0){

			$t_Settlement = Settlement::model()->findByPk($model->settlement);

			$model->settlement_name = $t_Settlement->name.' '.$t_Settlement->Short.'.';

		};


		

		$model->street_name = '';

		if($model->street>0){

			$t_Street = Street::model()->findByPk($model->street);

			$model->street_name = $t_Street->name.' '.$t_Street->Short.'.';

		};

		

		if (isset($_POST['Object']['floors'])){

			if (count($_POST['Object']['floors'])>0){

				foreach($_POST['Object']['floors'] as $f_number){

					$i_floor = new ObjectFloor();

					$i_floor->floor = intval($f_number);

					$floors[] = $i_floor;

				}

				$model->floors = $floors;

			}

		}else{

			$model->floors = array();

		};

		

		

		//станции метро

		if (isset($_POST['Object']['metroStations'])){

			if (count($_POST['Object']['metroStations'])>0){

				foreach($_POST['Object']['metroStations'] as $f_metro_id){

					$i_mStation = MetroStation::model()->findByPk($f_metro_id);

					if ($i_mStation->validate()){

						$nStations[] = $i_mStation;

					}

				}

				$model->metroStations = $nStations;

			}

		}else{

			$model->metroStations = array();

		};

		

		

		

		$photos = CUploadedFile::getInstancesByName('Object[photos]');

		$photos_title = empty($_POST['Object']['photos_title']) ? array() : $_POST['Object']['photos_title'];

		

		$load_photos_id = empty($_POST['Object']['load_photos_id']) ? array() : $_POST['Object']['load_photos_id'];

		$load_photos_title = empty($_POST['Object']['load_photos_title']) ? array() : $_POST['Object']['load_photos_title'];

		$load_photos_name = empty($_POST['Object']['load_photos_name']) ? array() : $_POST['Object']['load_photos_name'];

				

		if($model->validate()){

			$model->bBids = $model->createSemilar();

			$model->save();

			ObjectFloor::model()->deleteAll('object='.$model->id);

			foreach($model->floors as $i_floor){

				$i_floor->object = $model->id;

				$i_floor->save();

			};

			

			$model_photos = $model->photos;

			if (!empty($model_photos)) {

				foreach ($model_photos as $photo) {

					if (!in_array($photo->idphotos, $load_photos_id)){

						$photo->delete();

					}

				}

			}

			

			foreach ($load_photos_id as $key => $photo) {

				$newPhoto = Photo::model()->findByPk($photo);  

				//$newPhoto = new Photo;  

				$newPhoto ->title = $load_photos_title[$key];

				$newPhoto ->name = $load_photos_name[$key];

				$newPhoto ->object  = $model->id;

					

				if($newPhoto ->save()){} 

			}

			

			foreach ($photos as $key => $photo) {

				if (!empty($photo)) {

					$photoDir = Yii::getPathOfAlias('webroot').Yii::app()->params['uploadPath'].'/uploaded/photos/'.$model->id.'/';

					$photoPath = $photoDir.$photo->name;


					if (!is_dir($photoDir)) mkdir($photoDir, 0777, true);

					if (is_file($photoPath)) unlink($photoPath);

	

					$newPhoto = new Photo;  

					$newPhoto ->title = $photos_title[$key];

					$newPhoto ->name = $photo->name;

					$newPhoto ->object  = $model->id;

					

					if($newPhoto->validate()){

						$photo->saveAs($photoPath);

						$newPhoto ->save();

					} 

				}

			}

			$this->redirect(array('view','id'=>$model->id));

		};

	}



Как видно, что дополнительно преобразуются названяи города, название улицы, этажи, станции метро, фотографии.

Ясно что такому коду в контроллере не место. Поэтому вопрос такой: где лучше писать этот код (скорее всего в модели?) и, главное, как туда перенести все преобразование: либо тупо создать функции и прямо перенести в модель весь кож, либо через get/set полей которые не существуют в базе (создадим отдельно как свойства класса) и передавать во вьюшку не реальные названия полей, а "виртуальные"?

Либо может существует какой-то другой метод?

Заранее спасибо.

Я думаю в модели, но в модели формы, а не в avtiverecord

В модели формы? Первый раз слышу - что это такое?

afterSave

Модель формы, наверное имеется в виду наследник класса FormModel. Но я пока не дорос до понимая зачем усложнять и поверх ActiveRecord модели делать еще и formModel.

Я бы посоветовал использовать ActiveRecord а эти обработки делать либо в beforeSave(), либо в afterSave() (например заливку изображений) либо вообще в валидаторах (например если нужно триммировать имена от лишних пробелов)

Да, имеется ввиду FormModel. Как раз и для таких случаев "виртуальных полей" тоже используется.

Идеологически неправильно это засовывать в ActiveRecord.

Если такая стратегия сохранения используется каждый раз, beforeSave. Если в одном месте, а в других попроще — наследник CFormModel, метод save().

Кстати, хорошо бы это всё завернуть в транзакцию.

а точно beforeSave :)

Я не совсем понимаю почему надо использовать CFormModel?

У нас все данные, приходящие с формы, сохраняются; плюс данные обрабатываются и сохраняется дополнительная инфрмация в эту же модель (запись).

1 форма = 1 строка (обязательных полей) в базе ?

тогда преобразования все делай в переопределенном методе beforeSave, он вызывается перед сохранением, а лучше наверное beforeValidate, если тебе надо преобразовывать перед валидацией

Я знаю что такое beforeSave и beforeValidate)

Просто мне кажеться что такой код не очень хорошо запихивать в одну функцию.

Я думал как бы разложить этот код по мелким функциям, а в итоге приходит к тому с чего начали: все запихать в 1 функцию, только другую

код запихивают в разные функции, если он используется 2 и более раз, одноразовые коды не вижу смысла раскладывать

Иногда для повышения читабельности и избавления от необходимости комментирования, можно вынести код в функцию с говорящим названием. Если накладные расходы не мешают конкретной задаче.

для повышения читабельности надо каментить код, а читать его прыгая по функциям, которые используются 1 раз, даже для того чтобы посмотреть что она вернет и какие там проверки на ввод, не айс (имхо)


для повышения читабельности надо каментить код

Рекомендую вам почитать книгу "Чистый код" Роберта Мартина, там оговорены редкие случаи когда код нужно комментировать, и почему лучше понятные код лучше любого комментария.

Да не надо это пихать в beforeSave/afterSave

Повторюсь - это идеологически неправильно.

При изменении формы вносить изменения в модель что-ли?

Не должно быть так, чтоб при изменении какой-то формы приходилось из-за этого править модель.

Можно в beforeSave() - только не напрямую. В контроллере должен остаться код, читающий данные из $_POST. В модели нужно объявить соответствующие поля для этих данных - тогда можно будет обработку поместить в beforeSave().

Код в контроллере останется примерно такой:




  ...            

  if (isset($_POST['Object']['floors'])){

     $model->floors = $_POST['Object']['floors'];

  }                

  if (isset($_POST['Object']['metroStations'])){

     $model->metroStations = $_POST['Object']['metroStations'];

  }

  $model->photos = CUploadedFile::getInstancesByName('Object[photos]');

  ...

  $model->save();



В модели будет логика преобразования данных в объекты.

Или другой вариант - добавить в модель отдельный метод, в который передать дополнительные данные - это может быть удобно, если описанный метод сохранения используется не всегда (например, в другом контроллере модель тоже сохраняется, но без дополнительных данных):




  ...            

  

  if (isset($_POST['Object']['floors'])){

     $floors = $_POST['Object']['floors'];

  } else {

     $floors = null;

  }

  if (isset($_POST['Object']['metroStations'])){

     $metroStations = $_POST['Object']['metroStations'];

  } else {

     $metroStations = null;

  }

  $photos = CUploadedFile::getInstancesByName('Object[photos]');

  ...

  $model->saveWithData($floors, $metroStations, $photos, ...);