Главная > Программирование > PHP Перевод стандартов PSR-0, PSR-1, PSR-2, PSR-3, PSR-4
Оригинал: http://www.php-fig.org/psr/ Альтернативные переводы PSR на русский язык можно найти здесь:
Принятые стандарты
PSR-0 – Стандарт автозагрузкиНиже представлены требования, обязательные к исполнению в целях обеспечения совместимости механизмов автозагрузки. Обязательные требования
Примеры
Знак подчёркивания в именах пространств имён и классов
Представленные здесь стандарты должны восприниматься как минимально необходимый набор правил для обеспечения совместимости автозагрузчиков. Вы можете проверить, насколько вы следуете указанным правилам, воспользовавшись следующим примером реализации SplClassLoader (ориентирован на загрузку классов PHP 5.3). Пример реализации Ниже представлен пример функции, иллюстрирующей, как описанные выше требования влияют на процесс автозагрузки: <?php function autoload($className) { $className = ltrim($className, '\\'); $fileName = ''; $namespace = ''; if ($lastNsPos = strrpos($className, '\\')) { $namespace = substr($className, 0, $lastNsPos); $className = substr($className, $lastNsPos + 1); $fileName = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR; } $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php'; require $fileName; } Реализация SplClassLoader Ниже представлен пример реализации SplClassLoader, способного выполнять автозагрузку ваших классов при условии, что вы следуете описанным выше требованиям. В настоящий момент такой подход является рекомендуемым для загрузки классов PHP 5.3 при условии соблюдения данного стандарта: PSR-1 – Базовый стандарт оформления кодаДанный раздел описывает стандартные элементы, являющиеся существенными для обеспечения высокой технической совместимости кода, созданного и/или поддерживаемого различными разработчиками. Слова «НЕОБХОДИМО» / «ДОЛЖНО» («MUST»), «НЕДОПУСТИМО» («MUST NOT»), «ТРЕБУЕТСЯ» («REQUIRED»), «НУЖНО» («SHALL»), «НЕ ПОЗВОЛЯЕТСЯ» («SHALL NOT»), «СЛЕДУЕТ» («SHOULD»), «НЕ СЛЕДУЕТ» («SHOULD NOT»), «РЕКОМЕНДУЕТСЯ» («RECOMMENDED»), «МОЖЕТ» / «ВОЗМОЖНО» («MAY») и «НЕОБЯЗАТЕЛЬНО» («OPTIONAL») в этом документе следует понимать так, как это описано в RFC 2119 (и его переводе). 1. Общие положения
2. Файлы 2.1. PHP-теги PHP-код ОБЯЗАТЕЛЬНО следует заключать в полную версию (<?php ?>) тегов или укороченную (сокращённую запись echo) версию (<?= ?>) тегов и НЕДОПУСТИМО заключать ни в какие иные разновидности тегов. 2.2. Кодировка символов PHP-код ДОЛЖЕН быть представлен только в кодировке UTF-8 без BOM-байта. 2.3. Побочные эффекты В файлах СЛЕДУЕТ либо объявлять структуры (классы, функции, константы и т.п.) и не создавать побочных эффектов (например: передавать данные в выходной поток, модифицировать настройки и т.п.), либо реализовывать логику, порождающую побочные эффекты, но НЕ СЛЕДУЕТ делать одновременно и то, и другое. Под «побочными эффектами» понимается реализация логики, не связанной с объявлением классов, функций, констант и т.п. – даже подключение внешнего файла уже является «побочным эффектом». «Побочные эффекты» включают (но не ограничиваются этим перечнем): передачу данных в выходной поток, явное использование require или include, изменение настроек, генерирование ошибочных ситуаций или порождение исключений, изменение глобальных или локальных переменных, чтение из файла или запись в файл и т.п. Ниже представлен пример файла, содержащий в себе как объявления структур, так и порождение побочных эффектов, т.е. ситуации, которой стоит избегать: <?php // побочный эффект: изменение настроек ini_set('error_reporting', E_ALL); // побочный эффект: подключение файла include "file.php"; // побочный эффект: передача данных в выходной поток echo "\n"; // объявление function foo() { // тело функции } Следующий пример демонстрирует файл с объявлениями без побочных эффектов – т.е. образец рекомендуемой реализации: <?php // объявление function foo() { // тело функции } // условное объявление -- это НЕ побочный эффект if (! function_exists('bar')) { function bar() { // тело функции } } 3. Имена пространств имён и имена классов Имена пространств имён и имена классов ДОЛЖНЫ следовать стандарту PSR-0. В конечном итоге это означает, что каждый класс должен располагаться в отдельном файле и в пространстве имён с хотя бы одним верхним уровнем (именем производителя). Имена классов ДОЛЖНЫ быть объявлены с использованием т.н. «StudlyCaps» (каждое слово начинается с большой буквы, между словами нет разделителей). Код, написанный для PHP 5.3 и более новых версий, ДОЛЖЕН использовать формальные пространства имён, например: <?php // PHP 5.3 и новее: namespace Vendor\Model; class Foo { } В коде, написанном для PHP 5.2.x и ниже, СЛЕДУЕТ при именовании классов соблюдать соглашение о псевдопространствах имён с префиксом в виде имени производителя (Vendor_): <?php // PHP 5.2.x и ранее: class Vendor_Model_Foo { } 4. Константы, свойства и методы классов Здесь под «классом» следует понимать также интерфейсы (interface) и примеси (trait). 4.1. Константы Константы классов ДОЛЖНЫ быть объявлены в верхнем регистре с использованием символа подчёркивания в качестве разделителя слов, например: <?php namespace Vendor\Model; class Foo { const VERSION = '1.0'; const DATE_APPROVED = '2012-06-01'; } 4.2. Свойства В данном руководстве намеренно не приводится никаких рекомендаций относительно использования $StudlyCaps, $camelCase или $under_score вариантов именования свойств. Какой бы вариант именования ни был выбран, СЛЕДУЕТ сохранять его неизменным в рамках некоторого разумного объёма кода (например, на уровне производителя, пакета, класса или метода). 4.3. Методы Имена методов ДОЛЖНЫ быть объявлены с использованием т.н. «camelCase» (первое слово пишется в нижнем регистре, далее каждое слово начинается с большой буквы, а между словами нет разделителей). PSR-2 – Рекомендации по оформлению кодаДанные рекомендации расширяют и дополняют базовый стандарт оформления кода PSR-1. Цель данных рекомендаций – снижение сложности восприятия кода, написанного разными авторами; она достигается путём рассмотрения серии правил и ожиданий относительно форматирования PHP-кода. Стилистические правила, представленные здесь, получены путём обобщения опыта различных проектов. Сотрудничество многих авторов из многих проектов позволяет выработать единый набор принципов и использовать его в этих проектах. Таким образом, польза представленных рекомендаций – не столько в самих рекомендациях, сколько в их распространении. Слова «НЕОБХОДИМО» / «ДОЛЖНО» («MUST»), «НЕДОПУСТИМО» («MUST NOT»), «ТРЕБУЕТСЯ» («REQUIRED»), «НУЖНО» («SHALL»), «НЕ ПОЗВОЛЯЕТСЯ» («SHALL NOT»), «СЛЕДУЕТ» («SHOULD»), «НЕ СЛЕДУЕТ» («SHOULD NOT»), «РЕКОМЕНДУЕТСЯ» («RECOMMENDED»), «МОЖЕТ» / «ВОЗМОЖНО» («MAY») и «НЕОБЯЗАТЕЛЬНО» («OPTIONAL») в этом документе следует понимать так, как это описано в RFC 2119 (и его переводе). 1. Общие положения
1.1. Пример Следующий пример охватывает часть из вышеописанных правил: <?php namespace Vendor\Package; use FooInterface; use BarClass as Bar; use OtherVendor\OtherPackage\BazClass; class Foo extends Bar implements FooInterface { public function sampleFunction($a, $b = null) { if ($a === $b) { bar(); } elseif ($a > $b) { $foo->bar($arg1); } else { BazClass::bar($arg2, $arg3); } } final public static function bar() { // тело метода } } 2. Основные положения 2.1. Базовый стандарт оформления кода Код ДОЛЖЕН быть оформлен согласно всем правилам, указанным в стандарте PSR-1. 2.2. Файлы
2.3. Строки
2.4. Отступы Для оформления отступов ДОЛЖНЫ использоваться четыре пробела (но не знак табуляции). Примечание: использование только лишь пробелов (без смешивания их с табуляциями) позволяет избежать проблем с обработкой истории изменения кода, определением самих изменений, патчами и комментариями. Использование пробелов также позволяет легко добавлять небольшие отступы для выравнивания отдельных вложенных строк. 2.5. Ключевые слова и константы true / false / null
3. Определение пространств имён и блоков импорта
Пример: <?php namespace Vendor\Package; use FooClass; use BarClass as Bar; use OtherVendor\OtherPackage\BazClass; // ... далее следует PHP-код ... 4. Классы, свойства и методы Здесь под «классом» следует понимать также интерфейсы (interface) и примеси (trait). 4.1. Наследование и реализация
<?php namespace Vendor\Package; use FooClass; use BarClass as Bar; use OtherVendor\OtherPackage\BazClass; class ClassName extends ParentClass implements \ArrayAccess, \Countable { // константы, свойства, методы }
<?php namespace Vendor\Package; use FooClass; use BarClass as Bar; use OtherVendor\OtherPackage\BazClass; class ClassName extends ParentClass implements \ArrayAccess, \Countable, \Serializable { // константы, свойства, методы } 4.2. Свойства
В общем случае определение свойства выглядит так: <?php namespace Vendor\Package; class ClassName { public $foo = null; } 4.3. Методы
В общем случае определение метода выглядит так. Обратите внимание на круглые скобки, запятые, пробелы и фигурные скобки: <?php namespace Vendor\Package; class ClassName { public function fooBarBaz($arg1, &$arg2, $arg3 = []) { // тело метода } } 4.4. Аргументы методов
<?php namespace Vendor\Package; class ClassName { public function foo($arg1, &$arg2, $arg3 = []) { // тело метода } }
<?php namespace Vendor\Package; class ClassName { public function aVeryLongMethodName( ClassTypeHint $arg1, &$arg2, array $arg3 = [] ) { // тело метода } } 4.5. Ключевые слова abstract, final и static
<?php namespace Vendor\Package; abstract class ClassName { protected static $foo; abstract protected function zim(); final public static function bar() { // тело метода } } 4.6. Вызовы методов и функций В коде вызова функций и методов НЕ ДОЛЖНО быть пробела между именем функции или метода и открывающей круглой скобкой, НЕ ДОЛЖНО быть пробела после открывающей круглой скобки, НЕ ДОЛЖНО быть пробела перед закрывающей круглой скобкой. В списке аргументов НЕ ДОЛЖНО быть пробелов перед запятыми, но ДОЛЖЕН быть пробел после каждой запятой. <?php bar(); $foo->bar($arg1); Foo::bar($arg2, $arg3); Список аргументов МОЖЕТ быть разделён на несколько строк, каждая из которых дополнена слева одним отступом (четырьмя пробелами). В таком случае первый элемент списка аргументов ДОЛЖЕН начинаться с новой строки, и в каждой строке ДОЛЖЕН быть указан только один аргумент. <?php $foo->bar( $longArgument, $longerArgument, $muchLongerArgument ); 5. Управляющие конструкции Общие правила оформления управляющих конструкций:
Тело каждой управляющей конструкции ДОЛЖНО быть заключено в фигурные скобки. Это позволяет стандартизировать внешний вид управляющих конструкций с снизить риск возникновения ошибок при добавлении новых строк в тело конструкции. 5.1. Конструкции if, elseif и else Конструкция if выглядит следующим образом. Обратите внимание на круглые скобки, пробелы и фигурные скобки, а также на тот факт, что слова else и elseif располагаются в той же строке, что и закрывающая фигурная скобка предшествующего тела конструкции. <?php if ($expr1) { // тело if } elseif ($expr2) { // тело elseif } else { // тело else } Ключевое слово elseif СЛЕДУЕТ использовать вместо отдельного сочетания else и if. Так конструкция будет представлять собой одно слово. 5.2. Конструкции switch и case Конструкция switch выглядит следующим образом. Обратите внимание на круглые скобки, пробелы и фигурные скобки. Выражение case ДОЛЖНО быть смещено на один отступ (четыре пробела) от switch, а ключевое слово break (или иное слово, обозначающее выход из конструкции) ДОЛЖНО располагаться на том же уровне отступов, что и тело case. В том случае, когда в непустом теле case умышленно не используется break, ДОЛЖЕН быть комментарий в стиле // no break. <?php switch ($expr) { case 0: echo 'First case, with a break'; break; case 1: echo 'Second case, which falls through'; // no break case 2: case 3: case 4: echo 'Third case, return instead of break'; return; default: echo 'Default case'; break; } 5.3. Конструкции while и do while Конструкция while выглядит следующим образом. Обратите внимание на круглые скобки, пробелы и фигурные скобки. <?php while ($expr) { // тело конструкции } Соответственно, конструкция do while выглядит следующим образом. Обратите внимание на круглые скобки, пробелы и фигурные скобки. <?php do { // тело конструкции } while ($expr); 5.4. Конструкция for Конструкция for выглядит следующим образом. Обратите внимание на круглые скобки, пробелы и фигурные скобки. <?php for ($i = 0; $i < 10; $i++) { // тело for } 5.5. Конструкция foreach Конструкция foreach выглядит следующим образом. Обратите внимание на круглые скобки, пробелы и фигурные скобки. <?php foreach ($iterable as $key => $value) { // тело foreach } 5.6. Конструкция try catch Блоки конструкции try catch выглядят следующим образом. Обратите внимание на круглые скобки, пробелы и фигурные скобки. <?php try { // тело try } catch (FirstExceptionType $e) { // тело catch } catch (OtherExceptionType $e) { // тело catch } 6. Замыкания
Описание замыкания выглядит следующим образом. Обратите внимание на круглые скобки, запятые, пробелы и фигурные скобки. <?php $closureWithArgs = function ($arg1, $arg2) { // тело }; $closureWithArgsAndVars = function ($arg1, $arg2) use ($var1, $var2) { // тело }; Список аргументов и переменных МОЖЕТ быть разделён на несколько строк, каждая из которых дополнена слева одним отступом (четырьмя пробелами). В таком случае первый элемент списка ДОЛЖЕН начинаться с новой строки, и в каждой строке ДОЛЖЕН быть указан только один элемент. Когда последний список (аргументов или переменных) разделён на несколько строк, закрывающая круглая скобка и открывающая фигурная скобка ДОЛЖНЫ располагаться на одной строке и быть разделены одним пробелом. Ниже представлены примеры замыканий со списком аргументов и без него, а также со списком переменных, располагающимся на нескольких строках. <?php $longArgs_noVars = function ( $longArgument, $longerArgument, $muchLongerArgument ) { // тело }; $noArgs_longVars = function () use ( $longVar1, $longerVar2, $muchLongerVar3 ) { // тело }; $longArgs_longVars = function ( $longArgument, $longerArgument, $muchLongerArgument ) use ( $longVar1, $longerVar2, $muchLongerVar3 ) { // тело }; $longArgs_shortVars = function ( $longArgument, $longerArgument, $muchLongerArgument ) use ($var1) { // тело }; $shortArgs_longVars = function ($arg) use ( $longVar1, $longerVar2, $muchLongerVar3 ) { // тело }; Обратите внимание, что правила оформления замыканий также распространяются на случай, когда замыкание используется в качестве аргумента прямо в вызове функции или метода. <?php $foo->bar( $arg1, function ($arg2) use ($var1) { // тело }, $arg3 ); 7. Заключение В этом руководстве намеренно не рассмотрены правила и лучшие практики по оформлению многих элементов, список которых включает в себя, но не ограничивается следующим:
В будущем данные рекомендации МОГУТ быть пересмотрены и расширены, чтобы охватить те или иные элементы кода и практики оформления. Приложение A. Опрос Примечание переводчика: в оригинальном тексте стандарта содержится приложение с результатами опроса, на основе которого были приняты те или иные решения. Приложение представляет собой большой массив неудобочитаемых данных и не несёт явной пользы для понимания стандарта. Вы всегда можете ознакомиться с представленными там данными здесь: http://www.php-fig.org/psr/psr-2/. PSR-3 – Интерфейс протоколированияДанный документ описывает общий интерфейс библиотек протоколирования. Основная цель данного документа – позволить библиотекам получать объект Psr\Log\LoggerInterface и использовать его простым и универсальным образом для реализации протоколирования. В случае, если некий фреймворк или CMS нуждается в расширенном функционале, МОЖНО расширять данный интерфейс, но СЛЕДУЕТ сохранять совместимость с описанными в данном документе правилами. Это позволит сторонним библиотекам, применяемым при разработке приложения, использовать централизованную систему протоколирования. Слова «НЕОБХОДИМО» / «ДОЛЖНО» ("MUST"), «НЕДОПУСТИМО» ("MUST NOT"), «ТРЕБУЕТСЯ» ("REQUIRED"), «НУЖНО» ("SHALL"), «НЕ ПОЗВОЛЯЕТСЯ» ("SHALL NOT"), «СЛЕДУЕТ» ("SHOULD"), «НЕ СЛЕДУЕТ» ("SHOULD NOT"), «РЕКОМЕНДУЕТСЯ» ("RECOMMENDED"), «МОЖЕТ» / «ВОЗМОЖНО» ("MAY") и «НЕОБЯЗАТЕЛЬНО» ("OPTIONAL") в этом документе следует понимать так, как это описано в RFC 2119 (и его переводе). Слово «разработчик» (implementor) в данном документе следует понимать как «автор, реализующий интерфейс LoggerInterface в неких библиотеке или фреймворке, связанных с протоколированием». Пользователи систем протоколирования упоминаются как просто «пользователи». 1. Спецификации 1.1 Основы Интерфейс LoggerInterface предоставляет восемь методов протоколирования, соответствующих восьми уровням протоколирования, описанным в RFC 5424 (отладка (debug), информация (info), замечание (notice), предупреждение (warning), ошибка (error), критическая ошибка (critical), тревога (alert), авария (emergency)). Девятый метод, «протокол» (log) принимает в качестве первого аргумента уровень протоколирования. Вызов этого метода с передачей константы одного из уровней протоколирования ДОЛЖЕН приводить к тому же результату, что и вызов соответствующего переданному уровню протоколирования специального метода. Вызов этого метода с передачей уровня протоколирования, не описанного в данной спецификации, ДОЛЖЕН приводить к порождению исключения Psr\Log\InvalidArgumentException в случае, если конкретная реализация системы протоколирования не поддерживает переданный уровень протоколирования. Пользователям НЕ СЛЕДУЕТ использовать собственные уровни протоколирования без полной уверенности в том, что конкретная реализация системы протоколирования их поддерживает. 1.2 Сообщения
Ниже для ознакомления представлен пример обработки плейсхолдеров. <?php /** * Подстановка значений в плейсхолдеры сообщения. */ function interpolate($message, array $context = array()) { // Построение массива подстановки с фигурными скобками // вокруг значений ключей массива context. $replace = array(); foreach ($context as $key => $val) { $replace['{' . $key . '}'] = $val; } // Подстановка значений в сообщение и возврат результата. return strtr($message, $replace); } // Сообщение с плейсхолдером, имя которого обрамлено // фигурными скобками. $message = "User {username} created"; // Массив context с данными для замены плейсхолдера на // итоговое значение. $context = array('username' => 'bolivar'); // Результат: "User bolivar created" echo interpolate($message, $context); 1.3 Контекст
1.4 Вспомогательные классы и интерфейсы
2. Пакет Описанные интерфейсы и классы, равно как и соответствующие классы исключений и тестовые сценарии для проверки вашей реализации системы протоколирования предоставляются в составе пакета psr/log. 3. Интерфейс Psr\Log\LoggerInterface <?php namespace Psr\Log; /** * Описывает систему протоколирования. * * Сообщение ДОЛЖНО быть строкой или объектом, реализующим __toString(). * * Сообщение МОЖЕТ содержать плейсхолдеры в виде {foo}, где foo будет * заменено на значение элемента массива context с ключом "foo". * * Массив context может содержать произвольные данные. Единственное * предположение, допустимое для разработчиков, заключается в том, что * если в массиве переда объект исключения для построения трассировки * стека, он ДОЛЖЕН находиться в элементе массива с ключом "exception". * * См. полную спецификацию интерфейса здесь: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md */ interface LoggerInterface { /** * Авария, система неработоспособна. * * @param строка $message * @param массив $context * @return null */ public function emergency($message, array $context = array()); /** * Тревога, меры должны быть предприняты незамедлительно. * * Примеры: весь веб-сайт недоступен, БД недоступна и т.д. Вплоть до * отправки SMS-сообщения ответственному лицу. * * @param строка $message * @param массив $context * @return null */ public function alert($message, array $context = array()); /** * Критическая ошибка, критическая ситуация. * * Пример: недоступен компонент приложения, неожиданное исключение. * * @param строка $message * @param массив $context * @return null */ public function critical($message, array $context = array()); /** * Ошибка на стадии выполнения, не требующая неотложного вмешательства, * но требующая протоколирования и дальнейшего изучения. * * @param строка $message * @param массив $context * @return null */ public function error($message, array $context = array()); /** * Предупреждение, нештатная ситуация, не являющаяся ошибкой. * * Пример: использование устаревшего API, неверное использование API, * нежелательные эффекты и ситуации, которые, тем не менее, * не обязательно являются ошибочными. * * @param строка $message * @param массив $context * @return null */ public function warning($message, array $context = array()); /** * Замечание, важное событие. * * @param строка $message * @param массив $context * @return null */ public function notice($message, array $context = array()); /** * Информация, полезные для понимания происходящего события. * * Пример: авторизация пользователя, протокол взаимодействия с БД. * * @param строка $message * @param массив $context * @return null */ public function info($message, array $context = array()); /** * Детальная отладочная информация. * * @param строка $message * @param массив $context * @return null */ public function debug($message, array $context = array()); /** * Протоколирование с произвольным уровнем. * * @param смешанный $level * @param строка $message * @param массив $context * @return null */ public function log($level, $message, array $context = array()); } 4. Интерфейс Psr\Log\LoggerAwareInterface <?php namespace Psr\Log; /** * Описывает систему, поддерживающую протоколирование. */ interface LoggerAwareInterface { /** * Устанавливает объект протоколирования в объект * системы, поддерживающей протоколирование. * * @param LoggerInterface $logger * @return null */ public function setLogger(LoggerInterface $logger); } 5. Класс Psr\Log\LogLevel <?php namespace Psr\Log; /** * Описывает уровни протоколирования. */ class LogLevel { const EMERGENCY = 'emergency'; const ALERT = 'alert'; const CRITICAL = 'critical'; const ERROR = 'error'; const WARNING = 'warning'; const NOTICE = 'notice'; const INFO = 'info'; const DEBUG = 'debug'; } PSR-4 – Улучшенная автозагрузкаСлова «НЕОБХОДИМО» / «ДОЛЖНО» ("MUST"), «НЕДОПУСТИМО» ("MUST NOT"), «ТРЕБУЕТСЯ» ("REQUIRED"), «НУЖНО» ("SHALL"), «НЕ ПОЗВОЛЯЕТСЯ» ("SHALL NOT"), «СЛЕДУЕТ» ("SHOULD"), «НЕ СЛЕДУЕТ» ("SHOULD NOT"), «РЕКОМЕНДУЕТСЯ» ("RECOMMENDED"), «МОЖЕТ» / «ВОЗМОЖНО» ("MAY") и «НЕОБЯЗАТЕЛЬНО» ("OPTIONAL") в этом документе следует понимать так, как это описано в RFC 2119 (и его переводе). 1. Обзор Данный стандарт описывает спецификацию автозагрузки классов на основе путей к файлам. Он полностью совместим (и может использоваться как дополнение) с любой другой спецификацией автозагрузки, включая PSR-0. Данный стандарт также описывает правила размещения файлов, предназначенных для автозагрузки. 2. Спецификация
При загрузке файла, соответствующего полностью определённому имени класса, используются следующие правила:
В реализации автозагрузчика НЕДОПУСТИМО порождать исключения, ошибочные ситуации любого уровня и НЕ СЛЕДУЕТ возвращать какое бы то ни было значение. 3. Примеры В таблице ниже представлены примеры соответствий полностью определённого имени класса, префикса пространства имён, базового каталога и итогового пути к файлу.
Примеры реализации автозагрузчиков, соответствующих данной спецификации, представлены в файле с примерами. Примеры реализации НЕДОПУСТИМО рассматривать как часть спецификации, т.к. они МОГУТ измениться в любое время. Запись опубликована 2014.09.15 автором admin745wesj Материал взят с сайта:Главная > Программирование > PHP |