Spec-Zone.ru › Phalcon 2

Маршрутизация

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

Определение маршрутов

Phalcon\Mvc\Router предоставляет расширенные возможности маршрутизации. В режиме MVC вы можете определять маршруты и сопоставлять их с необходимыми контроллерами/действиями. Маршрут определяется следующим образом:

// Create the router
$router = new \Phalcon\Mvc\Router();

//Define a route
$router->add(
    "/admin/users/my-profile",
    array(
        "controller" => "users",
        "action"     => "profile",
    )
);

//Another route
$router->add(
    "/admin/users/change-password",
    array(
        "controller" => "users",
        "action"     => "changePassword",
    )
);

$router->handle();

Метод add() получает в качестве первого параметра шаблон и необязательно набор путей в качестве второго параметра. В этом случае, если URI точно равен: /admin/users/my-profile, то будет выполнен контроллер «users» с действием «profile». В настоящее время маршрутизатор не выполняет контроллер и действие, он только собирает эту информацию, чтобы сообщить правильному компоненту (например, Phalcon\Mvc\Dispatcher), что это контроллер/действие, которое он должен выполнить.

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

// Create the router
$router = new \Phalcon\Mvc\Router();

//Define a route
$router->add(
    "/admin/:controller/a/:action/:params",
    array(
        "controller" => 1,
        "action"     => 2,
        "params"     => 3,
    )
);

В приведенном примере, используя подстановки, мы делаем маршрут допустимым для многих URI. Например, обратившись к следующему URL (/admin/users/a/delete/dave/301), тогда:

Контроллер users
Действие delete
Параметр dave
Параметр 301

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

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

Эти подстановки помогают писать регулярные выражения, которые более читабельны для разработчиков и легче понимаются. Поддерживаются следующие подстановки:

Подстановка Регулярное выражение Использование
/:module /([a-zA-Z0-9_-]+) Сопоставляет допустимое имя модуля только с буквенно-цифровыми символами
/:controller /([a-zA-Z0-9_-]+) Сопоставляет допустимое имя контроллера только с буквенно-цифровыми символами
/:action /([a-zA-Z0-9_]+) Сопоставляет допустимое имя действия только с буквенно-цифровыми символами
/:params (/.*)* Сопоставляет список необязательных слов, разделенных косыми чертами. Используйте только эту подстановку в конце маршрута
/:namespace /([a-zA-Z0-9_-]+) Сопоставляет имя пространства имен одного уровня
/:int /([0-9]+) Сопоставляет целое число параметра

Имена контроллеров написаны в camelized стиле, это означает, что символы (-) и (_) удаляются, а следующий символ преобразуется в заглавную букву. Например, some_controller преобразуется в SomeController.

Поскольку вы можете добавлять столько маршрутов, сколько вам нужно, используя add(), порядок добавления маршрутов указывает на их релевантность. Последние добавленные маршруты более релевантны, чем первые. Внутренне все определенные маршруты просматриваются в обратном порядке, пока Phalcon\Mvc\Router не найдет тот, который соответствует заданному URI, и не обработает его, проигнорировав остальные.

Параметры с именами

Пример ниже демонстрирует, как определять имена для параметров маршрута:

$router->add(
    "/news/([0-9]{4})/([0-9]{2})/([0-9]{2})/:params",
    array(
        "controller" => "posts",
        "action"     => "show",
        "year"       => 1, // ([0-9]{4})
        "month"      => 2, // ([0-9]{2})
        "day"        => 3, // ([0-9]{2})
        "params"     => 4, // :params
    )
);

В приведенном примере маршрут не определяет часть «controller» или «action». Эти части заменяются фиксированными значениями («posts» и «show»). Пользователь не будет знать контроллер, который фактически обрабатывается запросом. Внутри контроллера эти именованные параметры можно получить следующим образом:

class PostsController extends \Phalcon\Mvc\Controller
{

    public function indexAction()
    {

    }

    public function showAction()
    {

        // Return "year" parameter
        $year = $this->dispatcher->getParam("year");

        // Return "month" parameter
        $month = $this->dispatcher->getParam("month");

        // Return "day" parameter
        $day = $this->dispatcher->getParam("day");

    }

}

Обратите внимание, что значения параметров получаются из диспетчера. Это происходит потому, что это компонент, который в конечном итоге взаимодействует с драйверами вашего приложения. Кроме того, есть и другой способ создания именованных параметров как части шаблона:

$router->add(
    "/documentation/{chapter}/{name}.{type:[a-z]+}",
    array(
        "controller" => "documentation",
        "action"     => "show"
    )
);

Вы можете получить их значения так же, как и раньше:

class DocumentationController extends \Phalcon\Mvc\Controller
{

    public function showAction()
    {

        // Returns "name" parameter
        $name = $this->dispatcher->getParam("name");

        // Returns "type" parameter
        $type = $this->dispatcher->getParam("type");

    }

}

Короткая синтаксис

Если вам не нравится использовать массив для определения путей маршрута, доступен и альтернативный синтаксис. Следующие примеры дают тот же результат:

// Short form
$router->add("/posts/{year:[0-9]+}/{title:[a-z\-]+}", "Posts::show");

// Array form
$router->add(
    "/posts/([0-9]+)/([a-z\-]+)",
    array(
       "controller" => "posts",
       "action"     => "show",
       "year"       => 1,
       "title"      => 2,
    )
);

Смешение массива и короткого синтаксиса

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

//First position must be skipped because it is used for
//the named parameter 'country'
$router->add('/news/{country:[a-z]{2}}/([a-z+])/([a-z\-+])',
    array(
        'section' => 2, //Positions start with 2
        'article' => 3
    )
);

Маршрутизация к модулям

Вы можете определять маршруты, пути которых включают модули. Это особенно подходит для приложений с несколькими модулями. Можно определить маршрут по умолчанию, который включает подстановку имени модуля:

$router = new Phalcon\Mvc\Router(false);

$router->add('/:module/:controller/:action/:params', array(
    'module' => 1,
    'controller' => 2,
    'action' => 3,
    'params' => 4
));

В этом случае имя модуля всегда должно быть частью URL. Например, следующий URL: /admin/users/edit/sonny, будет обработан как:

Модуль admin
Контроллер users
Действие edit
Параметр sonny

Или вы можете связать определенные маршруты с определенными модулями:

$router->add("/login", array(
    'module' => 'backend',
    'controller' => 'login',
    'action' => 'index',
));

$router->add("/products/:action", array(
    'module' => 'frontend',
    'controller' => 'products',
    'action' => 1,
));

Или связать их со специфическими пространствами имен:

$router->add("/:namespace/login", array(
    'namespace' => 1,
    'controller' => 'login',
    'action' => 'index'
));

Пространства имен/имена классов должны передаваться раздельно:

$router->add("/login", array(
    'namespace' => 'Backend\Controllers',
    'controller' => 'login',
    'action' => 'index'
));

Ограничения по HTTP-методам

Когда вы добавляете маршрут, используя просто add(), маршрут будет доступен для любого HTTP-метода. Иногда можно ограничить маршрут определенным методом, это особенно полезно при создании RESTful приложений:

// This route only will be matched if the HTTP method is GET
$router->addGet("/products/edit/{id}", "Products::edit");

// This route only will be matched if the HTTP method is POST
$router->addPost("/products/save", "Products::save");

// This route will be matched if the HTTP method is POST or PUT
$router->add("/products/update")->via(array("POST", "PUT"));

Использование преобразований

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

//The action name allows dashes, an action can be: /products/new-ipod-nano-4-generation
$router
    ->add('/products/{slug:[a-z\-]+}', array(
        'controller' => 'products',
        'action' => 'show'
    ))
    ->convert('slug', function($slug) {
        //Transform the slug removing the dashes
        return str_replace('-', '', $slug);
    });

Группы маршрутов

Если набор маршрутов имеет общие пути, их можно сгруппировать для удобства поддержки:

$router = new \Phalcon\Mvc\Router();

//Create a group with a common module and controller
$blog = new \Phalcon\Mvc\Router\Group(array(
    'module' => 'blog',
    'controller' => 'index'
));

//All the routes start with /blog
$blog->setPrefix('/blog');

//Add a route to the group
$blog->add('/save', array(
    'action' => 'save'
));

//Add another route to the group
$blog->add('/edit/{id}', array(
    'action' => 'edit'
));

//This route maps to a controller different than the default
$blog->add('/blog', array(
    'controller' => 'blog',
    'action' => 'index'
));

//Add the group to the router
$router->mount($blog);

Вы можете перемещать группы маршрутов в отдельные файлы, чтобы улучшить организацию и повторное использование кода в приложении:

class BlogRoutes extends Phalcon\Mvc\Router\Group
{
    public function initialize()
    {
        //Default paths
        $this->setPaths(array(
            'module' => 'blog',
            'namespace' => 'Blog\Controllers'
        ));

        //All the routes start with /blog
        $this->setPrefix('/blog');

        //Add a route to the group
        $this->add('/save', array(
            'action' => 'save'
        ));

        //Add another route to the group
        $this->add('/edit/{id}', array(
            'action' => 'edit'
        ));

        //This route maps to a controller different than the default
        $this->add('/blog', array(
            'controller' => 'blog',
            'action' => 'index'
        ));

    }
}

Затем смонтировать группу в маршрутизаторе:

//Add the group to the router
$router->mount(new BlogRoutes());

Сопоставление маршрутов

Для того чтобы маршрутизатор проверил маршрут, соответствующий заданному URI, должен быть передан допустимый URI. По умолчанию URI маршрутизации берется из переменной $_GET[‘_url’], которая создается модулем модуля переписывания. Несколько правил переписывания, которые отлично работают с Phalcon:

RewriteEngine On
RewriteCond   %{REQUEST_FILENAME} !-d
RewriteCond   %{REQUEST_FILENAME} !-f
RewriteRule   ^(.*)$ index.php?_url=/$1 [QSA,L]

Следующий пример демонстрирует, как использовать этот компонент в автономном режиме:

// Creating a router
$router = new \Phalcon\Mvc\Router();

// Define routes here if any
// ...

// Taking URI from $_GET["_url"]
$router->handle();

// or Setting the URI value directly
$router->handle("/employees/edit/17");

// Getting the processed controller
echo $router->getControllerName();

// Getting the processed action
echo $router->getActionName();

//Get the matched route
$route = $router->getMatchedRoute();

Именование маршрутов

Каждый маршрут, который добавляется в маршрутизатор, хранится внутри как объект Phalcon\Mvc\Router\Route. Этот класс инкапсулирует все детали каждого маршрута. Например, мы можем присвоить имя пути, чтобы однозначно идентифицировать его в нашем приложении. Это особенно полезно, если вы хотите создать URL из него.

$route = $router->add("/posts/{year}/{title}", "Posts::show");

$route->setName("show-posts");

//or just

$router->add("/posts/{year}/{title}", "Posts::show")->setName("show-posts");

Затем, например, используя компонент Phalcon\Mvc\Url, мы можем создать маршруты по его имени:

// returns /posts/2012/phalcon-1-0-released
echo $url->get(array(
    "for" => "show-posts",
    "year" => "2012",
    "title" => "phalcon-1-0-released"
));

Примеры использования

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

// matches "/system/admin/a/edit/7001"
$router->add(
    "/system/:controller/a/:action/:params",
    array(
        "controller" => 1,
        "action"     => 2,
        "params"     => 3
    )
);

// matches "/es/news"
$router->add(
    "/([a-z]{2})/:controller",
    array(
        "controller" => 2,
        "action"     => "index",
        "language"   => 1
    )
);

// matches "/es/news"
$router->add(
    "/{language:[a-z]{2}}/:controller",
    array(
        "controller" => 2,
        "action"     => "index"
    )
);

// matches "/admin/posts/edit/100"
$router->add(
    "/admin/:controller/:action/:int",
    array(
        "controller" => 1,
        "action"     => 2,
        "id"         => 3
    )
);

// matches "/posts/2010/02/some-cool-content"
$router->add(
    "/posts/([0-9]{4})/([0-9]{2})/([a-z\-]+)",
    array(
        "controller" => "posts",
        "action"     => "show",
        "year"       => 1,
        "month"      => 2,
        "title"      => 4
    )
);

// matches "/manual/en/translate.adapter.html"
$router->add(
    "/manual/([a-z]{2})/([a-z\.]+)\.html",
    array(
        "controller" => "manual",
        "action"     => "show",
        "language"   => 1,
        "file"       => 2
    )
);

// matches /feed/fr/le-robots-hot-news.atom
$router->add(
    "/feed/{lang:[a-z]+}/{blog:[a-z\-]+}\.{type:[a-z\-]+}",
    "Feed::get"
);

// matches /api/v1/users/peter.json
$router->add('/api/(v1|v2)/{method:[a-z]+}/{param:[a-z]+}\.(json|xml)',
    array(
        'controller' => 'api',
        'version' => 1,
        'format' => 4
    )
);
Будьте внимательны к символам, разрешенным в регулярных выражениях для контроллеров и пространств имен. Поскольку они становятся именами классов, а те в свою очередь передаются через файловую систему, злоумышленники могут использовать их для считывания несанкционированных файлов. Безопасное регулярное выражение: /([a-zA-Z0-9_-]+)

Поведение по умолчанию

Phalcon\Mvc\Router имеет поведение по умолчанию, предоставляющее очень простую маршрутизацию, которая всегда ожидает URI, соответствующий следующему шаблону: /:controller/:action/:params

Например, для URL, такого как http://phalconphp.com/documentation/show/about.html, этот маршрутизатор переведет его следующим образом:

Контроллер documentation
Действие show
Параметр about.html

Если вы не хотите использовать эти маршруты по умолчанию в своем приложении, вы должны создать маршрутизатор, передав false в качестве параметра:

// Create the router without default routes
$router = new \Phalcon\Mvc\Router(false);

Установка маршрута по умолчанию

Когда к вашему приложению обращаются без какого-либо маршрута, маршрут '/' используется для определения путей, которые должны использоваться для отображения начальной страницы вашего сайта/приложения:

$router->add("/", array(
    'controller' => 'index',
    'action' => 'index'
));

Пути не найдены

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

//Set 404 paths
$router->notFound(array(
    "controller" => "index",
    "action" => "route404"
));

Установка путей по умолчанию

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

//Setting a specific default
$router->setDefaultModule('backend');
$router->setDefaultNamespace('Backend\Controllers');
$router->setDefaultController('index');
$router->setDefaultAction('index');

//Using an array
$router->setDefaults(array(
    'controller' => 'index',
    'action' => 'index'
));

Обработка дополнительных/конечных косых черт

Иногда к маршруту можно обратиться с дополнительными/конечными косыми чертами в конце маршрута. Эти дополнительные косые черты приведут к статусу «не найдено» в диспетчере. Вы можете настроить маршрутизатор на автоматическое удаление косых черт с конца обрабатываемого маршрута:

$router = new \Phalcon\Mvc\Router();

//Remove trailing slashes automatically
$router->removeExtraSlashes(true);

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

$router->add(
    '/{language:[a-z]{2}}/:controller[/]{0,1}',
    array(
        'controller' => 2,
        'action'     => 'index'
    )
);

Обратные вызовы соответствия

Иногда маршруты должны соответствовать, если они удовлетворяют определенным условиям. Вы можете добавить произвольные условия к маршрутам, используя обратный вызов «beforeMatch». Если эта функция возвращает false, маршрут будет обрабатываться как несоответствующий:

$router->add('/login', array(
    'module' => 'admin',
    'controller' => 'session'
))->beforeMatch(function($uri, $route) {
    //Check if the request was made with Ajax
    if ($_SERVER['HTTP_X_REQUESTED_WITH'] == 'xmlhttprequest') {
        return false;
    }
    return true;
});

Вы можете повторно использовать эти дополнительные условия в классах:

class AjaxFilter
{
    public function check()
    {
        return $_SERVER['HTTP_X_REQUESTED_WITH'] == 'xmlhttprequest';
    }
}

И использовать этот класс вместо анонимной функции:

$router->add('/get/info/{id}', array(
    'controller' => 'products',
    'action' => 'info'
))->beforeMatch(array(new AjaxFilter(), 'check'));

Ограничения на имена хостов

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

$router->add('/login', array(
    'module' => 'admin',
    'controller' => 'session',
    'action' => 'login'
))->setHostName('admin.company.com');

Имя хоста также может быть регулярными выражениями:

$router->add('/login', array(
    'module' => 'admin',
    'controller' => 'session',
    'action' => 'login'
))->setHostName('([a-z+]).company.com');

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

//Create a group with a common module and controller
$blog = new \Phalcon\Mvc\Router\Group(array(
    'module' => 'blog',
    'controller' => 'posts'
));

//Hostname restriction
$blog->setHostName('blog.mycompany.com');

//All the routes start with /blog
$blog->setPrefix('/blog');

//Default route
$blog->add('/', array(
    'action' => 'index'
));

//Add a route to the group
$blog->add('/save', array(
    'action' => 'save'
));

//Add another route to the group
$blog->add('/edit/{id}', array(
    'action' => 'edit'
));

//Add the group to the router
$router->mount($blog);

Источники URI

По умолчанию информация URI извлекается из переменной $_GET[‘_url’], которая передается Rewrite-Engine в Phalcon. Вы также можете использовать $_SERVER[‘REQUEST_URI’], если это необходимо:

$router->setUriSource(Router::URI_SOURCE_GET_URL); // use $_GET['_url'] (default)
$router->setUriSource(Router::URI_SOURCE_SERVER_REQUEST_URI); // use $_SERVER['REQUEST_URI'] (default)

Или вы можете вручную передать URI методу «handle»:

$router->handle('/some/route/to/handle');

Тестирование ваших маршрутов

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

//These routes simulate real URIs
$testRoutes = array(
    '/',
    '/index',
    '/index/index',
    '/index/test',
    '/products',
    '/products/index/',
    '/products/show/101',
);

$router = new Phalcon\Mvc\Router();

//Add here your custom routes
//...

//Testing each route
foreach ($testRoutes as $testRoute) {

    //Handle the route
    $router->handle($testRoute);

    echo 'Testing ', $testRoute, '<br>';

    //Check if some route was matched
    if ($router->wasMatched()) {
        echo 'Controller: ', $router->getControllerName(), '<br>';
        echo 'Action: ', $router->getActionName(), '<br>';
    } else {
        echo 'The route wasn\'t matched by any route<br>';
    }
    echo '<br>';

}

Маршрутизатор с аннотациями

Этот компонент предоставляет вариант, интегрированный с сервисом аннотаций. Используя эту стратегию, вы можете записывать маршруты непосредственно в контроллеры, вместо добавления их в регистрацию сервиса:

$di['router'] = function() {

    //Use the annotations router
    $router = new \Phalcon\Mvc\Router\Annotations(false);

    //Read the annotations from ProductsController if the uri starts with /api/products
    $router->addResource('Products', '/api/products');

    return $router;
};

Аннотации можно определить следующим образом:

/**
 * @RoutePrefix("/api/products")
 */
class ProductsController
{

    /**
     * @Get("/")
     */
    public function indexAction()
    {

    }

    /**
     * @Get("/edit/{id:[0-9]+}", name="edit-robot")
     */
    public function editAction($id)
    {

    }

    /**
     * @Route("/save", methods={"POST", "PUT"}, name="save-robot")
     */
    public function saveAction()
    {

    }

    /**
     * @Route("/delete/{id:[0-9]+}", methods="DELETE",
     *      conversors={id="MyConversors::checkId"})
     */
    public function deleteAction($id)
    {

    }

    public function infoAction($id)
    {

    }

}

Используются только методы, помеченные допустимыми аннотациями, как маршруты. Список поддерживаемых аннотаций:

Имя Описание Использование
RoutePrefix Префикс, который добавляется к каждому URI маршрута. Эта аннотация должна быть размещена в блоке документации класса @RoutePrefix(“/api/products”)
Route Эта аннотация помечает метод как маршрут. Эта аннотация должна быть размещена в блоке документации метода @Route(“/api/products/show”)
Get Эта аннотация помечает метод как маршрут, ограничивая HTTP-метод GET @Get(“/api/products/search”)
Post Эта аннотация помечает метод как маршрут, ограничивая HTTP-метод POST @Post(“/api/products/save”)
Put Эта аннотация помечает метод как маршрут, ограничивая HTTP-метод PUT @Put(“/api/products/save”)
Delete Эта аннотация помечает метод как маршрут, ограничивая HTTP-метод DELETE @Delete(“/api/products/delete/{id}”)
Options Эта аннотация помечает метод как маршрут, ограничивая HTTP-метод OPTIONS @Option(“/api/products/info”)

Для аннотаций, добавляющих маршруты, поддерживаются следующие параметры:

Имя Описание Использование
methods Определяет один или несколько HTTP-методов, которым должен соответствовать маршрут @Route(“/api/products”, methods={“GET”, “POST”})
name Определяет имя маршрута @Route(“/api/products”, name=”get-products”)
paths Массив путей, подобный передаваемому в Phalcon\Mvc\Router::add @Route(“/posts/{id}/{slug}”, paths={module=”backend”})
conversors Хеш конверторов, которые необходимо применить к параметрам @Route(“/posts/{id}/{slug}”, conversors={id=”MyConversor::getId”})

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

$di['router'] = function() {

    //Use the annotations router
    $router = new \Phalcon\Mvc\Router\Annotations(false);

    //Read the annotations from Backend\Controllers\ProductsController if the uri starts with /api/products
    $router->addModuleResource('backend', 'Products', '/api/products');

    return $router;
};

Регистрация экземпляра маршрутизатора

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

Вам нужно добавить код ниже в свой файл загрузки (например, index.php или app/config/services.php, если вы используете Phalcon Developer Tools)

/**
* add routing capabilities
*/
$di->set('router', function(){
    require __DIR__.'/../app/config/routes.php';
    return $router;
});

Вам нужно создать app/config/routes.php и добавить код инициализации маршрутизатора, например:

$router = new \Phalcon\Mvc\Router();

$router->add("/login", array(
    'controller' => 'login',
    'action' => 'index',
));

$router->add("/products/:action", array(
    'controller' => 'products',
    'action' => 1,
));

return $router;

Реализация собственного маршрутизатора

Интерфейс Phalcon\Mvc\RouterInterface должен быть реализован для создания собственного маршрутизатора, заменяющего предоставленный Phalcon.

© 2011–2016 Phalcon Framework Team
Licensed under the Creative Commons Attribution License 3.0.
https://docs.phalconphp.com/en/2.0.0/reference/routing.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API