Разберемся, как правильно с применением ООП сохранять и загружать данные из базы. Существуют такие подходы:
- примитивный подход с использованием PDO/массивов
- ActiveRecord http://design-pattern.ru/patterns/active-record.html
- DataMapper http://design-pattern.ru/patterns/data-mapper.html
- TableDataGateway http://design-pattern.ru/patterns/table-data-gateway.html
В этом варианте мы не используем никаких классов, а просто загружаем/вставляем данные в базу с использованием PDO:
<?php
$query = $pdo->prepare("SELECT * FROM news WHERE categId = :categId LIMIT 20");
$query->execute(array(':categId'    =>  $categId));
$news = $query->fetchAll(); // получаем массив массивовЭтот способ не требует создания никаких классов, но он очень ограничен и ведет к плохому коду если в нашем приложении больше 1-2 таблиц: ты начнешь путаться, где какой массив и какие у него поля. В общем, с таким подходом ничего хорошего нам не светит.
Однако этот подход наиболее эффективен при работе с огромным количеством записей.
Такой подход например поддерживается классом Zend_Db_Select в Zend Framework 1: http://framework.zend.com/manual/1.12/ru/zend.db.select.html
Остальные подходы подразумевают создание класса-сущности (entity), который представляет собой одну запись в таблице. Например, для работы с таблицей новостей мы можем создать сущность News, представляющую собой одну новость:
<?php
class News
{
    public $id;
    public $title;
    public $text;
    public $categId;
    /**
     * Дата в виде объекта DateTime
     */
    public $date;
    
    /**
     * Возвращает возраст новости в днях
     */
    public function getAgeDays()
    {
        // Находим разницу между сегодня и датой публикации
        $today = new DateTime();
        $interval = $today->diff($this->date);
        return $interval->d;
    }
    /**
     * Проверяет все ли поля заполнены перед вставкой в БД
     */
    public function validate(ErrorList $errors)
    {
        if (!$this->title) {
            $errors->add('title', 'Необходимо указать название новости');
        }
        
        if (!$this->text) {
            $errors->add('text', 'Необходимо заполнить текст новости');
        }
    }
}Заметь, что в класс мы можем поместить вспомогательные методы, работающие с этой новостью — удобно (хотя стоит ли вставлять валидацию в сущность — спорный вопрос так как в нашем варианте у нее нет доступа к БД и она например не может проверить заголовок на уникальность — чтобы это было возможно, надо переносить валидацию в другое место). Теперь у нас есть новость, давай посмотрим, как можно сохранить или загрузить ее из базы данных.
Код, реализующий загрузку и сохранение сущностей в SQL базу данных еще называется ORM (Object-Relational Mapper). ORM пытаются избавить нас от необходимости писать однотипные примитивные SQL запросы, позволяя нам работать на более высоком уровне.
Это более простой способ. При его использовании методы для сохранения/загрузки сущности из БД добавляются прямо в нее. Чтобы не копипастить их в каждый класс, их обычно добавляют в базовый класс, а сущность наследуют от него. При этом обычно в сущности делается метод, возвращающий информацию о соответствии полей объекта таблице и полям в базе данных (чтобы можно было правильно составить SQL запрос):
<?php
class News extends ActiveRecordBase
{
    .....
    
    protected function getTableName()
    {
        return 'news'; // имя таблицы с новостями
    }
    
    protected function getFields()
    {
        // список полей, которые отображаются на таблицу
        return array('id', 'title', 'text', 'date', 'categId'); 
    }
    
    /**
     * Вызывается перед вставкой в таблицу
     */
    protected function beforeInsert()
    {
        if (!$this->date) {
            // ставим дату создания если не задана
            $this->date = new DateTime(); 
        }
    }
}Соответственно, вот как выглядит пример поиска записей, вставки и обновления записи:
<?php
$news = new News($pdo); // в некоторых фреймворках передавать объект БД не надо − 
                        // сущность сама берет объект откуда-нибудь
// возвращает массив объектов-новостей
$lastestNews = $news->findLatestNews(); 
// возвращает новость с номером 10
$someNews = $news->getById(10);
// меняем название
$someNews->title = 'Новое название';
// Обновляем запись в БД    
$someNews->save();
$newNews = new News($pdo);
$newNews->title = 'Сенсация!';
$newNews->text = 'Текст новости';
// вставка в БД. После нее поля id и date заполняются автоматически
$newNews->save(); Такой подход используется, например в Yii 1: http://www.yiiframework.com/doc/guide/1.1/ru/database.ar
Также он использовался в Doctrine 1.
Этот подход относительно прост, но он имеет недостаток: мы смешиваем бизнес-логику (методы работы со свойствами новости) и работу с БД в одном классе. Хотя объект-новость вполе может сущестовать и сам по себе. Для решения этой проблемы нам нужен DataMapper.
В DataMapper мы выносим код сохранения/загрузки сущностей (и все что связано с базой данных) в отдельный класс. Вот пример такого класса:
<?php
class NewsMapper
{
    ....
    public function save(News $news) { ... }
    public function getById($id) { ... }
    public function findLatestNews() { ... }
}И вот пример использования:
<?php
$mapper = new NewsMapper($pdo);
// Поиск новости по id
$someNews = $mapper->getById(10);
// меняем название
$someNews->title = 'Новое название';
// Обновляем запись в БД    
$mapper->save($someNews);
// создание новой
$newNews = new News();
$newNews->title = 'Сенсация!';
$newNews->text = 'Текст новости';
// вставка в БД
$mapper->save();Этот подход используется в ORM Doctrine2: http://odiszapc.ru/doctrine/ Только там Mapper называется Repository.
Это что-то напоминающее DataMapper, но он может быть реализован без объектов-сущностей. Например, в ZF есть Zend_Db_Table который его реализует: http://framework.zend.com/manual/1.12/ru/zend.db.table.html — там результаты возвращаются в виде объектов класса Zend_Db_Table_Row. Соответственно, для любых сущностей используется один и тот же класс и это сильно напоминает подход с массивами.
Doctrine 2 — это библиотека реализующая паттерн DataMapper. Ты просто добавляешь в свои сущности аннотации, задающие соответствие полей объектов и полей в базе данных, а Doctrine дает тебе классы-репозитории, которые позволяют загружать и сохранять твои объекты в базу данных. Doctrine 2 — очень мощная и популярная, хотя и непростая для начинающего, библиотека. Чтобы с ней работать, надо понимать саму идею ORM, паттерны UnitOfWork и IdentityMap. И придется много читать мануал по ней.
Напишу еще несколько вещей, которые мы не рассмотрели, но которые есть в больших ORM вроде Doctrine 2:
- свой язык запросов DQL, похожий на SQL
- описание через конфиг или аннотации: ты можешь с помощью специальных комментариев-аннотаций указать, как поля объекта связаны с полями в таблице: http://odiszapc.ru/doctrine/basic_mapping/
- IdentityMap ( http://design-pattern.ru/patterns/identity-map.html ): если ты повторно выбираешь ту же самую сущность из базы, тебе возвращается ссылка на существующую сущность. Доктрина следит чтобы каждая сущность существовала ровно в одном экземпляре, и это помогает избежать противоречий когда есть несколько экземпляров и непонятно в каком из них актуальные данные
- UnitOfWork ( http://design-pattern.ru/patterns/unit-of-work.html ): когда ты делаешь изменения в сущностях, они не сохраняются автоматически. Ты должен явно вызвать метод flush() и тогда EntityManager найдет все изменившиеся, новые и удаленные сущности и соответственно обновит/вставит/удалит записи в базе одной транзакцией.
- работа с ассоциациями (связями). Например, Новость может относиться к Категории и быть помечена Тегами, а также под ней могут быть оставлены Комментарии (у которых в свою очередь есть Авторы). При этом если мы должны иметь возможность создавать такие связи и разрывать их. Представь, как сложно такое реализовать самому (трудно представить? попробуй напиши код).
Doctrine 2 не требует от тебя унаследовать класс-сущность от какого-то базового класса, он позволяет связать любой класс с базой данных — главное чтобы в нем были методы get../set.. для чтения и записи полей. Также, придется потратить время на то, чтобы разобраться, как правильно использовать этот ORM и как настроить в нем кеширование метаданных, чтобы он работал с приемлемой скоростью.
В общем, если у тебя маленькое число таблиц, то ты можешь попробовать обойтись простым DataMapper. Но если у тебя много таблиц, и есть связи между ними то использование Doctrine 2 поможет отойти от написания SQL запросов к манипуляции объектами, сделать код проще и короче и сэкономить твое время. Если же у тебя высоконагруженный проект, то возможно от сложных ORM придется отказаться.
а eloquent ?