Задать вопрос
  • На собеседовании сказали, что не все функции - замыкания. Так ли это?

    @lazarevBoris
    Тред просто огонь!:)
    Дело в том, что такой же спор (из 8 страниц, на эту же тему) был не так давно на другом ресурсе, где мудрый дядька под ником "javascript" объяснял окружающим (которые тоже уперлись рогом, как и многие здесь), почему в js по факту всё является замыканием.
    Поэтому было крайне интересно, чем же и здесь дело закончится.
    И в очередной раз было доказано, что где бы ни была объявлена функция (а начиная с es6 и let - и блок кода) - она образует замыкание с цепочкой внешних лексических сред (или хотя бы одной LE), что полностью соответствует определению, предысторию которого хорошо описал Александр.
    Что наглядно изобразил Alexandroppolus (надеюсь, вы не будете против, если я достану ваш пример из jsfiddle и размещу здесь, чтобы он не пропал с течением времени):

    description:
    функция, созданная внутри iframe, замкнула внутрифреймовую глобальную переменную, и продолжает работать, даже когда фрейм удалили. Кнопкой frameValue можно убедиться, что в window той переменной нет.

    html:
    <script>
      var globalValue = 1000;
      var globalCounter = function() {
        return globalValue++;
      };
    </script>
    <div>
      <button>iframe counter</button>
      <button>globalCounter</button><br >
      <button>frameValue</button>
      <button>globalValue</button>
    </div>

    JS:
    const iframeHTML = 'iframe...<' + `script>
      var frameValue = 1;
      var counter = function() {
        // debugger;
        return frameValue++;
      };
      
      window.parent.iframeCounter = counter;
    <` + '/script>';
    
    function createTempFrame() {
    	const blob = new Blob([iframeHTML], {type: "text/html"});
      const frm = document.createElement('iframe');
      frm.src = URL.createObjectURL(blob);
      
      document.body.appendChild(frm);
      
      setTimeout(() => {
      	document.body.removeChild(frm);
      }, 900);
    }
    
    window.onload = function () {
      const buttons = document.querySelectorAll('button');
      
      buttons[0].onclick = function() {
        alert(iframeCounter());
      };
    
      buttons[1].onclick = function() {
        alert(globalCounter());
      };
      
      buttons[2].onclick = function() {
        alert(window.frameValue);
      };
      
      buttons[3].onclick = function() {
        alert(window.globalValue);
      };
     
      createTempFrame();
    };


    Так что да, автор треда, который ходил на собеседование, был прав, замыкание в его примере есть.
    Ответ написан
    1 комментарий
  • На собеседовании сказали, что не все функции - замыкания. Так ли это?

    Alexandroppolus
    @Alexandroppolus
    кодир
    По версии Ильи Кантора, глобальная функция при создании записывает себе лексическим окружением (Scope) глобальный объект, а в момент выполнения обращается к Scope.
    globalFunc.[[Scope]] = window;

    то есть формально да, это замыкание.

    но практического значения это не имеет, потому что глобальная функция с тем же успехом могла не записывать global себе в Scope, и при выполнении обращаться просто к global, который, к тому же, не ликвидируется сборщиком мусора

    upd: заметим, что глобальных объектов может быть несколько, например, ифреймы на странице, или контекст в нодовском модуле VM. Тогда замыкание глобальных переменных (как в примере из вопроса) таки имеет смысл.
    Ответ написан
    3 комментария
  • На собеседовании сказали, что не все функции - замыкания. Так ли это?

    @Arlekcangp
    Разработчик, Лид, Архитектор ПО
    Похоже вас поймали на том что в js кто-то решил переопределить термин "замыкание"... Т к javascript далеко не первый язык, в котором есть нечто, претендующее называться термином "замыкание", то следует в первую очередь рассмотреть общее определение. Возьмём его из википедии:

    Замыкание (англ. closure) в программировании — функция первого класса, в теле которой присутствуют ссылки на переменные, объявленные вне тела этой функции в окружающем коде и не являющиеся её параметрами. Говоря другим языком, замыкание — функция, которая ссылается на свободные переменные в своей области видимости.
    Замыкание, так же как и экземпляр объекта, есть способ представления функциональности и данных, связанных и упакованных вместе.
    Замыкание — это особый вид функции. Она определена в теле другой функции и создаётся каждый раз во время её выполнения. Синтаксически это выглядит как функция, находящаяся целиком в теле другой функции. При этом вложенная внутренняя функция содержит ссылки на локальные переменные внешней функции. Каждый раз при выполнении внешней функции происходит создание нового экземпляра внутренней функции, с новыми ссылками на переменные внешней функции.
    В случае замыкания ссылки на переменные внешней функции действительны внутри вложенной функции до тех пор, пока работает вложенная функция, даже если внешняя функция закончила работу, и переменные вышли из области видимости.[1]
    Замыкание связывает код функции с её лексическим окружением (местом, в котором она определена в коде). Лексические переменные замыкания отличаются от глобальных переменных тем, что они не занимают глобальное пространство имён. От переменных в объектах они отличаются тем, что привязаны к функциям, а не объектам.


    Как видим из этого длинного определения (а не только его первого предложения) интервьюер оказался формально прав (что является неожиданностью и для меня самого. Кто знает, возможно это веяние нового или всё же творчество чрезмерного ЧСВ js-кодерков, но не будем предвзятыми) Посмотрим дальше...Переходим в английскую версию той же страницы в википедии:

    In programming languages, a closure, also lexical closure or function closure, is a technique for implementing lexically scoped name binding in a language with first-class functions. Operationally, a closure is a record storing a function[a] together with an environment.[1] The environment is a mapping associating each free variable of the function (variables that are used locally, but defined in an enclosing scope) with the value or reference to which the name was bound when the closure was created.[b] Unlike a plain function, a closure allows the function to access those captured variables through the closure's copies of their values or references, even when the function is invoked outside their scope.


    О! А тут нет никаких "функций, вложенных в функцию" и нет никаких "создающихся каждый раз" Хм... Интересно. Ну за то есть абзац о том как появились замыкания:

    The concept of closures was developed in the 1960s for the mechanical evaluation of expressions in the λ-calculus and was first fully implemented in 1970 as a language feature in the PAL programming language to support lexically scoped first-class functions.[2]

    Peter J. Landin defined the term closure in 1964 as having an environment part and a control part as used by his SECD machine for evaluating expressions.[3] Joel Moses credits Landin with introducing the term closure to refer to a lambda expression whose open bindings (free variables) have been closed by (or bound in) the lexical environment, resulting in a closed expression, or closure.[4][5] This usage was subsequently adopted by Sussman and Steele when they defined Scheme in 1975,[6] a lexically scoped variant of Lisp, and became widespread.


    Ага. Оказывается первоначально они появились в такой не конвенциональной, с сегодняшней точки зрения VM, как SECD... И их создатель прямо говорит, что это код плюс данные. Далее термин применяют к лямбда-выражениям. Ничего не говорится, должны ли эти выражения возвращаться функцией, или достаточно того, что бы они захватывали переменные из своего лексического (или динамического, как в common lisp) окружения. Далее нам сообщают, что некие мистеры, Сасман и Стил, спустя каких то 11 лет добавили это в свой язык Scheme... Ну что же. Посмотрим замыкания в Scheme... А именно откроем книгу Сасмана и соавторов "Структура и интерпретация компьютерных программ" или "SICP" Первый раз (исключая содержание) слово "замыкание" встречается в главе 2 "Поcтроение абстракций c помощью данных"


    Важная идея в работе с составными данными — понятие замыкания (closure): клей для
    сочетания объектов данных должен позволять нам склеивать не только элементарные
    объекты данных, но и составные.


    Но, как выяснилось, это не те замыкания... Это замыкание в том смысле, что структуру одного типа (в данном случае пары) могут ссылаться на самих себя. И это то как термин "замыкание" понимают математики. Далее в SICP никаких других определений замыканий нет (несмотря на то, что само их использование конечно же есть) Ну ничего не остаётся как посмотреть что же сегодня называют замыканиями в языке Scheme... Гугл по этому запросу выдал первой ссылкой: https://www.cs.utexas.edu/ftp/garbage/cs345/schint....
    Цитирую:

    Procedures are Closures
    Scheme procedure's aren't really just pieces of code you can execute; they're closures.

    A closure is a procedure that records what environment it was created in. When you call it, that environment is restored before the actual code is executed. This ensures that when a procedure executes, it sees the exact same variable bindings that were visible when it was created--it doesn't just remember variable names in its code, it remembers what storage each name referred to when it was created.

    Since variable bindings are allocated on the heap, not on a stack, this allows procedures to remember binding environments even after the expressions that created those environments have been evaluated. For example, a closure created by a lambda inside a let will remember the let's variable bindings even after we've exited the let. As long as we have a pointer to the procedure (closure), the bindings it refers to are guaranteed to exist. (The garbage collector will not reclaim the procedure's storage, or the storage for the let bindings.)

    Here's an example that may clarify this, and show one way of taking advantage of it.

    Suppose we type the following expression at the Scheme prompt, to be interpreted in a top-level environment:

    Scheme> (let ((count 0))
               (lambda ()
                  (set! count (+ count 1))
                  count)))

    ##

    Evaluating this let expression first creates a binding environment with a binding for count. The initial value of this binding is 0. In this environment, the lambda expression creates a closure. When executed, this procedure will increment the count, and then return its value. (Note that the procedure is not executed yet, however--it's just created.) This procedure, returned by the lambda expression, is also returned as the value of the let expression, because a let returns the value of its last body expression. The read-eval-print loop therefore prints a representation of the (anonymous) procedure.


    Что имеем ? В Scheme - вообще любая функция - это замыкание! Т к она всегда захватывает контекст, даже если там пусто!. Что видим в коде ? Ну с точки зрения Scheme тут конечно вложенная функция, т к let - это на самом деле lambda, но так происходит потому, что в Scheme нет переменных в понимании js. В js же можно считать, что глобальный контекст - результат действия нечто, что эквивалентно let. Захватывает ли любая функция в js этот контекст ? Не знаю. Но точно знаю, что в вашем примере это определенно так - х часть контекста, который захватывается функцией. Кто хочет поспорить - идите спорьте с создателями Scheme (скажите, когда попкорн покупать, я посмотрю как вас пороть будут. Набежало тут вайтишников =)) Кто то может сказать "а вот у нас в js замыкания определяются не так" На что я отвечу: знаете, товарищи, а идите ка вы лесом! Если есть однажды созданный и определенный термин, нужны весьма веские причины что бы менять его определение. Желания левой ноги очередного вайтишника тут не достаточно.
    Ответ написан
    30 комментариев
  • На собеседовании сказали, что не все функции - замыкания. Так ли это?

    snaiper04ek
    @snaiper04ek
    Не стреляйте в эникея, он админит как умеет
    парень. Всё равно ты будешь использовать ту терминологию, которую используют на работе. Если там под замыканием подразумевается замыкание с инкапсуляцией, то после того как тебе сказали что "твой код - говно", было два варианта: 1)поговорить о терминах либо со ссылкой на официальную документацию, либо вместо с собеседником вывести определение исходя из смысла понятия, не прибегая к авторитетам вообще. 2) Сказать о том, что прочитал такое определение у %авторитет%, и сказать, что готов использовать то, которым пользуетесь вы на работе.

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

    Есть второй вариант: собеседник тебе говорит: "Ахтунг! Замыкание используется не просто для того, чтобы спасти переменную от удаления! Это ещё и способ сокрытия данных: замыканием можно использовать локальную глобальную переменную, вместо того чтобы использовать просто глобальную переменную, или же городить отдельный класс."
    В этом случае всё твое определение идёт в пешее эротическое, и ты соглашаешься, что для этого придётся обернуть функцию в функцию, чтобы у тебя была функция с локальными переменными, которые будут глобальными для этой функции в функции.
    Ответ написан
    13 комментариев
  • На собеседовании сказали, что не все функции - замыкания. Так ли это?

    dollar
    @dollar
    Делай добро и бросай его в воду.
    Очевидно, что любая функция может использовать внешние переменные. Причём, это верно практически для любого ЯП.

    Также локальные переменные внутри функции живут только в интервале времени, пока функция выполняется. И как только она завершает работу ("}" или "return"), локальные переменные (на стеке) уничтожаются.

    Даже если функция содержит другую функцию (и конечно же вызывает её), это не противоречит данному правилу, которое работает по умолчанию (т.е. даже без замыканий).

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

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

    Так что в вашем примере и правда нет замыкания.
    Ответ написан
  • Как правильно (и в какой момент авторизации) задать параметризованные scope в ЕСИА?

    @Sinator
    1) Авторизуете пользователя (получаете обратно code по запрошенным скоупам)
    2) Меняете code на access_token
    3) Запрашиваете данные по пользователю используя access_token (получаете список нужных вам организаций)
    4) Создаете еще один запрос через API аналогичный второму пункту и получаем ЕЩЕ ОДИН access_token, но уже для организаций (обратите внимание, что grant_type уже client_credentials, а code и state используется из ответа на первый шаг)
    $arParams = [
        'client_id'     => '*CLIENT_CODE*',
        'code'          => $_GET['code'],
        'grant_type'    => 'client_credentials',
        'state'         => $_GET['state'],
        'scope'         => join(' ', [
            "http://esia.gosuslugi.ru/org_inn?org_oid=*111111111*",
            "http://esia.gosuslugi.ru/org_ogrn?org_oid=*111111111*",
            "http://esia.gosuslugi.ru/org_ctts?org_oid=*111111111*",
            "http://esia.gosuslugi.ru/org_fullname?org_oid=*111111111*",
            "http://esia.gosuslugi.ru/org_type?org_oid=*111111111*",
            "http://esia.gosuslugi.ru/org_inn?org_oid=*222222222*",
            "http://esia.gosuslugi.ru/org_ogrn?org_oid=*222222222*",
            "http://esia.gosuslugi.ru/org_ctts?org_oid=*222222222*",
            "http://esia.gosuslugi.ru/org_fullname?org_oid=*222222222*",
            "http://esia.gosuslugi.ru/org_type?org_oid=*222222222*",
            "http://esia.gosuslugi.ru/org_inn?org_oid=*333333333*",
            "http://esia.gosuslugi.ru/org_ogrn?org_oid=*333333333*",
            "http://esia.gosuslugi.ru/org_ctts?org_oid=*333333333*",
            "http://esia.gosuslugi.ru/org_fullname?org_oid=*333333333*",
            "http://esia.gosuslugi.ru/org_type?org_oid=*333333333*",
        ]),
        'timestamp'     => date('Y.m.d H:i:s O'),
        'token_type'    => 'Bearer',
        'client_secret' => '*CLIENT_SECRET*',
    ];

    5) С полученным новым токеном делаем запрос на получение информации по организации (на каждую свой):
    https://esia-portal1.test.gosuslugi.ru/rs/orgs/222222222/ctts?embed=(contacts.elements)


    Документация у них конечно "дно" =)
    Ответ написан
    5 комментариев
  • Как правильно (и в какой момент авторизации) задать параметризованные scope в ЕСИА?

    @NikitaUs
    Да, божественная документация ЕСИА всех выводит из себя=) Я сам недавно начал с ЕСИА и потратил немало времени на то чтобы разобраться как правильно c ЭТИМ работать.

    Поскольку запрос на получение данных организации подразумевает передачу конкретного orgOid, то тут поможет только запрос двух маркеров последовательно. А вот scope для запроса данных организаций формируется вот в таком формате:
    http://esia.gosuslugi.ru/org_shortname?org_oid=1000346115 http://esia.gosuslugi.ru/org_fullname?org_oid=1000346115 http://esia.gosuslugi.ru/org_type?org_oid=1000346115 http://esia.gosuslugi.ru/org_ogrn?org_oid=1000346115 http://esia.gosuslugi.ru/org_inn?org_oid=1000346115 http://esia.gosuslugi.ru/org_kpp?org_oid=1000346115 http://esia.gosuslugi.ru/org_agencyterrange?org_oid=1000346115 http://esia.gosuslugi.ru/org_agencytype?org_oid=1000346115 http://esia.gosuslugi.ru/org_oktmo?org_oid=1000346115 http://esia.gosuslugi.ru/org_ctts?org_oid=1000346115 http://esia.gosuslugi.ru/org_addrs?org_oid=1000346115 http://esia.gosuslugi.ru/org_emps?org_oid=1000346115
    Ответ написан
    9 комментариев
  • Что означает синтаксис со вопросительным знаком и свойством после?

    alexey-m-ukolov
    @alexey-m-ukolov Куратор тега JavaScript
    Ответ написан
    Комментировать
  • Как установить программы по списку в chocolatey?

    @azarij
    В меру опытный никто
    вот тут это делают через Packages.config файл.
    https://superuser.com/questions/1132466/using-choc...
    тут документация https://chocolatey.org/docs/commands-install
    Ответ написан
    Комментировать
  • Symfony, у кого есть хорошая практика написания unit-тестов на codeception?

    glaphire
    @glaphire
    PHP developer
    Symfonycasts: testing with a bite - отличный вводной курс, написан для phpunit, но т.к. codeception написан поверх phpunit то легко адаптировать. Проект там для SF 3.3.
    Ответ написан
    1 комментарий
  • Попросили проверить код, на что смотреть нужно?

    index0h
    @index0h
    PHP, Golang. https://github.com/index0h
    Смотря зачем)). Я когда делаю Code Review критерии следующие:

    * Безопасность:
    - Каждый аргумент метода простого типа должен проверяться на тип в случае его проксирования и на граничные значения в случае обработки. Чуть что не так - бросается исключение. Если метод с кучкой аргументов на 80% состоит из поверки из аргументов - это вполне норм))
    - Никаких trigger_error, только исключения.
    - Исключения ДОЛЖНЫ быть человеко-понятны, всякие "Something went wrong" можно отдавать пользователю, но в лог должно попасть исключение со стектрейсом и человеко-понятным описанием, что же там пошло не так.
    - Каждый аргумент (объект) метода должен быть с тайпхинтингом на этот его класс, или интерфейс.
    - За eval как правило шлю на **й.
    - @ допускается только в безвыходных ситуациях, например проверка json_last_error.
    - Перед работой с БД - обязательная проверка данных.
    - Никаких == и !=. Со swtich - единственное исключение, по ситуации.
    - Если метод возвращает не только bool, а еще что-то - жесткая проверка с ===, или !== обязательна.
    - Никаких условий с присваиваниями внутри. while($row = ...) - тоже идет лесом.
    - Магические геттеры/сеттеры разрешаются только в безвыходных ситуациях, в остальном - запрещены.
    - Конкатенации в sql - только в безвыходных ситуациях.
    - Параметры в sql - ТОЛЬКО через плейсхолдеры.
    - Никаких глобальных переменных.
    - Даты в виде строки разрешаются только в шаблонах и в БД, в пхп коде сразу преобразуется в \DateTimeImmutable (в безвыходных ситуациях разрешено \DateTime)
    - Конечно зависит от проекта, но как приавло должно быть всего две точки входа: index.php для web и console(или как-то по другому назваться) - для консоли.

    * Кодстайл PSR-2 + PSR-5 как минимум, + еще куча более жестких требований (для начала все то что в PSR помечено как SHOULD - становится MUST)
    - В PhpStorm ни одна строчка не должна подсвечиваться (исключением является typo ошибки, например словарик не знает какой-то из аббревиатур, принятых в вашем проекте). При этом разрешается использовать /** @noinspection *** */ для безвыходных ситуаций.
    - Если кто-то говорит, что пишет в другом редакторе и у него не подсвечивается, на эти отговорки кладется ВОТ ТАКЕЕЕНЫЙ мужской половой **й и отправляется на доработку)).

    * Организация кода:
    - Никаких глобальных функций.
    - Классы без неймспейса разрешаются только в исключительно безвыходных ситуациях.

    * Тестируемость (в смысле простота тестирования) кода должна быть высокая.
    - Покрытие кода обязательно для всех возможных кейсов использования каждого публичного метода с моками зависимостей.

    * Принципы MVC:
    - Никаких обработок пользовательского ввода в моделях, от слова совсем.
    - Никаких ***ть запросов в БД из шаблонов.
    - Никаких верстки/js/css/sql-ин в контроллерах.
    - В моделях НИКАКОЙ МАГИИ, только приватные свойства + геттеры с сеттерами.
    - В моделях разрешено использовать метод save(при наличии такого разумеется) только в исключительных ситуациях. Во всех остальных - либо insert, либо update.

    * Принципы SOLD:
    - Никаких божественных объектов умеющих во все.
    - Если метод для внутреннего пользования - private, никаких public.
    - Статические методы разрешаются только в случае безвыходности.

    * Принцип DRY разрешено нарушать в случаях:
    - Явного разделения обязанностей
    - В тестах (каждый тест должен быть независимым, на сколько это возможно)

    * Работа с БД:
    - Запрос в цикле должен быть РЕАЛЬНО обоснован.
    - За ORDER BY RAND() - шлю на***й.
    - Поиск не по ключам (конечно если таблица НЕ на 5 строк) запрещен.
    - Поиск без LIMIT (опять же если таблица НЕ на 5 строк) запрещен.
    - SELECT * - запрещен.
    - Денормализация БД должна быть обоснована.
    - MyISAM не используется (так уж)) )
    - Множественные операции обязательно в транзакции, с откатом если чо пошло не так.
    - БД не должна содержать бизнес логики, только данные в целостном виде.
    - Не должно быть нецелесообразного дерганья БД там, где без этого можно обойтись.

    * Кэш должен очищаться по двум условиям (не по одному из, а именно по двум):
    - Время.
    - Протухание по бизнес логике.
    Разрешается по только времени в безвыходных ситуациях, но тогда время - короткий период.
    - При расчете ключей кэша должна использоваться переменная из конфигурации приложения (на случай обновлений кэш сбрасывается кодом, а не флашем кэш-сервера). В случае использования множества серверов - это очень удобный и гибкий инструмент при диплое.

    * О людях:
    - "Я привык писать так и буду дальше" - не вопрос, ревью пройдешь только когда поменяешь свое мнение.
    - "Я пишу в vim-е и мне так удобно" - здорово, код консолью я тоже в нем пишу)) но есть требования к коду, если в них не сможешь - не пройдешь ревью.
    - "Я скопировал этот страшный метод и поменял 2 строчки" - это конечно замечательно, но по блейму автор всего этого метода ты, так что давай без говняшек, хорошо?
    - "Оно же работает!" - вот эта фраза переводится примерно так: "да, я понимаю, что пишу полную хрень, но не могу писать нормально потому, что руки из жо", я правильно тебя понял?))
    - "У меня все работает!" - рад за тебя, а как на счет продакшна?
    - "Там все просто" - не используй слово "просто", от слова "совсем". Вот тебе кусок кода (первого попавшегося с сложной бизнес логикой), где там ошибка (не важно есть она, или нет)? Ты смотришь его уже 2 минуты, в чем проблема, там же все "просто"))

    * Всякое:
    ActiveRecord (это я вам как в прошлом фанат Yii говорю) - полное говно, примите за исходную. По факту у вас бесконтрольно по проекту гуляют модельки с подключением к БД. Не раз натыкался на то, что в тех же шаблонах вызывают save, или update (за такое надо сжигать).
    То, что используется Laravel - это печально((. Что бы выполнить требования приведенные выше, приходится "воевать" с фреймворком.

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

    UPD

    Формализировал данные критерии по ссылочке: https://github.com/index0h/php-conventions
    Ответ написан
    55 комментариев
  • Проект со сложной логикой на Symfony – как проектировать? Примеры?

    Fesor
    @Fesor
    Full-stack developer (Symfony, Angular)
    Как их организуете (их тогда будут сотни)?


    Раскидываю по неймспейсам. Скажем все действия относящиеся к юзерам находятся в папке Users.

    Только вы учитывайте что CQRS это прикольно но особо не нужно. К примеру это сразу подразумевает что вы используете UUID вместо автоинкрементов и прочей чуши. Можете сделать хотя бы как Дядя Боб предлагает в своей Clean Architecture. Просто сервис на каждое действие.

    Есть ли смысл выносить каждую доменную модель в модуль/микросервис


    Если вы не умеете писать монолиты так что бы потом все было относительно нормально, думать о микросервисах не стоит. Так же как делить проект на модули когда вы еще слабо представляете как прописывать границы этих модулей.

    Раньше для дополнительных действий мне достаточно было использовать что-то вроде beforeUpdate/afterCreate модели.


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

    Как не превратить кидание/получение событий типа PostBeforeEdit/PostBeforeEditHandler в "callback hell"?


    Просто забудьте об этих ивентах.

    ACL. Где храните указанную логику?


    Есть в симфони security vouters, а дальше все зависит от того что вы делаете.

    Как вы версионируете подобные проекты? А если нужна "N-1" рабочая версия на продакшене?


    git + docker теги в мастере. Ветки нужно плодить только тогда, когда у вас система деплоится кастомерам и нужно поддерживать сразу кучу версий. Называется это gitflow.

    На какие проекты (точнее, на код) можете посоветовать посмотреть для лучшего понимания? Ссылки на репозитории?

    На гитхабе катострофически мало примеров хороших приложений на симфони. Да и не только на симфони - в принципе найти в открытом доступе сложный проект - это нереально. NDA и все такое. Такие системы обычно очень дорогие и закрытые со всех сторон.

    p.s. почитайте книжки:

    - Эрик Эванс - Предметно ориентированное проектирование
    - Крэйг Ларман - Применение UML 2.0

    p.p.s. Все ваши загоны не имеют никакого смысла если вы не будете пользоваться практиками вроде Test-Driven-Development, ну или хотя бы покрывать систему интеграционными тестами. Без этого вы не сможете делать частный мелкий рефакторинг, а без этого ваша система быстро превратится в легаси.
    Ответ написан
  • Проект со сложной логикой на Symfony – как проектировать? Примеры?

    index0h
    @index0h
    PHP, Golang. https://github.com/index0h
    Как хранить бизнес-логику чтобы модели не превратились в монстров из десятков тысяч строк?

    Тут не совсем модели. Entity - это просто объект данных, умеет хранить их в себе и бросать исключения, если не правильные данные вставляете, все. Repository - умеет работать со своим Entity И БД.

    БЛ находится в классах сервисах.

    Читал про Command Bus где, если правильно понял, на каждое действие в системе – свой класс?

    Вообще под каждое дествие - не обязательно, тут нужно руководствоваться здравым смыслом.

    Как их организуете (их тогда будут сотни)?

    Иерархически. Путь к классу должен быть "понимаем".

    Есть ли смысл выносить каждую доменную модель в модуль/микросервис, хранить всю связанную логику где-то там внутри, а с остальными общаться по внешнему API?

    Только в тому случае, если вы уже делали такие же проекты и в точности знаете максимально точно границы каждого домена. Иначе - не стоит. Делайте монолитную систему, а разделение на микросервисы - только по факту необходимости.

    За ответы в клиентскую часть – отдельный сервис-фронтенд?

    Если в "сервис" вы вкладываете понятие простого класса, умеющего форматировать ответы вашего проекта - мысль здравая.
    Если ответы будут асинхронными (от сервера к другому) - имеет смысл выностить в отдельный клиентский класс.

    Каков оверхед?

    Ничтожный.

    Используют ли такое на практике?

    Да

    Какие подводные камни?

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

    Как не превратить кидание/получение событий типа PostBeforeEdit/PostBeforeEditHandler в "callback hell"?

    Если есть возможность отказаться от событийной модели - часто лучше отказаться.
    Листнеры доктрины конечно штука мощная, но работает не всегда очевидно.

    Функционал "PostBeforeEdit/PostBeforeEditHandler" часто дешевле и проще вынести в сервис, но опять же руководствуйтесь здравым смыслом.

    ACL Где храните указанную логику?

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

    Какие структуры для описанного выше – best practice?

    Если есть возможность привести к одноуровневому виду - сделайте. Если с точки зрения бизнеса может потребоваться иерархическая (не фиксированной вложенности) ACL - до последнего убеждайте, что это плохая идея, не повторяйте чужих ошибок.

    В моём понимании это выглядит как куча трёхмерных кубов доступа "crud – group – entity – field", как это сделать более плоским пока только одна идея – делать кучу таблиц many-to-many.

    Гибкая настройка вплоть до каждого поля 90% что не нужна. Если можно свести к понятию скопов прав - сделайте это.
    Структуру можно предлагать только зная ваш проект.

    Версионирование. Как вы версионируете подобные проекты?

    Semver.

    А если нужна "N-1" рабочая версия на продакшене?

    Значит на прод попадает ваша версия с тегом "N-1"))

    Есть ли смысл разделять версии в рамках единой кодовой базы проекта и как (неймспейсы, конфиг, модуль, что-то ещё)?

    Храните яйца в отдельных корзинках. Если модуль развивается полностью отдельно и может быть вынесен как зависимость проекта в vendor - делайте.

    И, самое главное – как всё это совместить?

    • РУКОВОДСТВУЙТЕСЬ ЗДРАВЫМ СМЫСЛОМ
    • Принимаете жесткие соглашения по правилам написания кода, например такие
    • Постарайтесь убедить бизнес в том, что без покрытия кода автотестами будет дороже, нестабильней и дольше. + Пишите тесты. Если объем тестов в 4 раза больше кода, который они тестируют - это норм. У меня бывали случаи, когда для критичного функционала тестов было в ~16 раз больше, чем кода.
    • Жесткие, обязательные кодревью.
    • Если задача крупная - декомпозируйте ее.
    • Технический долг - возвращайте обязательно И как можно скорее.
    • Перед тем как писать код для работы с внешним сервисом - имеет смысл написать его эмулятор.
    • Спешите только в случае серьезных проблем на проде)). Фичи "на вчера" отличаются от фич "на потом" только приоритетом выполнения, более ничем.
    Ответ написан
    6 комментариев
  • Symfony, SPA аутентификация, какова лучшая практика в настоящий момент?

    passionkillah
    @passionkillah
    Backend-разработчик
    Security прекрасно может быть использован в REST, потому что он, по сути, просто предоставляет вам middleware для аутентификации пользователя, запускающееся при каждом запросе к вашему API. Саму же реализацию вы можете как выбрать из готовых, так и написать собственную. А UserProvider в свою очередь предоставляет вам доступ к объекту текущего пользователя внутри вашего кода, что весьма удобно.

    Действительно, JWT достаточно, и никто не запрещает вам сделать реализацию без компонента security, однако его использование будет существенно удобнее при использовании этого бандла, который взаимодействует с security. В случае с ним вам не нужно будет даже писать реализацию middleware.
    Ответ написан
    2 комментария
  • Как правильно собрать локальную сеть с Active Directory и Cisco?

    MaxLK
    @MaxLK
    сети, виртуализация, СХД...
    По хорошему надо вообще все переделывать. DHCP переносить в АД, для АД делать домен xxx.local. У вас сейчас проблема в том, что DNS настроен не правильно и он не знает про DHCP. на 2800 оставлять только маршрутизацию наружу и NAT, к нему цеплять 3750 и настраивать VLAN и внутреннюю маршрутизацию, все остальное цеплять после 3750. тогда предложенный выше форвардинг DNS станет не нужен. И эта, резервный сервер в АД умер вместе с НТ - с тех времен сервера не делятся на основной и резервный, на них можно перенести разные роли, но работают они все одновременно.

    Тут сложно, не удобно и долго расписывать подробно как Вам все сделать. идея в том, что маршрутизатор должен маршрутизить внешнюю нагрузку, 3750 станет уровнем ядра/агрегации, остальной зоопарк уйдет на уровень доступа. в результате управление сетью будет в основном на 3750. интегрировав в АД DHCP и правильно настроив DNS Вы сможете автоматически раздавать адреса по разным VLANам и у Вас не будет проблем с разрешением имен как внешних, так и внутренних. кроме того у Вас сейчас используется доменное имя "внешнее", а адресация класса С не маршрутизируемая - получается коллапс. это сильно не правильно. особенно если ваш xxx.ru где-то зарегистрирован и ваш DNS понимает что он должен быть снаружи, а по факту он внутри.
    Ответ написан
    4 комментария
  • Как правильно собрать локальную сеть с Active Directory и Cisco?

    hint000
    @hint000
    у админа три руки
    Заходите на 192.168.4.3
    5dd5fbdf61240369768688.png
    5dd5fbe82a9c5317612722.png
    В пункте 3 прописываете 192.168.4.1 или\и 8.8.8.8, галку в 3.2 убираете (она не позволит прописать forwarders).
    Теперь о смысле. Если так не настроено, то:
    Клиент хочет узнать адрес yandex.ru, видит 192.168.4.3 в списке DNS, делает запрос у 192.168.4.3 о адресе yandex.ru, но 192.168.4.3 знает только о своём домене, он не знает про yandex.ru и отвечает клиенту отказом. По-хорошему клиент должен сразу задать тот же вопрос следующему DNS в списке (192.168.4.1) и получить нормальный ответ. Но в windows работа с DNS всегда была корявой, неадекватной. Винда как бы говорит вам "если я не получу правильный ответ от DNS с первой попытки, то вы у меня помучаетесь!"
    Если вы добавите forwarders, то 192.168.4.3 при запросе yandex.ru скажет (сам себе) "Так, про yandex.ru я ничего не знаю, значит спрошу у 192.168.4.1", и он спрашивает у 192.168.4.1, получает ответ, и затем передаёт этот ответ (от своего имени) клиенту. Т.е. клиент с первой попытки получит то, что ему нужно.

    Есть ещё один вариант. Будет ли он работать лучше - зависит от роутера Cisco. Прописываете на роутере два DNS: 192.168.4.3, 8.8.8.8. При этом в DHCP убираете 192.168.4.3 из списка DNS. Т.е. клиенты получают DNS 192.168.4.1, 8.8.8.8. При запросе имён из AD роутер должен переслать запрос на 192.168.4.3. При этом уже не нужно (и даже немножко вредно) прописывать forwarders на 192.168.4.3. В теории должно работать не хуже, чем первый вариант, но нужно проверять на практике, т.к. в каждой конкретной сети и с каждой железкой могут быть свои заморочки.
    Ответ написан
    3 комментария
  • Асинхронная загрузка XML и Google Maps в jQuery?

    qrazydraqon
    @qrazydraqon Автор вопроса
    Решение:

    коротко — при загрузке карты руками создать deferred-объект, задать при загрузке карты callback'ом его resolve()

    полностью:

    function loadXML() {
        return $.ajax({ url: 'data.xml', dataType: 'xml' });
    }
    
    function loadMap() {
        dfd = $.Deferred();
        var script = document.createElement("script");
        script.type = "text/javascript";
        script.src = "http://maps.google.com/maps/api/js?sensor=false&callback=finishMapLoading";
        document.body.appendChild(script);
        return dfd.promise();
    }
    
    function finishMapLoading() {
        dfd.resolve();
    }
    
    function loadData() {
        var LMap = loadMap();
        var LXML = loadCoupons();
        $.when( LMap, LXML ).then( function() {
            doSomethingAwesome();
        });
    }
    
    window.onload = function() {
        loadData();
    }
    
    Ответ написан
    1 комментарий
  • Как победить ошибку: "(104: Connection reset by peer) while reading response header from upstream?

    Slash-Zn
    @Slash-Zn Автор вопроса
    Вроде решил ошибку: https://danfa.net/forum/thread/1649/
    Ответ написан
    Комментировать
  • Как при событии на теге с data-id="1" найти другой тег с data-id="1"?

    delphinpro
    @delphinpro Куратор тега JavaScript
    frontend developer
    Точно так, как вы сами описали, только на языке кода. Волшебной функции FindWhatINeed() не существует.

    $('.parent') // берем родительские блоки
      .on('mouseenter', '[data-id]', event=>{ // вешаем обработчики на дочерние
        let id = $(event.target).data('id'); // узнаем id
        $(event.target).closest('.parent') // поднимаемся до своего родителя
          .siblings('.parent') // переходим к соседу
          .find('[data-id='+id+']') // берем в нем нужный дочерний
         //... что-то с ним делаем
    ;
      })


    Такой код будет работать в обе стороны.
    Если нужно только в одну, то делаем выборку только одного родителя
    $('.parent').eq(0) // берем родительские блоки - только первый
    Ответ написан
    Комментировать