ݺߣ

ݺߣShare a Scribd company logo
Сайт на Zend Framework в составе
    промышленной системы

        Сергей Морозов,
          Минск 2012
Сайты бывают разные
Отдельно стоящий сайт


   Внешение ресурсы                       • БД принадлежит только
                                            сайту.

                Бизнес-логика
                                          • Неограниченные
                                            привилегии.

Представление                             • Сайт разграничивает
                                База данных

                                            права пользователей.
Сайт в составе
                промышленной системы
                  Биллинг

                              • БД обслуживает
 Коммутатор
                                несколько приложений
                База данных

                              • Повышенные требова-
Инженерное ПО                   ния к безопасности
                              • Максимально
    Сервер                      ограниченные права
  клиентских
 приложений

                   Сайт
Разграничение прав доступа
                   средствами БД


•   Базы
    данных
•   Таблицы
•   Поля
Чего не хватает?

     • Разграничения на
       доступ к записям
     • Прочих
       ограничений,
       обусловленных
       бизнес-логикой
Ну например?

Если у пользователя есть права на выполнение запроса
SELECT
         id
 FROM
         users
WHERE
         login = :login
  AND
         password = SOME_SECRET_ALG(:password, :salt) ,

значит он может выполнить и
SELECT
         id, login, password
 FROM
         users
Как быть?

• Запретить доступ к таблицам напрямую
• Предоставить абстрактный интерфейс в виде
  пакетов хранимых процедур
Как будто ООП

БАЗА ДАННЫХ               ПРИЛОЖЕНИЕ


         Таблицы                   Данные



   Пакет 1                   Класс 1
     процедура_1_1               метод_1_1

     процедура_1_2               метод_1_2




   Пакет 2                   Класс 2
     процедура_2_1               метод _2_1

     процедура_2_2               метод _2_2
Преимущества подхода


• Централизованное
  разграничение прав
  доступа
• Инкапсуляция
• Разделение труда
Недостатки подхода


       • Бюрократия
       • Сложность
         реализации
       • Отсутствие чѐтких
         границ
Как выглядит код процедуры?

CREATE PROCEDURE AUTHENTICATE(
    psUsername IN VARCHAR2,
    psPassword IN VARCHAR2,
    viAccount_id OUT PLS_INTEGER,
    viStatus OUT VARCHAR2
)
BEGIN
   SELECT
           fiAccount_id,
           fiStatus
      INTO
           viAccount_id,
           viStatus
      FROM
           users
    WHERE
           fsUsername = psUsername
       AND
           fsPassword = SOME_SECRET_ALG(psPassword, 'some salt')
END;
Как выглядит вызов
                              процедуры?
<?php

$query = <<<QUERY
BEGIN
  AUTH.AUTHENTICATE(:psUsername, :psPassword, :piAccount_id, :piStatus);
END
QUERY;

$stmt = oci_parse($conn, $query);

oci_bind_by_name($stmt,   ':psUsername',     $username,     32,   SQLT_CHR);
oci_bind_by_name($stmt,   ':psPassword',     $password,     32,   SQLT_CHR);
oci_bind_by_name($stmt,   ':piAccount_id',   $account_id,   -1,   SQLT_INT);
oci_bind_by_name($stmt,   ':piStatus',       $status,       -1,   SQLT_INT);

oci_execute($stmt);
Как выглядит код функции?

CREATE TYPE TR_ACCOUNT IS RECORD (
   fiUser_id NUMBER(10),
   fsCurrency_code VARCHAR2(3),
   fxBalance NUMBER(10,2)
);

CREATE FUNCTION GET_ACCOUNT_INFO (
  piAccount_id NUMBER
) RETURN TR_ACCOUNT IS
BEGIN
  /* ... */
END;
Как выглядит вызов функции?

<?php

$query = <<<QUERY
DECLARE
  vrAccount BILLING.ACCOUNT;
BEGIN
  vrAccount := BILLING.GET_ACCOUNT_INFO(:piAccount_id);

  :piUser_id       := vrAccount.fiUser_id;
  :psCurrency_code := vrAccount.psCurrency_code;
  :pxBalance       := vrAccount.pxbalance;
END;
QUERY;

$stmt = oci_parse($conn, $query);

oci_bind_by_name($stmt,    ':piAccount_id',      $account_id,   -1, SQLT_INT);
oci_bind_by_name($stmt,    ':piUser_id',         $user_id,      -1, SQLT_INT);
oci_bind_by_name($stmt,    ':psCurrency_code',   $currency_code, 3, SQLT_CHR);
oci_bind_by_name($stmt,    ':pxBalance',         $balance,      22, SQLT_LNG);

oci_execute($stmt);

$result = array(
  'account_id'      =>   $account_id,
  'user_id'         =>   $user_id,
  'currency_code'   =>   $currency_code,
  'balance'         =>   $balance,
);
Как выглядит код функции?

CREATE TYPE TN_ACCOUNTS IS TABLE OF TR_ACCOUNT;

CREATE FUNCTION GET_ACCOUNTS(
  piUser_id NUMBER DEFAULT NULL
) RETURN TN_ACCOUNTS PIPELINED IS
BEGIN
  /* ... */
END;
Как выглядит вызов функции?

<?php

$query = <<<QUERY
SELECT
  *
FROM
  TABLE(BILLING.GET_ACCOUNTS(:piUser_id))
END;
QUERY;

$stmt = oci_parse($conn, $query);

oci_bind_by_name($stmt, ':piUser_id', $user_id, -1, SQLT_INT);

oci_execute($stmt);

oci_fetch_all($stmt, $result);
Рутину нужно
                 автоматизировать
Необходимо определить:
1. Наименования функций и
   процедур
2. Наименования и типы
   аргументов
3. Типы возвращаемых
   значений
4. Структуру табличных
   типов
Как получить описание
                  интерфейса в Oracle?
  SELECT
           PACKAGE_NAME, OBJECT_NAME, POSITION,
           DATA_LEVEL, ARGUMENT_NAME, DATA_TYPE,
           DATA_LENGTH, DEFAULTED, IN_OUT, TYPE_NAME,
           TYPE_SUBNAME
    FROM
           USER_ARGUMENTS
   WHERE
           PACKAGE_NAME = 'MY_PACKAGE'
ORDER BY
           PACKAGE_NAME, OBJECT_NAME, SEQUENCE, POSITION,
           DATA_LEVEL;
Обход графа объектов

Package

                            Types

  Procedures
                                    TR_ACCOUNT
      GET_ACCOUNT
                                        fiUser_id
           ptrResult
                                        fsCurrency_code
           piAccount_id
                                        fxBalance
           TR_ACCOUNT
           S
      GET_ACCOUNTS                  TN_ACCOUNTS


           ptnResult

           piUser_id

          TN_ACCOUNTS
          S
      UPDATE_ACCOUNT

               TR_ACCOUNT
PACKAGE_NAME
                                        Разбор результатов




                                                                                                                                                   TYPE_SUBNAME
                                                                  ARGUMENT_NA
                        OBJECT_NAME




                                                                                                   DATA_LENGTH
                                                 DATA_LEVEL




                                                                                                                                       TYPE_NAME
                                                                                                                 DEFAULTED
                                                                                       DATA_TYPE
                                      POSITION




                                                                                                                              IN_OUT
                                                                  ME
BILLING           CREATE_NUMBER          1             0      ACCOUNT_ID        BINARY_INTEGER                   N           IN

BILLING           CREATE_NUMBER          2             0      PLAN_ID           BINARY_INTEGER                   N           IN

BILLING           CREATE_NUMBER          3             0      NUMBER            BINARY_INTEGER                   N           IN

BILLING           CREATE_NUMBER          4             0      BEGIN_TIME        DATE                             N           IN

BILLING           CREATE_NUMBER          5             0      PRODUCT_ID        NUMBER                     22    N           OUT

BILLING           CREATE_NUMBER          6             0      LOGIN             VARCHAR2                         N           OUT

BILLING           CREATE_NUMBER          7             0      PASSWORD          VARCHAR2                         N           OUT
Разбор результатов
<?php

class Billing
{
  public function createNumber($account_id,
    $plan_id, $number, $begin_time,
    &$product_id, &$login, &$password)
  {
    /* implementation goes here */
  }
}
Разбор результатов


                                         DATA_LEVEL




                                                               ARGUMENT_
  PACKAGE_N




                                                                                                                                   TYPE_NAME
                                                                                               DATA_LENG
                  OBJECT_NA




                                                                                                           DEFAULTED




                                                                                                                                                 TYPE_SUBN
                                                                                   DATA_TYPE
                              POSITION




                                                                                                                        IN_OUT
                                                               NAME
  AME




                                                                                                                                                 AME
                  ME




                                                                                               TH
                                                                                                                                 BILLIN        TN_NUMBE
BILLING       GET_NUMBERS       0            0                             TABLE                           N           OUT
                                                                                                                                 G             RS
                                                                                                                                 BILLIN        TR_NUMBE
BILLING       GET_NUMBERS       1            1                             PL/SQL RECORD                   N           OUT
                                                                                                                                 G             R

BILLING       GET_NUMBERS       1            2        NUMBER_ID            NUMBER                22        N           OUT

BILLING       GET_NUMBERS       2            2        NUMBER               VARCHAR2              20        N           OUT

BILLING       GET_NUMBERS       3            2        BEAUTY_RATING_ID     NUMBER                22        N           OUT

BILLING       GET_NUMBERS       4            2        BEAUTY_RATING        VARCHAR2              20        N           OUT

BILLING       GET_NUMBERS       5            2        CATEGORY_ID          NUMBER                22        N           OUT

BILLING       GET_NUMBERS       1            0        PREFIX               VARCHAR2                        Y           IN

BILLING       GET_NUMBERS       2            0        BEAUTY_RATING_ID     NUMBER                22        Y           IN

BILLING       GET_NUMBERS       3            0        CATEGORY_ID          NUMBER                22        Y           IN
Разбор результатов
<?php

class Billing
{
  public function getNumbers($prefix,
    $beauty_rating_id = null, $category_id = null)
  {
    /* implementation goes here */
  }
}
Что получается в итоге?

• Один класс для пакета — «Фасад»
• По одному классу для каждой процедуры
• Ленивая загрузка
  (Zend_Loader_PluginLoader).
• Дополнительные плагины
Когда уже будет про Zend
                Framework?

• Zend_Tool_*
• My_Tool_Provider_DbIntf
• Zend_Application_Resource_Multidb
• My_Application_Resource_DbIntf
• Zend_CodeGenerator_*
Но не всѐ так просто

• Необязательные аргументы
 В зависимости от количества переданных
 аргументов нужно формировать
 соответствующий ему код SQL-запроса.

• Передача переменных по ссылке
 Перегрузка методов в PHP не позволяет
 передавать переменные по ссылке, поэтому
 для таких методов перегрузку использовать
 нельзя.
Повторное использование
                         запросов

<?php

function get_users(Zend_Db_Select $select = null)
{
  if (null === $select) {
    $select = Zend_Db_Select();
  }

    return $select
      ->from('users')
      ->query()
      ->fetchAll();
}
Что ещѐ можно
               автоматизировать?

• Обработка исключительных ситуаций:
  • Код ошибки
  • Маркер: поле, индекс, триггер и т. д.
• Форматирование и разбор дат
• Репликация master-slave
Переход на PostgreSQL

• По-другому получаем описание
  интерфейса
• Другой синтаксис вызова процедур
• Описание табличных типов в конечном
  итоге не нужно
Численные показатели

• Сроки
  – Две недели на прототип идеи
  – Полтора года активной разработки
  – Две недели на переход на PostgreSQL*
   * только вызовы процедур

• Код
  – Написано 621 NCLOC
  – Сгенерировано 7415 NCLOC*
   * для 150-ти процедур
Конец

More Related Content

Сайт на Zend Framework в составе промышленной системы

  • 1. Сайт на Zend Framework в составе промышленной системы Сергей Морозов, Минск 2012
  • 3. Отдельно стоящий сайт Внешение ресурсы • БД принадлежит только сайту. Бизнес-логика • Неограниченные привилегии. Представление • Сайт разграничивает База данных права пользователей.
  • 4. Сайт в составе промышленной системы Биллинг • БД обслуживает Коммутатор несколько приложений База данных • Повышенные требова- Инженерное ПО ния к безопасности • Максимально Сервер ограниченные права клиентских приложений Сайт
  • 5. Разграничение прав доступа средствами БД • Базы данных • Таблицы • Поля
  • 6. Чего не хватает? • Разграничения на доступ к записям • Прочих ограничений, обусловленных бизнес-логикой
  • 7. Ну например? Если у пользователя есть права на выполнение запроса SELECT id FROM users WHERE login = :login AND password = SOME_SECRET_ALG(:password, :salt) , значит он может выполнить и SELECT id, login, password FROM users
  • 8. Как быть? • Запретить доступ к таблицам напрямую • Предоставить абстрактный интерфейс в виде пакетов хранимых процедур
  • 9. Как будто ООП БАЗА ДАННЫХ ПРИЛОЖЕНИЕ Таблицы Данные Пакет 1 Класс 1 процедура_1_1 метод_1_1 процедура_1_2 метод_1_2 Пакет 2 Класс 2 процедура_2_1 метод _2_1 процедура_2_2 метод _2_2
  • 10. Преимущества подхода • Централизованное разграничение прав доступа • Инкапсуляция • Разделение труда
  • 11. Недостатки подхода • Бюрократия • Сложность реализации • Отсутствие чѐтких границ
  • 12. Как выглядит код процедуры? CREATE PROCEDURE AUTHENTICATE( psUsername IN VARCHAR2, psPassword IN VARCHAR2, viAccount_id OUT PLS_INTEGER, viStatus OUT VARCHAR2 ) BEGIN SELECT fiAccount_id, fiStatus INTO viAccount_id, viStatus FROM users WHERE fsUsername = psUsername AND fsPassword = SOME_SECRET_ALG(psPassword, 'some salt') END;
  • 13. Как выглядит вызов процедуры? <?php $query = <<<QUERY BEGIN AUTH.AUTHENTICATE(:psUsername, :psPassword, :piAccount_id, :piStatus); END QUERY; $stmt = oci_parse($conn, $query); oci_bind_by_name($stmt, ':psUsername', $username, 32, SQLT_CHR); oci_bind_by_name($stmt, ':psPassword', $password, 32, SQLT_CHR); oci_bind_by_name($stmt, ':piAccount_id', $account_id, -1, SQLT_INT); oci_bind_by_name($stmt, ':piStatus', $status, -1, SQLT_INT); oci_execute($stmt);
  • 14. Как выглядит код функции? CREATE TYPE TR_ACCOUNT IS RECORD ( fiUser_id NUMBER(10), fsCurrency_code VARCHAR2(3), fxBalance NUMBER(10,2) ); CREATE FUNCTION GET_ACCOUNT_INFO ( piAccount_id NUMBER ) RETURN TR_ACCOUNT IS BEGIN /* ... */ END;
  • 15. Как выглядит вызов функции? <?php $query = <<<QUERY DECLARE vrAccount BILLING.ACCOUNT; BEGIN vrAccount := BILLING.GET_ACCOUNT_INFO(:piAccount_id); :piUser_id := vrAccount.fiUser_id; :psCurrency_code := vrAccount.psCurrency_code; :pxBalance := vrAccount.pxbalance; END; QUERY; $stmt = oci_parse($conn, $query); oci_bind_by_name($stmt, ':piAccount_id', $account_id, -1, SQLT_INT); oci_bind_by_name($stmt, ':piUser_id', $user_id, -1, SQLT_INT); oci_bind_by_name($stmt, ':psCurrency_code', $currency_code, 3, SQLT_CHR); oci_bind_by_name($stmt, ':pxBalance', $balance, 22, SQLT_LNG); oci_execute($stmt); $result = array( 'account_id' => $account_id, 'user_id' => $user_id, 'currency_code' => $currency_code, 'balance' => $balance, );
  • 16. Как выглядит код функции? CREATE TYPE TN_ACCOUNTS IS TABLE OF TR_ACCOUNT; CREATE FUNCTION GET_ACCOUNTS( piUser_id NUMBER DEFAULT NULL ) RETURN TN_ACCOUNTS PIPELINED IS BEGIN /* ... */ END;
  • 17. Как выглядит вызов функции? <?php $query = <<<QUERY SELECT * FROM TABLE(BILLING.GET_ACCOUNTS(:piUser_id)) END; QUERY; $stmt = oci_parse($conn, $query); oci_bind_by_name($stmt, ':piUser_id', $user_id, -1, SQLT_INT); oci_execute($stmt); oci_fetch_all($stmt, $result);
  • 18. Рутину нужно автоматизировать Необходимо определить: 1. Наименования функций и процедур 2. Наименования и типы аргументов 3. Типы возвращаемых значений 4. Структуру табличных типов
  • 19. Как получить описание интерфейса в Oracle? SELECT PACKAGE_NAME, OBJECT_NAME, POSITION, DATA_LEVEL, ARGUMENT_NAME, DATA_TYPE, DATA_LENGTH, DEFAULTED, IN_OUT, TYPE_NAME, TYPE_SUBNAME FROM USER_ARGUMENTS WHERE PACKAGE_NAME = 'MY_PACKAGE' ORDER BY PACKAGE_NAME, OBJECT_NAME, SEQUENCE, POSITION, DATA_LEVEL;
  • 20. Обход графа объектов Package Types Procedures TR_ACCOUNT GET_ACCOUNT fiUser_id ptrResult fsCurrency_code piAccount_id fxBalance TR_ACCOUNT S GET_ACCOUNTS TN_ACCOUNTS ptnResult piUser_id TN_ACCOUNTS S UPDATE_ACCOUNT TR_ACCOUNT
  • 21. PACKAGE_NAME Разбор результатов TYPE_SUBNAME ARGUMENT_NA OBJECT_NAME DATA_LENGTH DATA_LEVEL TYPE_NAME DEFAULTED DATA_TYPE POSITION IN_OUT ME BILLING CREATE_NUMBER 1 0 ACCOUNT_ID BINARY_INTEGER N IN BILLING CREATE_NUMBER 2 0 PLAN_ID BINARY_INTEGER N IN BILLING CREATE_NUMBER 3 0 NUMBER BINARY_INTEGER N IN BILLING CREATE_NUMBER 4 0 BEGIN_TIME DATE N IN BILLING CREATE_NUMBER 5 0 PRODUCT_ID NUMBER 22 N OUT BILLING CREATE_NUMBER 6 0 LOGIN VARCHAR2 N OUT BILLING CREATE_NUMBER 7 0 PASSWORD VARCHAR2 N OUT
  • 22. Разбор результатов <?php class Billing { public function createNumber($account_id, $plan_id, $number, $begin_time, &$product_id, &$login, &$password) { /* implementation goes here */ } }
  • 23. Разбор результатов DATA_LEVEL ARGUMENT_ PACKAGE_N TYPE_NAME DATA_LENG OBJECT_NA DEFAULTED TYPE_SUBN DATA_TYPE POSITION IN_OUT NAME AME AME ME TH BILLIN TN_NUMBE BILLING GET_NUMBERS 0 0 TABLE N OUT G RS BILLIN TR_NUMBE BILLING GET_NUMBERS 1 1 PL/SQL RECORD N OUT G R BILLING GET_NUMBERS 1 2 NUMBER_ID NUMBER 22 N OUT BILLING GET_NUMBERS 2 2 NUMBER VARCHAR2 20 N OUT BILLING GET_NUMBERS 3 2 BEAUTY_RATING_ID NUMBER 22 N OUT BILLING GET_NUMBERS 4 2 BEAUTY_RATING VARCHAR2 20 N OUT BILLING GET_NUMBERS 5 2 CATEGORY_ID NUMBER 22 N OUT BILLING GET_NUMBERS 1 0 PREFIX VARCHAR2 Y IN BILLING GET_NUMBERS 2 0 BEAUTY_RATING_ID NUMBER 22 Y IN BILLING GET_NUMBERS 3 0 CATEGORY_ID NUMBER 22 Y IN
  • 24. Разбор результатов <?php class Billing { public function getNumbers($prefix, $beauty_rating_id = null, $category_id = null) { /* implementation goes here */ } }
  • 25. Что получается в итоге? • Один класс для пакета — «Фасад» • По одному классу для каждой процедуры • Ленивая загрузка (Zend_Loader_PluginLoader). • Дополнительные плагины
  • 26. Когда уже будет про Zend Framework? • Zend_Tool_* • My_Tool_Provider_DbIntf • Zend_Application_Resource_Multidb • My_Application_Resource_DbIntf • Zend_CodeGenerator_*
  • 27. Но не всѐ так просто • Необязательные аргументы В зависимости от количества переданных аргументов нужно формировать соответствующий ему код SQL-запроса. • Передача переменных по ссылке Перегрузка методов в PHP не позволяет передавать переменные по ссылке, поэтому для таких методов перегрузку использовать нельзя.
  • 28. Повторное использование запросов <?php function get_users(Zend_Db_Select $select = null) { if (null === $select) { $select = Zend_Db_Select(); } return $select ->from('users') ->query() ->fetchAll(); }
  • 29. Что ещѐ можно автоматизировать? • Обработка исключительных ситуаций: • Код ошибки • Маркер: поле, индекс, триггер и т. д. • Форматирование и разбор дат • Репликация master-slave
  • 30. Переход на PostgreSQL • По-другому получаем описание интерфейса • Другой синтаксис вызова процедур • Описание табличных типов в конечном итоге не нужно
  • 31. Численные показатели • Сроки – Две недели на прототип идеи – Полтора года активной разработки – Две недели на переход на PostgreSQL* * только вызовы процедур • Код – Написано 621 NCLOC – Сгенерировано 7415 NCLOC* * для 150-ти процедур