Класс для сокращения времени разработки

Существует много PHP PDO-классов, согласованных. Однако я считаю, что они не допускают гибкости. Поэтому я создал тот, который помогает сократить время разработки настолько, насколько это возможно, но выполняет эту работу (возможно, помимо части разъединения, но позволяет отслеживать, подключена ли база данных через $database->isConnected). Не могли бы вы указать на любые недостатки и возможные улучшения?

<?php 
    class db
    {
        public $isConnected;
        protected $datab;
        public function __construct($username, $password, $host, $dbname, $options=array()){
            $this->isConnected = true;
            try { 
                $this->datab = new PDO("mysql:host={$host};dbname={$dbname};charset=utf8", $username, $password, $options); 
                $this->datab->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 
                $this->datab->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
            } 
            catch(PDOException $e) { 
                $this->isConnected = false;
                throw new Exception($e->getMessage());
            }
        }
        public function Disconnect(){
            $this->datab = null;
            $this->isConnected = false;
        }
        public function getRow($query, $params=array()){
            try{ 
                $stmt = $this->datab->prepare($query); 
                $stmt->execute($params);
                return $stmt->fetch();  
                }catch(PDOException $e){
                throw new Exception($e->getMessage());
            }
        }
        public function getRows($query, $params=array()){
            try{ 
                $stmt = $this->datab->prepare($query); 
                $stmt->execute($params);
                return $stmt->fetchAll();       
                }catch(PDOException $e){
                throw new Exception($e->getMessage());
            }       
        }
        public function insertRow($query, $params){
            try{ 
                $stmt = $this->datab->prepare($query); 
                $stmt->execute($params);
                }catch(PDOException $e){
                throw new Exception($e->getMessage());
            }           
        }
        public function updateRow($query, $params){
            return $this->insertRow($query, $params);
        }
        public function deleteRow($query, $params){
            return $this->insertRow($query, $params);
        }
    }
    //USAGE 
    /*      
        Connecting to DataBase
        $database = new db("root", "", "localhost", "database", array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'));

        Getting row
        $getrow = $database->getRow("SELECT email, username FROM users WHERE username =?", array("yusaf"));

        Getting multiple rows
        $getrows = $database->getRows("SELECT id, username FROM users");

        inserting a row
        $insertrow = $database ->insertRow("INSERT INTO users (username, email) VALUES (?, ?)", array("yusaf", "[email protected]"));

        updating existing row           
        $updaterow = $database->updateRow("UPDATE users SET username = ?, email = ? WHERE id = ?", array("yusafk", "[email protected]", "1"));

        delete a row
        $deleterow = $database->deleteRow("DELETE FROM users WHERE id = ?", array("1"));
        disconnecting from database
        $database->Disconnect();

        checking if database is connected
        if($database->isConnected){
        echo "you are connected to the database";
        }else{
        echo "you are not connected to the database";
        }

    */
48 голосов | спросил Yusaf Khaliq 4 AM000000100000003231 2013, 10:04:32

2 ответа


65

Лично я должен сказать, что близко ко всем классам PDO, которые я видел до сих пор, страдают от одной и той же проблемы: они, по сути, совершенно бессмысленны.
Позвольте мне быть ясным: PDO предлагает простой, простой в обслуживании и опрятный интерфейс сам по себе, обертывая его в пользовательский класс, чтобы лучше соответствовать потребностям конкретного проекта, по существу, использует достойный инструмент OO, и изменить его так, чтобы вы не могли повторно использовать его так же легко в других проектах. Если бы вы писали обертку вокруг MySQLi, я мог бы понять рассуждения, но PDO? Нет, я действительно пытаюсь понять логику этого , если :

Вам нужно было написать класс mapper для работы с ним, который устанавливает соединение между запрошенными вами таблицами и моделями данных, в которые вы храните данные. В отличие от того, как работает Zend\Db.
MySQL, как вы знаете, не такой гибкий, как PHP, с точки зрения типов данных. Если вы собираетесь написать слой абстракции DB, здравый смысл подсказывает, что этот слой отражает это: он должен использовать касты, константы, фильтры , а также подготовленные инструкции , чтобы отразить это.
Самый зрелый код там также предлагает API, который не требует, чтобы вы писали свои собственные запросы:

$query = $db->select('table')
            ->fields(array('user', 'role', 'last_active')
            ->where('hash = ?', $pwdHash);

Эти уровни абстракции часто (если не всегда) предлагают другое преимущество, они строят для вас запросы на основе , к какой базе данных вы подключаетесь к . Если вы используете mysql, они построят запрос MySQL, если вам придётся переключиться на PostgreSQL, они будут генерировать pg-запросы без необходимости переписывать тысячи запросов. Если вы хотите сохранить и написать свой собственный слой абстракции, убедитесь, что вы тоже предложили нечто подобное. Если вы этого не сделаете, вы вернетесь к первому: приступите к трудоемким, бессмысленным приключениям, которые не будут стоить того.

Альтернативный подход заключается в expand классе PDO. Опять же, это было сделано до и теоретически отлично. Хотя, опять же, это может быть личное, это нарушает один принцип, который поддерживается многими разработчиками, которых я знаю: не расширяйте или не пытайтесь изменить объект , у вас нет . PDO - это основной объект, поэтому довольно ясно, что вы его не владеете.
Предположим, вы должны написать что-то вроде:

class MyDO extends PDO
{
    public function createProcedure(array $arguments, $body)
    {
        //create SP on MySQL server, and return
    }
}

И давайте предположим, что после некоторой серьезной отладки и тестирования вы действительно получили эту работу. Большой! Но что если, в будущем, PDO получил свой собственный метод createProcedure? он, вероятно, превзойдет вас и может быть более мощным. Это само по себе не проблема, но предположим, что подпись тоже другая:

public function createProcedure (stdClass $arguments)
{
}

Это означало бы, что вы или должны отказаться от вашего метода и реорганизовать всю свою базу кода, чтобы петь на мелодию PDO, который был последним и наибольшим ударом, или вам придется изменить свой метод на:

public function createProcedure(array $arguments, $body)
{
    $arguments = (object) $arguments;//create stdClass
    //parse $body and:
    $arguments->declare = $body[0];
    $arguments->loops = (object) array('loop1' => $body[1], 'loop2' => $body[2]);
    $arguments->body = (object) array(
        'main' => $body[3],
        'loop1_body' => $body[4],
        'loop2_body' => $body[5]
    );
    return parent::createProcedure($arguments);
}

Это означало бы, что для всего кода, который вы написали, на самом деле вам нужно вызвать 2 метода, превратив ваш умный метод createProcedure в мертвый вес. Так что, вы могли бы сказать? Ну, не думайте, что вы еще не в лесу, потому что Этот альтернативный метод выше незаконный , он не может быть написан, он не может работать , он не должен работать, это всего лишь оттенки неправильного, вот почему:

Принцип Лискова гласит, что ребенок (класс, расширяющий PDO), не может изменять подпись унаследованных методов, если эти изменения являются нарушением контракта, что означает, что ожидаемые типы (типы-подсказки) могут не быть более строгим или отличным от типов, определенных в родительском (то есть: array vs stdClass) не допускается). Допускаются дополнительные аргументы, если они необязательные .
Если сам метод PDO принимает только один аргумент типа stdClass, тогда ваш дочерний класс может добавлять только необязательные аргументы и должен либо отбрасывать подсказку типа, либо поддерживать он (т. е. намек на stdClass), который сломает весь существующий код), или вообще не намекайте (что так же подвержено ошибкам, как и получается).

Более того, через пару месяцев люди могут использоватьсторонний код (фреймворки), которые полагаются на метод createProcedure и передают ему экземпляр stdClass. Вам придется снова изменить свой метод, на неопределенную и подверженную ошибкам подпись:

public function createProcedure($arrOrObject, $body = null)
{
    if ($arrOrObject instanceof stdClass)
    {
        return parent::createProcedure($arrOrObject);
    }
    if ($body === null)
    {
        //What now??
    }
    //parse body
}

Если $body имеет значение null, а $arrOrObject - это массив, пользователь может структурировать массив $arrOrObject таким же образом как PDO хотел бы видеть структурированный объект, и в этом случае json_decode(json_encode($arrOrObject)); выполнит трюк (не кастинг, потому что актер не делает листинг рекурсивный), но так же вероятно, что код, вызывающий ваш метод, содержит ошибку. Что делать? конвертировать в объект и try-catch с дополнительными служебными данными, которые могут вызвать?

Это приводит меня к последнему, а сейчас самое большое упущение:
При использовании объекта-оболочки обычно рекомендуется реализовать метод медленной магии __call, который проверяет, был ли вызов метода предназначен для объекта-оболочки, или если он предназначен для обернутого объекта .
Используя свой объект, я могу установить другой атрибут в расширении PDO, но поскольку вам не удалось реализовать метод setAttribute, я не могу изменить кодировку и не могу изменить, как PDO имеет значение NULL. Который может рассматриваться только как вопиющее упущение. Тем более, что вы ожидаете, что пользователь передаст голые константы PDO в конструктор. В принципе, наименьшее количество должно : add:

public function __call($method, array $arguments )
{
    return call_user_func_array($this->datab, $arguments);
}

Таким образом, вы можете разоблачить фактический обернутый объект пользователю в том смысле, что методы, которые вы еще не реализовали, все еще можно использовать. Новые методы, которые могут быть реализованы в будущем, также будут автоматически доступны.
Более того, вы сможете проверять /проверять аргументы и регистрировать, какие методы используются пользователями, чтобы вы могли точно настроить свой класс, чтобы лучше отражать то, как он используется.

Резюме:

  • Построение класса абстракции в удобном для пользователя raw-API, таком как PDO, есть, IMHO, как сломанный карандаш: Бессмысленный
  • Расширение PDO означает, что вам не нужно создавать методы pass-through , которые вызывают обернутый API (например, создание метода prepare), но это означает, что существует шанс вам придется реорганизовать свой код при изменении PDO
  • Если вы все еще чувствуете, что используете объект-оболочку, подумайте о том, чтобы обернуть его вокруг чего-то менее известного, но в некоторых отношениях лучше (mysqli_* - это то, что я намекаю), но реализовать магический __call и, при необходимости, методы __callStatic.
  • Опять же, если вы собираетесь обрабатывать: работайте над полным уровнем абстракции, который позволяет пользователям сопоставлять таблицы с моделями данных и брать их оттуда. Убедитесь, что эти модели данных могут быть легко использованы для создания HTML-форм, например, для того, чтобы эти формы могли быть связаны с БД мгновенно, бот не забывает о дезинфекции ваших данных, конечно.

В самом коде:

Есть одна вещь, которую вы имеете , чтобы исправить свой код, прежде всего, и это ваш конструктор:
Я мог бы выбрать такой объект:

$database = new db("user", "pwd", "host", "mydb",
             array(PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ));

Настройка режима выборки по умолчанию для извлечения объектов, а новый экземпляр PDO будет передан правильный массив атрибутов. К сожалению, сразу после создания этого объекта PDO вы решаете, что режим выборки по умолчанию должен быть PDO::FETCH_ASSOC, потому что еще две строки вниз:

$this->datab->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);

Мне бы очень хотелось использовать этот код. Кроме того, это просто не складывается:

try
{ 
    $this->datab = new PDO("mysql:host={$host};dbname={$dbname};charset=utf8", $username, $password, $options); 
    $this->datab->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 
    $this->datab->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
} 
catch(PDOException $e)
{ 
    $this->isConnected = false;
    throw new Exception($e->getMessage());
}

Try-кетчуп бросок? Зачем? Не удалось подключиться, PDOException сообщает мне, почему, это то, что я хочу знать, зачем ловить это исключение и бросать новый, более общий? Что делать, если я прошел PDO::ATTR_ERRMODE => PDO::ERRMODE_SILENT, а соединение не выполнено? Вы, наверное, лучшезаменив его следующим образом:

$this->datab = new PDO($connString, array(
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'//careful with this one, though
));
foreach($options as $attr => $value)
{
    $this->datab->setAttribute($attr, $value);
}

И разрешить исключение . Если соединение с БД выходит из строя, сценарий не может делать то, что он должен делать в любом случае. PDOExceptions полезны в сценарии, в котором вы используете транзакции. Если 999 запросов преуспели, но 1000-й запрос завершился неудачно, вместо того, чтобы вставлять частичные данные, перехватывая исключение и откатываясь от транзакции, это то, что вы делаете, но перехват исключения для повторного его повторения просто глупо.

Но, опять же, я не собираюсь мешать вам делать то, что вы хотите, возможно, вы можете доказать, что я ошибаюсь, и на самом деле сделать что-то отличное. Но для этого вы должны знать, что там уже есть:

ответил Elias Van Ootegem 5 PM00000030000001931 2013, 15:28:19
5

Вид старого сообщения, но мне пришлось добавить что-то, где я увидел эту строку

$this->datab->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 
$this->datab->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);

Мне кажется, что у вас были жесткие настройки настроек сразу после этой строки:

$this->datab = new PDO("mysql:host={$host};dbname={$dbname};charset=utf8", $username, $password, $options); 

Что означает (независимо от ATTR_MODE, ATTR_DEFAULT_MODE), который пользователь устанавливает как $options в конструкторе будет переопределяться setAttribute. Который, на мой взгляд, кажется бессмысленным. Я бы предложил изменить конструктор на что-то вроде:

setAttribute
ответил samayo 3 PM00000010000000231 2015, 13:05:02

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132