Возможно, всё что я напишу ниже – очевидно, и все этим пользуются давно, но я вот недавно только это понял и придумал, так что, может, кому и пригодится.
Yii2 и расширение yii2-mongodb к сожалению, не работает с вложенными документами, тем самым оставляя за бортом существенное преимущество документоориентированной БД.
В документации предлагают использовать расширение для вложенных документов, но можно обойтись и без него.
Предположим, у нас есть модель, формирующая PDF-файл для загрузки, и мы хотим следить за количеством его скачиваний, IP-адресами скачавших и, например, временем, когда файл был загружен.
Для простоты я предполагаю, что сам файл хранится в строке, но это, конечно, может быть совсем не так – он может лежать где-то в хранилище или формироваться функцией.
Основная модель (часть)
/** * @property string $pdf_data стока с данным, которая потом преобразуется в файл * @property array $downloads_data здесь хранятся сведения о загрузках * */ Class PdfData extends \yii\mongodb\ActiveRecord /** @inheritdoc */ public static function collectionName() { return [‘database’, ‘pdf’] } /** @inheritdoc */ public function attributes() { return [ ‘pdf_data’, ‘downloads_data’ ]; }
Дополнительная модель – для проверки и присвоения значений элементам массива
use \MongoDB\BSON\UTCDateTime /** * Класс для формирования сведений о факте загрузки файла */ class DowmnloadData extends \yii\base\Model { /** @var \MongoDB\BSON\UTCDateTime $datetime */ public $datetime; /** @var string $clientIp */ public $clientIp; /** @var string $clientHost */ public $clientHost; /** @var string $clientUserAgent */ public $clientUserAgent; /** @var string $referer */ public $referer; /** @var bool $result */ public $result = false; /** @inheritdoc */ public function rules() { return [ ['datetime', 'default', 'value' => function() { return new UTCDateTime(strtotime("now") * 1000); }], ['clientIp', 'default', 'value' => function() { return Yii::$app->request->getUserIP(); }], ['clientHost', 'default', 'value' => function() { return Yii::$app->request->getUserHost(); }], ['clientUserAgent', 'default', 'value' => function() { return Yii::$app->request->getUserAgent(); }], ['referer', 'default', 'value' => function() { return Yii::$app->request->getReferrer(); }], ['result', 'boolean'], ]; }
Далее, в действии контроллера, которое отдает файл наружу, примерно следующее:
// -- skip -- /** * @param string $id идентификатор основной модели * @return null * @throws \yii\web\NotFoundHttpException */ public function actionDownload($id) { if(($model = PdfData::findOne($id)) === null) throw new \yii\web\NotFoundHttpException(Yii::t('app', 'File not found')); $downloadData = new DowmnloadData(); if(!empty($model->pdf_data)) { $downloadData->result = true; $downloadData->validate(); // Так мы добиваемся присвоения значений по-умолчанию $data = $model->downloads_data; // Забрали существующие сведения о загрузках $data[] = $downloadData->attributes; // Добавили к ним новые // array_values для гарантироанного сохранения массива (а не объекта) в mongodb $model->updateAttributes(['downloads_data' => array_values($data)]); // Отправляем файл Yii::$app->response->sendContentAsFile($model->pdf_data, 'we are the champions.pdf', [ 'mimeType' => 'text/xml', 'inline' => true ]); } return null; }
Таким образом внутри массива downloads_data основной модели мы имеем все атрибуты, которые придумали в DowmnloadData, и можем их потом как угодно показывать и анализировать, не умножая сверх необходимого при этом ни атрибуты основной модели, ни число коллекций в БД.