CakePHP: Manual11/Models

Модели

Что такое модель?

Что она делает? Модель разделяет область кода от представления, отделяя код приложения.

Модель это общая точка доступа к базе данных, а именно, к определенной таблице в базе данных. По умолчанию, каждая модель использует таблицу, чье имя – это множественное число от имени модели, например: модель 'User' использует таблицу 'users'. Модель может также включать в себя правила верификации данных, информацию об ассоциациях, и индивидуальные методы к таблице, которую использует. Вот как простая модель 'User' может выглядеть в Cake:

Пример модели User, расположение – /app/models/user.php

<?php

//AppModel дает вам всю функциональность моделей Cake

class User extends AppModel

{

    // Всегда полезно включать эту переменную.

    var $name 'User';

    // Это используется для верификации данных, подробнее в соответствующей главе.

    var $validate = array();

    // Вы также можете объявить ассоциации.

    // Посмотрите раздел 6.3 для более подробной информации.

    var $hasMany = array('Image' =>

                   array('className' => 'Image')

                   );

    // Вы также можете включить собственные функции:

    function makeInactive($uid)

    {

        //Вставьте свой код сюда...

    }

}

?>

Функции моделей

Со стороны PHP, модели это классы расширения App Model класса. App Model класс изначально объявлен в директории cake/, но если вы хотите создать собственный, поместите его в app/app_model.php. Он должен включать в себя методы, которые будут делиться двумя или более моделями. Он сам по себе расширит класс Модели которая находится в стандартной библиотеке Cake в cake/libs/model.php.

Этот раздел будет рассказывать о самых часто используемых функциях моделей Cake, не стоит забывать, что полезно использовать http://api.cakephp.org для полной справки.

Функция User-Defined

Пример индивидуального метода таблицы в модели – это пара методов для скрытия/отображения постов в блоге

Пример функции модели

<?php

class Post extends AppModel

{

   var $name 'Post';

   function hide ($id=null)

   {

      if ($id)

      {

          $this->id $id;

          $this->saveField('hidden''1');

      }

   }

   function unhide ($id=null)

   {

      if ($id)

      {

          $this->id $id;

          $this->saveField('hidden''0');

      }

   }

}

?>

Извлечение ваших данных

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

findAll

Возвращает определенные поля количеством не больше $limit, соответствующих $conditions, начинается просмотр со страницы $page (по умолчанию 1). $conditions должна выглядеть как бы она выглядела в SQL-запросе: $conditions="race='wookie' AND thermal_detonators > 3", например.

Когда опция $recursive установлена на целое значение выше 1, операция findAll() попытается вернуть модели ассоциированные с найденным операцией findAll(). Если у ваших данных несколько владельцев, то рекурсив операции findAll() в вашей модели Property возвратит все ассоциированные модели.

find

Возвращает определенные (или же все, если нету специфики) поля из первой записи, соответствующие $conditions.

Если опция $recursive установлена на целое значение между 1 и 3, операция find() попытается вернуть модели ассоциированные с найденными операцией find(). Рекурсивный поиск может работать на три уровня вглубь. Если у ваших данных несколько владельцев, рекурсивная операция find() в вашей модели Property вернет ассоциативные модели до трех уровней вглубь.

findAllBy<fieldName>

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

<?

$this->Post->findByTitle('My First Blog Post');

$this->Author->findByLastName('Rogers');

$this->Property->findAllByState('AZ');

$this->Specimen->findAllByKingdom('Animalia');

?>

Результатом будет масив, форматированный как в find() или findAll().

findNeighbours

Возвращает массив с соседними моделями (только с определенными полями), определенными по $field и $value, отсортированными по SQL-условиям, $conditions.

Это полезно в случае, когда вы хотите разместить ссылки «Предыдущее» и «Следующее», что позволят пользователям путешествовать по каким-то упорядоченным, последовательным данным в вашей модели. Это работает только с полями, имеющими нумерацию или датирование.

<?

class ImagesController extends AppController

{

    function view($id)

    {

        // Скажем мы хотим показать изображение...

        $this->set('image'$this->Image->find("id = $id");

        // Но также мы хотим чтобы у нас были предыдущие и следующие изображения...

        $this->set('neighbours'$this->Image->findNeighbours(null'id'$id);

    }

}

?>

Это дает полный массив $image['Image'], вместе с $neighbours['prev']['Image']['id'] и $neighbours['next']['Image']['id'] в нашем отображении.

field

Возвращает как строку одно поле из первой записи, соответствующей $conditions отсортированных по $order.

findCount

Возвращает количество записей, что соответствуют данному условию.

generateList

Это функция — ярлык к получению списка пар ключевых значений — в особенности для создания тега select в html из списка ваших моделей. Используйте параметры $conditions, $order и $limit также как в findAll(). $keyPath и $valuePath для указания модели, где именно искать ключи и значения для генерирования списка. Например, если вы хотите сгенерировать список ролей, базируемый на модели Role. Полный запрос может выглядеть похоже на это:

<?

$this->set(

    'Roles',

    $this->Role->generateList(null'role_name ASC'null'{n}.Role.id''{n}.Role.role_name')

);

?>

//Это может вернуть что-то похожее на это:

read

Используйте эту функцию, чтобы получить поля и их значения из ныне загруженной записи, или записи определенной по $id.

Пожалуйста отметьте что операция read() сделает выборку только с первого уровня ассоциативных моделей, не обращая внимания на значение $recursive в модели. Чтобы получить дополнительные уровни, используйте find() и findAll().

query

execute

Настраиваемые SQL-запросы можно сделать, используя query() и методы execute() модели. Разница между ними в том, что query() используется для настройки SQL-запросов (результаты которой возвращаются), а execute() используется для настройки SQL-команд (которые не требует возврата результатов).

Настройка запросов SQL с query()

<?php

class Post extends AppModel

{

  var $name 'Post';

  function posterFirstName() {

    $ret $this->query("SELECT first_name FROM posters_table WHERE poster_id = 1");

    $firstName $ret[0]['first_name'];

    return $firstName;

  }

}

?>

Сложные условия поиска (используя массивы)

Большинство модельных поисковиков подразумевают вовлечение пересылки параметров условий разными способами. Простейший способ приблизиться к этому – использовать фрагмент SQL-выражения WHERE, но если вам нужно больше контроля, вы можете использовать массивы. Массивы яснее и легче читать, а также легче строить запросы. Этот синтаксис также разбивает элементы запроса (поля, значения, операторы и т.д.) на более понятные, манипулируемые части. Это позволяет Cake генерировать более результативные запросы, гарантируя подходящий синтаксис SQL.

Самое простой массивный запрос выглядит так:

Структура даже не требует объяснений: это найдет любой пост, название которого будет соответствовать “This is a post”. Отметьте, что мы могли использовать только “title” как имя поля, но создавая запросы, полезно указывать и имя модели, так как это улучшает четкость кода и помогает предотвратить несостыковки в будущем. Что насчет других типов соответствий? Все так же просто. Скажем мы хотим найти все посты где в названии нет “This is a post”:

Все что мы добавили это '<>' перед выражением. Cake может проанализировать любой действительный оператор SQL, включая выражения как LIKE, BETWEEN или REGEX, так же как вы оставляете место между оператором и выражением или значением. Одно исключение в стиле (...) соответствия. Скажем вы хотите найти посты, названия которых соответствуют одному из списка значений:

Добавление дополнительных фильтров в условия это так же просто как добавления дополнительных пар ключ/значение в массив:

<?

array

(

  "Post.title"   => array("First post""Second post""Third post"),

  "Post.created" => "> " date('Y-m-d'strtotime("-2 weeks"))

)

?>

По умолчанию, Cake добавляет многократные условия с логическим AND; что значит, фрагмент ниже будет соответствовать только постам, что были созданы в прошедшие две недели, и название которых соответствуют одному из данных. Однако, мы можем также легко найти посты, соответствующие любому условию:

<?

array

("or" =>

  array

    (

      "Post.title" => array("First post""Second post""Third post"),

      "Post.created" => "> " date('Y-m-d'strtotime("-2 weeks"))

    )

)

?>

Cake принимает все действующие логические операторы SQL, включая AND, OR, NOT, XOR и т.д., и они могут быть в верхнем или нижнем регистре, как вам больше нравится. Скажем у вас есть hasMany/belongsTo отношения между Posts и Authors, результат поиска в Posts будет в LEFT JOIN. Скажем, вы хотите найти все посты, что содержат ключевое слово или которые были созданы в прошедшие две недели, но вы хотите ограничиться постами, написанными Бобом:

<?

array 

("Author.name" => "Bob""or" => array

    (

        "Post.title" => "LIKE %magic%",

        "Post.created" => "> " date('Y-m-d'strtotime("-2 weeks")

    )

)

?>

Сохранение данных

Чтобы сохранить что-либо, вам нужно снабдить сохранение данными, которые нужно записать. Данные передаваемые в метод save() должны быть размещены в следующей форме:

<?

Array

(

    [ModelName] => Array

        (

            [fieldname1] => 'value'

            [fieldname2] => 'value'

        )

)

?>

В случае, если вам нужно чтобы ваши данные были отправлены в контроллер таким способ, будет проще использовать HTML Хелпер, потому что он создает элементы формы, называя их именно так, как ожидает этого Cake. Вы не обязаны его использовать, однако: только убедитесь, что имена элементов формы выглядят как data[Modelname][fieldname]. Однако, использование $html->input('Model/fieldname') — проще.

Форма данных автоматически форматируется и помещается в $this->data в ваш контроллер, так что сохранение ваших данных из веб-формы – это дело одного клика. Функция редактирования для вашего собственного контроллера может выглядеть так:

<?

function edit($id) {

  //Заметьте: Нужная модель автоматически загружена для нас в $this->Property.

  // Проверяем есть ли у нас форма для данных...

  if (empty($this->data)) {

    $this->Property->id $id;

    $this->data $this->Property->read();//размножаем поля формы для данной строки

  } else {

    // Вот здесь мы пытаемся сохранить наши данные. Автоматическая верификация.

    if ($this->Property->save($this->data['Property'])) {

      //Выводить сообщение и перенаправление.

      $this->flash('Ваша информация сохранена.''/properties/view/'.$this->data['Property']['id'], 2);

    }

    //если в каких-то полях данные не правильные или сохранение провалилось, форма сообщит

  }

}

?>

Обратите внимания как операция сохранения помещена внутрь условия: когда вы пытаетесь сохранить данные в модель, Cake автоматически пытается проверить их, используя правила, которые ему предоставили. Узнать больше о проверке данных, вы можете в главе «Верификация данных». Если вы не хотите чтобы save() пытался проверить ваши данные, используйте save($data, false)

Другие полезные функции сохранения:

del

Удаляет модель, выбранную по $id.

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

Возвращает true при успешном выполнении.

saveField

Используется для сохранения значения отдельного поля.

getLastInsertId

Возвращает ID записи, которая создана последней.

Колбеки модели

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

beforeFind

beforeFind() колбек вызывается сразу перед началом операции поиска. Поместить любой передающий поиску код сюда. Когда вы перепишете это в свою модель, верните значение true, когда захотите чтобы начался поиск, или false, когда захотите отменить его.

afterFind

Используйте этот колбек, чтобы модифицировать результаты, которые вернулись после операции поиска, или впишите любой другой после-поисковый код. Параметры для этой функции – вернувшиеся результаты из операции поиска модели, а вернувшееся значение – это модифицированные результаты.

beforeValidate

Используйте этот колбек для модифицирования данных перед их проверкой. Это также используется для добавления дополнительных, более сложных правил проверки, используя Model::invalidate(). В этом контексте, модель данных доступна через $this->data. Эта функция также должна возвращать true, иначе выполнение save() будет отменено.

beforeSave

Поместите любой передающий сохранению код в эту функцию. Эта функция выполняется сразу после проверки данных (только в случае что данные были проверены, иначе save() отменяется, а этот колбек не выполняется), но перед тем как данные сохранены. Это функция также должна возвращать true если вы хотите, чтобы операция сохранения продолжалась, и false если хотите чтобы была отменена.

Одним из использований beforeSave может быть формирования времени и даты для сохранения в специфическом месте базы данных:

<?

// Дата/время поля созданы HTML-хелпером:

// Этот код будет виден в отображении

$html->dayOptionTag('Event/start');

$html->monthOptionTag('Event/start');

$html->yearOptionTag('Event/start');

$html->hourOptionTag('Event/start');

$html->minuteOptionTag('Event/start');

/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/

// Функция колбека модели, используемая для того, чтобы сшить

// данные о дате в месте для сохранения.

// Этот код будет виден в модели Event:

function beforeSave()

{

    $this->data['Event']['start'] = $this->_getDate('Event''start');

    return true;

}

function _getDate($model$field)

{

    return date('Y-m-d H:i:s'mktime(

        intval($this->data[$model][$field '_hour']),

        intval($this->data[$model][$field '_min']),

        null,

        intval($this->data[$model][$field '_month']),

        intval($this->data[$model][$field '_day']),

        intval($this->data[$model][$field '_year'])));

}

?>

afterSave

Поместите любой код, который будет выполнятся после каждого сохранения в колбеке.

beforeDelete

Поместите любой передающий удалению код в эту функцию. Это функция должна возвращать true если вы хотите чтобы удаление продолжилось или false, чтобы отменилось.

afterDelete

Поместите любой код, который будет выполнятся после каждого удаления в этот метод колбека.

Переменные модели

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

$primaryKey

Если эта модель относится к таблице базы данных, и главный ключ (primary key) не называется 'id', используйте эту переменную, чтобы сообщить Cake'у имя главного ключа.

$recursive

Это устанавливает число уровней, которые вы хотите чтобы Cake выбирал в операциях find() и findAll().

Представьте что у вас есть группы (Groups) в которых множество пользователей (Users), у которых будет много статей (Articles).

Model::recursive options

$recursive = 0 Cake делает выборку данных группы

$recursive = 1 Cake делает выборку группы и ассоциирует ее с пользователями

$recursive = 2 Cake делает выборку группы, ассоциирует ее с пользователями, а пользователи ассоциируются со статьями

$transactional

Говорит Cake'у делать ли доступным транзакции для этой модели (например начать/совершить/откатить). Значение логического типа. Доступно только для баз данных с поддержкой.

$useTable

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

$validate

Массив, используемый для проверки входящих в эту модель данных. См. главу «Верификация данных».

$useDbConfig

Помните что настройки базы данных можно устанавливать в /app/config/database.php? Используйте эту переменную, чтобы переключаться между ними – просто используйте имя переменной с настройками соединения с базой данных, которую вы создали в конфигурационном файле. По умолчанию, вы должны были догадаться, 'default'.

Ассоциации

Введение

Одна из самых сильных возможностей Cake PHP это построение отношений предоставленных моделью. В Cake PHP, связи между таблицами управляются через ассоциации. Ассоциации — это клей между логическими единицами, состоящими в отношениях.

Есть четыре типа ассоциаций в Cake PHP:

  1. hasOne
  2. hasMany
  3. belongsTo
  4. hasAndBelongsToMany

Когда ассоциации между моделями объявлены, Cake автоматически сделает выборку моделей пребывающих в отношении с моделью, с которой вы работаете. Например, если модель Post имеет отношения к модели Author используя тип ассоциации hasMany, делая запрос к $this->Post->findAll() в контроллере будет сделана как выборка записей Post, так и всех записей Author, к которым они относятся.

Чтобы корректно использовать ассоциации, лучше следовать схеме присвоения имен Cake PHP. Если вы следуете этой схеме, вы можете пользоваться скаффолдингом для визуализации данных вашего приложения, потому что скаффолдинг вылавливает и использует ассоциации между моделями. Конечно же вы всегда можете настроить ассоциации моделей для работы, если не используете схему присвоения имен Cake PHP, но мы прибережем эти советы на потом. А пока, давайте просто следовать схеме. Схема присвоения имен, которая нас волнует – это сторонние ключи, имена моделей и таблиц.

Вот обзор того, что Cake ожидает от имен этих разных элементов: (взгляните на «Соглашение Cake» для подробной информации о присвоении имен)

  1. Сторонние ключи(Foreign Keys): [имя модели в единичном числе]_id. Например, сторонний ключ в таблицы “authors”, возвращаясь к теме о постах принадлежащих авторам, будет именоваться “post_id”.
  2. Имена таблиц(Table Names): [имя объекта во множественном числе]. С тех пор как нам понадобилось хранить информацию о постах блога и их авторах, имена таблиц “posts” и “authors” соответсвенно.
  3. Имена моделей(Model Names): [Camel Cased, имя таблицы в единичном числе]. Имя модели для таблицы “posts” – “Post”, а имя модели для таблицы “authors” – “Author”.

Скаффолдинг Cake PHP ожидает, что ваши ассоциации построены в том же порядке, что и колонки. То есть, если у меня есть Articles с типом ассоциаций belongsTo, то есть принадлежит трем другим моделям (Author, Editor и Publisher), мне понадобятся три ключа: author_id, editor_id и publisher_id. Скаффолдинг ожидает, что ваши ассоциации происходят в том же порядке, что и ключи в таблице (наприер, сначала Author, второй Editor и последний Publisher).

Чтобы показать как некоторые из этих ассоциаций работают, давайте продолжим использовать приложение блога как пример. Представьте, что мы собираемся создать простую систему управления пользователями блога. Я предполагаю это будет не для того, чтобы следить за пользователями, а для того чтобы у каждого пользователя был ассоциированный профиль (Profile, пользователь hasOne профиль). Пользователи также смогут создавать комментарии оставляя ассоциацию с ними (пользователь hasMany комментарии). Сделав пользовательскую систему, мы переходим к тому, чтобы позволить постам относится к тег-объектам, используя тип ассоциаций hasAndBelongsToMany (имеетИПринадлежитМногим, то есть пост имеет и принадлежит многим тегам).

Объявления и запросы с hasOne

Чтобы установить эту ассоциацию, мы предполагаем что вы уже создали модели User и Profile. Чтобы объявить hasOne-ассоциацию между ними, нам понадобится добавить массив к модели, чтобы сообщить Cake'у как они будут относится. Вот как это выглядит:

/app/models/user.php hasOne

<?php

class User extends AppModel

{

    var $name 'User';

    var $hasOne = array('Profile' =>

                        array('className'    => 'Profile',

                              'conditions'   => '',

                              'order'        => '',

                              'dependent'    =>  true,

                              'foreignKey'   => 'user_id'

                        )

                  );

}

?>

Массив $hasOne это то, что Cake использует для создания ассоциации между User и Profile моделями. Каждый ключ в массиве позволяет настроить ассоциацию:

  1. className (обязателен): имя класса модели, которую вы хотите ассоциировать. Для нашего примера имя класса модели 'Profile'.
  2. conditions: фрагменты условий SQL, которые объявляют отношения. Мы можем использовать это, чтобы сообщить Cake'у что ассоциировать нужно только Profile с зеленым заголовком, если захотим. Чтобы объявить условие подобное этому, нужно присвоить фрагменту SQL-условия значение для этого ключа: "Profile.header_color = 'green'".
  3. order: упорядочивание ассоциируемых моделей. Если мы хотим присвоить специфический порядок ассоциируемым моделям, устанавливает значение этого ключа, используя выражение упорядочивания SQL: "Profile.name ASC", например.
  4. dependent: если установлено true, то ассоциируемая модель уничтожается, когда уничтожается первая. Например, если профиль “Cool Blue” ассоциирован с “Bob”, и я удаляю пользователя “Bob”, профиль “Cool Blue” тоже удаляется.
  5. foreignKey: имя стороннего ключа, что указывает на ассоциируемую модель. Вот оно, если вы используете базу данных, не следуя схеме присвоения имен Cake PHP.

Теперь когда мы выполняем запросы find() или findAll(), используя Profile модель, мы также увидим здесь и ассоциативную модель User:

<?

$user $this->User->read(null'25');

print_r($user);

?>

Объявления и запросы с belongsTo

Теперь пользователь может увидеть свой профиль, нам нужно объявить ассоциацию так, чтобы пользователь мог видеть только свой профиль. Это делается использованием типа ассоциаций belongsTo. В модели Profile, мы делаем следующее:

<?

/app/models/profile.php belongsTo

<?php

class Profile extends AppModel

{

    var $name 'Profile';

    var $belongsTo = array('User' =>

                           array('className'  => 'User',

                                 'conditions' => '',

                                 'order'      => '',

                                 'foreignKey' => 'user_id'

                           )

                     );

}

?>

Массив $belongsTo это то, что Cake использует для создания ассоциаций между User и Profile моделями. Каждый ключ в массиве позволяет вам настраивать ассоциацию:

  1. className (обязателен): имя класса модели, которую вы хотите ассоциировать. В нашем примере это 'User'.
  2. conditions: фрагменты условий SQL, которые объявляют отношения. Мы можем использовать это чтобы сообщить Cake'у что нужно ассоциировать только с активным пользователем. Вы можете сделать это, присвоением ключу значения: "User.active = '1'", или чем-то подобным.
  3. order: упорядочивание ассоциируемых моделей. Если вы хотите придать специфическое упорядочивание ассоциируемым моделям, установите значение для этого ключа, используя выражения SQL для упорядочивания: "User.last_name ASC", например.
  4. foreignKey: имя стороннего ключа, которое указывает на ассоциируемую модель. Вот оно, если вы не следуете схеме присвоения имен.

Теперь, когда мы выполняем запросы find() или findAll(), используя Profile модель, мы должны увидеть нашу ассоциированную модель User:

<?

$profile $this->Profile->read(null'4');

print_r($profile);

?>

Объявления и запросы с hasMany

Теперь User и Profile модели ассоциированы и работают как надо, давайте создадим систему, в которой записи пользователей будут ассоциированы с записями комментариев. Это делается в модели User так:

/app/models/user.php hasMany

<?php

class User extends AppModel

{

    var $name 'User';

    var $hasMany = array('Comment' =>

                         array('className'     => 'Comment',

                               'conditions'    => 'Comment.moderated = 1',

                               'order'         => 'Comment.created DESC',

                               'limit'         => '5',

                               'foreignKey'    => 'user_id',

                               'dependent'     => true,

                               'exclusive'     => false,

                               'finderQuery'   => ''

                         )

                  );

    // Вот отношение hasOne, которое мы объявили раньше...

    var $hasOne = array('Profile' =>

                        array('className'    => 'Profile',

                              'conditions'   => '',

                              'order'        => '',

                              'dependent'    =>  true,

                              'foreignKey'   => 'user_id'

                        )

                  );

}

?>

Массив $hasMany это то, что использует Cake для создания ассоциаций между User и Comment моделями. Каждый ключ массива позволяет вам настроить ассоциацию:

  1. className (Обязателен): имя класса модели, которую вы хотите ассоциировать. В нашем примере это 'Comment'.
  2. conditions: фрагменты условий SQL, которые объявляют отношения. Мы можем использовать это для того, чтобы сообщить Cake'у, что нужно ассоциировать только комментарии, которые могут быть модерированы. Это можно сделать присвоением ключу значения "Comment.moderated = 1", или что-то схожее.
  3. order: упорядочивание ассоциируемых моделей. Если вы хотите придать специфическое упорядочивание ассоциируемым моделям, установите значение ключа, используя SQL-выражения для упорядочивания: "Comment.created DESC", например.
  4. limit: максимальное количество ассоциируемых моделей, которые вы хотите чтобы Cake добавил в выборку. Для этого примеры, мы не хотим отбирать все комментарии пользователя, а всего пять.
  5. foreignKey: имя стороннего ключа, которое указывает на ассоциируемую модель. Вот оно, если вы не следуете схеме присвоения имен.
  6. dependent: если установлено значение true, то ассоциируемая модель будет удалена в случае, если будет удалена первая. К примеру есть профиль “Cool Blue” ассоциированный с “Bob”, и я удаляю пользователя “Bob”, профиль “Cool Blue” тоже будет удален.
  7. exclusive: Если установлено значение true, все ассоциированные объекты будут удалены во время одного запроса SQL минуя их beforeDelete колбек. Полезно для более простых ассоциаций, потому что это может быть намного быстрее.
  8. finderQuery: установите полное SQL-выражение для добавления в выборку ассоциации. Это отличный способ для сложных ассоциаций, которые зависят от нескольких таблиц. Если автоматические ассоциации Cake не работают, вот где вы можете их настроить.

Теперь когда мы выполняем запросы find() и findAll(), используя модель User, мы должны увидеть все ассоциированные комментарии:

<?

$user $this->User->read(null'25');

print_r($user);

?>

Хоть мы и не документируем процесс, но было бы очень полезно объявить ассоциацию Comment belongsTo User, чтобы каждая из моделей видела друг друга.

Объявления и запросы с hasAndBelongsToMany

Теперь, когда вы освоили простейшие ассоциации, давайте перейдем к последнему типу ассоциаций hasAndBelongsToMany (или HABTM). Эта последняя ассоциация – самая сложная, но также и одна из самых полезных. Ассоциация НАВТМ полезна, когда у вас есть две модели, которые связаны между собой связной таблицей. Связная таблица содержит индивидуальные строки, которые относятся друг к другу.

Разница между hasMany и hasAndBelongsToMany в том, что с hasMany ассоциированная модель не общая. Если User hasMany Comments, это *только* пользователь ассоциирован с теми комментариями. С НАВТМ, ассоциированная модель – общая. Это хорошо для того, что мы будем делать дальше: ассоциировать модель Post с моделью Tag. Пока модель Tag принадлежит модели Post, мы не хотим чтобы она была 'истощена', мы хотим продолжить, ассоциировать ее с другими постами.

Для этого там нужно установить корректные таблицы для этой ассоциации. Конечно, нам понадобится таблица “tags” для модели Tag, и таблица “posts” для постов, но также нам будет нужна связная таблица для этой ассоциации. Схема присвоения имен для связных таблиц НАВТМ это [имя первой модели во множественном числе]_[имя второй модели во множественном числе], где имена моделей должны идти в алфавитном порядке:

Связные таблицы HABTM: Простые модели и имена их связных таблиц

  1. Posts и Tags: posts_tags
  2. Monkeys и Ice Cubes: ice_cubes_monkeys
  3. Categories и Articles: articles_categories

Связные таблицы HABTM должны состоять минимум из двух сторонних ключей моделей, которые они связывают. Для нашего примера, “post_id” и “tag_id” это все что нам нужно.

Вот как будут выглядеть дампы SQL для нашего Posts HABTM Tags примера:

--

-- Table structure for table `posts`

--

CREATE TABLE `posts` (

  `id` int(10) unsigned NOT NULL auto_increment,

  `user_id` int(10default NULL,

  `title` varchar(50default NULL,

  `body` text,

  `created` datetime default NULL,

  `modified` datetime default NULL,

  `status` tinyint(1NOT NULL default '0',

  PRIMARY KEY  (`id`)

) TYPE=MyISAM;

-- --------------------------------------------------------

--

-- Table structure for table `posts_tags`

--

CREATE TABLE `posts_tags` (

  `post_id` int(10) unsigned NOT NULL default '0',

  `tag_id` int(10) unsigned NOT NULL default '0',

  PRIMARY KEY  (`post_id`,`tag_id`)

) TYPE=MyISAM;

-- --------------------------------------------------------

--

-- Table structure for table `tags`

--

CREATE TABLE `tags` (

  `id` int(10) unsigned NOT NULL auto_increment,

  `tag` varchar(100default NULL,

  PRIMARY KEY  (`id`)

) TYPE=MyISAM;

Давайте объявим ассоциацию в модели Post с нашими настройками таблиц:

/app/models/post.php hasAndBelongsToMany

<?php

class Post extends AppModel

{

    var $name 'Post';

    var $hasAndBelongsToMany = array('Tag' =>

                               array('className'    => 'Tag',

                                     'joinTable'    => 'posts_tags',

                                     'foreignKey'   => 'post_id',

                                     'associationForeignKey'=> 'tag_id',

                                     'conditions'   => '',

                                     'order'        => '',

                                     'limit'        => '',

                                     'unique'       => true,

                                     'finderQuery'  => '',

                                     'deleteQuery'  => '',

                               )

                               );

}

?>

Массив $hasAndBelongsToMany это что Cake использует для создания ассоциации между моделями Post и Tag. Каждый ключ таблицы позволяет вам настроить ассоциацию:

  1. className (обязателен): имя класса модели, которую вы хотите ассоциировать. Для нашего примера это будет 'Tag'.
  2. joinTable: это для случая если база данных не использует схему присвоения имен Cake. Если имя вашей таблицы не похоже на [имя первой модели во множественном числе]_[имя второй модели во множественном числе], вы можете вписать сюда имя вашей связной таблицы.
  3. foreignKey: имя стороннего ключа в связной таблице которое направляет на данную модель. Вот оно, если вы не следуете схеме присвоения имен.
  4. associationForeignKey: имя стороннего ключа, который направляет на ассоциируемую модель.
  5. conditions: фрагменты условий SQL, которые объявляют отношения. Мы можем использовать это, чтобы сообщить Cake'у что нужно ассоциировать с тегами, которые были одобрены. Вы можете это сделать присваивая значение ключу: "Tag.approved = 1", или что-то схожее.
  6. order: упорядочивание ассоциируемых моделей. Если вы хотите придать определенное упорядочивание ассоциируемым моделям, установите значение ключа, используя выражения SQL для упорядочивания: "Tag.tag DESC", например.
  7. limit: максимальное количество ассоциируемых моделей, которое вы хотите чтобы Cake добавил в выборку. Используется, чтобы ограничить число ассоциируемых тегов, которые будут в выборке.
  8. unique: если установлено значение true, дублирование ассоциируемых объектов будет игнорироваться средствами доступа и запросами. В основном если ассоциации отдельные, устанавливайте true. В этом случае тег “Awesomeness” может быть приписать только посту “Cake Model Assosiations” и только раз, и будет виден только единожды в масиве результатов.
  9. finderQuery: установите полное выражение SQL для добавления в выборку ассоциации. Это отличный способ для сложных ассоциаций, которые зависят от нескольких таблиц. Если автоматические ассоциации Cake не работают, вот где вы можете их настроить.
  10. deleteQuery: полный запрос SQL, который будет использован для удаления ассоциаций между НАВТМ моделями. Если вам не нравится как Cake представляет удаление, или вы хотите настроить это по-другому, вы можете изменить ход работы удаления, введя свой запрос здесь.

Теперь выполняя find() или findAll() запросы, используя модель Post, мы должны увидеть также ассоциированную модель Tag:

<?

$post $this->Post->read(null'2');

print_r($post);

?>

Сохранение данных ассоциируемых моделей

Важно запомнить одну вещь при работе с ассоциативными моделями. Это то, что сохранение данных должно происходить через соответствующую модель Cake. Если сохраняете новый пост и ассоциированные с ним комментарии, тогда вам нужно использовать обе модели Post и Comment во время операции сохранения.

Если ни одна из ассоциированных моделей в системе еще не существует (например, вы хотите сохранить новый пост и соответствующий комментарий одновременно), вам нужно сначала сохранить главную, родительскую модель. Чтобы понять как это работает, давайте представим, что у нас есть действие в нашем Post Controller которое управляет сохранением новых постов и соответствующих комментариев. Пример ниже подразумевает, что вы воздали один пост и один комментарий.

/app/controllers/posts_controller.php (частично)

function add()

{

    if (!empty($this->data))

    {

        //Мы можем сохранить данные поста:

        //они должны быть в $this->data['Post']

       

        $this->Post->save($this->data);

        //Теперь нам нужно сохранить данные комментария

        //Но сначала нам нужно узнать ID

        //поста, который мы только что сохранили...

        $post_id = $this->Post->getLastInsertId();

        //Теперь мы добавляем эту информацию в сохраняемые данные

        //и сохраняем комментарий.

        $this->data['Comment']['post_id'] = $post_id;

        //Поскольку у нас Post hasMany Comments, мы можем получить доступ

        //к модели комментария через модель Post:

        $this->Post->Comment->save($this->data);

    }

}

Если, все же, родительская модель уже существует в системе (например, добавление комментария к существующему посту), нам нужно знать ID родительской модели перед сохранением. Мы можем поместить этот ID в ссылку или как скрытый элемент в форме...

/app/controllers/posts_controller.php (partial)

//Вот как будет выглядеть ссылка, если мы используем ее для передачи параметра...

function addComment($post_id)

{

    if (!empty($this->data))

    {

        //Вы можете захотеть сделать данные $post_id более защищенными,

        //но этого будет достаточно для работающего примера..

        $this->data['Comment']['post_id'] = $post_id;

        //Поскольку у нашего Post hasMany Comments, мы можем получить доступ

        //доступ к модели Comment через модель Post:

        $this->Post->Comment->save($this->data);

    }

}

Если ID был пропущен как скрытый элемент формы, вы можете захотеть назвать поле (если используете HTML хелпер) это находится в конце данных, где и должно быть:

If the ID for the post is at $post['Post']['id']...

<?php echo $html->hidden('Comment/post_id', array('value' => $post['Post']['id'])); ?>

Таким образом, доступ к ID родительской модели Post можно получить от $this->data['Comment']['post_id'] и все готово к простому запросу $this->Post->Comment->save($this->data).

Этот самый способ будет работать если вы сохраняете несколько дочерних моделей, просто поместите их запрос save() в цикл (и не забудьте очистить информацию модели, используя Model::create()).

Подбивая итоги, если вы сохраняете ассоциированные данные (для belongTo, hasOne и hasMany типов ассоциаций), главное получение ID родительской модели и сохранение его в дочерней модели.

Сохранение hasAndBelongsToMany типа ассоциаций

Сохранение моделей, которые ассоциированы типами hasOne, belongTo и hasMany довольно просто: вы просто заполняете поле стороннего ключа ассоциированной модели. Когда это сделано вы просто делаете запрос save() на модели, и все связано корректно.

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

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

Когда вы сохраняете модель саму по себе в Cake, имя тега (если вы используете HTML Хелпер) выглядит как 'Модель/имя_поля'. Давайте просто начнем с создания формы, которая создаст наш пост:

/app/views/posts/add.thtml

Форма для создания постов

<h1>Write a New Post</h1>

<table>   

    <tr>   

        <td>Title:</td> 

        <td><?php echo $html->input('Post/title')?></td>

    </tr>

    <tr>       

        <td>Body:<td>

        <td><?php echo $html->textarea('Post/body')?></td>

    </tr>

    <tr>

        <td colspan="2">

            <?php echo $html->hidden('Post/user_id', array('value'=>$this->controller->Session->read('User.id')))?>

            <?php echo $html->hidden('Post/status' , array('value'=>'0'))?>

            <?php echo $html->submit('Save Post')?>

        </td>

    </tr>

</table>

Форма как она есть сейчас будет просто создавать записи в Post. Давайте добавим кое-какой код, который позволит нам привязывать данный пост к одному или нескольким тегам:

/app/views/posts/add.thtml (Дополнительный код для ассоциирования с тегами)

<h1>Write a New Post</h1>

<table>

    <tr>

        <td>Title:</td>

        <td><?php echo $html->input('Post/title')?></td>

    </tr>

    <tr>

        <td>Body:</td>

        <td><?php echo $html->textarea('Post/body')?></td>

    </tr>

    <tr>

        <td>Related Tags:</td>

        <td><?php echo $html->selectTag('Tag/Tag'$tagsnull, array('multiple' => 'multiple')) ?>

        </td>

    </tr>

    <tr>

        <td colspan="2">

            <?php echo $html->hidden('Post/user_id', array('value'=>$this->controller->Session->read('User.id')))?>

            <?php echo $html->hidden('Post/status' , array('value'=>'0'))?>

            <?php echo $html->submit('Save Post')?>

        </td>

    </tr>

</table>

Для запроса $this->Post->save() в контроллере, чтобы сохранить связи между этим новым постом и ассоциацией с тегами, имя поля должно быть в форме "Tag / Tag" (отображаемое имя атрибута будет выглядеть как 'data[ИмяМодели][ИмяМодели][]'). Подтвержденные данные должны быть одним ID или массивом ID-номеров связанных записей. Поскольку мы используем возможность выбора не одного тега, то подтвержденные данные для Tag / Tag будут массивом ID-номеров.

Переменная $tags здесь – просто массив где ключи это ID-номера возможных тегов, а значения это отображаемые имена тегов в многоэлементном выборе.

Смена ассоциаций на лету, используя bindModel() и unbindModel()

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

Давайте сделаем несколько моделей так что мы сможем посмотреть как bindModel() и unbindModel() работают. Мы начнем с двух моделей:

leader.php и follower.php

<?php

class Leader extends AppModel

{

    var $name 'Leader';

    var $hasMany = array(

        'Follower' => array(

            'className' => 'Follower',

            'order'     => 'Follower.rank'

        )

    );

}

?>

<?php

class Follower extends AppModel

{

    var $name 'Follower';

}

?>

Теперь, в Leader Controller, мы можем использовать find() в модели Leader. Как вы можете увидеть выше, ассоциативный массив в модели Leader объявляет ассоциацию “Leader hasMany Followers”. Для демонстрации замысла давайте используем unbindModel() чтобы убрать эту ассоциацию.

leaders_controller.php (частично)

function someAction()

{

    //Это делает выборку Leaders и ассоциированные с ними Followers

    $this->Leader->findAll();

    //Давайте уберем hasMany...

    $this->Leader->unbindModel(array('hasMany' => array('Follower')));

   

    //Теперь используя функцию поиска вернем Leaders, но уже без Followers

    $this->Leader->findAll();

    //ЗАМЕЧАНИЕ: unbindModel распространится только на следующую функцию поиска..

    //Дополнительный запрос поиска будет использовать настроенную ассоциированную информацию.

    //Мы уже использовали функцию findAll() после unbindModel(), так что это сделает выборку

    //Leaders с ассоциированными Followers еще раз...

    $this->Leader->findAll();

}

Функция unbindModel() работает также с остальными ассоциациями: просто измените названия типа ассоциаций и имя класса модели. Основное применение unbindModel():

Общий unbindModel() пример

$this->Model->unbindModel(array('associationType' => array('associatedModelClassName')));

Теперь мы успешно убрали ассоциацию на лету, давайте добавим. Наша модель Leader нуждается в какой-то ассоциации с Principles. Файл модели для модели Principles голый, за исключением одной переменной $name выражения. Давайте ассоциируем Principles с нашей Leader на лету (но только для следующего запроса функции поиска):

leaders_controller.php (частично)

funciton anotherAction()

{

    //Нету никакой Leader hasMany Principles в leader.php-файле модели, так что

    //поиск здесь просто сделает выборку Leaders.

    $this->Leader->findAll();

    //Давайте используем bindModel(), чтобы добавить ассоциацию к Principles модели:

    $this->Leader->bindModel(

        array('hasMany' => array(

                'Principle' => array(

                    'className' => 'Principle'

                )

            )

        )

    );

    //Теперь когда мы сделали корректную ассоциацию, мы можем использовать простую функцию поиска

    //чтобы сделать выборку Leaders с ассоциированными Principles:

    $this->Leader->findAll();

}

Функция bindModel() может быть удобной при создании новых ассоциаций, но она также может быть полезна если вы хотите изменить сортировку или другие параметры в данной ассоциации на лету.

Вот теперь это есть у вас. Основное использование bindModel это герметизация обычного ассоциативного массива внутрь массива, чьи ключи названы после типа ассоциации, которую вы пытаетесь создать:

//Общий bindModel() пример

<?

$this->Model->bindModel(

        array('associationName' => array(

                'associatedModelClassName' => array(

                    // обычные ассоциативные ключи здесь...

                )

            )

        )

    );

?>

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