Сегодня я покажу небольшой пример разработки классов - моделей.
Модель - это класс, который используется для хранения данных о каких-либо сущностях, например, о пользователях, статьях, платежах и т.д.
Разработка классов - моделей с помощью волшебных методов __set, __get, __isset, __unset часто используется во многих веб-приложениях и фреймворках, например, в известном фреймворке Yii.
Для простоты, будем использовать только первые два волшебных метода. С их помощью мы сможем написать сеттеры и геттеры для доступа к данным, а сами данные будем хранить в массиве.
Пример абстрактной модели
Теперь мы можем создавать множество классов моделей, не создавая каждый раз публичные свойства для доступа к данным, а просто задав валидаторы.
Приведенный выше класс Model можно расширить:
Модель - это класс, который используется для хранения данных о каких-либо сущностях, например, о пользователях, статьях, платежах и т.д.
Разработка классов - моделей с помощью волшебных методов __set, __get, __isset, __unset часто используется во многих веб-приложениях и фреймворках, например, в известном фреймворке Yii.
Для простоты, будем использовать только первые два волшебных метода. С их помощью мы сможем написать сеттеры и геттеры для доступа к данным, а сами данные будем хранить в массиве.
Пример абстрактной модели
abstract class Model
{
private $data = array();
/**
* Устанавливает значение свойства.
*
* @param string $propertyName
* @param mixed $value
*/
public function __set($propertyName, $value)
{
$this->validate($propertyName, $value);
$this->data[$propertyName] = $value;
}
/**
* Возвращает значение свойства.
*
* @param string $propertyName
*/
public function __get($propertyName)
{
if (!array_key_exists($propertyName, $this->data))
{
throw new Excpetion("Свойство '$propertyName' не существует.");
}
return $this->data[$propertyName];
}
/**
* Возвращает схему валидации свойств.
* @return IValidator[]
*/
abstract protected function getValidationSchema();
/**
* Производит валидацию значения свойства.
*
* @param string $propertyName
* @param mixed $value
*/
private function validate($propertyName, $value)
{
$validationSchema = $this->getValidationSchema();
if (empty($validationSchema))
{
return;
}
$validator = (isset($validationSchema[$propertyName])) ? $validationSchema[$propertyName] : null;
if (empty($validator))
{
throw new Exception("Недопустимое свойство '$propertyName'");
}
if (!$validator->validate($value))
{
throw new Exception("Невалидное значение " . var_export($value) . " для свойства '$propertyName'");
}
}
}
Если унаследоваться от такой модели, то можно выполнять следующий код:$user->name = 'Adam'; $user->age = 10;Но для работы класса Model требуется разработать несколько валидаторов - они будут проверять корректность устанавливаемого значения. Например, чтобы имя пользователя было строкой, потому что плохо, когда человека зовут 0.5, например =)
/**
* Интерфейс валидатора.
*/
interface IValidator
{
/**
* Производит валидацию переданного значения.
*
* @param mixed $value
* @return bool
*/
function Validate($value);
}
/**
* Валидатор строк.
*/
class StringValidator implements IValidator
{
private $maxLength = null;
public function __construct($maxLength = null)
{
$this->maxLength = $maxLength;
}
public function Validate($value)
{
$valid = is_string($value);
if ($valid && ($this->maxLength !== null))
{
$valid = (strlen($value) <= $this->maxLength);
}
return $valid;
}
}
/**
* Валидатор чисел.
*/
class IntegerValidator implements IValidator
{
public function Validate($value)
{
return is_int($value);
}
}
/**
* Валидатор пользователя.
*/
class UserValidator implements IValidator
{
public function Validate($value)
{
return ($value instanceof User);
}
}
Определим два класса - модели: User - модель пользователя, Article - модель статьи.class Article extends Model
{
protected function getValidationSchema()
{
return array(
"author" => new UserValidator(),
"title" => new StringValidator(30),
"content" => new StringValidator(),
);
}
}
class User extends Model
{
protected function getValidationSchema()
{
return array(
"name" => new StringValidator(30),
"age" => new IntegerValidator()
);
}
}
Теперь мы можем создать парочку моделей:$user = new User(); $user->name = "test name"; $user->age = 10; $article = new Article(); $article->author = $user; $article->title = "test article"; $article->content = "test content";При этом, будет происходить автоматическая валидация данных и мы можем положиться на корректность моделей.
Теперь мы можем создавать множество классов моделей, не создавая каждый раз публичные свойства для доступа к данным, а просто задав валидаторы.
Приведенный выше класс Model можно расширить:
- Добавить инстанциирование полей модели значениями по-умолчанию.
- Добавить возможность конвертировать модель в массив, JSON, XML и т.д.
Можете заняться этим самостоятельно в собственных проектах =)
При использовании данного подхода не будет работать auto completion в IDE. Придется держать в памяти все наборы атрибутов моделей.
ОтветитьУдалитьКогда в проекте много подобных моделей с неизвестным набором полей, это может создавать серьезные неудобства.
Для нового человека в команде это может стать критичным. Порог вхождения резко возрастет.
Интересное замечание. Ответ продублирую в статью.
ОтветитьУдалитьВ IDE всё будет замечательно работать =) Подсмотрел в Yii. У класса можно добавить в комментариях следующее:
/**
* @property string $name
* @property integer $age
*/
Тогда auto completion будет работать и набор полей всем будет известен.
Не забывайте про рефакторинг - при переименовании поля (а такое случается), найти все использования свойства становится проблематично (= трудоемко).
ОтветитьУдалитьЭто не проблема именно данного подхода - это проблема IDE в большей степени. Если вы, например, захотите переименовать функцию - то будет точно также проблематично найти все её использования.
ОтветитьУдалитьВ отношении рефакторинга очень нравится как сделано в Visua Studio 2010 для C#. Мечтаю, что когда нибудь такое будет и для PHP =)