Правила     Закладки     Карма    Календарь    Журналы    Помощь    Поиск    PDA    Чат   
        СМС-ки
   
Пейджер выключен!
Страницы: (2) [1] 2  ( Перейти к первому непрочитанному сообщению )  
Фильтр авторов:    показать 
  скрыть
  Ответ в темуСоздание новой темыСоздание опроса

> SOLID что такие и с чем едят, серия статей части. S и O от SOLID
bestxp  
 ۩  [x]    Дата
Цитировать сообщение

Пользователя сейчас нет на форуме



орангутанг
******

Профиль
Группа: Форумчанин
Завсегдатай форума
Сообщений: 2059
Пользователь №: 36605
На форуме: 4 года, 3 месяца, 11 дней
Карма: 113




1. Вступление
Сегодня мы поговорим об ООП, в частности о принципах SOLID, управлению зависимостями в php-коде и Ioc контейнерах (Inversion of Control).
Аббревиатура SOLID является самой известной (после ООП), она говорит нам о 5 принципах “хорошего дизайна ПО”. Автор аббревиатуры SOLID - Роберт Мартин, он придумал саму аббревиатуру и описал 5 принципов . На самом деле он описал больше, но звучных буквосочетаний не придумал, поэтому стоит почитать первоисточник. Заметьте, что Мартин именно описал принципы, он не является их автором. Зачем эти правила? Они помогают построить архитектуру приложения, которое со временем, возможно, будет проще (дешевле) поддерживать и развивать. Помогают писать повторно используемый код.
2. Принципы SOLID
1. Принцип единой ответственности (Single Responsibility Principle)
2. Принцип открытия/закрытия (Open/Closed Principle)
3. Принцип подстановки Лискоу (Liskov Substitution Principle)
4. Принцип отделения интерфейса (Interface Segregation Principle)
5. Принцип инверсии зависимостей (Dependency Inversion Principle) с которым как раз и тесно связана вторая часть лекции управление зависимостями в php-коде и Ioc контейнеры (Inversion of Control).

В данной статье будет рассмотрен c примерами несколько первых



2.1. Single Responsibility Principle - Принцип единой ответственности
Если сказать просто, то принцип SRP можно расшифровать как: На каждый объект должна быть возложена одна единственная обязанность. Или не должно быть больше одной причины для изменения класса. Почему? Потому что это ведет к хрупкости дизайна (пишем один функционал - ”отваливается” другой). То есть, если часть некоторого модуля не имеет никаких ссылок на другую часть этого модуля, то эти части можно разделить на разные модули. Если модули могут меняться независимо, то тоже разделить нужно.
Начинать стоит с простого: если можно отделить – надо отделить. Необходимо чтобы внутри одного модуля весь функционал был связан между собой (высокая связанность, high cohesion, такое словосочетание вы наверное слышали). Принцип работает только в одну сторону: если подмножество A некоторого модуля не имеет ссылок на подмножество B, то это не значит что B не имеет ссылок на A, причем скорее всего именно B будет ссылаться на A.
Следуя данному принципу весь код будет распадаться на множество маленьких модулей, многие из которых выродятся до одной функции. Это нормально, даже хорошо. Функции потом можно группировать в модули по логической связности, добиваясь все того же high cohesion.
Модули будут зависеть друг от друга, они будут выстраиваться в ориентированный граф. Расположив зависимости сверху вниз можно условно разделить модули на верхне- и нижне- уровневые. На само “пространство”, в котором мы пытаемся упорядочить модули, многомерно. Придумать одно отношение порядка для всего этого пространства невозможно. Но для двух модулей, между которыми есть путь, можно сказать какой из них верхнеуровневый, а какой нижнеуровневый.
Зависимость между модулями может быть:
• Ссылочной, когда модуль A непосредственно обращается к модулю B, его функциям и данным.
• Наследованием, когда модуль A является частным случаем B.
• Зависимостью по состоянию, когда два модуля оперируют одним внешним состоянием (глобальные переменные, файлы, БД) и влияют на работу друг друга. Это плохая зависимость, от нее надо избавляться.
• Зависимостью по времени. Когда для работы одного модуля требуется вызов функций другого модуля в нужные моменты. Это самый плохой вид зависимости, он него надо избавляться однозначно всеми возможными способами.
Пример:
class Person
{
public function getName()...
public function setName()...
public function echoName()...
}

Следуя приведенному принципу видно, что этот класс можно разделить. Сейчас он отвечает за установку, получение данных, а также за какой-то их вывод. Лучшей структурой, опять же руководствуясь принципом Single Responsibility, будет:
class Person 
{
public function getName()...
public function setName()...
}

class NameView
{
public function doOutput($name)...
}

class Controller
{
public function personAction()
{
// Get a person from some data source
$person = ...;
$view = new NameView();
$view->doOutput($person->getName());
}
}

Здесь мы выделили вывод данных в отдельный класс и организовали класс контроллер. Пример может показаться надуманным, но второй вариант кода в действительности более устойчивый к изменениям и лучше подходит для повторного использования. Person класс просто хранит данные (в него легко при надобности можно будет добавить новые данные, что не затронет остальной функционал), класс NameView только для вывода (а в дальнейшем выводить может понадобиться разными вариантами) и класс Controller реагирует на запросы (которые опять же здесь могут добавляться, изменяться, не трогая другие классы), получая все необходимые данные и выполняя что необходимо через другие классы и их методы.
2.2. Open/Closed Principle - Принцип открытия/закрытия
Этот принцип можно расшифровать, как программные сущности должны быть открыты для расширения, но закрыты для модификации. Возможно, Вы слышали другую его формулировку: "Не хакай ядро". На практике этот принцип обеспечивается путем правильного написания классов и чрезвычайно избирательного использования ключевых слов "private" и "final", что позволяет в дальнейшем расширить базовый класс, не внося изменений в него.
Пример:
class Person 
{
public function getName()...
public function setName()...
}

class Employee extends Person
{
...
}

class NameView
{
public function doOutput(Person $person)
{
if (Person instanceof Employee)
{
echo ‘Hi employee ‘, $person->getName();
}
else
{
Echo ‘Hi person ‘, $person->getName();
}
}
}


class Controller
{
public function personAction()
{
// Get a person from some data source
$person = ...;
$view = new NameView();
$view->doOutput($person);
}
}

В этом примере добавился класс Employee вроде логично расширяющий класс Person. Но в классе NameView добавились проверки на тип, а что если нам понадобиться добавить новый тип, например студент, расширяющий класс Person . Добавляя его, нам придется изменять и класс NameView, что не есть хорошо. Следуя описанному принципу эту проблему можно решить двумя способами. Либо добавить класс PersonWelcomeMessage, который генерирует приветствие, и расширить его в EmployeeWelcomeMessage и StudentWelcomeMessage для каждого типа, но это несколько избыточно в данном примере. Либо можно добавить в класс Person функцию getTitle, которая и определит что это за персона, изменяясь в каждом наследнике класса Person. В этом случае при добавлении нового класса, расширяющего Person, будет необходимо работать только с этим новым классом, не изменяя остальные.
class Person 
{
...
public function getName()...
public function setName()...

public function getTitle()
{
return ‘person’;
}
}


class Employee extends Person
{
...
public function getTitle()
{
return ‘employee’;
}
}


class NameView
{
public function doOutput(Person $person)
{
echo ‘Hi ‘, $person->getTitle(), ‘ ‘, $person->getName();
}
}


Еще пример:
class Logger { 
public function log($text) { // Сохраняем текст в лог (лог у нас будет храниться в файлах) }
}
class Product {
private $_logger;
public function __construct() { $this->_logger = new Logger(); }
/* Продать товар */
public function sale() {
// … продаем товар
// Записываем дату продажи в лог $this->_logger->log('Sale time: '. time());

}
}

Чем плох этот код? Изменение требований: лог надо хранить в БД.
class DBLogger { 
public function log($text) { // Сохраняем что-то в лог (лог у нас будет храниться в БД) }
}
class Product {
private $_logger;
public function __construct() {
// Меняем класс Product, чтоб поменять логер (помните про SRP?)
$this->_logger = new DBLogger();
}
/* Продать товар*/
public function sale() {
// Продаем товар // ... //
Записываем дату продажи в лог $this->_logger->log('Sale time: '. time());
}
}

А если, снова какие изменения? Готовимся к борьбе с изменениями требований (а не к борьбе с менеджерами).
interface ILogger { 
public function log($text);
}
class Logger implements ILogger {
public function log($text) { // Сохраняем что-то в лог (лог у нас будет храниться в файлах) }
}
class DBLogger implements ILogger {
public function log($text) { // Сохраняем что-то в лог (лог у нас будет храниться в БД) }
}
class Product {
private $_logger;
public function __construct(ILogger $logger) { $this->_logger = $logger; }
/* Продать товар */
public function sale() {
// Продаем товар
// ...
// Записываем дату продажи в лог

$this->_logger->log('Sale time: '. time());
}
}


Готов выслушать вопросы.


--------------------
PMПисьмо на e-mail пользователюСайт пользователяICQ
    4   Для быстрого поиска похожих сообщений выделите 1-2 слова в тексте и нажмите сюда Для быстрой цитаты из этого сообщения выделите текст и нажмите сюда
OleKh  
Дата
Цитировать сообщение

Пользователя сейчас нет на форуме



Здесь живет
******

Профиль
Группа: Форумчанин
Завсегдатай форума
Сообщений: 753
Пользователь №: 32032
На форуме: 5 лет, 1 месяц, 23 дня
Карма: 18




Цитата (bestxp @ 25.03.2013 - 21:06)

// Записываем дату продажи в лог
$this->_logger->log('Sale time: '. time());

И как понять, куда записывается лог, в файл или в базу? если название метода одинаково
PMПисьмо на e-mail пользователю
    0   Для быстрого поиска похожих сообщений выделите 1-2 слова в тексте и нажмите сюда Для быстрой цитаты из этого сообщения выделите текст и нажмите сюда
OleKh  
[x] Дата
Цитировать сообщение

Пользователя сейчас нет на форуме



Здесь живет
******

Профиль
Группа: Форумчанин
Завсегдатай форума
Сообщений: 753
Пользователь №: 32032
На форуме: 5 лет, 1 месяц, 23 дня
Карма: 18




+1
Супер, вот реализовал интерфейс.

interface ILogger {
public function log($text);
}
class Logger implements ILogger {
public function log($text) { // Сохраняем что-то в лог (лог у нас будет храниться в файлах) }
echo 'File - '.$text;
}
}

class DBLogger implements ILogger {
public function log($text) { // Сохраняем что-то в лог (лог у нас будет храниться в БД) }
echo 'DB - '.$text;
}
}

class Product {
private $_logger;
public function __construct(ILogger $logger) { $this->_logger = $logger; }

public function sale() {
$this->_logger->log('Sale time: '. time());
}
}

//$file = false;
$file = true;

// if($file == true) { // this is not necessary... )

if(!$file) {
$log = new DBLogger;
} else {
$log = new Logger;
}

$product = new Product ($log);
$product->sale();

PMПисьмо на e-mail пользователю
    0   Для быстрого поиска похожих сообщений выделите 1-2 слова в тексте и нажмите сюда Для быстрой цитаты из этого сообщения выделите текст и нажмите сюда
Игорь_Vasinsky  
Дата
Цитировать сообщение

Пользователя сейчас нет на форуме



c начала 2017 года в Новосибирске.
******

Профиль
Журнал
Группа: ★ЛжеЭксперт★
Завсегдатай форума
Сообщений: 26381
Пользователь №: 21350
На форуме: 7 лет, 2 месяца, 13 дней
Карма: 737




Цитата
if($file == true) {

ай-ай-ай

Для ТС - спасиб. удобно рассматривать описание какой либо логики "на пальцах" - усваивается лучше.

в принципе так и должна быть спроектирована и логика и реализация при ооп подходе.

Жду продолжения.


--------------------
HTML, CSS (Bootstrap), JS(JQuery, ExtJS), PHP, MySQL, MSSql (TSql, BI OLAP, MDX), Git, SVN, CodeIgnater, Yii 2, JiRA, Redmine, Bitbucket, Composer
PMПисьмо на e-mail пользователю
    1   Для быстрого поиска похожих сообщений выделите 1-2 слова в тексте и нажмите сюда Для быстрой цитаты из этого сообщения выделите текст и нажмите сюда
Игорь_Vasinsky  
Дата
Цитировать сообщение

Пользователя сейчас нет на форуме



c начала 2017 года в Новосибирске.
******

Профиль
Журнал
Группа: ★ЛжеЭксперт★
Завсегдатай форума
Сообщений: 26381
Пользователь №: 21350
На форуме: 7 лет, 2 месяца, 13 дней
Карма: 737




оффтоп
кстати, хочу сообщить на будущее что конструкции вида

Цитата
if($file)

на моей практике некоторые хостеры не понимали, но понимали

if($file === true) или if(!$file)


--------------------
HTML, CSS (Bootstrap), JS(JQuery, ExtJS), PHP, MySQL, MSSql (TSql, BI OLAP, MDX), Git, SVN, CodeIgnater, Yii 2, JiRA, Redmine, Bitbucket, Composer
PMПисьмо на e-mail пользователю
    0   Для быстрого поиска похожих сообщений выделите 1-2 слова в тексте и нажмите сюда Для быстрой цитаты из этого сообщения выделите текст и нажмите сюда
killer8080  
Дата
Цитировать сообщение

Пользователя сейчас нет на форуме



Здесь живет
******

Профиль
Группа: Эксперт
Группа переписки
Сообщений: 8925
Пользователь №: 26630
На форуме: 6 лет, 2 месяца, 25 дней
Карма: 603




Цитата (Игорь_Vasinsky @ 25.03.2013 - 23:23)
кстати, хочу сообщить на будущее что конструкции вида

Цитата if($file)
на моей практике некоторые хостеры не понимали, но понимали

if($file === true) или if(!$file)

ты наверно что то путаешь blink.gif
PMПисьмо на e-mail пользователю
    0   Для быстрого поиска похожих сообщений выделите 1-2 слова в тексте и нажмите сюда Для быстрой цитаты из этого сообщения выделите текст и нажмите сюда
Игорь_Vasinsky  
Дата
Цитировать сообщение

Пользователя сейчас нет на форуме



c начала 2017 года в Новосибирске.
******

Профиль
Журнал
Группа: ★ЛжеЭксперт★
Завсегдатай форума
Сообщений: 26381
Пользователь №: 21350
На форуме: 7 лет, 2 месяца, 13 дней
Карма: 737




да нет. точно помню что не реагировал. пришлось идти от обратного !true


--------------------
HTML, CSS (Bootstrap), JS(JQuery, ExtJS), PHP, MySQL, MSSql (TSql, BI OLAP, MDX), Git, SVN, CodeIgnater, Yii 2, JiRA, Redmine, Bitbucket, Composer
PMПисьмо на e-mail пользователю
    0   Для быстрого поиска похожих сообщений выделите 1-2 слова в тексте и нажмите сюда Для быстрой цитаты из этого сообщения выделите текст и нажмите сюда
killer8080  
Дата
Цитировать сообщение

Пользователя сейчас нет на форуме



Здесь живет
******

Профиль
Группа: Эксперт
Группа переписки
Сообщений: 8925
Пользователь №: 26630
На форуме: 6 лет, 2 месяца, 25 дней
Карма: 603




Цитата (Игорь_Vasinsky @ 25.03.2013 - 23:49)
да нет. точно помню что не реагировал. пришлось идти от обратного !true

Такого быть не может, причина точно была в чем то другом. smile.gif
PMПисьмо на e-mail пользователю
    0   Для быстрого поиска похожих сообщений выделите 1-2 слова в тексте и нажмите сюда Для быстрой цитаты из этого сообщения выделите текст и нажмите сюда
I++  
[x] Дата
Цитировать сообщение

Пользователя сейчас нет на форуме



Здесь живет
******

Профиль
Группа: Форумчанин
Завсегдатай форума
Сообщений: 894
Пользователь №: 30580
На форуме: 5 лет, 6 месяцев, 3 дня
Карма: 43




Я примерно вот такой говнокод юзаю:

<?php

interface
ILogger
{
public static function log($text);
}

class ScreenLogger implements ILogger
{
public static function log($text)
{
echo 'Screen - '.$text;
}
}


class FileLogger implements ILogger
{
public static function log($text)
{
echo 'File - '.$text;
}
}


class DBLogger implements ILogger
{
public static function log($text)
{
echo 'DB - '.$text;
}
}


class Log
{
protected static $log_type = 'ScreenLogger';

public static function type($class_name)
{
if(class_exists($class_name) && is_subclass_of($class_name, ILogger)){
self::$log_type = $class_name;
}else{
$log = self::$log_type;
$log::log('Class '.$class_name.' not exists or not implements ILogger');
}
}


public static function msg($text)
{
$log = self::$log_type;
$log::log($text);
}
}


class Product
{
public function sale()
{
Log::msg('Sale time: '. time());
}
}


Log::type(FileLogger);

$product = new Product();
$product->sale();

//--------------------------------//

$vip_client = true;

if(!$vip_client){
$product->sale();
}else{
Log::type(DBLogger);
$product->sale();
}


Таким образом я могу пройтись по всему проекту с Log и увидить где у меня логи идут, а когда DBLogger, FileLogger, AssLogir, ShitLager ну есть кое какие проблемы, а второе пихать в конструктор и таскать с собой это постоянно утомительно, можно вообще даже тип не объявлять задать стандартный какой будет и все и похрену дым, а где нужно прописал тип, но могут быть проблемы кое какие но об этом я смолчу :)
PMПисьмо на e-mail пользователюСайт пользователя
    0   Для быстрого поиска похожих сообщений выделите 1-2 слова в тексте и нажмите сюда Для быстрой цитаты из этого сообщения выделите текст и нажмите сюда
bestxp  
 ۩  Дата
Цитировать сообщение

Пользователя сейчас нет на форуме



орангутанг
******

Профиль
Группа: Форумчанин
Завсегдатай форума
Сообщений: 2059
Пользователь №: 36605
На форуме: 4 года, 3 месяца, 11 дней
Карма: 113




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

PS

в твоем примере is_subclass_of($class_name, ILogger) можно заменить на ($class_name instanceof ILogger)

принимает как кавычки так и без для 'ILogger' а по хорошему использовать типизацию, тогда не нужно будет делать и проверок
public static function type(ILogger $class_name) это будет правильнее


--------------------
PMПисьмо на e-mail пользователюСайт пользователяICQ
    0   Для быстрого поиска похожих сообщений выделите 1-2 слова в тексте и нажмите сюда Для быстрой цитаты из этого сообщения выделите текст и нажмите сюда
OleKh  
Дата
Цитировать сообщение

Пользователя сейчас нет на форуме



Здесь живет
******

Профиль
Группа: Форумчанин
Завсегдатай форума
Сообщений: 753
Пользователь №: 32032
На форуме: 5 лет, 1 месяц, 23 дня
Карма: 18




Игорь_Vasinsky, спс, по поводу if($file) или if(!$file), исправил, вспомнил кое-что, т.к. не моё, процитирую автора. Как говорят чтобы запомнить.
Цитата
... в жизне - правда одна, лжи много  в программировании - наоборот - одна ложь, остальное правда ...

Теперь мне понятно, почему это нужно было хорошенько запомнить.
Cорри за оффтоп.
PMПисьмо на e-mail пользователю
    0   Для быстрого поиска похожих сообщений выделите 1-2 слова в тексте и нажмите сюда Для быстрой цитаты из этого сообщения выделите текст и нажмите сюда
SlavaFr  
Дата
Цитировать сообщение

Пользователя сейчас нет на форуме



Новичок
******

Профиль
Журнал
Группа: Форумчанин
Завсегдатай форума
Сообщений: 1552
Пользователь №: 22453
На форуме: 6 лет, 11 месяцев, 26 дней
Карма: 105




Цитата (OleKh @ 25.03.2013 - 19:45)
И как понять, куда записывается лог, в файл или в базу? если название метода одинаково

Попытка узнать эту информацию является первым нарушением правила "Single Responsibility". В функции sale понадобилось произвести log, какого подтипа этот лог, и имеется ли он вообще, тебя интересовать не должно. В том и прелесть Single Responsibility, что ты занимаешься только одной задачей, а не пытаешься решить все проблемы сразу smile.gif


--------------------
↓↓↓↓↓↓↓↓↓↓
ответ может быть здесь
или в mysql_error();
PMПисьмо на e-mail пользователю
    1   Для быстрого поиска похожих сообщений выделите 1-2 слова в тексте и нажмите сюда Для быстрой цитаты из этого сообщения выделите текст и нажмите сюда
OleKh  
Дата
Цитировать сообщение

Пользователя сейчас нет на форуме



Здесь живет
******

Профиль
Группа: Форумчанин
Завсегдатай форума
Сообщений: 753
Пользователь №: 32032
На форуме: 5 лет, 1 месяц, 23 дня
Карма: 18




Цитата (bestxp @ 25.03.2013 - 21:06)

2.2. Open/Closed Principle - Принцип открытия/закрытия ...
На практике этот принцип обеспечивается путем правильного написания классов и чрезвычайно избирательного использования ключевых слов "private" и "final",

) правильное написание классов - должно быть неотъемлемой частью всех принципов SOLID. Или нет?
Пример Open/Closed Principle приведен как-то непонятно, всё в кучу намешалось. В поисках более подробной информации нашел очень похожий материал.
http://blogerator.ru/page/oop-tverdye-obek...ncipy-solid-php

Цитата
На практике этот принцип обеспечивается путем чрезвычайно избирательного использования ключевых слов «private» и «final».


Цитата (bestxp @ 25.03.2013 - 21:06)
Следуя описанному принципу эту проблему можно решить двумя способами. Либо добавить класс PersonWelcomeMessage, который генерирует приветствие, и расширить его в EmployeeWelcomeMessage и StudentWelcomeMessage для каждого типа, но это несколько избыточно в данном примере. Либо можно добавить в класс Person функцию getTitle, которая и определит что это за персона, изменяясь в каждом наследнике класса Person.


Следуя описанному принципу нужно что-то открыть (public), а что-то закрыть(private, protected, final), или наоборот, а не расширять и добавлять. В общем, пример не совсем в тему.
PMПисьмо на e-mail пользователю
    0   Для быстрого поиска похожих сообщений выделите 1-2 слова в тексте и нажмите сюда Для быстрой цитаты из этого сообщения выделите текст и нажмите сюда
bestxp  
 ۩  Дата
Цитировать сообщение

Пользователя сейчас нет на форуме



орангутанг
******

Профиль
Группа: Форумчанин
Завсегдатай форума
Сообщений: 2059
Пользователь №: 36605
На форуме: 4 года, 3 месяца, 11 дней
Карма: 113




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

то есть вместо кучи If и case мы заменяем на IоС контейнер и делаем кучу нужных классов, как пример логгер, мы не пишем туда if , а пишем новый логгер и его используем, а старый лежит или используется в другом месте.


--------------------
PMПисьмо на e-mail пользователюСайт пользователяICQ
    0   Для быстрого поиска похожих сообщений выделите 1-2 слова в тексте и нажмите сюда Для быстрой цитаты из этого сообщения выделите текст и нажмите сюда
OleKh  
Дата
Цитировать сообщение

Пользователя сейчас нет на форуме



Здесь живет
******

Профиль
Группа: Форумчанин
Завсегдатай форума
Сообщений: 753
Пользователь №: 32032
На форуме: 5 лет, 1 месяц, 23 дня
Карма: 18




Цитата (bestxp @ 27.03.2013 - 20:36)
класс закрыт для изменения и открыт для дополнения

В Open/Closed Principle есть 2 примера не связанные между собой. О каком классе идет речь?

В первом примере не нахожу ничего закрытого для изменений. Во втором есть одно приватное свойство, оставшееся как-то без внимания.
PMПисьмо на e-mail пользователю
    0   Для быстрого поиска похожих сообщений выделите 1-2 слова в тексте и нажмите сюда Для быстрой цитаты из этого сообщения выделите текст и нажмите сюда
  Быстрый ответ
Информация о Госте
Введите Ваше имя
Кнопки кодов
Для вставки цитаты, выделите нужный текст и
НАЖМИТЕ СЮДА
Введите сообщение
Смайлики
:huh:  :o  ;) 
:P  :D  :lol: 
B)  :rolleyes:  <_< 
:)  :angry:  :( 
:unsure:  :blink:  :ph34r: 
     
Показать всё

Опции сообщения  Включить смайлики?
 Включить подпись?
 
1 Пользователей читают эту тему (1 Гостей и 0 Скрытых Пользователей)
0 Пользователей:

Опции темыСтраницы: (2) [1] 2  Ответ в темуСоздание новой темыСоздание опроса