• Из кого состоит команда разработки WEB приложения?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Профессии появляются по мере роста сложности.
    Проект состоит из технологий. Технологии требуют навыков.
    Навыки могут быть у одного человека, а могут у нескольких.

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

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

    Чтобы эта усталость росла медленнее и человек не увольнялся - берут другого, что неизбежно ведет к появлению проблем вида "кто из вас главнее" или "кто из вас положит голову на плаху, когда не получилось".

    Вокруг такое БЫТИЕ (обусловленное тем, что способности должны вести к деньгам и ни к чему другому), и даже если сами исполнители понимают, что неважно, кто виноват и кто главный - сам бизнесмен не желает принимать это, т.к. считает себя главным, именно эта идея сделала его бизнесменом. И если никто из вас не принимает ответственность он начинает считать вас бездарями, а если принимает - то увольняет того, кто принял.

    Корень всех зол невежество (отсутствие знаний). А так как бизнес всегда хочет готовое - то невежества с каждым днем больше и команды все больше.

    Вроде решение простое - учите своих людей.

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

    Плохо и то и другое, если ваша цель прибыль и успех.
    Но хорошо и то и другое, если ваш цель - образование и забота.

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

    "Проклятый советский союз где все зарабатывали плюс минус одинаково, а квартиру не нужно было покупать, а медицину вам просто давали в подарок"
    Ответ написан
  • Будкемп или курсы?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Каждая штука дает тебе свои преимущества.

    Так курсы или "гос-услуги" отлично бы вкатили в стране, где бизнес режут и ограничивают (Беларусь?). Там "бумажка и букашка" - мало кого интересует умеешь ты или нет - но документ быть обязан. Но так было пару лет назад. Теперь здесь принимают только тех, у кого есть рекомендация от других внутрисистемных, то есть твой диплом или бумажка помогут тебе получить 200 долларов в месяц и работу в стиле "делаем что сказали" и (что важно!) "отвечаем за грехи дружков" - их же нельзя наказывать, а тебя можно. То есть придется обладать лютым терпением и смирением, т.к. отвечать придется за тупость, которую ты бы в жизни не допустил, а "принцип курятника" - плюй на нижнего, толкай ближнего и рвись наверх - единственное развлечение, которое доступно тем, кто вообще ничего не умел и не хочет.

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

    Меня не брали на работу долгое время (точнее как - брали, но не в те конторы, где продукт известен на всю страну). Они требовали от меня знания технологий, которыми я не владел. И я подумал - может пройти курсы, наверное их можно получить там. Я пошел. Хорошо человек адекватный попался. Он сказал - "я говорит с тебя не копейки не возьму, но блин - иди уже работай, ты и так всё знаешь". Мне тогда сказали что я всё знаю, а я до сих пор уверен, что мало чего знаю. Так вот - в России так не будут работать, т.к. людей с совестью с каждым годом капитализма меньше и меньше, и у вас это намного жестче чем было в Беларуси. С тебя возьмут деньги, начитают лекцию, дадут бумажку. А по приходу на фирму тебе зададут вопросы, которые ты не знаешь, и пойдешь опять домой, вроде и курсы проходил и пофиг.

    Вот другая ситуация. Лет 10 назад я записался на курсы по какому-то там "личностному росту", где рассказывали там за бизнес что-то, за инвестиции, и за жизнь немного. Я был всегда человеком, которого приучили не верить никому, а разбираться, по своему, но обязательно проверять, что говорят. И я не понимал, что они несут, руководствуясь своими, почти советскими, взглядами. Я задавал много вопросов тренеру, вопросов сложных, которые в моей голове не находили решения. Вопросе на 10-ом я услышал четкую фразу, которая навсегда положила конец в моей голове любым курсам. "Ты своими вопросами мешаешь людям развиваться, я должен давать теорию, а я только и делаю что тебе всё объясняю". У меня был тогда шок, я не мог поставить на место человека, к которому пришел учиться. Он по сути настраивал толпу против меня, хотя я заплатил ему, как и все в зале. Чуть позже кто-то из зала пояснил мне ответ на мой вопрос, когда всех отправили по домам. Но это не меняет принципа, и того что "может произойти" - если твои вопросы сложные или слишком правильные - ты виновен. То есть бывают курсы, цель которых получить деньги и развеселить вас, о каких вообще знаниях ты в итоге говоришь?

    Следующий момент. Представим крутую контору. Большую такую, в которой, чтобы открыть форточку и выйти на перерыв нужно писать заявление, чтобы сохранилось в документообороте. Если ты демонстрируешь слишком высокие для работников знания - нужно создавать прецедент и тебя повышать, создавая у остальных иллюзию что это возможно. С одной стороны - ты ставишь под сомнение всех, кто рядом с тобой работает, твой коллектив. Ты лучше их стало быть они должны быть наказаны, а ты поощрен. Им это не нравится. Со второй стороны это не нравится руководству, ведь тебе надо приплачивать, а "так хорошо сидели", и "не мути воду" это самая вежливая форма, в которой тебе хочется ответить. Поэтому все сидят и молчат. Но денег то надо. И чтобы получить прибыль нужно продемонстрировать успех на РЫНКЕ. То есть программист берет какую-то старую технологию, обдумывает её, запаковывает, и правдами-неправдами добивается права выступить на какой-то конференции. И так случается, что слушатели конференции за то, что он обладает хорошими педагогическими способностями - всё понимают, и начинают покупать этот запакованный старый труд наших дедов. Сотруднику дают компенсацию, он молодец, он "сделал прорыв в науке". Он сделал прорыв на рынке, а наука тут не причем. Наука - это дать большинству простое объяснение существующего так, чтобы следующий раз объяснять не пришлось. А рынок - это выручить за это прибыль. Это противоположные стороны шкалы. Но что в сухом остатке? На рынок вышла технология, которую пока еще мало кто знает, и другие конторы поменьше начинают требовать от тебя и таких как ты знаний этой хреновины. Но ей может научить только этот чел, кто придумал, а ему никто не даст, не уволившись, а увольняться он не хочет, т.к. зарплатка всё таки попу греет.

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

    Мы подходим к тому, что писали Ленин и Сталин. По мере монополизации предприятий количество национализма, нацизма, а далее фашизма и вместе с этим безработица постоянно растет, а уровень зарплат постоянно падает. То есть предприятия в итоге нанимают тех, кто поехал в другую страну, там всему научился и вернулся домой "каяться", где делает ту же работу за меньшие деньги и возможность побыть с семьёй, т.к. там, на чужбине, требуется выкладываться на все 100, чтобы удерживать лидерство. Это в целом улучшает уровень жизни малого числа людей в обмен на ухудшение уровня жизни большого числа людей. И государство в этом случае не имеет альтернативы, кроме как вырезать на корню все места, где люди об этом размышляют. Способы разные есть, сегодня вот фабрики ботов (ранее в России, а ныне в Беларуси), в Беларуси например тупо закрытие бизнеса именем измены Родине, в Украине от брата слышал, что обувной магазин в Киеве сожгли к чертям, потому что не договорились и тд. Это не потому, что "государство плохое", а потому что оно в ситуации, когда или так или выметайтесь из кабинетов.

    Ты спросил - какие курсы выбрать? В капитализме - это неважно. Ты должен освоить технологию, и если для этого тебе понадобится пройти 10 курсов и просидеть дома в тестировании полгода - это никого не волнует и никто не заметит. Поэтому правильный вариант - это изучив что-то немедленно придумывать, что на почве этого можно сделать и искать работу предлагая это в качестве преимущества.

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

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

    Медитируйте.
    Ответ написан
    2 комментария
  • Как можно объединить условия трех зависимых сущностей в policy?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Прежде чем читать ниже, обратите внимание на ответ Дмитрий. Четко. Понятно. По делу. Я дам теорию.

    Контроль доступа тема немножко сложнее, чем просто по документации создать политики.

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

    Чтобы что-то сделать (удалить), вам нужно проверять доступы не к модели, а к действию. Вы их можете положить в модель или в политику, но как вы прикажете базе данных "не возвращать после SELECT записи в которых есть такой-то параметр", когда политика на вход ждет экземпляр модели, который уже получен из базы?

    Политика это про "что можно видеть и что не можно".
    Политика - фильтрует_список ИЛИ запрещает_действие_с_одной_записью (по факту - фильтрует одну запись до пустой переменной и дальнейшее выполнение не имеет смысла).
    Разрешения - запрещают действие, и они отталкиваются часто от довольно сложных запросов, как-то проверить время, рабочий день, параметры среды, параметры десятка моделей и тд.

    Что мы получаем в итоге. Если вы хотите канонично хранить все в политике - создайте внутри политики меню метод, возвращающий Eloquent\Builder или Query\Builder, который нужно выполнить. И не ожидайте что он выполнится "автоматически", вообще в контроле доступа автоматическая магия только вредит. Вызовите его в контроллере так, как вы вызываете $this->validate(), вы же не ожидаете что валидация "выполнится сама", хотя и могла бы, но это неочевидно для тех кто после вас код читать будет.

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

    Вы либо в модели делаете метод newPolicy/getPolicy(), возвращающий политику, в которой есть метод, ожидающий на вход и ту модель и эту, и делающие все действия. В итоге это уже не про "как в ларавеле", это про обычный ООП - написали метод в политике, создали класс, вызвали, и про хранение "где удобно чтоб лежало".

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

    Я обычно пишу свой класс гейта, в который вообще передаю список разрешений (с поддержкой разных ИЛИ даже может быть) из реквеста, который там в статическом методе лежит, прогоняет эти разрешения. Если действие еще и зависит от запросов - я там же и запрос положу в методе, вызову его из реквеста, выполню, проверю результат. И только потом достану модель из БД, и продолжу что-то с ней делать.

    А с помощью политик или обычных ифов я скорее скрою поля в ответе json. И даже тут можно настроить Serializer и группы, которые это будут делать на основе реквеста или же политики.

    ====

    Вообще про контроль доступа много можно начитать, но я здесь вкратце дам то что вы там найдете. Есть подходы ACL, RBAC, ABAC и ещё какие-то менее известные.

    ACL - это про "разрешения". У вас есть список действующих лиц, список ключей (разрешений), список ключниц (ролей), а в коде написаны замки (ифы), которые ключами открываются. В итоге ваш класс берет ключи на ключнице, и пытается открыть замок, если не открылось - бросает 403. Есть ли тут сложности? Есть. Пример был описан на одной из статей на хабре про звездолет. Наняли одного человека - он монтажник двигателя. Потом наняли второго, а первого переназначили на повара. У повара должны быть чистые руки, у механика - грязные. При этом права у них одинаковые (согласно их умений в жизни), но права повара - отключают некоторые права механика и наоборот. А потом мы добавим третью роль, которая например дает права обратно и всё, мы поплыли. Тут и появляется та штука, что мы встречали в apache - deny first / allow first. По мне deny first - безопаснее. Если есть хоть один запрет на полномочие, то можешь потом хоть сто раз его добавить - его не будет. Можно кидать другое исключение что-то вроде "полномочие запрещено".

    RBAC - это придумка Yii (вернее в оригинале это концепт с ролями как выше написал, но реализовали его потом на PHP разработчики Yii, очень своеобразно), который попытался магически сделать ACL. Он основывался на том, что изначально у чего-то (модели, ресурса) есть методы проверки ключей, но чем "круче" роль пользователя (наследование), тем более злой метод применяется. Это жутко неудобно писать на самом деле, потому как наследование начинает путаться с композицией, а иерархия ролей ломаться, потому как постоянно хочется создать роль на основе существующей, и со временем хочется на основе двух существующих и вообще там такой лес начинается что просто беда.

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

    При этом у лары здравая идея с политиками, которые точно нужны например, когда вы выдатаете пользователей и баланс на их кошельках. При всем уважении баланс могут смотреть администраторы, менеджеры и сам юзер, но не его друзья (которые могут получить список юзеров под действием "дай список моих друзей"). И это вопрос даже не ролей, это вопрос наличия ключа "могу смотреть балансы". Обратите внимание, что модель при этом есть, но у нее не видны несколько полей. Технически можно запихнуть все методы ABAC и назвать это политикой, и это будет правильно с точки зрения "все доступы - в политиках" - но вот автоматическое их применение лара не дотянула до приемлемого уровня, а оно и не надо.

    Так что контроль доступа это про "настроить все способы защиты и применять те, которые сейчас нужны по методике ABAC" - то есть в обычных функциях проверять хочешь ключи-разрешения, хочешь - роли, хочешь день недели, а хочешь - поле в модели.

    Разумеется, контроль доступа чаще всего делают "настраиваемым в админке". Но попробуй-ка настроить в админке обычную функцию, которая может делать что-угодно. Вот и пришли к выводу, что в админке настраивают часть ACL, что называется дают разрешения в роли, вяжут роль к юзеру, а потом от этих разрешений зависит какие действия делает обычная функция. Некоторые даже делали flow-контроль, позволяющий чуть ли не в графическом редакторе строить логику добавляя "или", "и", "больше", "меньше" - но этим реально пользуются потом только программисты, никто не хочет ничего сломать, поэтому - в админке дают разрешения, а по ним написан код. Самый правильный способ.
    Ответ написан
    1 комментарий
  • Правильно ли рендерить вёрстку на сервере?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Позвольте поправить.

    Это не "правильно", это "возможно".

    Использование API уменьшает число данных в одном запросе, но заставляет вас делать авторизацию, делать всевозможные защиты. Если у вас есть фронтенд, который так и так делает запросы асинхронные (fetch/$.ajax), то разумнее конечно возвращать данные.

    С другой стороны, если ваш фронтенд не умеет в виртуальное дом-дерево (у вас не фреймворк, а нативный яваскрипт) - то при первой отдаче страницы в конце html вы можете отправить
    <template id="mytemplate"><div>{{data}}</div></template>
    , чтобы его схватить яваскриптом и генерировать верстку на основе него.

    Делать апи запрос, который возвращает html - это возможно, но это не то что "неправильно", это вы соединяете две задачи, каждая из которых может в будущем быть дополнена, в одну - и усложняете доработку в итоге. Если они по отдельности - доделать это проще. Апи запрос может быть использован где-то еще, если с версткой - то нет. Верстка же может быть изменена под новую тему оформления, если вместе - то сложнее.

    Считается, что доделывать должно быть легко, поэтому это "неправильно".

    Но вернуть верстку первым запросом когда человек только на сайт вошел - это нормальная практика и так было, пока было мало фронтенд фреймворков, в которых верстка вшивается в код самого фронтенда. Пока люди использовали handlebars/jquery это было нормально и вполне работало, а главное - не требовался специалист, который знает конкретный фреймворк, т.к. верстку знает большинство.
    Ответ написан
    Комментировать
  • Можно ли извне изменить fetch данные которые получает сайт?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Для этого JWT токен придумали, который, если изменить, не пройдет проверку, т.к. содержит в себе секцию подписи, которая была создана с учетом того, что содержит сам токен. Если влезть в данные токена и что-то там поменять, то функция проверки свалится в ошибку.

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

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

    Кроме того, если вы размышляете о фронтенде отдельно, то после запаковки вашего кода с помощью webpack, дотянуться до места, где хранится текущий уровень полномочий, не меняя исходный код, нельзя - в публичной области видимости нет доступа к модулю auth, он работает как безымянная функция, которая лежит в оперативной памяти, но из консоли в него не достучишься. Ну да, теоретически можно влезть в оперативку и долго долго маясь найти адрес где оно лежит, но это чрезвычайно трудно и не стоит того (понимая, что всё что получит хакер - это покажет кнопку "удалить запись", которой у него не было, и при попытке удалить - сервер все равно пошлет его куда подальше)

    Есть различные TamperMonkey позволяющие выполнить js скрипт при входе на страницу, но для этого опять же нужно его выполнить внутри вашего кода, а туда он не попадает, а значит и подменить внутри него ничего не может.
    Ответ написан
    Комментировать
  • Словарь для брута rar архива возможно сделать через Блокнот?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Я очень рекомендую не использовать при "комбинациях" какой-то конкретный файл или словарь. Точнее не так. Словарь или файл годен для "того что надо скомбинировать", но никак не для "всех возможных комбинаций"

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

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

    Для простых сочетаний букв без определенного порядка берёте python (даже есть сайт с онлайн-редактором, чтобы попробовать) и импортируете itertools, там есть функции permutations/combinations. Для "форматов" или "вариантов" уже придется поиграть в программиста. Эти функции всегда возвращают "незавершенный цикл" и по мере обхода генерируют ОДНО значение, а старое забывают.

    Если вы загрузите в скрипт файл в несколько гигов - ляжет по памяти. Если уже очень хочется прям файл, то его читать надо по одной строке, получим как бы сказать генератор с указателем "где закончили". Но еще лучше это как написали выше, вам надо вспомнить если не сам пароль, то хотя бы вашу методику "как вы обычно придумываете пароли" и составить кусок кода, который пройдет все ваши варианты - скомбинирует исходные буквы с шаблонами их написания и в моменте будет выдавать вам одну, а когда выдаст - создаст новую, первую забудет.
    Ответ написан
    1 комментарий
  • Как испраить ошибку: SQLSTATE: Duplicate alias: имя таблицы указано больше одного раза?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Пожалуйста, называйте связи так, как называется модель, которую вы хотите получить.

    items() ? чего? вы хотите получить CartProduct. назовите cartProducts()

    очень редко, но бывает, когда CartProduct можно получить двумя способами. Тогда cartProductsByModel1(), cartProductsByModel2()

    исключение - абстрактные паттерны, деревья. там можно назвать parent/children/treeParent/treeChild

    <?php
    
    Class Cart {
        // ...
    
        // вот здесь написано, что CartProduct может быть одновременно в двух и более корзинах. не могу представить себе логику, где участник заказывает на сайте товар, а потом этот же товар (который ты заморозил только что) кто-то еще заказывает
        public function items(): BelongsToMany
        {
            return $this->belongsToMany(CartProduct::class, 'cart_products');
            
            // Cart.hasMany(CartProduct) + CartProduct.belongsTo(Cart) + CartProduct.belongsTo(Product)
            // и второе
            // https://github.com/illuminate/database/blob/master/Eloquent/Concerns/HasRelationships.php#L477
            // первым параметром "тип", вторым - "через что". Вы указываете, что "связь много ко многим" должна вернуть "связь много ко многим"
            // при связи Много-Ко-Многим надо Cart.belongsToMany(Product::class, 'cart_products')
        }
    }


    <?php
    
    class CartProduct {
        // ...
    
        // здесь написано, что CartProduct старше чем Product. Это выразится в том, что вы сможете "привязать" CartProduct только из самого Product, причем один раз. Это дважды неверно.
        public function product(): HasOne
        {
            return $this->hasOne(ProductVariant::class, 'id');
    
            // Во первых каждый продукт с его количеством может быть куплен много раз, а вариант всего лишь показывает на его подвид
            // Во вторых модель CartProduct создается позже чем Product, а значит писать $product.addChild($cartProduct) это снова дичь
            // Product.hasMany(ProductVariant::class) + CartProduct.belongsTo(ProductVariant::class) + $cartProduct.associate($productVariant);
        }
    
        // то же самое. CartProduct старше чем Cart, хотя он в ней лежит. BelongsTo задает старшинство и позволяет добавлять родителя в потомок, а hasOne указывает связь, которая нужна только для select, но не годится для insert
        // Потому что hasOne() - это возможность добавить потомка, причем в Eloquent нету метода addChild() его ещё и самому писать надо типа CartProduct.cart().insert($array) и это тут же нарушает идею отложенной записи, т.к. выполняет запрос немедленно и требует транзакции прямо в модели, тогда как транзакция относится к задаче, а не к модели
        public function cart(): HasOne
        {
            return $this->hasOne(Cart::class);
        }
    }
    Ответ написан
    Комментировать
  • Как организовать опциональное условие в запросе?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Не вижу ничего плохого в том, чтобы использовать if () вместо ->when()

    Будьте осторожны с "приходит - фильтруем, не приходит - отдаем все", учитывайте что может прийти только ключ, а еще может прийти пустое значение - пустой массив или пустая строка или цифра 0. А ещё "все" бывает 100 тысяч. А еше база бывает 15 миллионов, и выбрать 12000-ую страницу из 15 миллионов не так то просто, особенно когда у вас есть сортировка. Сортировку внедрять везде - это трудно.

    Лучше всего отталкиваться от array_key_exists(). В вашем случае "если роль находится в допустимом списке то фильтровать" несколько ограничивает вас на пример "роль это константа". В проекте константой является не роль, а разрешение, а роль это для удобства пользователя - группа разрешений. Ролей человек наделает тьму, а вот разрешений сколько кодер накодит столько и будет. Но, да, есть фиксированные роли (они же - базовые) GUEST/USER/BANNED/ROOT. Это не меняет тот факт, что поведение "если присланное клиентом не находится в нашем списке, то программа не работает" - в вашем случае она должна сработать но вернуть пустой список.

    Просто проверяешь запрос на наличие поля (для этого убираешь в ларавеле там мидлвар ConvertEmptyStringsToNull, т.к. это семантически неверное поведение для апи, хотя и рекомендуемое в ларе).

    when и колбэки - это процессорные вызовы, контексты, и лишний расход памяти, ифы это легче. Если б они давали что-то кроме вертикалости написания, но ведь нет. Не то чтобы я призываю писать вас вложенных тысячу ифов, ифы тоже надо уметь упрощать до 1-2 уровня или массива-маппинга.
    Ответ написан
    1 комментарий
  • Какие используются практики для отладки кода в проекте, построенном на микросервисах в Docker контейнерах?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Если вас интересует неверная работа одного микросервиса, то точно так же как вы делали. Контейнеры так или иначе содержат веб-проект и умеют отвечать на запросы с морды/браузера/курла.

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

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

    В компьютере много устройств. Если бы они напрямую друг с другом общались, мы бы никогда не добились такого развития цифровой техники. В компьютере есть ШИНА, и управляющее устройство, которое поочередно работает то с первым устройством, то со вторым, и когда они одновременно что-то хотят - они ставятся один за другим с проверкой "кто раньше и кто сейчас может".

    Таким образом дебажится лог запросов и ответов с "шины" (эта шина пишется хорошим сеньором, либо ставится и долго настраивается хорошим сеньором), чтобы понять кто когда кому чего спрашивал.

    Если в вашем проекте сервисы друг другу запросы шлют как хотят, что вы не можете это проследить и некому рассказать - трехмесячный отсчет до того момента, пока "вас тоже выгоняют на рынок" уже пошел. Это проект убийца. Владельцы его у кого-то купили, или им архитекторы насоветовали наставить полторы тысячи технологий потому что они "новые", а теперь никто не знает как это вообще включать. И они рандомом подбирают разрабов до тех пор пока кто-то не воскрикнет Эврика и всё сделает, тогда ему кинут купюрку и тоже выгонят. Как сказал Ротенберг - "у нас в стране много кулибиных, дайте бизнесу волю и они сами ЧТО-ТО-ТАМ сделают" (цитата).

    Если вы все же очень хотите сохранить это рабочее место (но дальше - только хуже) - вы можете либо дампать в браузере, делая прямые запросы, логику смотреть в бд, а если логика прям жесткая - подключите пакет Monolog\Logger в dev-dependencies ко всем проектам, и включите Handler который складывает все логи в канал с одним и тем же местом хранения, и потом смотрите в каком порядке логи идут (у вас как раз и получится логгер вместо шины, только он не дает никому комманд, а только пишет что за чем произошло, это тоже задача шины и её мощь)

    Берегите себя. Бизнес о вас не будет заботится. А в текущей обстановке с чрезвычайными мерами, когда даже священников просят втирать дичь - тем более.
    Ответ написан
    2 комментария
  • Как сделать логирование для многопоточного curl?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Почитать и подумать (желательно):

    https://github.com/6562680/support/blob/main/src/X...
    https://github.com/6562680/support/blob/main/src/X...

    Устанавливаем:

    composer require monolog/monolog
    composer require gzhegow/support:1.16.21


    Запускаем:

    require __DIR__ . '/vendor/autoload.php';
    
    
    // const
    define('__LOGS__', __DIR__ . '/logs');
    
    // vars
    $channelName = 'curl';
    
    // dirs
    if (! is_dir($var = __LOGS__)) mkdir($var, 0775, true); // на unix есть ограничение в /etc/php/fpm с помощью umask и listen/owner, и вместо 0775 может быть создано меньше полномочий
    
    // init
    $logger = new \Monolog\Logger($channelName, [
      new \Monolog\Handler\StreamHandler(__LOGS__ . '/monolog/' . date('Y-m-d') . '/' . date('H') . '_' . $channelName . '.log'), // в файл
      // new \Monolog\Handler\StreamHandler('php://stdout'), // для консоли
      // new \Monolog\Handler\StreamHandler('php://output'), // для браузера
      // куда-нибудь ещё...
    ]);
    
    $xcurl = \Gzhegow\Support\XCurl::getInstance();
    
    // data
    $urls = [
      // index => url
      'myIndex1' => 'https://google.com',
      0 => 'https://google.com',
    ];
    
    // action
    $curls = [];
    foreach ($urls as $index => $url) {
      // >>> логируем, что делаем запрос на внешний адрес
      $logger->notice('Fetching url: ' . $urls[ $index ]);
    
      $ch = curl_init($url);
      // curl_setopt($ch, CURLOPT_HEADER, 0); // и т.д.
    
      $curls[ $index ] = $ch;
    }
    
    $retry = null; // создаем пустую переменную, она по ссылке будет передавать состояние в каждом шаге цикла
    $retries = 3; // три повтора, условия выхода из бесконечного цикла
    foreach ($generator = $xcurl->walkmulti($curls, $retry) as $step => [ $index, $curl ]) {
      // $retry автоматически становится null в начале каждой проверки (так написал в walkmulti). То есть если проверок не сделать, $retry будет null и повторов не будет.
    
      // проверяем ошибку. добавь столько ЕСЛИ, сколько нужно. длина контента. время запроса. код ответа, всё в этот ЕСЛИ
      if (curl_errno($curl)) {
        // проверяем условие, после которого повторы делать не надо. например, у нас только три попытки
        if ($step >= $retries) {
          // >>> логируем, что больше не выйдет
          $logger->notice('Unable to retry url: ' . $urls[ $index ]);
    
          $retry = false; // false = больше не спрашивай про него, результат не сохраняй
          // $retry = null; // null = сохрани результат последнего и больше не спрашивай про него
          continue;
        
        } else {
          // >>> логируем что пошел повтор
          $logger->notice('Retrying url: ' . $urls[ $index ] . '. Retries left: ' . $retries - $step);
    
          $ch = curl_init($urls[ $index ]); // в curl нельзя повторно выполнить запрос по тому же ресурсу, собираем заново такой же как был, вероятно - с новой прокси
          // curl_setopt($ch, CURLOPT_HEADER, 0); // и т.д.
    
          $retry = $ch; // передаем по ссылке новый курл, который после всех проверок выполнится в пачке ещё раз
          continue;
        }
      }
    }
    
    $results = $generator->getReturn();
    
    var_dump($results); // [ 'myIndex1' => <content>, 0 => <content> ]
    Ответ написан
    Комментировать
  • Компиляция Vue-приложения в обычный js скрипт? Или не возможно?

    gzhegow
    @gzhegow Автор вопроса
    aka "ОбнимиБизнесмена"
    Последний год использую symfony/encore и пишу обертку для php, которая читает файлы manifest.json, получая ссылку на них, вместо генераторов и билдеров, написанных разработчиками фреймворка.

    Позволяет require и import любых библиотек, любые пресеты и препроцессоры, и все бонусы с этим связанные. И вообще сам Vue при этом необязателен, можно хоть из своих js файлов проект собрать.
    Ответ написан
    Комментировать
  • Как указать Access-Control-Allow-Origin в php?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    CORS защищает от запроса не со стороннего сайта (сервера), а со стороннего клиента (браузера).

    С помощью CORS мы даем команду "проверь, что ajax запрос был запущен со страницы, которую мы отправили клиенту", т.к. заголовок origin при ajax запросе будет равен домену, на странице которого находился браузер, когда запрос делал. Этот origin в итоге и проверяется на соответствие разрешенному.

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

    Если вы хотите запретить запрос с другого сайта (сервера), это сделать геморойно и простейшая для реализации защита - это капча на случай если другой сервер делает слишком много запросов = условная проверка IP адреса, который сервер отсылает (но его можно подменить используя прокси, поэтому читай дальше)

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

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

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

    Полная защита может быть так сделана - все запросы логики разрешены только аяксом и стоят корсы, что только с вашего сайта. А все не-ajax и не-GET запросы проверяются на список разрешенных IP адресов. Но даже тут заголовки можно сгенерировать так "как будто это ajax запрос" и все равно пробить.
    Ответ написан
    Комментировать
  • Как соединить две модели в коллекции?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Коллекция это про пачку.

    Создав класс коллекции компаний и запихнув туда контакты ты сказал примерно так "несколько компаний имеют общие контакты в рамках этой пачки компаний".

    Иногда это имеет смысл, но это такие редкие случаи, что так не надо делать. "независимость" модели это про другое, это про то что контакты являются частью одной задачи из ТЗ, а компании - другой. Но это не мешает одним иметь другое в подчинении, если в тз написано что компания имеет контакты.

    Если ты решил сделать что контакты имеют не только компания или же разные компании могут иметь одни и те же контакты, то это новая таблица company_contact, где указаны все связки компаний с контактами, а не коллекция.
    Ответ написан
    3 комментария
  • Как правильно назвать pivot-таблицу при отношении многие ко многим в laravel?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Правильно это делать таблицу под вашу предметную область.

    Если связка "появляется" при разработке функционала шоп - то префикс - шоп. Затем идет таблица, которая имеет "больший охват" и затем таблица которая имеет "меньший охват" (либо если охват туманно звучит - то как читаешь задание - продукты лежат в категории, значит так и есть категория первая, продукт - вторая). Скорее всего в продукт отсутствует префикс "шоп" потому что вы решили, что продукт это глобалка, дескать разрабатываю магазин - значит зачем мне писать ShopProduct, если можно - Product. В то же время в магазине оказалось что есть Category, а потом вы решили сделать ShopCategory, потому что Category уже была, но вы, возможно, задумались что это кусок совершенно другого функционала.

    И правильно задумались, т.к. общий функционал "ядро" не включает ваше поведение проекта, функционал ядра называется ядром потому что его можно использовать в десяти разных проектах почти не изменяя. Если уж совсем следовать стандарту то удобно называть модельки ядра префиксом Info, например InfoDictionary, InfoCurrency, InfoFile, InfoImage - но его можно и опускать, если вы понимаете, что вы специально это сделали. Категория на мой взгляд имеет больший охват ответственностей (она старше, т.к. продукты в ней лежат по задумке), чем продукт.

    То есть правильное название:
    shop_category_product (для таблиц - shop_category и product)
    shop_blog_category_shop_product (для таблиц - blog_category и shop_product, префикс - обозначает кусок техзадания, в котором появилась возможность нахождения продукта в категории блога). В данном случае это исключает, что сами продукты могут быть по категориям, хотя чаще всего это так. Поэтому возможно правильнее было бы иметь shop_category и shop_product, связку shop_category_product, а в shop_category сделать belongsTo на blog_category, показав что есть связь между той категорией и этой.

    Вообще я когда-то раньше делал модели для пивотов и даже наследовал их класс Pivot, но потом понял, что это по большей части ничего не дает, и проще соединить таблицами через связь, чем класс создавать.

    Бывает когда там нужна модель, но почему бы ей не быть просто моделью, если она вам нужна, чем делать для нее специальный тип, который отличается от модели только тем, что не умеет во множественное число и игнорирует установку дат, и чаще всего не имеет id. Когда вы захотите это оптимизировать на работу "по 100 штук" и попробуете применить $model->chunk(100) сие ругнется, что в таблице нет айди и так невозможно. То есть использование собственных пивотов во имя "порядка" и "эстетики" создает проблемы.

    И ради бога, выпилите из ларавельной функции getTable() {} которая в Illuminate\Eloquent\Model преобразование во множественное число. Вы обожжетесь не один раз пытаясь преобразовать "people" в "person" и тратите драгоценное процессорное время на анализ окончаний, который в результате не дает вам ничего, кроме эстетики. Привыкните делать таблицы в единственном числе. С ними проще работать.

    Можно то как хочешь, но нужно так.
    Ответ написан
  • Есть ли способ добавить свои методы не трогая модель в laravel?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Или так

    <?php
    
    namespace App\Model;
    
    use ВашПакет\Product;
    
    class ProductDecorator
    {
      protected $decorated;
    
      public function __construct(Product $product) {
        $this->decorated = $product;
      }
    
      public function __call($method, $args) { return call_user_func_array([ $this->decorated, $method ], $args); }
      public function __callStatic($method, $args) { return call_user_func_array([ static::class, $method ], $args); }
    
      public function newMethod(): void
      {
        echo "Hello World!";
      }
    }


    В первом случае модель на автомате получит другую таблицу (она из имени класса делается), придется ставить protected $table = 'table_name'; а как захочешь добавить префикс в БД идти искать где префиксы вписывать ибо имена статикой. А добавишь в конфиг базы префикс - придется использовать модели в миграциях, а там в основном строки.

    Во втором случае некоторые методы могут не работать из-за того что возвращают void например.

    Так что:

    <?php
    class ProductService
    {
      public function doSome(Product $product) : Product
      {
        $product->updated_at = new \DateTime('now');
        return $product;
      }
    }
    Ответ написан
    Комментировать
  • Можно ли через .htaccess сократить id get запроса сайт.ru/profile.php?id=2 до сайт.ru/id2?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Да, только оно будет читаться наоборот.

    "Если /id2 (или начинается на /id\d+) то перейти на страницу /profile.php?id=2 либо /profile/id/2"

    которая уже будет разбираться скриптом, как будто было profile.php?id=2
    Ответ написан
    3 комментария
  • Есть ли listIterator в php?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Как сказал Ипатьев, в php всё делается с помощью цикла foreach (да, здесь не только for и while), а чтобы получать курсор - у каждого списка или хешмапа (здесь для удобства массив это и то и другое, что закинешь внутрь тем и будет) - есть внутренний указатель который можно двигать функциями current()/prev()/next()/reset()/end().

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

    ArrayIterator() применяется почти никогда, если применяется то разве что для сведения нескольких "итерабельных" типов к единому способу обхода (чрезвычайно редкая задача), особенно когда в php появились интерфейсы \Traversable и проверка is_iterable(), позволяющая тупо передать что-угодно в foreach() чтобы его обойти.
    Ответ написан
    Комментировать
  • Неправильно работает округление, это баг?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    floor() - округление "отбрасывает дробную часть" при положительном аргументе, и округляет в меньшую сторону при отрицательном. То есть [ floor(-1.65/0.55) = -3 ], а [ floor(1.65/0.55) = 2 ]
    round() - округляет по математическим правилам
    ceil() - округляет в большую сторону
    Ответ написан
    1 комментарий
  • Laravel: почему возвращается 404я страница при обращении к js-файлам в папке /js/chunks?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Опишите ваш сервер. Что там, nginx? Конфиги дайте.

    Скорее всего у вас либо билд косячит и файлов нету (надо npm run dev или типа того запустить), либо билд создает их не там где сервер хочет их видеть.

    В конфиге сервера может стоять даже перенаправление что-то вроде "если начинается на /js" искать вообще в другом месте. По дефолту конечно такого не бывает, но в процессе работы такое могут вкостылить.
    Ответ написан
    Комментировать
  • Не работает whereIn если данных больше 700,почему так?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    В базах данных есть лимиты на число подстановочных элементов.

    Если вы пытаетесь запросить 700 критериев - это значит вы неверно выполняете задачу (хотя кажется, что все верно).

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

    Если вы взяли ваши критерии из файла, то вам следует разбить ваш запрос на повторяющийся с меньшим числом критериев, "делать по 100", например.
    Ответ написан