Форум программистов, компьютерный форум, киберфорум
PHP: ООП
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.61/18: Рейтинг темы: голосов - 18, средняя оценка - 4.61
0 / 0 / 0
Регистрация: 27.04.2019
Сообщений: 22

Dependency Injection Container, попробовал на практике - не понял смысла

09.09.2019, 11:28. Показов 3987. Ответов 9

Студворк — интернет-сервис помощи студентам
Всем привет, начал изучать паттерн dependency injection, вроде, все понятно и логично.

Насколько понял, для удобного управления зависимости используются специальные контейнеры.

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

Задав эти вопросы на форумах, мне посоветовали взять любой DI-контейнер, и попробовать с ним поработать, тогда и станет понятно.

Решил я взять очень простой DI-контейнер, от разработчиков symfony - Pimple.

Написал класс SystemBlock (системный блок) с 3-4 зависимостями:
  • Блок питания
  • Видеокарта
  • Процессор
  • Операционная система

Пример вызова класса SystemBlock без контейнера:

PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
use Less\SystemBlock;
 
$systemBlock = new SystemBlock\SystemBlock(
    new SystemBlock\GigabytePower,
    new SystemBlock\GeForceVideo,
    new SystemBlock\IntelProcessor
);
 
 
$os = new SystemBlock\LinuxOS;
$os->setUser(new SystemBlock\OSUser("Вася", "Vasya", true));
 
$systemBlock->setOperatingSystem($os);
$systemBlock->start();
и пример с использованием контейнера:

PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php
use Less\SystemBlock;
 
$container = new \Pimple\Container;
 
$container["power"] = function ($c) {
    return new SystemBlock\GigabytePower;
};
 
$container["video"] = function ($c) {
    return new SystemBlock\GeForceVideo;
};
 
$container["processor"] = function ($c) {
    return new SystemBlock\IntelProcessor;
};
 
$container["system_block"] = function ($c) {
    return new SystemBlock\SystemBlock(
        $c["power"],
        $c["video"],
        $c["processor"]
    );
};
 
$systemBlock = $container["system_block"];
 
$os = new SystemBlock\LinuxOS;
$os->setUser(new SystemBlock\OSUser("Вася", "Vasya", true));
 
$systemBlock->setOperatingSystem($os);
$systemBlock->start();

Файлы с классами и интерфейсами выложил на GitHub

Возникшие вопросы:

1. Правильно ли я понял, что сервисы в контейнер мы добавляем в файле единой точки входа в приложение ?

Что делать, если у меня у случая, когда нужно вызвать нужный сконфигурированный сервис загрузки товаров на сайт, например:
1.1. Загрузка происходит через файл bin/import.php при запуске скрипта через крон.
1.2. При загрузка происходит через админку admin/import.php сайта по кнопке.

В первом и втором случае мне необходим один и тот же сконфигурированный сервис. Мне необходимо в начале каждого файла bin/import.php и admin/import.php добавлять сервисы в контейнер и конфигурировать, или нужно вынести в отдельный общий файл ? Покажите пример пожалуйста, как это делается, и какие могут быть нюансы ?

2. Из моих примеров управления зависимостями через контейнер и вручную, я не заметил никакой разницы. В чем преимущество контейнера ? Почему то, что я сделал вручную - неудобно ?

или контейнер нужен в тех случаях, когда один и тотже сервис используется несколько раз ? И в будущем удобно будет тестировать ? Заменил только в контейнере на тестовый сервис ?

И честно говоря, для меня самое сложное - это понять пункт

В каком месте приложения нужна инициализация контейнера ? Можно ли выносить в отдельный файл? Как подключать этот файл ? И т.д.

За помощь и развернутый ответ буду благодарен!
0
Лучшие ответы (1)
IT_Exp
Эксперт
34794 / 4073 / 2104
Регистрация: 17.06.2006
Сообщений: 32,602
Блог
09.09.2019, 11:28
Ответы с готовыми решениями:

Что такое Dependency Injection?
Уважаемые форумчане, для дурака, объясните, DI - это когда мы делаем так: $transport = new Transport(); $mailer = new...

Dependency Injection
Здравствуйте, друзья. Учу java, разбираюсь в концепции инверсии контроля и di. Сформировал свой пример из реальной жизни, как я это...

Dependency injection реализация
Здравствуйте, уважаемые форумчане! Такой вопрос: недавно начал осваивать паттерн dependency injection при помощи контейнера Autofac, но...

9
Эксперт PHP
3899 / 3237 / 1353
Регистрация: 01.08.2012
Сообщений: 10,904
09.09.2019, 12:56
Цитата Сообщение от SystemException Посмотреть сообщение
$container["power"] = function ($c) {
* * return new SystemBlock\GigabytePower;
};
$container["video"] = function ($c) {
* * return new SystemBlock\GeForceVideo;
};
$container["processor"] = function ($c) {
* * return new SystemBlock\IntelProcessor;
};
$container["system_block"] = function ($c) {
* * return new SystemBlock\SystemBlock(
* * * * $c["power"],
* * * * $c["video"],
* * * * $c["processor"]
* * );
};
Процессор, видеокарта и т.п. - это не сервисы, это сущности. Для удобства можно запихнуть в контейнер какой-нибудь конфиг, сервис отправки e-mail и т.п., а это всё лишнее. Создание класса SystemBlock можно вынести в отдельный фабричный класс.

Самого внедрения зависимости здесь нет. Простой абстрактный пример с внедрением:

PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
class Computer
{
    // Внедряем зависимость компьютера от видео и процессора
    public function __construct(Video $video, Processor $processor)
    {
        // Тут что-то делаем с переменными $video и $processor
    }
}
 
class Video{}
class Processor{}
 
$video = new Video();
$processor = new Processor();
 
$computer = new Computer($video, $processor);
Цитата Сообщение от SystemException Посмотреть сообщение
сервисы в контейнер мы добавляем в файле единой точки входа в приложение ?
Обычно да, где-то в начале приложения.

Цитата Сообщение от SystemException Посмотреть сообщение
1.1. Загрузка происходит через файл bin/import.php при запуске скрипта через крон.
1.2. При загрузка происходит через админку admin/import.php сайта по кнопке.
Можно сделать отдельный файл startup.php, в нём сделать инициализацию контейнера и добавление в него сервисов. И подключать этот файл во все точках входа.

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

Цитата Сообщение от SystemException Посмотреть сообщение
В чем преимущество контейнера ?
Одно из преимуществ - автоматическое внедрение зависимостей. Что-то вроде:

PHP
1
2
3
4
5
6
7
8
9
10
11
12
<?php
class Service
{
    public function __construct(Calculator $calculator, Transformator $transformator){}
}
 
class Calculator{}
class Transformator{}
 
$container = new Container();
// Создаст Calculator и Transformator, затем передаст их в конструктор Service
$computer = $container->make('Service');
Преимущество в том, что не нужно создавать Calculator и Transformator вручную.
Цитата Сообщение от SystemException Посмотреть сообщение
Почему то, что я сделал вручную - неудобно ?
Удобно, просто пример не очень удачный, поскольку сущности обычно создаются и заполняются параметрами вручную. Поэтому нет необходимости в контейнере.

Цитата Сообщение от SystemException Посмотреть сообщение
контейнер нужен в тех случаях, когда один и тотже сервис используется несколько раз ?
Он используется когда нужно разрешать зависимости.

Цитата Сообщение от SystemException Посмотреть сообщение
В каком месте приложения нужна инициализация контейнера ?
Контейнер создаёт контроллер и запускает какой-то метод, например action_index. В метод action_index мы подставляем нужные нам сервисы, контейнер их разрешает. Если у сервисов есть зависимости, контейнер разрешает и их.
2
0 / 0 / 0
Регистрация: 27.04.2019
Сообщений: 22
09.09.2019, 13:22  [ТС]
Jodah, спасибо большое! Честно, еще больше запутался.

Процессор, видеокарта и т.п. - это не сервисы, это сущности. Для удобства можно запихнуть в контейнер какой-нибудь конфиг, сервис отправки e-mail и т.п., а это всё лишнее.
Я терминалогией не очень владею, но в разных статья читал, что, все объекты, которые кладем в контейнер - это сервисы.

Например, похожее описание на хабре
А именно:
Как и многие другие DI контейнеры, Pimple поддерживает два вида данных: сервисы и параметры.


Самого внедрения зависимости здесь нет.
не совсем понял, чем отличается ваш пример, от моей попытки (файлы приложены на гитхабе) ?

Вот пример:

PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?php
 
class SystemBlock {
    /**
     * Блок питания
     * @var PowerInterface
     */
    private $power = null;
    /**
     * Видеокарта
     * @var VideoInterface
     */
    private $video = null;
    /**
     * Процессор
     * @var ProcessorInterface
     */
    private $processor = null;
    /**
     * Операционная система
     * @var OperatingSystemInterface
     */
    private $os = null;
    public function __construct(PowerInterface $power, VideoInterface $video, ProcessorInterface $processor) {
        $this->power = $power;
        $this->video = $video;
        $this->processor = $processor;
    }
    public function start() : bool {
        if(!$this->power->run()) {
            echo "Блок питания не включен!", PHP_EOL;
            return false;
        }
        $this->video->init();
        $this->processor->init();
        # Если установлена операционная система - пробуем запустить
        if(null !== $this->os) {
            $this->os->tryLoading();
        }
        return true;
    }
    public function setOperatingSystem(OperatingSystemInterface $os) : void {
        $this->os = $os;
    }
}

Вроде тоже самое, внедрение через конструктор.

Потому что здесь нет управления зависимостями через контейнер.
Объясните пожалуйста, что есть управление зависимостями через контейнер, если можно с примерами?
Уже много статей прочитал, и никак не разберусь, что есть что.

Одно из преимуществ - автоматическое внедрение зависимостей
Это обязательное условие для контейнеров? Если автоматически не умеет внедрять - нет смысла в контейнере ?
Откуда контейнер знает, какой сервис/параметр из контейнера нужен для сервиса, который мы запрашиваем ?


Он используется когда нужно разрешать зависимости.
А как понять, в каких случаях требуется, а в каких нет? Можно примеры пожалуйста ?
0
Эксперт PHP
3899 / 3237 / 1353
Регистрация: 01.08.2012
Сообщений: 10,904
09.09.2019, 14:00
Цитата Сообщение от SystemException Посмотреть сообщение
все объекты, которые кладем в контейнер - это сервисы.
Если написано "Вход только для персонала" - это не значит, что войдя в комнату вы станете персоналом. В контейнере можно хранить что угодно, можно даже просто числа/строки/массивы и т.п., если функционал позволяет. Но они от этого сервисами не становятся.

Цитата Сообщение от SystemException Посмотреть сообщение
не совсем понял, чем отличается ваш пример, от моей попытки (файлы приложены на гитхабе) ?
А, я на гитхаб не заходил, посмотрел только тот код, который вы в 1-ом сообщении указали.

Пробежался взглядом, в целом верно. Только не совсем понятно, зачем вот это:
PHP
1
2
3
$container["power"] = function ($c) {
    return new SystemBlock\GigabytePower;
};
Мне кажется, лишнее.

Цитата Сообщение от SystemException Посмотреть сообщение
что есть управление зависимостями через контейнер
Всё в порядке, внедрение зависимостей у вас есть.

Цитата Сообщение от SystemException Посмотреть сообщение
Это обязательное условие для контейнеров? Если автоматически не умеет внедрять - нет смысла в контейнере ?
Ну да, он для этого и нужен. Без него пришлось бы все зависимые объекты вручную создавать/передавать.

Цитата Сообщение от SystemException Посмотреть сообщение
Откуда контейнер знает, какой сервис/параметр из контейнера нужен для сервиса, который мы запрашиваем ?
Допустим, есть такой метод:
PHP
1
public function someMethod(Processor $processor) { #... }
В PHP есть такая вещь, как рефлексии. С их помощью можно получить информацию о классе, существующих у него методах, количестве и типе входящих параметров метода и т.п. С помощью рефлексий контейнер узнаёт, что метод someMethod ожидает первым параметром объект класса Processor.

Цитата Сообщение от SystemException Посмотреть сообщение
А как понять, в каких случаях требуется, а в каких нет?
Пример 1. Я хочу в контроллере отправить почту. Для этого есть определённый сервис. Создавать его вручную и разрешать зависимости мне влом, поэтому отдаю эту задачу контейнеру:

PHP
1
2
3
4
5
6
7
8
9
10
<?php
use App\MailService;
 
class SomeController
{
    public function someMethod(MailService $mail)
    {
        $mail->sendMessage('Hello');
    }
}
Пусть он сам создаст MailService.

Пример 2. Мне нужно создать объект, заполнить данными из формы и отправить в сервис. В этом случае я создам сущность вручную, поскольку в неё нужно передать какие-то параметры, о которых контейнер не в курсе. А вот сам сервис я опять создам через контейнер.

PHP
1
2
3
4
5
6
7
8
9
10
11
12
<?php
use App\SomeService;
use App\SomeEntity;
 
class SomeController
{
    public function someMethod(SomeService $service)
    {
        $entity = new SomeEntity($_POST['data']);
        $service->doSomething($entity);
    }
}
Контейнер не сможет создать SomeEntity, поскольку не знает, откуда взять данные для передачи в 1-ый параметр. Но если у сущности нет зависимостей, то можно и вручную создать, и через контейнер.

PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
use App\SomeService;
use App\SomeEntity;
 
class SomeController
{
    public function someMethod(SomeService $service)
    {
        $entity = new SomeEntity();
        $entity->title = $_POST['title'];
        
        $service->doSomething($entity);
    }
 
    public function someMethod2(SomeService $service, SomeEntity $entity)
    {
        $entity->title = $_POST['title'];
        
        $service->doSomething($entity);
    }
}
Хотя я бы предпочёл 1-ый вариант. Разрешать зависимости только тогда, когда это необходимо. Мне кажется, такой код очевидней.
2
0 / 0 / 0
Регистрация: 27.04.2019
Сообщений: 22
10.09.2019, 21:09  [ТС]
Jodah, спасибо большое!

Если автоматически не умеет внедрять - нет смысла в контейнере ?
Ну да, он для этого и нужен. Без него пришлось бы все зависимые объекты вручную создавать/передавать.
все же не могу понять почему так..(

1. Рефлексия появилась не так давно, получается до нее были нормальные DI-контейнеры ?
2. Существует очень много DI-контейнеров, выборочно посмотрел из packagist несколько популярных контейнеров - не у всех есть "автоподключение".
Например, тот же контейнер Pimple, который входит в микрофреймворк от разработчиков symfony - Silex.
Весь код этого контейнера, около 100 строк кода. Никакими рефлексиями там не попахивает.
Как тогда эти контейнеры существуют, и являются на столько популярны даже как отдельный компонент, если не удовлетворяет одному из важных критериев контейнера ?
3. Получается, если придерживаться правильному тону разработки приложения, а именно, писать код для интерфейсов, и иметь зависимости от тех же интерфейсов, то контейнеров с рефлексией особого толка не будет ?
0
Эксперт PHP
3899 / 3237 / 1353
Регистрация: 01.08.2012
Сообщений: 10,904
10.09.2019, 23:02
Лучший ответ Сообщение было отмечено SystemException как решение

Решение

Цитата Сообщение от SystemException Посмотреть сообщение
Рефлексия появилась не так давно
Только что на 5.3 проверил, там она есть.

Цитата Сообщение от SystemException Посмотреть сообщение
Например, тот же контейнер Pimple
Мне кажется, это всё же скорее Service Locator, чем DI-контейнер. Поскольку он действительно просто хранит в себе сервисы, какие-то фабрики и обычные значения, а разрешение зависимостей перекладывает на плечи разработчика.

Это не плохо, это просто другой инструмент с другим функционалом.

К слову, немного погуглил и увидел примерно такое же мнение в комментариях на хабре.

Цитата Сообщение от SystemException Посмотреть сообщение
Как тогда эти контейнеры существуют
Значит кому-то удобно работать с этой штукой. Лично я бы его не взял, поскольку не вижу для себя профита от легковесности с ущербом в функционале.

Цитата Сообщение от SystemException Посмотреть сообщение
то контейнеров с рефлексией особого толка не будет ?
У некоторых контейнеров есть возможность связать интерфейс с реализацией. Пример:

PHP
1
$container->bind(DatabaseInterface::class, Database::class);
И всё, теперь везде можно прописывать DatabaseInterface, а контейнер автоматом подставит Database. Захочется нам заменить класс базы на другой или даже фейковый (для тестов) - меняем его только в одном месте:
PHP
1
$container->bind(DatabaseInterface::class, FakeDatabase::class);
Есть неплохая статья про функционал Ларавеловского контейнера, там всё это есть: https://habr.com/ru/post/331982/
2
209 / 191 / 49
Регистрация: 15.03.2016
Сообщений: 1,229
14.09.2019, 15:35
Цитата Сообщение от Jodah Посмотреть сообщение
<?php
class Computer
{
* * // Внедряем зависимость компьютера от видео и процессора
* * public function __construct(Video $video, Processor $processor)
* * {
* * * * // Тут что-то делаем с переменными $video и $processor
* * }
}
class Video{}
class Processor{}
$video = new Video();
$processor = new Processor();
$computer = new Computer($video, $processor);
технически ведь можно и так сделать:
PHP
1
$computer = new Computer(new Video(), new Processor());
и не надо будет в конструкторе писать "Video $video"
зачем вообще ПХП нужно это слово "Video" ?
в C++ вон просто передаёшь объект по ссылке/указателю и всё
0
Эксперт PHP
3899 / 3237 / 1353
Регистрация: 01.08.2012
Сообщений: 10,904
14.09.2019, 18:39
Цитата Сообщение от полудух Посмотреть сообщение
технически ведь можно и так сделать:
Вручную разрешать зависимости, когда это может сделать кто-то за меня? Не люблю такое.

Мне больше нравится один раз прописать зависимости, а затем делать так:

PHP
1
$computer = $container->make(Computer::class);
Или так (в контроллере):

PHP
1
2
3
4
public function action_index(Computer $computer)
{
    ...
}
И неважно, что, к примеру, компьютер хочет видео, видео хочет подключение к БД, которое, в свою очередь, хочет конфиг и т.д.

Цитата Сообщение от полудух Посмотреть сообщение
зачем вообще ПХП нужно это слово "Video" ?
Если исходить из идеи, что хороший код должно быть легко использовать правильно и трудно использовать неправильно, тайп хинтинг позволяет запретить разработчику передавать в функцию/метод заведомо не подходящие данные.

Вот открою я завтра чужой код, а там будет такое:

PHP
1
2
3
4
public function someMethod($video, $processor)
{
    ...
}
И поди разбери, что туда надо передавать. То ли массив, то ли объект, то ли true/false.
0
209 / 191 / 49
Регистрация: 15.03.2016
Сообщений: 1,229
14.09.2019, 20:43
частенько бывают ситуации, когда название класса далеко не статично (в том же MVC, например)
я обозначаю так: $videoO (object) / $videoM (map) / $videoA (array)
а ещё комменты есть )
0
Эксперт PHP
3899 / 3237 / 1353
Регистрация: 01.08.2012
Сообщений: 10,904
14.09.2019, 21:16
Цитата Сообщение от полудух Посмотреть сообщение
частенько бывают ситуации, когда название класса далеко не статично
Не совсем понимаю, о чём речь, поэтому сложно ответить.

Цитата Сообщение от полудух Посмотреть сообщение
я обозначаю так: $videoO (object) / $videoM (map) / $videoA (array)
Получается тот же тайпхинтинг, только не обязательный.

Из минусов - возможно неочевидный текст ошибки в случае её наличия. Например:

PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Test
{
    public function run1(array $arr)
    {
        return implode(',', $arr);
    }
 
    public function run2($arr)
    {
        return implode(',', $arr);
    }
}
 
$test = new Test();
$test->run1(123); // Argument 1 passed to Test::run1() must be of the type array, int given
$test->run2(123); // Warning: implode(): Invalid arguments passed
В первом случае сразу видишь причину ошибки - переданы данные не того типа.
Во втором уже не так очевидно, надо дебажить.
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
BasicMan
Эксперт
29316 / 5623 / 2384
Регистрация: 17.02.2009
Сообщений: 30,364
Блог
14.09.2019, 21:16
Помогаю со студенческими работами здесь

Объяснение dependency injection
Никак не доходить принцип Dependency Injection. Пересмотрел кучу роликов, почитал много инфы, но все равно остаются вопросы. Например, у...

Dependency injection vs aggregation
Всем привет. Может кто на примере объяснить разницу между инъекцией и агрегацией? Везде в литературе указано практически тот же вариант -...

Unit test dependency injection
Посоветуйте литературы по сабжу. А еще лучше примеры проектов покрытые тестами.

Зачем нужна Dependency Injection?
Я знаю, что DI это круто и нужно, более того, я сам его использую в своих проектах, использую Autofac. Но я не могу понять, в чём его...

Dependency Injection в Asp Identity
Я изменил в классе ApplicationUser в Asp identity тип поля Id на int. Вот код: public class ApplicationUser : IdentityUser&lt;int,...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
10
Ответ Создать тему
Новые блоги и статьи
PhpStorm 2025.3: WSL Terminal всегда стартует в ~
and_y87 14.12.2025
PhpStorm 2025. 3: WSL Terminal всегда стартует в ~ (home), игнорируя директорию проекта Симптом: После обновления до PhpStorm 2025. 3 встроенный терминал WSL открывается в домашней директории. . .
Access
VikBal 11.12.2025
Помогите пожалуйста !! Как объединить 2 одинаковые БД Access с разными данными.
Новый ноутбук
volvo 07.12.2025
Всем привет. По скидке в "черную пятницу" взял себе новый ноутбук Lenovo ThinkBook 16 G7 на Амазоне: Ryzen 5 7533HS 64 Gb DDR5 1Tb NVMe 16" Full HD Display Win11 Pro
Музыка, написанная Искусственным Интеллектом
volvo 05.12.2025
Всем привет. Некоторое время назад меня заинтересовало, что уже умеет ИИ в плане написания музыки для песен, и, собственно, исполнения этих самых песен. Стихов у нас много, уже вышли 4 книги, еще 3. . .
От async/await к виртуальным потокам в Python
IndentationError 23.11.2025
Армин Ронахер поставил под сомнение async/ await. Создатель Flask заявляет: цветные функции - провал, виртуальные потоки - решение. Не threading-динозавры, а новое поколение лёгких потоков. Откат?. . .
Поиск "дружественных имён" СОМ портов
Argus19 22.11.2025
Поиск "дружественных имён" СОМ портов На странице: https:/ / norseev. ru/ 2018/ 01/ 04/ comportlist_windows/ нашёл схожую тему. Там приведён код на С++, который показывает только имена СОМ портов, типа,. . .
Сколько Государство потратило денег на меня, обеспечивая инсулином.
Programma_Boinc 20.11.2025
Сколько Государство потратило денег на меня, обеспечивая инсулином. Вот решила сделать интересный приблизительный подсчет, сколько государство потратило на меня денег на покупку инсулинов. . . .
Ломающие изменения в C#.NStar Alpha
Etyuhibosecyu 20.11.2025
Уже можно не только тестировать, но и пользоваться C#. NStar - писать оконные приложения, содержащие надписи, кнопки, текстовые поля и даже изображения, например, моя игра "Три в ряд" написана на этом. . .
Мысли в слух
kumehtar 18.11.2025
Кстати, совсем недавно имел разговор на тему медитаций с людьми. И обнаружил, что они вообще не понимают что такое медитация и зачем она нужна. Самые базовые вещи. Для них это - когда просто люди. . .
Создание Single Page Application на фреймах
krapotkin 16.11.2025
Статья исключительно для начинающих. Подходы оригинальностью не блещут. В век Веб все очень привыкли к дизайну Single-Page-Application . Быстренько разберем подход "на фреймах". Мы делаем одну. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru