<?xml version='1.0' encoding='UTF-8'?>
<rss version='2.0'>
	<channel>
		<title>Lily Software</title>
		<link>https://lily-software.com</link>
		<description>Создание интернет сайтов в Краснодаре, web дизайн, web консалтинг</description>
		<generator>Cotonti</generator>
		<language>ru</language>
		<pubDate>Sat, 11 Apr 2026 21:45:31 +0300</pubDate>

		<item>
			<title>File System</title>
			<description><![CDATA[<p style="text-align:justify;">Компонент для работы с файловыми хранилищами на основе <a href="https://flysystem.thephpleague.com" target="_blank" rel="nofollow noreferrer noopener">Flysystem</a>.<br />
Он позволяет работать как с локальной файловой системой, так и с удаленными хранилищами  используя единый интерфейс. Например с Amazon AWS S3, Azure Blob Storage, Yandex Objet Storage, BunnyCdn, Google Cloud Storage, WebDav, FTP, SFTP и др.</p>

<p>Класс для работы с локальной файловой системой входит в состав <strong>Cotonti Lib</strong> и не требует доплнительной установки других библиотек. Его интерфейс совместим с <code>\League\Flysystem\FilesystemOperator</code> и они взаимозаменяемы.</p>

<p>Для подключения другой (удаленной) файловой системы нужно установить <a href="https://flysystem.thephpleague.com/docs/" target="_blank" rel="nofollow noreferrer noopener">Flysystem и нужный адаптер</a>.</p>

<p>Для этого в <code>composer.json</code> нужно добавить строки:</p>

<pre class="brush:plain;">
"league/flysystem": "^3.0",
"league/flysystem-aws-s3-v3": "^3.0"</pre>

<p>В этом примере мы добавили адаптер для файловых хранилищ, поддерживающий AWS S3.</p>

<p>И выполнить в командной строке:</p>

<pre class="brush:bash;">
composer update</pre>

<p>Composer для Windows имеет баг который может в файле <code>lib/composer/autoload_static.php</code> сделать что то такое:</p>

<pre class="brush:php;">
public static $fallbackDirsPsr4 = array (
  0 =&gt; 'C:\\OpenServerData\\domains\\my-site.loc\\public_html\\lib',
);
</pre>

<p>Надо вернуть как было:</p>

<pre class="brush:php;">
public static $fallbackDirsPsr4 = array (
  0 =&gt; DIR . '/../..' . '/lib',
);</pre>

<p>Теперь адатер файловой системы готов к использованию в коде Ваших расширений.</p>

<p> </p>

<p style="text-align:justify;">Для создания экземпляра можно воспользоваться инструкцией к соотвествующему <a href="https://flysystem.thephpleague.com/docs/" target="_blank" rel="nofollow noreferrer noopener">адаптеру</a> или воспользоваться <strong>"фабрикой"</strong> из <strong>Cotonti Lib</strong>.<br />
Использование фабрики является предпочтительным при создании расширений т.к. позволяет конфигурировать подключения к файловым системам и использовать нужные в зависимости от конфигурации.</p>

<p> </p>

<h3>Конфигурирование файловых систем.</h3>

<p> </p>

<p>В файл <code>datas/config.php</code> нужно добавить элемент такого вида:</p>

<pre class="brush:php;">
$cfg['filesystem'] = [
	
    // Подключение к Yandex Object Storage
    'Yandex.Cloud' =&gt; [
        'adapter' =&gt; '\League\Flysystem\AwsS3V3\AwsS3V3Adapter',
        'config' =&gt; [
            //'version' =&gt; 'latest', // Optional
            'endpoint' =&gt; 'https://storage.yandexcloud.net',
            'region' =&gt; 'ru-central1',
            'bucket' =&gt; 'my-bucket-name',
            'accessKey' =&gt; 'MyAccessKey',
            'secretKey' =&gt; 'MySecretKey',

            // install: composer require league/flysystem-path-prefixing:^3.3 to use prefix
            //'pathPrefix' =&gt; 'a/path/prefix',
        ],
    ],
	
    // Подключение к другому серверу по FTP
    'FTP' =&gt; [
        'adapter' =&gt; '\League\Flysystem\Ftp\FtpAdapter',
        'config' =&gt; [
            'client' =&gt; [
                'host' =&gt; 'my-pretty-host.tld',
                'root' =&gt; '/var/www/files/storage/here',
                'username' =&gt; 'server-user',
                'password' =&gt; 'someThingReallySecret',
            ],

            // install: composer require league/flysystem-path-prefixing:^3.3 to use prefix
            //'pathPrefix' =&gt; 'a/path/prefix',
        ],
    ],
	
    // Подключение к BunnyCDN
    'BunnyCDN' =&gt; [
        'adapter' =&gt; '\PlatformCommunity\Flysystem\BunnyCDN\BunnyCDNAdapter',
        'config' =&gt; [
            'storageZoneName' =&gt; 'test-files',
            'apiKey' =&gt; 'my-api-key',
            'pullZoneUrl' =&gt; 'https://test-files.b-cdn.net',
            //'region' =&gt; 'de', // optional

            // install composer require league/flysystem-path-prefixing:^3.3 to use prefix
            //'pathPrefix' =&gt; 'a/path/prefix',
        ],
    ],
];	
</pre>

<p>В этом примере мы сконфигурировали подключение к <strong>Yandex Object Storage</strong> и к удаленному серверу по <strong>FTP</strong>.</p>

<p><strong>Ключ массива</strong> - это название подключения, может быть любым.</p>

<p style="text-align:justify;"><strong>'adapter'</strong> - имя класса адаптера. Поддерживаются и псевдонимы. Например вместо <code>'\League\Flysystem\AwsS3V3\AwsS3V3Adapter'</code> можно использовать <code>'Aws'</code> или <code>'AwsS3'</code>. Фабрика их поймет.</p>

<p><strong>'config'</strong> - конфигурация подключения. Т.к. файловые системы имют очень разные API и способы подключения, то для каждого адаптера 'config' свой.<br />
За подробностями можно обратиться к описанию самого <a href="https://flysystem.thephpleague.com/docs/" target="_blank" rel="nofollow noreferrer noopener">адаптера</a> и в код <a href="https://github.com/Alex300/cotonti-lib/blob/master/lib/filesystem/FilesystemFactory.php" target="_blank" rel="nofollow noreferrer noopener">файла фабрики</a>.</p>

<p style="text-align:justify;">Если вы хотите добавить в фабрику подключение адаптера, которого в ней нет или создать подключение с более специфичной конфигурацией, то это можно сделать либо через хук <code>filesystem.getFilesystem</code> либо определив функцию <code>cot_getFilesystem($fileSystemName, $pathPrefix)</code>.</p>

<p> </p>

<p>Пример использования:</p>

<pre class="brush:php;">
use \filesystem\FilesystemFactory;

// Получить экземпляр класса файловой системы по имени подлючения из конфига выше
$filesystem = FilesystemFactory::getFilesystem('Yandex.Cloud');

// Запись в файл
$filesystem-&gt;write($path, $contents);</pre>

<p> </p>

<p>Методы класса <code>\League\Flysystem\Filesystem</code> описаны в <a href="https://flysystem.thephpleague.com/docs/usage/filesystem-api/" target="_blank" rel="nofollow noreferrer noopener">документации</a>.</p>
]]></description>
			<pubDate>Tue, 24 Oct 2023 18:38:00 +0300</pubDate>
			<link><![CDATA[https://lily-software.com/free-scripts/cotonti-lib/file-system]]></link>
		</item>
		<item>
			<title>Image bundle</title>
			<description><![CDATA[<p>Компонент для работы с изображениями. Работает как с библиотекой <a href="https://www.php.net/manual/ru/book.image.php" target="_blank" rel="nofollow noreferrer noopener">GD</a>, так и с <a href="https://www.php.net/manual/ru/book.imagick.php" target="_blank" rel="nofollow noreferrer noopener">ImageMagick</a>. Последняя поддерживает большее количество форматов изображений, в т.ч. HEIC/HEIF, которые используются в смартфонах iPhone от Apple.</p>

<p>По умолчанию, если устанвлена ImageMagick, то используется она.</p>

<p>Чтобы использовать этот компонент в Ваших расширениях, добавте в начало php-файла</p>

<pre class="brush:php;">
&lt;?php

use image\Image;</pre>

<p> </p>

<p>Получить используемый драйвер:</p>

<pre class="brush:php;">
$driver = Image::currentDriver();</pre>

<p>Метод возвращает одну из констант: <code>Image::DRIVER_IMAGICK</code> или <code>Image::DRIVER_GD</code> или <code>null</code>, если ни одна из библиотек недоступна и обработка изображений невозможна.</p>

<p> </p>

<p>Получить список поддерживаемых форматов в виде массива:</p>

<pre class="brush:php;">
$supportedFormats = Image::supportedFormats();</pre>

<p> </p>

<p>Загрузка изображения для дальнейшей обработки:</p>

<pre class="brush:php;">
$image = Image::load($path, ?array $options = null);</pre>

<p>в <code>$path</code> можно передать<br />
- строку с путем к файлу или URL удаленного файла<br />
- ресурс, полученный от <a href="https://www.php.net/manual/ru/function.fopen.php" target="_blank" rel="nofollow noreferrer noopener"><code>fopen()</code></a><br />
- строку с бинарными данными, содержащими избражение или строку закодированную в <strong>base64</strong> c бинарными данными изображения<br />
- <a href="https://ru.wikipedia.org/wiki/Data:_URL" target="_blank" rel="nofollow noreferrer noopener">data URL</a></p>

<p>в <code>$options</code> можно указать какой именно использовать, например:</p>

<pre class="brush:php;">
$image = Image::load('/path/to/file', ['driver' =&gt; Image::DRIVER_GD]);</pre>

<p>Этот метод возвращает экземпляр класса <code>\image\imagick\Image</code> или <code>\image\gd\Image</code>, которые являются наследниками класса <code>\image\AbstractImage</code>. Или будет выброшено исключение в случае неудачи.</p>

<p> </p>

<h3>Методы класса для работы с изображениями:</h3>

<p> </p>

<p>Можно получить экземпляр класса <code>\Imagick</code> или <code>\GdImage</code>|<code>resource</code> (в зависимости от используемой библиотеки) и работать с ними напрямую:</p>

<pre class="brush:php;">
$image-&gt;getData();</pre>

<p> </p>

<p>Сохранить изображение:</p>

<pre class="brush:php;">
$image-&gt;save(?string $fileName = null, ?int $quality = null, ?string $format = null);</pre>

<p><code>$fileName</code> - полный путь и имя файла для сохранения. Если не указано, то изменения будут сохранены в ранее загруженный файл. Иначе будет создан новый файл, а старый останется без изменений.</p>

<p><code>$quality</code> - качество. Используется только если формат сохраняемого файла поддерживает этот параметр.</p>

<p><code>$format</code> - формат сохраняемого изображения. Если не указано, будет использовано расширение файла.</p>

<p> </p>

<p>Кодирование формата в указанный формат:</p>

<pre class="brush:php;">
$image-&gt;encode(string $format, ?int $quality = null, ?string $fileName = null);</pre>

<p><code>$format</code> - формат изображения. Например: 'jpeg', 'png', 'heif'. Или 'data-url' для кодирования изображения в data Url;</p>

<p><code>$quality</code> - качество. Используется только если формат сохраняемого файла поддерживает этот параметр.</p>

<p><code>$fileName</code> - Имя файла для сохранения изображения. Если <code>null</code> - изображение будет возвращено в виде строки бинарных данных.</p>

<p> </p>

<pre class="brush:php;">
$image-&gt;crop(int $width, int $height, ?int $x = null, ?int $y = null);</pre>

<p>Обрезает изображение.</p>

<p>Параметры <code>$x</code> и <code>$y</code> координаты правого верхнего угла области для нового изображения.</p>

<p><code>$width</code> и <code>$height</code> - ширина и высота области нового изображения.</p>

<p> </p>

<pre class="brush:php;">
$image-&gt;fixOrientation();</pre>

<p>Поворачивает изображение в соотвествии с данными об ориентации из EXIF</p>

<p> </p>

<pre class="brush:php;">
$image-&gt;flipHorizontally();
$image-&gt;flipVertically();</pre>

<p>Отразить изображение горизонтально или вертикально соотвественно</p>

<p> </p>

<pre class="brush:php;">
$image-&gt;getWidth();
$image-&gt;getHeight();</pre>

<p>Возвращают ширину и высоту изображения.</p>

<p> </p>

<pre class="brush:php;">
$image-&gt;getOrientation();</pre>

<p>Возвращает ориентацию изображения из EXIF.</p>

<p> </p>

<pre class="brush:php;">
$image-&gt;paste(AbstractImage $image, $x, $y, $alpha = 100);</pre>

<p>Вставляет одно изображение в другое. Может использоваться для наложения "водяного знака" или создания коллажей.</p>

<p><code>$image</code> - вставляемое изображение. Экземпляр класса <code>\image\imagick\Image</code> или <code>\image\gd\Image</code>,</p>

<p><code>$x</code>, <code>$y</code> - координаты левого верхнего угла вставляемого изображения</p>

<p><code>$alpha</code> - прозрачность вставляемого изображения. 0 - полностью прозрачное. 100 - полностью непрозрачное.</p>

<p> </p>

<pre class="brush:php;">
$image-&gt;removeAnimation()</pre>

<p>Удаление анимации. <strong>Gd</strong> и так не поддерживает анимацию. При использовании <strong>Imagick</strong>, в изображении будет оставлен только первый кадр.</p>

<p> </p>

<pre class="brush:php;">
$image-&gt;resize($width = null, $height = null, string $filter = Image::FILTER_UNDEFINED)</pre>

<p>Изменение размера изображения.</p>

<p><code>$width</code>, <code>$height</code> - новые ширина и высота изображения. Могут указываться как целое число в пикселях (1920) или процент ('80%'). Если ширина или высота не указана (null), то ее значение будет расчитано пропорционально.</p>

<p><code>$filter</code> - фильтр применяемый для масштабирования. Одна из констант <code>\image\Image::FILTER_XXX</code>. <strong>GD</strong> не поддерживает фильтров и игнорирует этот параметер.</p>

<p> </p>

<pre class="brush:php;">
$image-&gt;rotate($angle, $background = null)</pre>

<p>Поворачивает изображение на указанное количество градусов</p>

<p> </p>

<pre class="brush:php;">
$image-&gt;strip()</pre>

<p>Удаляет EXIF данные из изображения. <strong>Gd</strong> удаляет эти данные в любом случае.</p>

<p> </p>

<pre class="brush:php;">
$image-&gt;thumbnail(
    $width, 
    $height, 
    string $resize = Image::THUMBNAIL_OUTBOUND, 
    bool $upscale = false, 
    string $filter = Image::FILTER_LANCZOS
)</pre>

<p>Создает миниатюру изображения</p>

<p><code>$width</code>, <code>$height</code> - новые ширина и высота изображения. Могут указываться как целое число в пикселях (1920) или процент ('80%'). Если ширина или высота не указана (null), то ее значение будет расчитано пропорционально.</p>

<p><code>$resize</code> - как создавать миниатюру. Одна из констант:</p>

<ul>
	<li><code>\image\Image::THUMBNAIL_OUTBOUND</code> - масштабирует изображение пропорционально таким образом, чтобы его ширина и высота были не меньшне указанных размеров. Если вторая сторона при этом превышает указанное значение - она обрезается.</li>
	<li><code>\image\Image::THUMBNAIL_INSET</code> - Исходное изображение масштабируется таким образом, чтобы оно полностью умещалось в размерах миниатюры (соотношение ширины и высоты изображения не меняется).    </li>
	<li><code>\image\Image::THUMBNAIL_HEIGHT</code> - Миниатюра масштабируется так, чтобы ее высота равнялась желаемой высоте (соотношение ширины и высоты изображения не меняется).    </li>
	<li><code>\image\Image::THUMBNAIL_WIDTH</code> - Миниатюра масштабируется так, чтобы ее ширина равнялась желаемой ширине (соотношение ширины и высоты изображения не меняется).</li>
</ul>

<p><code>$upscale</code> - увеливать изображение, если оно меньше указанных размеров.</p>

<p><code>$filter</code> - фильтр применяемый для масштабирования. Одна из констант <code>\image\Image::FILTER_XXX</code>. <strong>GD</strong> не поддерживает фильтров и игнорирует этот параметер.</p>

<p> </p>

<p> </p>

<p>Пример использования. Сделаем из исходного изображения миниатюру 160x160 и сохраним ее в формате jpeg:</p>

<pre class="brush:php;">
$image = Image::load('/path/to/image.heic');

// Сделаем миниатюру
$image-&gt;thumbnail(160, 160, Image::THUMBNAIL_OUTBOUND);

// Сохраним в формате JPEG
$image-&gt;save('/path/to/image_160x160.jpg', 80);

// Выгрузим изображение из памяти:
unset($image);</pre>

<p> </p>
]]></description>
			<pubDate>Tue, 24 Oct 2023 12:48:00 +0300</pubDate>
			<link><![CDATA[https://lily-software.com/free-scripts/cotonti-lib/image-bundle]]></link>
		</item>
		<item>
			<title>View. PHP-шаблонизатор.</title>
			<description><![CDATA[<p style="text-align:justify;">Исторически сложилось так, что Cotonti использует шаблонизатор CoTemplate, что накладывает значительные ограничения на возможности самих шаблонов, даже несмотря на то, что за последние годы он стал намного удобнее.</p>

<p style="text-align:justify;">Но мы знаем, что PHP сконструирован специально<strong> для ведения Web-разработок</strong> и его код <strong>может внедряться непосредственно в HTML</strong>.<br />
PHP — сам по себе является не только очень мощьным языком программирования, но и самодостаточным шаблонизатором, позволяющим делать качественные шаблоны без ущерба для логики приложения.</p>

<p style="text-align:justify;">Такой стиль шаблонизации на PHP называют «pure-шаблонизация», т.е. чистая шаблонизация, основанная на возможностях самого PHP.</p>

<p style="text-align:justify;"> </p>

<p style="text-align:justify;">Класс <strong>View</strong> позволяет использовать шаблоны на чистом PHP.</p>

<p style="text-align:justify;"> </p>

<p style="text-align:justify;"><strong>Преимущества:</strong></p>

<p style="text-align:justify;">- Чистый PHP будет работать быстрее шаблонизаторов (интрепретаторов), написанных на PHP, т.к. нет программной "прослойки" между PHP и шаблоном, выполнение которой, как ни крути, а требует рессурсов. Хотя, многие современные шаблонизаторы компилируют шаблоны в PHP код.</p>

<p style="text-align:justify;">- PHP и есть шаблонизатор и в шаблоне доступны все его возможности.</p>

<p style="text-align:justify;">- Не нужно изучать дополнительный синтаксис. Знания HTML и шести - десяти конструкций на PHP вполне достаточно для верстки шаблона почти любой сложности.</p>

<p style="text-align:justify;"> </p>

<p style="text-align:justify;"><strong>Недостатки:</strong></p>

<p style="text-align:justify;">- Кто то считает, что «ужасный синтаксис». По-моему - это весьма субъективно и зависит от личных предпочтений. В моделях и контроллерах он уже не выглядит настолько ужасным )</p>

<p style="text-align:justify;">- Слишком много лишнего текста в шаблонах. Тоже зависит от личных предпочтений и субъективно.</p>

<p style="text-align:justify;"> </p>

<p style="text-align:justify;">Итак к делу.</p>

<p style="text-align:justify;">По существу применение <strong><span class="classname">View</span></strong> состоит из двух шагов:</p>

<p style="text-align:justify;">1. Ваш скрипт контроллера создает экземпляр <span class="classname">View</span> и объявляет переменные этого экземпляра.</p>

<p style="text-align:justify;">2. Контроллер приказывает <span class="classname">View</span> воспроизвести данный вид используя шаблон.</p>

<p style="text-align:justify;"> </p>

<p><strong>Скрипт контроллера</strong></p>

<p>В качестве простого примера предположим, что ваш контроллер имеет список данных по книгам, который нужно вывести пользователю. Скрипт контроллера может выглядеть примерно так:</p>

<pre class="brush:php;">
// Для наглядности, у нас есть массив с данными о книгах
$data = array(
	array(
	    'author' =&gt; 'Hernando de Soto',
	    'title' =&gt; 'The Mystery of Capitalism'
	),
	array(
	    'author' =&gt; 'Henry Hazlitt',
	    'title' =&gt; 'Economics in One Lesson'
	),
	array(
	    'author' =&gt; 'Milton Friedman',
	    'title' =&gt; 'Free to Choose'
	)
);

// теперь присваиваем данные по книгам экземпляру View
$view = new View();
$view-&gt;books = $data;

// и выполняем скрипт вида "booklist.php"
echo $view-&gt;render('library.booklist.php');</pre>

<p> </p>

<p><strong>Скрипт шаблона:</strong></p>

<p style="text-align:justify;">Теперь нам нужен сопутствующий скрипт шаблона "library.booklist.php". Это такой же скрипт PHP, как и остальные, за одним исключением: он выполняется в области видимости экземпляра View, это означает, что $this ссылается на экземпляр View. Переменные, присваиваемые в контроллере для скрипта вида, являются открытыми свойствами экземпляра View. Таким образом, базовый скрипт вида может выглядеть следующим образом:</p>

<pre class="brush:xml;">
&lt;p&gt;Список книг&lt;/p&gt;
&lt;?php if ($this-&gt;books): ?&gt;

&lt;!-- Таблица из нескольких книг. --&gt;
&lt;table&gt;
    &lt;tr&gt;
        &lt;th&gt;Author&lt;/th&gt;
        &lt;th&gt;Title&lt;/th&gt;
    &lt;/tr&gt;

    &lt;?php foreach ($this-&gt;books as $key =&gt; $val): ?&gt;
    &lt;tr&gt;
        &lt;td&gt;&lt;?=htmlspecialchars($val['author']) ?&gt;&lt;/td&gt;
        &lt;td&gt;&lt;?=htmlspecialchars($val['title']) ?&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;?php endforeach; ?&gt;

&lt;/table&gt;

&lt;?php else: ?&gt;

&lt;p&gt;Нет книг для отображения.&lt;/p&gt;

&lt;?php endif; ?&gt;</pre>

<p style="text-align:justify;"> </p>

<p style="text-align:justify;">Класс View также подерживает присваивание переменных в стиле CoTemplate:</p>

<pre class="brush:php;">
$view-&gt;assign(array(
    'PAGE_TITLE'  =&gt;  $title,
    'BREADCRUMBS' =&gt;  cot_breadcrumbs($crumbs, cot::$cfg['homebreadcrumb']),
));</pre>

<p style="text-align:justify;">что аналогично:</p>

<pre class="brush:php;">
$view-&gt;PAGE_TITLE = $title;
$view-&gt;BREADCRUMBS = cot_breadcrumbs($crumbs, cot::$cfg['homebreadcrumb']);</pre>

<p style="text-align:justify;"> </p>

<h4>Основные методы класса <strong>View</strong>:</h4>

<p> </p>

<p><strong>render($viewFile, $type = 'module', $admin = null, $return = true)</strong></p>

<p>Рендерит  HTML код из шаблона и полученных данных.Принимает следующие параметры:</p>

<p><strong>$viewFile</strong> - имя файла шаблона. Если не указано расширение или оно не входит в свойство класса <strong>$_extensions</strong>, будет использовано <strong>".php".<br />
$type </strong>- тип расширения. Может быть 'plug', 'module' or 'core'<br /><strong>$admin - </strong>использовать файл admin-темы, если возможно. По умолчанию эту опцию метод пытается определить исходя из параметра <strong>$viewFile</strong><br /><strong>$return </strong>- Вернуть готовый HTML-код в виде строки (если TRUE) или выполнить require($viewFile)</p>

<p>Файл шаблона ищется по тем же правилам, что и при использовании функции <strong>cot_tplfile()</strong> для CoTemplate.</p>

<p> </p>

<p> </p>

<p><strong>addScriptPath($path, $prepend = true)</strong></p>

<p>Добавить дополнительный путь для поиска шаблонов.</p>

<p><strong>$path</strong> - Путь к папке с шаблонами. <br /><strong>$prepend </strong>- Поместить путь в начало стека.</p>

<p> </p>

<p> </p>

<p><strong>scriptFile($base, $type = 'module', $admin = null)</strong></p>

<p>Ищет и возвращает полый путь к файлу шаблона или <strong>false</strong>, если шаблон не найден. Параметры аналогичны методу <strong>render();</strong></p>
]]></description>
			<pubDate>Sat, 13 Sep 2014 23:21:00 +0300</pubDate>
			<link><![CDATA[https://lily-software.com/free-scripts/cotonti-lib/view]]></link>
		</item>
		<item>
			<title>SOM. (Simple Object Manipulation)</title>
			<description><![CDATA[<h4>Общие сведения</h4>

<p style="text-align:justify;"><strong>SOM (Simple Object Manipulation)</strong> - это простая ORM, позволяющая работать с записями в базе данных посредством моделей, объектов, обладающих определенным набором методов, свойств и связями с другими моделями.</p>

<p style="text-align:justify;">SOM является логическим продолжением <a href="https://lily-software.com/go.php?https://github.com/trustmaster/cot-factory/blob/master/system/orm.php" target="_blank"><strong>ORM</strong></a> из <strong><a href="https://lily-software.com/go.php?https://github.com/trustmaster/cot-factory" target="_blank">cot-factory</a></strong> и представляет собой усовершенствованную и доработанную ORM из упомянутой библиотеки. Спасибо <a href="https://lily-software.com/go.php?https://github.com/ghengeveld" target="_blank">Gert Hengeveld</a> и <a href="https://lily-software.com/go.php?https://github.com/trustmaster" target="_blank">Владимиру Сибирову</a> (trastmaster) за их труд над <strong>cot-factory</strong>.</p>

<p style="text-align:justify;"> </p>

<p style="text-align:justify;">Модели данных создаются в рамках паттерна MVC и соответствуют его концепции. Модели инкапсулируют основные операции с базой данных и позволяют абстрагироваться от непосредственной работы с ней и сосредоточится на программировании.</p>

<h4> </h4>

<h4>Требования</h4>

<p>Для правильной работы необходим php не ниже версии 5.3</p>

<p> </p>

<h4>Использование нескольких соединений с бд</h4>

<p style="text-align:justify;">Иногда бывает необходимо использовать несколько соединений с базой данных. Например когда Вы используете данные, которые хранятся в разных БД или пишите конвертер для переноса данных из одной БД в другую. SOM предоставляет Вам такую возможность.</p>

<p>Для этого в файл <strong>datas/config.php</strong> вашего сайта добавте описание нужных соединений:</p>

<pre class="brush:php;">
$cfg['db_connect_name'] = array(
      'adapter' =&gt; 'mysql',   // 'pgsql' or 'mongo'
      'host' =&gt; '127.0.0.1',
      'port' =&gt; null,
      'username' =&gt; '12345',
      'password' =&gt; '6789',
      'dbname' =&gt; 'my_data_base',
 );</pre>

<p>Поддерживаются базы данных <strong>MySql</strong> и <strong>PostgreSQL</strong>. <strong>Mongo</strong> - эксперементально.</p>

<p>Чтобы установить соединение вызовите фабричный метод:</p>

<pre class="brush:php;">
$db_my_conn = Som::getAdapter('db_connect_name');</pre>

<p style="text-align:justify;">который вернет объект <strong>Som_Model_Mapper</strong> нужного типа. После этого можно отправлять ему запросы обычным образом:</p>

<pre class="brush:php;">
$db_my_conn-&gt;query("SELECT * FROM `cot_some_table` ORDER BY id ASC");</pre>

<p style="text-align:justify;">Также Вы можете передавать имена соединений моделям при инициализации, если необходимо использовать соединения, отличные от системного в Cotonti.</p>

<p style="text-align:justify;">Соединения кешируются на период выполнения. Это значит, что Вы можете вызывать метод <strong>Som::getAdapter</strong> с одним и тем же параметром столько - сколько Вам нужно. Объект соединения с БД не будет создаваться каждый раз заново, а будет возвращаться ссылка на один раз созданный объект.</p>

<p> </p>

<h4>Создание моделей</h4>

<p>Конкретная модель создается классом, который наследуется от класса <strong>Som_Model_Abstract</strong>. Соотвественно, он насследует все его методы. В создаваемом классе модели обязательно должны быть определены следующие свойства:</p>

<pre class="brush:php;">
protected static $_db;
protected static $_tbname = 'katalogshop_sale';
</pre>

<p>и опционально</p>

<pre class="brush:php;">
protected static $_primary_key = 'prod_id';</pre>

<p><strong>$_db хранит</strong> ссылку на объект базы данных<br /><strong>$_tbname</strong> - имя таблицы в БД. Указывается явно при объявлении.<br /><strong>$_primary_key</strong> - название поля - первичный ключ таблицы. Если не указано, то используется поле 'id'.</p>

<p style="text-align:justify;">В конце файла класса модели необходимо вызвать статический метод этого класса <strong>_init()</strong>. По умолчанию будет выполнен соотвествующий метод родительского класса. Это т.н. «статический конструктор», который выполняет начальную инициализацию модели, заполняет свойства $_db и загружает экстраполя.<br />
 </p>

<p>пример модели расписания для группы ВУЗА:</p>

<pre class="brush:php;">
&lt;?php
defined('COT_CODE') or die('Wrong URL.');

/**
 * Модель Schedule
 *
 * Расписание
 *
 * @method static vuz_model_Schedule getById($pk);
 * @method static vuz_model_Schedule fetchOne($conditions = array(), $order = '');
 * @method static vuz_model_Schedule[] find($conditions = array(), $limit = 0, $offset = 0, $order = '');
 *
 * @property int    $id
 * @property string $title         Заголовок
 * @property string $description   Описание
 * @property string $text          Текст
 * @property string $begin         Дата начала
 * @property string $end           Дата окончания
 * @property string $created       Дата создания
 * @property string $created_by    Кем создано
 * @property string $updated       Дата обновления
 * @property string $updated_by    Кем обновлено
 *
 * @property vuz_model_Group $sgroup
 *
 * @property string $displayTitle
 */
class vuz_model_Schedule extends Som_Model_Abstract
{
    /**
     * @var Som_Model_Mapper_Abstract
     */
    protected  static $_db = null;
    protected  static $_tbname = '';
    protected  static $_primary_key = 'id';

    /**
     * Static constructor
     */
    public static function __init($db = 'db'){
        global $db_soc_groups_schedule;

        static::$_tbname = $db_soc_groups_schedule;
        parent::__init($db);
    }

    /**
     * Привязывает файлы из модуля Files
     */
    protected function afterInsert(){
        cot_files_linkFiles('sgrp_schedule', $this-&gt;_data['id']);
    }

    /**
     * @return bool
     */
    public function delete(){

        // Remove all files and images
        $files = files_model_File::find(array(
            array('file_source', 'sgrp_schedule'),
            array('file_item', $this-&gt;_data['id'])
        ));
        if(!empty($files)){
            foreach($files as $fileRow){
                $fileRow-&gt;delete();
            }
        }

         return parent::delete();
    }

    public static function fieldList() {
        return array(
            'id'  =&gt;
                array(
                    'type'    =&gt; 'bigint',
                    'primary' =&gt; true,
                    'description' =&gt; 'id'
                ),
            'title'  =&gt;
                array(
                    'type'      =&gt; 'varchar',
                    'length'    =&gt; '255',
                    'description' =&gt; 'Заголовок'
                ),
            'description'  =&gt;
                array(
                    'type'      =&gt; 'varchar',
                    'nullable'  =&gt; true,
                    'default'   =&gt; '',
                    'length'    =&gt; '255',
                    'description' =&gt; 'Краткое описание'
                ),
            'text' =&gt;
                array(
                    'type'      =&gt; 'text',
                    'nullable'  =&gt; true,
                    'default'   =&gt; '',
                    'description' =&gt; 'Текст'
                ),
            'begin'  =&gt;
                array(
                    'type'      =&gt; 'datetime',
                    'default'   =&gt; date('Y-m-d H:i:s', cot::$sys['now']),
                    'description' =&gt; 'Дата начала'
                ),
            'end'  =&gt;
                array(
                    'type'      =&gt; 'datetime',
                    'description' =&gt; 'Дата окончания'
                ),
            'created'  =&gt;
                array(
                    'type'      =&gt; 'datetime',
                    'safe'      =&gt; true,
                    'description' =&gt; 'Дата создания'
                ),
            'created_by'  =&gt;
                array(
                    'type'      =&gt; 'bigint',
                    'safe'      =&gt; true,
                    'description' =&gt; 'Кем создано'
                ),
            'updated'  =&gt;
                array(
                    'type'      =&gt; 'datetime',
                    'safe'      =&gt; true,
                    'description' =&gt; 'Дата обновления'
                ),
            'updated_by'  =&gt;
                array(
                    'type'      =&gt; 'bigint',
                    'safe'      =&gt; true,
                    'description' =&gt; 'Кем обновлено'
                ),
            'sgroup' =&gt;
                array(
                    'type' =&gt; 'link',
                    'description' =&gt; 'Группа Вуза',
                    'link' =&gt;
                        array(
                            'model'    =&gt; 'vuz_model_Group',
                            'relation' =&gt; Som::TO_ONE_NULL,
                            'label' =&gt; 'sgrp_title',
                        ),
                ),
        );
    }

    // === Методы для работы с шаблонами ===
    /**
     * Returns all Group tags for coTemplate
     *
     * @param vuz_model_Schedule|int $item vuz_model_Vuz object or ID
     * @param string $tagPrefix Prefix for tags
     * @param array $urlParams
     * @param int $textlength
     * @param bool $cacheitem Cache tags
     * @return array|void
     */
    public static function generateTags($item, $tagPrefix = '', $urlParams = array(), $textlength = 500, $cacheitem = true){

        static $extp_first = null, $extp_main = null;
        static $cacheArr = array();

        if (is_null($extp_first)){
            $extp_first = cot_getextplugins('vuz.schedule.tags.first');
            $extp_main  = cot_getextplugins('vuz.schedule.tags.main');
        }

        /* === Hook === */
        foreach ($extp_first as $pl){
            include $pl;
        }
        /* ===== */

        if(empty($urlParams)) $urlParams = array();
        if(empty($urlParams['m'])) $urlParams['m'] = 'group';
        if(empty($urlParams['a'])) $urlParams['a'] = 'schedule';

        if ( ($item instanceof vuz_model_Schedule) &amp;&amp; is_array($cacheArr[$item-&gt;id]) ) {
            $temp_array = $cacheArr[$item-&gt;id];
        }elseif (is_int($item) &amp;&amp; is_array($cacheArr[$item])){
            $temp_array = $cacheArr[$item];
        }else{
            if (is_int($item) &amp;&amp; $item &gt; 0){
                $item = vuz_model_Schedule::getById($item);
            }
            /** @var vuz_model_Schedule $item  */
            if ($item){

                // Права
                // Задание может править автор или староста
                $scheduleAuth['isadmin'] = cot_auth('vuz', 'a', 'A');
                if($scheduleAuth['isadmin']){
                    $scheduleAuth['auth_write'] = true;

                }elseif(cot::$usr['id'] == $item-&gt;created_by){
                    $scheduleAuth['auth_write'] = true;

                }else{
                    $grpAuth['isLeader'] = $item-&gt;sgroup-&gt;isLeader(cot::$usr);
                    if($grpAuth['isLeader']) $taskAuth['auth_write'] = true;

                }

                if(empty($urlParams['gid']) &amp;&amp; !empty($grpIds)) $urlParams['gid'] = $item-&gt;sgroup-&gt;sgrp_id;
                $urlParams['sid'] = $item-&gt;id;

                $itemUrl = cot_url('vuz', $urlParams);
                $itemEditUrl = '';
                if($scheduleAuth['auth_write']){
                    $editUrlParams = $urlParams;
                    $editUrlParams['a'] = 'editSchedule';
                    $itemEditUrl = cot_url('vuz', $editUrlParams);
                }

                $itemDelUrl = '';
                if($scheduleAuth['auth_write']){
                    $editUrlParams = $urlParams;
                    $editUrlParams['a'] = 'deleteSchedule';
                    $itemDelUrl  = cot_confirm_url(cot_url('vuz', $editUrlParams),  'vuz', 'shedule_deleteConfirm');
                }

                $text = cot_parse($item-&gt;text);
                $text_cut = cot_cut_more($text);
                if ($textlength &gt; 0 &amp;&amp; mb_strlen($text_cut) &gt; $textlength){
                    $text_cut = cot_string_truncate($text_cut, $textlength);
                }
                $cutted = (mb_strlen($text) &gt; mb_strlen($text_cut)) ? true : false;

                $date_format = 'datetime_medium';
                $date_format = 'date_fulltext';
                $beginStamp = strtotime($item-&gt;begin);
                $endStamp = !empty($item-&gt;end) ? strtotime($item-&gt;end) : 0;
                $temp_array = array(
                    'URL' =&gt; $itemUrl,
                    'EDIT_URL' =&gt; $itemEditUrl,
                    'DELETE_URL' =&gt; $itemDelUrl,
                    'ID' =&gt; (int)$item-&gt;id,
                    'TITLE' =&gt; htmlspecialchars($item-&gt;TITLE),
                    'DISTPLAY_TITLE' =&gt; htmlspecialchars($item-&gt;displayTitle),
                    'DESC' =&gt; htmlspecialchars($item-&gt;description),
                    'BEGIN' =&gt; cot_date($date_format, $beginStamp),
                    'BEGIN_RAW' =&gt; $beginStamp,
                    'END' =&gt; cot_date($endStamp, $beginStamp),
                    'END_RAW' =&gt; $endStamp,
                    'TEXT' =&gt; $item-&gt;text,
                    'TEXT_CUT' =&gt; $text_cut,
                    'TEXT_IS_CUT' =&gt; $cutted,

                    'CREATED' =&gt; $item-&gt;created,
                    'CREATE_DATE' =&gt; cot_date($date_format, strtotime($item-&gt;created)),
                    'CREATED_RAW' =&gt; strtotime($item-&gt;created),

                    'UPDATED' =&gt; $item-&gt;updated,
                    'UPDATE_DATE' =&gt; cot_date($date_format, strtotime($item-&gt;updated)),
                    'UPDATED_RAW' =&gt; strtotime($item-&gt;updated),
                );

                /* === Hook === */
                foreach ($extp_main as $pl)
                {
                    include $pl;
                }
                /* ===== */
                $cacheitem &amp;&amp; $cacheArr[$item-&gt;id] = $temp_array;
            }else{

            }
        }

        $return_array = array();
        foreach ($temp_array as $key =&gt; $val){
            $return_array[$tagPrefix . $key] = $val;
        }

        return $return_array;
    }

}

vuz_model_Schedule::__init();</pre>

<p style="text-align:justify;">Здесь очень важным является метод <strong>fieldList()</strong>, возвращающий массив с описанием полей в таблице. Обратите внимание на то, что модель не проверяет физическое наличие полей в таблице БД, а наивно доверяет возвращаемому массиву.<br />
 Ключом массива является имя поля, а значением массив с описанием в формате ключ=&gt;значение и может содержать следующие данные:</p>

<p style="text-align:justify;"><strong>'name'</strong> - название поля<br /><strong>'primary'</strong> - может быть только у одного поля. Значение <strong>true </strong>указывает на то, что поле явлается первичным ключом таблицы.<br /><strong>'type'</strong> - тип поля. Это SQL тип или 'link', если это поле является связью с другой моделью.<br /><strong>'length'</strong> - необязательно. Длина значения, например для <strong>varchar</strong><br /><strong>'nullable'</strong> - необязательно. 'nullable' =&gt; false указывает на то, что поле не может принимать пустое значение.<br /><strong>'default'</strong> - необязательно. Значение по умолчанию. При создании нового экземпляра модели поле будет проинициализировано этим значением. Однако при получении существующего объекта, поле будет проинициализировано данными из БД.<br /><strong>'description'</strong> - необязательно. Описание поля.<br /><strong>'safe'</strong> - необязательно. Если <strong>TRUE</strong> , то поле является «защищенным» при заполнении модели данными методом <strong>setData()</strong>, при попытке заполнения этого поля выбросит исключение, если пользователь не является администратором или действие просходит не в панели управления.</p>

<p>Обращаться к полям модели можно как к обычным свойствам объекта.</p>

<p>Например:</p>

<pre class="brush:php;">
$sсhedule = vuz_model_Schedule::getById(15);   // Получить существующий объект
$endStamp = !empty($sсhedule-&gt;end) ? strtotime($sсhedule-&gt;end) : 0;</pre>

<p> </p>

<h4>Связи между моделями</h4>

<p style="text-align:justify;">Модель может содержать связи с другими моделями. Поддерживаются связи <strong>один-ко-многим</strong> и <strong>многие-ко-многим</strong>. Например, наше расписание принадлежит конкретной группе ВУЗ'а реализованной моделью <strong>vuz_model_Group</strong>. Эта связь описывается так:</p>

<pre class="brush:php;">
'sgroup' =&gt;
    array(
        'name' =&gt; 'sgroup',
        'type' =&gt; 'link',
        'description' =&gt; 'Группа Вуза',
        'link' =&gt;
            array(
                'model'    =&gt; 'vuz_model_Group',
                'relation' =&gt; Som::TO_ONE_NULL,
                'label' =&gt; 'title',
            ),
        ),</pre>

<p>Здесь в массив с описанием доля добавляется элемент <strong>'link'</strong>, описывающий связь:</p>

<p><strong>'model' </strong>- имя класса связываемой модели<br /><strong>'relation'</strong> - тип связи. Возможные значения (константы класса Som):<br />
    - Som::TO_ONE  - один ко многим<br />
    - Som::TO_ONE_NULL  - один ко многим, разрешено пустое значение<br />
    - Som::TO_MANY - многие ко многим<br />
    - Som::TO_MANY_NULL - многие ко многим, разрешено пустое значение<br /><strong>'label'</strong> - необязательно. Свойство связываемого класса которое можно использовать для автоматического вывода связанных объектов. Напрмер <strong>'title'</strong>.<br /><strong>'localkey'</strong> - необязательно. Имя поля в данной модели (а точнее в таблице БД), где хранится id связанного объекта. Используется только для связей <strong>один-ко-многим</strong> т.к. для хранения связей многие-ко-многим необходима отдельная таблица в БД, которая при необходимости создается автоматически.<br />
Если параметр <strong>localkey</strong> не указан, то будет использовано само поле в котором описана связь. В данном случае - <em>sgroup</em>. <strong>Обратите внимание</strong>, что поле должно существовать в таблице в БД.</p>

<p>При обращении к полю связанному с другой моделью получим ссылку на объект этой модели. Например:</p>

<pre class="brush:php;">
$prod = Product::getById(2);
echo $prod-&gt;topic-&gt;name; // Выведет поле name экземпляра класса «ProductTopic» (для один-ко-многим)
</pre>

<p>или</p>

<pre class="brush:php;">
$prod = Product::getById(2);
// Это сработает, если у нас для поля $topics объявлена связь многие-ко-многим
foreach ($prod-&gt;topics as $topic){
    // do something ...
    echo $topic-&gt;name;
}
</pre>

<p>Запрос к базе данных на получение связанного объекта происходит только по необходимости. Т.е. в момент обращения</p>

<p>Полю со связью один-ко-многим можно присвоить id связанного объекта или сам объект</p>

<p>Например:</p>

<pre class="brush:php;">
$prod = Product::getById(5);
$topic = ProductTopic::getById(2);

$prod-&gt;topic = 2;      // Присвоили id
$prod-&gt;topic = $topic; // Присвоили экземпляр класса ProductTopic(). По сути эти действия идентичны.
</pre>

<p>Присваивать можно либо самому объявленному полю, либо полю из БД, указанному в localkey</p>

<p>Присвоить можно только экземпляр того класса, каким объявлена связь.</p>

<p>Присваивание значений полям со связями многие-ко-многим аналогично с той разницей, что помимо id или самого объекта допускается присваивание содержащего их массива.</p>

<p> </p>

<h4>Основные методы класса Som_Model_Abstract</h4>

<p><strong>static _init($db = 'db')</strong></p>

<p>Статический конструктор. Должен вызываться при включении файла конкретного класса модели. Выполняет начальную инициализацию модели, заполняет свойства $_db и $_columns. Он принимает один единственный необязательный параметр $db - тип базы данных, по-умолчанию используется соединение с БД Cotonti.</p>

<p> </p>

<p><strong>__construct($data = null)</strong></p>

<p>Конструктор экземпляра. Автоматически вызывается при создании экземпляра модели.</p>

<p>Может принимать в качестве параметра другой экземпляр той же модели или массив (ключ-значение). В этом случае поля объекта будут инициализированы соответствующими значениями.</p>

<p> </p>

<p><strong>protected function afterDelete()</strong></p>

<p>Выполняется после удаления объекта. По умолчанию метод ничего не делает, но Вы можете переопределить его для выполнения неких действий, спецефичных для Вашей модели.</p>

<p> </p>

<p><strong>protected function afterInsert()</strong></p>

<p>Выполняется после добавления нового объекта в БД. По умолчанию метод ничего не делает, но Вы можете переопределить его для выполнения неких действий, спецефичных для Вашей модели.</p>

<p> </p>

<p><strong>protected function afterSave()</strong></p>

<p>Выполняется после сохранения объекта независимо от того, есть соотвествующая запись в БД или нет. По умолчанию метод ничего не делает, но Вы можете переопределить его для выполнения неких действий, спецефичных для Вашей модели.</p>

<p> </p>

<p><strong>protected function afterSetData($data)</strong></p>

<p>Выполняется после заполнения модели данными методом <strong>setData()</strong>; По умолчанию метод ничего не делает, но Вы можете переопределить его для выполнения неких действий, спецефичных для Вашей модели.</p>

<p> </p>

<p><strong>protected function afterUpdate()</strong></p>

<p>Выполняется после существующего объекта. По умолчанию метод ничего не делает, но Вы можете переопределить его для выполнения неких действий, спецефичных для Вашей модели.</p>

<p> </p>

<p><strong>protected function beforeDelete()</strong></p>

<p style="text-align:justify;">Выполняется перед удалением объекта. По умолчанию метод возвращает TRUE, но Вы можете переопределить его для выполнения неких действий, спецефичных для Вашей модели. Если метод вернет FALSE, удаление не произойдет и метод <strong>afterDelete()</strong> выполнен не будет.</p>

<p> </p>

<p><strong>protected function beforeInsert()</strong></p>

<p style="text-align:justify;">Выполняется перед сохранением нового объекта. По умолчанию метод возвращает TRUE, но Вы можете переопределить его для выполнения неких действий, спецефичных для Вашей модели. Если метод вернет FALSE, добавления объекта в БД не произойдет и метод <strong>afterInsert()</strong> выполнен не будет.</p>

<p> </p>

<p><strong>protected function beforeSave($data)</strong></p>

<p style="text-align:justify;">Выполняется перед сохранением объекта независимо от того, есть соотвествующая запись в БД или нет. По умолчанию метод возвращает TRUE, но Вы можете переопределить его для выполнения неких действий, спецефичных для Вашей модели. Если метод вернет FALSE, сохранение не произойдет и метод <strong>afterSave()</strong> выполнен не будет.</p>

<p> </p>

<p><strong>protected function beforeSetData($data)</strong></p>

<p style="text-align:justify;">Выполняется перед заполнением модели данными методом <strong>setData()</strong>; По умолчанию метод возвращает TRUE, но Вы можете переопределить его для выполнения неких действий, спецефичных для Вашей модели. Если метод вернет FALSE, заполнение данными не произойдет и метод <strong>afterSetData()</strong> выполнен не будет.</p>

<p> </p>

<p><strong>protected function beforeUpdate()</strong></p>

<p style="text-align:justify;">Выполняется перед сохранением существующего объекта. По умолчанию метод возвращает TRUE, но Вы можете переопределить его для выполнения неких действий, спецефичных для Вашей модели. Если метод вернет FALSE, сохранение объекта не произойдет и метод <strong>afterUpdate()</strong> выполнен не будет.</p>

<p> </p>

<p><strong>static count($conditions) </strong></p>

<p>Возвращает количество элементов, соотвествующих условию, переданному в параметре $conditions</p>

<p> </p>

<p><strong>dec($pair = array(), $conditions = '')</strong></p>

<p>Декремент. Уменьшает заданные поля на заданные значения. Запись в БД производится сразу при вызове метода.</p>

<p> </p>

<p><strong>delete()</strong></p>

<p>Удалить данных объект из базы данных</p>

<p> </p>

<p><strong>protected static fetch($conditions = array(), $limit = 0, $offset = 0, $order = '')</strong></p>

<p style="text-align:justify;">Защищенный Метод. Он осуществляет выборку объектов из БД. Возвращает массив экземпляров класса, если объект существует или null если ничего не найдено. Публичные методы <strong>getById()</strong>, <strong>fetchOne()</strong> и <strong>find()</strong> являются по сути обертками для этого метода и делегируют запрос ему.</p>

<p style="text-align:justify;">Хоть этот метод и нельзя вызвать извне, но его полезно переопределять для создания выборок, специфичных для конкретной модели. В этом случае методы getById(), fetchOne() и find() будут делегировать запрос переопределенному методу, даже если сами они и не переопределены.</p>

<p> </p>

<p><strong>static fetchOne($conditions, $order = null)</strong></p>

<p>Получить объект по заданным условиям. Возвращает экземпляр класса, если объект существует или null в противном случае.</p>

<p>где:</p>

<p><strong>$conditions</strong> - условия<br /><strong>$order</strong> - условие для сортировки - поле или поля чере запятую. В последнем случае для каждого поля можено указать порядок сортировки (ASC или DESC). Также может принимать массив условий, элементами которого являются массивы, ключи которых - поля, а значения - порядок сортировки.</p>

<p>Получаемый объект кешируется на время выполнения аналогично методу <strong>getById()</strong>.</p>

<p> </p>

<p><strong>static find($conditions, $limit = 0, $offset = 0, $order = '')</strong></p>

<p>Найти объекты по заданным условиям. Возвращает массив экземпляров класса, если объект существует или null если ничего не найдено.</p>

<p>где:</p>

<p><strong>$conditions</strong> - условия<br /><strong>$limit</strong> - Количество выбираемых элементов<br /><strong>$offset</strong> - Смещение выборки от начально элемента<br /><strong>$order</strong> - условие для сортировки - поле или поля чере запятую. В последнем случае для каждого поля можено указать порядок сортировки (ASC или DESC). Также может принимать массив условий, элементами которого являются массивы, ключи которых - поля, а значения - порядок сортировки.</p>

<p> </p>

<p><strong>static getById($pk, $staticCache = true)</strong></p>

<p style="text-align:justify;">Получить объект по первичному ключу. Возвращает экземпляр класса, если объект существует или null в противном случае. Если параметр $staticCache - true, то результат работы кешируется на время жизни скрипта. Т.е. если для конкретной модели вызывать несколько раз этот метод с одним и тем же параметром, запрос к БД выполняется только один раз, потом возвращается ссылка на объект из кеша. При сохранении/удалении объекта кеш сбрасывается.</p>

<p>Кеширование нужно отключить, если используется несколько объектов с одним первичным ключом и разными наборами опций.</p>

<p> </p>

<p><strong>static getDbConfig()</strong></p>

<p>Возвращает настройки БД для данного объекта в виде массива.</p>

<p> </p>

<p><strong>static fields()</strong></p>

<p>Возвращает массив полей, описанных в модели, с типами данных и связей.</p>

<p> </p>

<p><strong>getId()</strong></p>

<p>Возвращает значение первичного ключа объекта</p>

<p> </p>

<p><strong>getValidators($field = null) </strong></p>

<p>Возвращает валидаторы для указанного поля объекта или все валидаторы объекта. Валидаторы используются для проверки данных объекта при его сохранении.</p>

<p> </p>

<p><strong>inc($pair = array(), $conditions = '')</strong></p>

<p>Инкремент. Увеличивает заданные поля на заданные значения. Запись в БД производится сразу при вызове метода.</p>

<p> </p>

<p><strong>static primaryKey()</strong></p>

<p>Возвращает название поля первичного ключа модели.</p>

<p> </p>

<p><strong>rawValue($column)</strong></p>

<p>Возвращает значение поля $column «как есть». Никакие преобразования не производятся. Для связей "ко многим" связанные объекты из БД не выбираются, возвращается массив значений первичного ключа связанных объектов (массив их id)</p>

<p> </p>

<p><strong>save($data = null)</strong></p>

<p>Сохранить текущий объект. качестве аргумента можно передать массив (ключ-значение). В этом случае перед сохранением поля объекта будут перезаписаны данными из массива.</p>

<p> </p>

<p><strong>setData($data, $safe=true)</strong></p>

<p>Безопасное заполнение модели данными. Принимает следующие параметры:</p>

<p><strong>$data</strong> - массив данных в формате имя_поля =&gt; значение. Например $_POST.<br /><strong>$safe </strong>- Если параметр установлен в TRUE и при этом действие происходит не в панели управления и пользователь не администратор ($usr['isadmin'] != TRUE), то при попытке передать в массиве $data имя_поля, которое в описании в моделе имеет элемент 'safe' =&gt; TRUE, будет выброшено исключение.</p>

<p style="text-align:justify;">Данные при установке значений проходят проверку на соотвествие типам данных. Вам не нужно заботиться о рутинной проверке вводимых пользователями данных.</p>

<p> </p>

<p><strong>setValidator($field, $validators)</strong></p>

<p>Устанавливает валидатор(ы) для заданного поля объекта</p>

<p> </p>

<p><strong>toArray()</strong></p>

<p>Возвращает поля текущего объекта в виде массива.</p>

<p> </p>

<p><strong>static updateRows($data, $condition = '')</strong></p>

<p>Обновить все записи в таблице, соотвествующие условию $condition.</p>

<p>Принимает следующие параметры:<br /><strong>$data</strong> - массив данных в формате имя_поля =&gt; значение.<br /><strong>$condition </strong>- Любое допустимое условие для выборки</p>

<p> </p>

<p><strong>validate($fields = null)</strong></p>

<p>Проверяет модель на наличие ошибок. Если в качестве параметра передан массив, то проверяет только те поля, которые перечислены в массиве. Проверка осуществляется при помощи установленных для данного объекта валидаторов. Также поля проверяются на соответствие типу данных.</p>

<p>Этот метод полезно переопределять для создания проверок спецефичных для конкретной модели</p>

<p> </p>

<h4>Получение объектов</h4>

<p>Получение объектов осуществляется описанными выше методами <strong>getById()</strong>, <strong>fetchOne()</strong> и <strong>find()</strong></p>

<p> </p>

<h4>Условия для выборки</h4>

<p>Условия для выборки передаются через параметр <strong>$conditions</strong> методов<strong> fetchOne()</strong> и <strong>find()</strong>.</p>

<p>Рассмотрим синтаксис этого параметра:</p>

<p>Он может быть:</p>

<p><strong>Cтрока, соотвествующая SQL - синтаксису WHERE</strong></p>

<p>Например:</p>

<pre class="brush:php;">
$productArr = Product::find('id &gt; 2 AND id_rasdel = 2');</pre>

<p style="text-align:justify;">В этом случае синтаксис зависит от типа БД и применять его стоит только в крайнем случае, т.к. может ограничить переносимость скрипта между разными типами БД. К тому же это не безопасно. Тут надо проверять передаваемые условия, чтобы исключить возможность sql инъекций, экранировать строковые значения.</p>

<p style="text-align:justify;"> </p>

<p><strong>Массив </strong></p>

<p><strong>Элемент массива строка</strong></p>

<p><em><strong>1. строка соотвествует формату &lt;колонка&gt; &lt;оператор (&lt;, &gt;, =, больше или равно, меньше или равно, не равно)&gt; &lt;значение&gt;</strong></em></p>

<p>Например:</p>

<pre class="brush:php;">
$products = Product::find(array('id &gt;= 2', 'id &lt; 5'));</pre>

<p>Строковые значения в этом случае экранируются автоматически.</p>

<p>Будьте внимательны! Выборка</p>

<pre class="brush:php;">
$this-&gt;item = Unittest_Model_Item::find(array('uti_id=1 OR uti_id=3'));</pre>

<p>будет интерпретирована как</p>

<pre class="brush:php;">
SELECT `unittest_item`.* FROM `unittest_item` WHERE (uti_id = '1 OR uti_id=3')</pre>

<p>а это, скорее всего, не то чего Вы ожидали. Для правильной работы подобных запросов используйте массив массивов в качестве параметра</p>

<p> </p>

<p><em><strong>2. Строка не соответствует указанному формату </strong></em></p>

<p>Например:</p>

<pre class="brush:php;">
$products = Product::find(array('id IN(1,2,5)'));</pre>

<p>В этом случае условие принимается 'как есть' и добавляется к предложению WHERE, по аналогии с Cтрока, соотвествующая SQL - синтаксису WHERE Элементы массива добавляются к предложению WHERE с добавлением AND между ними.</p>

<p>Нарпимер:</p>

<pre class="brush:php;">
$products = Product::find(array('id &gt;= 2', 'id &lt; 5'));</pre>

<p>Соотвествует условию:</p>

<pre class="brush:php;">
WHERE katalogshop_sale.id &gt;= 2 AND katalogshop_sale.id &lt; 5</pre>

<p> </p>

<p><strong>Элемент массива - массив</strong></p>

<p>Массив может состоять из 4-х элементов, из которых первые два являются обязательными.</p>

<p>Нарпимер:</p>

<pre class="brush:php;">
$products = Product::find(array(
    array('id', '2'),
    array('id', '5', '&lt;','OR')
));
</pre>

<p>Расмотрим элементы массива:</p>

<p>1-ый - поле в БД<br />
2-ой - значение<br />
3-ий - операнд (&lt; &gt; = больше или равно, меньше или равно, не равно и т.п.), если не указано, то принимается равным '='<br />
4-ый - Условие объединения (AND ИЛИ OR), если не указано, то принимается равным 'AND'</p>

<p>Так приведенный пример соотвествует условию:</p>

<pre class="brush:php;">
WHERE katalogshop_sale.id = 2 OR katalogshop_sale.id &lt; 5</pre>

<p>Если перый элемент массива равен 'RAW' или 'SQL' то к предложению WHERE добавляется второй элемент (значение) «Как есть», по аналогии с <strong><em>Cтрока, соотвествующая SQL - синтаксису WHERE </em></strong></p>

<p><strong>Значение может быть массивом</strong></p>

<p>Например:</p>

<pre class="brush:php;">
$cats = array(1, 2, 4);
$prod = Product::find(array(
    array('id_rasdel', $cats)
));
</pre>

<p>Будет соответствовать:</p>

<pre class="brush:php;">
WHERE katalogshop_sale.id_rasdel IN (1,2,4);</pre>

<p>а</p>

<pre class="brush:php;">
$cats = array(1, 2, 4);
$prod = Product::find(array(
    array('id_rasdel', $cats, '&lt;&gt;')
));
</pre>

<p>Будет соответствовать условию:</p>

<pre class="brush:php;">
WHERE katalogshop_sale.id_rasdel NOT IN (1,2,4);
</pre>

<p>Строковые значения в переданном массиве экранируются.</p>

<p><strong>Значение может быть NULL</strong></p>

<p>Например:</p>

<pre class="brush:php;">
$prod = Product::find(array(
    array('id_rasdel', null)
));
</pre>

<p>Будет соответствовать:</p>

<pre class="brush:php;">
WHERE katalogshop_sale.id_rasdel IS NULL;
</pre>

<p>а</p>

<pre class="brush:php;">
$prod = Product::find(array(
    array('id_rasdel', null, '&lt;&gt;')
));

Будет соответствовать условию:
</pre>

<pre class="brush:php;">
WHERE katalogshop_sale.id_rasdel IS NOT NULL; </pre>

<p> </p>

<p><strong>Построение LIKE условий</strong></p>

<p>Если значение содержит '*', то в предложение WHERE будет добавлено LIKE</p>

<p>Например:</p>

<pre class="brush:php;">
$prod = Product::find(array(
    array('name', '*водонагреватель*')
));
</pre>

<p>Будет соответствовать условию:</p>

<pre class="brush:php;">
WHERE katalogshop_sale.name LIKE '%водонагреватель%';
</pre>

<p>Так, к примеру для MySql <strong>'*'</strong> заменяется на <strong>'%'</strong></p>

<p>Строковые значения экранируются.</p>

<p> </p>

<p> </p>

<h4>Валидаторы. Проверка объектов.</h4>

<p>Как уже отмечалось ранее, при сохранении объекта, его данные проходят проверку на соответствие типу данных, указанному в описании поля.</p>

<p>Дополнительную проверку данных можно обеспечить при помощи валидаторов.</p>

<p>Поддерживается 2 типа валидаторов:</p>

<p> </p>

<p><strong>Строковые валидаторы </strong></p>

<p style="text-align:justify;">В качестве строки выступает название типа данных: <strong>int, integer, bool, boolean, double, float, isnull, notnull, empty, notempty</strong>. Последние 4 - не являются типами данных и осуществляют проверку на соотвествие значения 'NULL' и на пустое значение.</p>

<p> </p>

<p><strong>Callback функции </strong></p>

<p>В качестве валидатора можно передать функцию. Она должна по результатом проверки возвращать 'true' или строку с описанием ошибки.</p>

<p><em>Передача имени функции</em></p>

<p>Пример:</p>

<pre class="brush:php;">
function myFunction($value){
// some validation
return true;
}
$prod = Product::getById(2);
$prod-&gt;setValidator('description', 'myFunction');
</pre>

<p> </p>

<p><em>Использование анонимных функций </em></p>

<p>Пример:</p>

<pre class="brush:php;">
$prod-&gt;setValidator('description', function($value){
    if(mb_strlen($value) &gt; 30)  return true;
    return "Описание слишком короткое";
});
</pre>

<p> </p>

<p><em>Использование методов классов </em></p>

<p>Пример:</p>

<pre class="brush:php;">
class MyClass{
    public static function test($value)    {
        // some validation
        return true;
    }
}
$prod-&gt;setValidator('description', array('MyClass', 'test'));
</pre>

<p>Для нестатичного метода класса определение валидатора схоже с примером выше, с той разницей, что перед установкой валидатора нужно создать экземпляр класса и потом уже создать массив:</p>

<pre class="brush:php;">
class MyClass {
    public function myMethod($value){
        // some validation
        return true;
    }
}
$object = new MyClass;
$prod-&gt;setValidator('description', array($object, 'myMethod'));
</pre>

<p>И, наконец, Вы можете определить в своем классе магический метод _invoke(). В этом случае в качестве валидатора просто передайте экземпляр класса.</p>

<pre class="brush:php;">
class MyClass{
    public function __invoke($value) {
        // some validation
        return true;
    }
}

$object = new MyClass();
$prod-&gt;setValidator('description', $object);
</pre>

<p> </p>

<p><strong>Установка валидаторов </strong></p>

<p>Валидаторы можно установить на стадии проектирования модели или в период выполнения в контроллере.</p>

<p>Для установки валидатора в классе модели следует переопределить метод <strong>validators()</strong>. Например:</p>

<pre class="brush:php;">
protected function validators() {
    return array(
        array('specialization,birthdate,city,category,staff,skills,education', 'required'),
        array('staff', function ($value) {
            // Сделать невозможным указание одновременно уровней в штатном расписании «Стажер» c любым другим уровнем
            if(!empty($value) &amp;&amp; in_array(5, $value) &amp;&amp; count($value) &gt; 1) {
                return 'Нельзя указать «Стажер» одновременно с другими уровнями в штатном расписании';
            }
            $ret = (count($value) &lt; 3) ? true : 'Можно использовать не более двух уровней в штатном расписании';

            return $ret;
        }),
    );
}</pre>

<p> </p>

<p style="text-align:justify;">Во время выполнения установить валидатор для поля объекта можно методом <strong>setValidator($field, $validators)</strong> Он принимает в качестве параметров имя поля и валидатор любого из перечисленных типов.</p>

<p>Например:</p>

<pre class="brush:php;">
$prod = Product::getById(2);
$prod-&gt;setValidator('description', 'notempty');
$prod-&gt;setValidator('description', function($value){
    if(mb_strlen($value) &gt; 30)  return true;
    return "Описание слишком короткое";
});
</pre>

<p> </p>

<p><strong>Получение списка валидаторов </strong></p>

<p>получить валидаторы можно методом <strong>getValidators($field = null)</strong> Он возвращает массив валидаторов, установленных для заданного поля или все валидаторы объекта, если параметр опущен.</p>

<p> </p>

<h4>Сеттеры и Геттеры</h4>

<p style="text-align:justify;">Как и указывалось выше, доступ к полям записи в БД, представленной объектом производится через его свойства. Однако Вы можете переопределить стандартное поведение для некоторых полей или даже добавить объекту новые свойства, не привязанные к полям в БД.</p>

<p style="text-align:justify;">Делается это при помощи волшебных методов - сеттеров и геттеров.</p>

<p style="text-align:justify;">Например вы в классе модели можете добавить новое динамическое поле:</p>

<pre class="brush:php;">
setMyField($val){
    $this-&gt;_myField = $val;
}

getMyField(){
    return $val;
}</pre>

<p style="text-align:justify;">Как видим формат имени метода: <strong>set&lt;имя_поля_с_большой_буквы&gt;</strong>. Префикс «set» используется для сеттера, а «get» - для геттера.</p>

<p style="text-align:justify;">После этого мы можем обращаться к этим полям:</p>

<pre class="brush:php;">
$object = module_model_Object::getById(2);

$object-&gt;myField = 15;
echo $object-&gt;myField;</pre>

<p style="text-align:justify;">Данный пример очень простой. Вы можете создать более сложную логику для своих сеттеров и геттеров. Переопределение сеттеров и геттеров для существующих полей происходит аналогично. Просто создайте метод с названием <strong>set&lt;имя_существующего_поля_с_большой_буквы&gt;</strong>.</p>

<p style="text-align:justify;">Обратите внимание на то, что <strong>сеттер должен принимать значение</strong>.</p>
]]></description>
			<pubDate>Tue, 13 May 2014 07:42:00 +0300</pubDate>
			<link><![CDATA[https://lily-software.com/free-scripts/cotonti-lib/som]]></link>
		</item>
		<item>
			<title>Loader. Загрузчик</title>
			<description><![CDATA[<p style="text-align:justify;">Этот компонент <strong>устарел </strong>и используется только для обратной совместимости. Используйте вместо него встроенный в Cotonti автозагрузчик Composer'a.<br />
<br />
Класс <strong>«Loader»</strong>, объявленный в файле «Loader.php» предназначен для автоматической загрузки описаний классов и интерфейсов. Автозагрузка происходит в момент первого обращения к классу и Вам не нужно беспокоиться о том, подключили Вы php-файл, содержащий описание класса или нет.</p>

<p>Для регистрации автозагрузчика в стэке <strong>__autoload</strong> служит статический метод <strong>register()</strong>. Вызывается так:</p>

<pre class="brush:php;">
// Автозагрузка
require_once './lib/Loader.php';
Loader::register();</pre>

<p style="text-align:justify;">Регистрация автозагрузчика происходит в плагине cotonti-lib, так что Вам не нужно заботиться о подключении автозагрузчика в своих расширениях.</p>

<p style="text-align:justify;">Метод регистрирует автозагрузчик только один раз. По этому даже если Вы его подключаете явно, ничего плохого не случится.</p>

<p style="text-align:justify;"> </p>

<p style="text-align:justify;">Как происходит автозагрузка:</p>

<p style="text-align:justify;">Строка, задающая класс, согласно стандарта кодирования <strong>psr-0 </strong>(<a href="https://lily-software.com/go.php?https://gist.github.com/Thinkscape/1234504">https://gist.github.com/Thinkscape/1234504</a>)<strong>, </strong> преобразуется в относительный путь посредством замены знаков подчеркивания разделителями директорий, используемыми в вашей ОС, и добавления расширения «.php». Например, для класса «files_model_File» на Windows будет использоваться путь «files\model\File.php».</p>

<p style="text-align:justify;">Затем полученный файл ищется в папках «modules», «plugins», «lib» и папках заданных в настройке конфигурации include_path. Будет загружен первый встретившийся файл.</p>

<p style="text-align:justify;">Так, если у Вас установлен <a href="https://lily-software.com/sozdanie-internet-sajtov/free-scripts/cotonti-files">модуль Файлов - «Files»</a>, то при первом обращении к упомянотому выше классу  «files_model_File» будет загружен файл «modules/files/model/File.php»</p>
]]></description>
			<pubDate>Tue, 06 May 2014 22:11:00 +0300</pubDate>
			<link><![CDATA[https://lily-software.com/free-scripts/cotonti-lib/loader]]></link>
		</item>
	</channel>
</rss>