• Объекты, имеющие поля сложных типов (Immutable или нет)?

    Vamp
    @Vamp
    В состояние объекта входит состояние объектов сложных типов. С формальной точки зрения объекты обоих ваших классов являются изменяемыми. Однако в JVM существует понятие effectively final - это мутабельный объект, состояние которого не изменяется, хоть и не форсируется модификаторами private и final. Если JVM поймёт, что конкретный объект вашего класса не меняется (другими словами, является effectively final), то к этому объекту могут применяться оптимизации, характерные для неизменяемых объектов.
    Ответ написан
    Комментировать
  • При инициализации класса, загружаются ли в память его instace-члены?

    Vamp
    @Vamp
    Могут загружаться, а могут и нет. JVMS не определяет должны ли загружаться все, часть или вообще никакие типы, на которые ссылается загружаемый класс. Так что данное поведение остаётся на усмотрение разработчика JVM.

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

    Пример:
    import java.time.LocalDateTime;
    import java.util.concurrent.CountDownLatch;
    
    public class A {
    
        private static LocalDateTime ldt;
    
        private static CountDownLatch cdl;
    
        static {
            ldt = LocalDateTime.now();
            cdl = null;
        }
    
        public static void main(String[] args) {
            System.out.println("123");
        }
    
    }

    При запуске данного кода с аргументом JVM "-verbose:class" выведется список всех загруженных классов. Пример запуска на openjdk 12:

    ...
    [0,100s][info][class,load] java.time.temporal.TemporalAccessor source: shared objects file
    [0,100s][info][class,load] java.time.temporal.Temporal source: shared objects file
    [0,100s][info][class,load] java.time.temporal.TemporalAdjuster source: shared objects file
    [0,101s][info][class,load] java.time.chrono.ChronoLocalDateTime source: shared objects file
    [0,101s][info][class,load] java.time.LocalDateTime source: shared objects file
    ...

    Видно, что загрузился LocalDateTime и все имплементируемые им интерфейсы, но CountDownLatch в списке отсутствует, несмотря на то, что существует статическая переменная с этим типом.

    Аналогично происходит и с instance членами - классы, на которые они ссылаются, будут загружаться только по мере необходимости (использования). Вполне нормальна ситуация, когда объекты одного класса активно используются в программе, но при этом не все используемые им типы загружены. Более того, это относится не только к членам класса или объекта, но и к локальным переменным и даже просто коду:

    import java.math.BigDecimal;
    import java.time.LocalDateTime;
    
    public class A {
    
        public static void main(String[] args) {
            if ("world".equals(System.getenv("HELLO"))) {
                System.out.println(LocalDateTime.now());
            } else {
                System.out.println(BigDecimal.TEN);
            }
        }
    
    }

    В этом примере будет загружен класс BigDecimal, но не LocalDateTime. Если инвертировать условие или запустить код с выставленной переменной окружения HELLO=world, то в списке классов появится LocalDateTime, но BigDecimal будет отсутствовать.
    Ответ написан
    2 комментария
  • Почему рекурсия в java уходит в бесконечность?

    Vamp
    @Vamp
    Идеальная задачка для пошаговой отладки. Она есть в любой приличной IDE. Покажу на примере IDEA:

    1. Ставьте на строчке, которую хотите отладить, так называемую точку прерывания (breakpoint).

    5de7eb1ceef0b714535201.png

    2. Запускайте программу в отладочном режиме.

    5de7eb4300731205266204.png

    3. Программа начнёт выполняться как обычно, но когда исполнение дойдёт до строки, помеченной breakpoint'ом, выполнение остановится и в дебаг окне отобразится текущий стек трейс, значения локальных переменных и самое важное для вашей проблемы - кнопки пошагового продолжения.

    5de7ebd9dd5e7287922482.png(на скриншоте я уже проехал несколько шагов вперёд)

    Вот эти 5de7ecb0c3d98348194703.png
    Первая кнопка продвинет выполнение программы на одну строчку. Вторая сделает то же самое, но если в выполняемой строке вызывается какой-то метод, то отладка шагнёт внутрь метода и шаги продолжатся уже в теле вызываемого метода.

    Выполняя отладку по шагам, вы будете точно представлять себе что происходит внутри программы и появится понимание откуда растут ноги у бага. Чем раньше вы освоите отладку в пошаговом режиме, тем проще будет в будущем.
    Ответ написан
  • Тип данных с плавающей точкой в php?

    Vamp
    @Vamp
    Храните и отображайте в обычной строке. Влезет столько знаков, сколько нужно.
    Ответ написан
  • Где можно почитать/посмотреть про принципы проектирования и написания ПО для стратегически важных объектов?

    Vamp
    @Vamp
    Такие стандарты существуют. Наиболее известные - MISRA C и MISRA C++. Как можно догадаться, для языков С и С++, соответственно. Изначально созданы для автомобильной промышленности, но распространились и на аэрокосмическую отрасль, медицинскую технику, военных и прочие критичные к надёжности области. В том числе и АЭС.

    Из MISRA C выросли SEI CERT C/C++, AUTOSAR General Software Specification, JPL Institutional Coding Standard for the C (стандарт NASA) и ещё куча других.

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

    Помимо этого, существует ворох стандартов, описывающих подходы к организации процесса разработки и контроля качества. У каждой отрасли свои стандарты. Например, AS9100.

    Если вы интересуетесь вопросом чтобы применять подобные техники в повседневном программировании, то не стоит - никакого удовольствия и конкурентного преимущества вы от этого не получите. Если конечно ваше повседневное программирование не включает в себя разработку панели управления АЭС.

    Update 2019-12-30:
    На хабре появилась интересная статья с кратким обзором MISRA.
    Ответ написан
    Комментировать
  • Как правильно между запросами cURL делать паузу?

    Vamp
    @Vamp
    Сразу после вызова функции.

    for ($i=0; $i < 3; $i++) { 
      echo curlNew();
      sleep($sleepTime);
    }
    Ответ написан
    Комментировать
  • Как пользоваться gRPC?

    Vamp
    @Vamp
    1. Надо смотреть какое именно API предоставляет сервер. Если JSON REST, то gRPC не подойдёт. gRPC - отдельный протокол, который должны поддерживать и клиент, и сервер, чтобы можно было по нему взаимодействовать.

    2. Если хотите взаимодействовать с удалённым сервером, то нужен только клиент. Если хотите сами быть для кого-то сервером, предоставляющим gRPC API, то нужен сервер. На PHP нельзя сделать gRPC сервер, видимо поэтому и предлагают node.js. Попробуйте поднять сервер на линуксе, вместо openserver. Тогда появятся команды, описанные в доке, и вообще разработка станет гораздо проще.

    3. Простых доков по gRPC не видел.

    Помимо самого gRPC вам придётся разобраться ещё и с protobuf, так как gRPC основан на нём. Его тоже необходимо установить либо как модуль к PHP, либо как composer зависимость. Помимо этого, удалённый сервер должен предоставить proto файл, на основе которого вы будете делать gRPC клиента. Этот файл необходимо скомпилировать при помощи protoc (компилятора protobuf) с использованием специального плагина к нему - grpc_php_plugin. В результате получится набор PHP скриптов, реализующих gRPC клиента для доступа к API удалённого сервера. Вот эти скрипты вы и будете использовать в своих скриптах для общения с сервером.
    Ответ написан
    3 комментария
  • Как сформировать XML в SOAP на PHP?

    Vamp
    @Vamp
    Вам придется вручную составить XML. Встроенных средств для работы с атрибутами у SoapClient нет.

    $xw = new XMLWriter();
    $xw->openMemory();
    $xw->startElementNS('ns1', 'GetTariffRequest', null);
        $xw->startElement('NeedList');
            $xw->text('1');
        $xw->endElement();
    
        $xw->startElement('Subject');
            $xw->writeAttribute('SbjKey', '1');
        $xw->endElement();
    $xw->endElement();
    
    $s = new SoapClient(...);
    $s->GetTariff(new SoapVar($xw->outputMemory(), XSD_ANYXML));

    XMLWriter здесь только как пример. Составлять строку с XML вы можете любым другим способом.
    Ответ написан
    Комментировать
  • Разные development и production окружения не нарушают концепцию Docker?

    Vamp
    @Vamp
    В dev окружении почти никогда не бывает такой же образ как в prod. Но docker позволяет сделать очень близко к проду при помощи наследования. То есть prod наследуется от, допустим, php:7.3.3-fpm, устанавливает нужные модули и опции php.ini, а dev образ наследуется от prod и доустанавливает xdebug, composer, node, модифицирует только нужные для дева опции php.ini.

    Такая организация позволяет почти не тратить время на актуализацию dev образа. Поменялся prod - в одну команду пересобрали и dev. Очень удобно.

    Корнем всей иерархии будет это базовый prod образ, в котором нет никаких файлов проекта. Уже от него наследуются dev и образы с запакованным приложением. В контейнер на основе dev образа монтируете рабочую директорию проекта и работаете как удобно.

    Иерархия наследования получается примерно такая:
    php:7.3.3-fpm
    └─ prod:base
       ├─ dev:latest
       ├─ prod:0.0.1
       ├─ prod:0.1.15
       └─ prod:1.0.4
          └─ prod:latest  (плавающий тег, указывающий
                           на самый свежий релиз)

    При таком подходе у вас будет 3 докерфайла - prod:base, dev и финальный prod.

    Можно обойтись двумя докер файлами, если на прод подсовывать скрипты проекта через volume, как и в dev. Я как раз использую этот вариант, так как в моей компании применяется continuous delivery. Грубо говоря, это когда почти каждый коммит в master сразу улетает на прод. Если каждый раз собирать новый образ, становится слишком много образов, за которыми приходится отдельно следить и удалять старые, чтобы избежать переполнения реестра. Плюс возникают сложности с обеспечением атомарности деплоя, так как сервис становится недоступен в момент рестарта контейнера, из-за чего приходится городить балансировку и/или blue-green деплоймент.

    С volume всё просто - залил в него новый код, переключил симлинк и всё готово. А базовый образ меняется относительно редко - обычно когда выходит новая версия PHP. В этом случае можно и ручками обновить базовый образ на сервере. Но этот вариант хорош только когда мало серверов. Для больших кластеров конечно лучше работает вариант с упаковкой приложения в отдельный образ.
    Ответ написан
    4 комментария
  • Почему разные выходные значения Java и Python?

    Vamp
    @Vamp
    Причина в том, что второй вызов метода вычисляется в значение, не влезающее в тип данных long. То есть возникает классический integer overflow. В java и php старшие биты, не поместившиеся в long, просто отбрасываются. В python и ruby на уровне языка поддерживается bignum арифметика, поэтому результат другой. То есть числа могут быть сколь угодно велики без опасности возникновения переполнения. В php такое тоже возможно при помощи модуля bcmath или gmp. В java аналогичную арифметику предоставляет класс BigInteger:

    import java.math.BigInteger;
    
    public class A {
        public static void main(String[] args) {
            BigInteger output_1 = generate(400732734464L, -74, 12);
            BigInteger output_2 = generate(1641401281110016L, 100, 14);
    
            System.out.println(output_1);
            System.out.println(output_2);
        }
    
        public static BigInteger generate(long val1, int val2, int val3) {
            return BigInteger.valueOf(val2 & 255)
                .add(BigInteger.valueOf(val1))
                .shiftLeft(val3);
        }
    }
    Ответ написан
    Комментировать
  • Как ограничить область действия зависимостей?

    Vamp
    @Vamp
    Лучше всего форкнуть проект библиотеки и переписать код на spring 5. Возможно это будет не слишком трудоёмко, учитывая, что зависимость от spring у библиотеки транзитивная. То есть в коде библиотеки может и не быть кода, зависящего от spring, поэтому может быть достаточно обновить только версию непосредственной зависимости, которая уже тащит spring.

    Альтернативным вариантом является использование maven-shade-plugin. Этот плагин упаковывает зависимости проекта в единый jar файл и, опционально, может перемещать классы в другой пакет. Фича называется class relocation. То есть вы можете собрать shaded версию библиотеки soap-ws, у которой классы spring будут релоцированы из пакета org.springframework в, например, vildaraj.shaded.org.springframework. В этом случае вы можете в основном проекте использовать какую угодно версию spring без конфликтов.
    Ответ написан
    Комментировать
  • Какой путь прописать для upload_tmp_dir?

    Vamp
    @Vamp
    upload_tmp_dir нужно указывать в пределах open_basedir, иначе скрипты сайта не смогут получить доступ к загружаемым файлам. Аналогично нужно настроить ещё session.save_path и sys_temp_dir, иначе не будут работать сессии и функции для работы с временными файлами.

    Смысл появляется когда у вас больше одного сайта на сервере. В этом случае open_basedir используется для изоляции сайтов друг от друга - чтобы взлом одного сайта не привёл ко взлому всех остальных сайтов на сервере.

    Пару лет назад я описывал рабочую схему для усиления безопасности в другом вопросе на тостере. Взгляните.
    Ответ написан
  • Как подключить google authenticator?

    Vamp
    @Vamp
    Сложно сказать в чём у вас ошибка, так как вы не предоставили код. Вероятно, криво составили QR, раз уж ошибка возникает при его сканировании. Более подробно о том как нужно составлять урл для QR-кода можно прочитать в вики.

    Я внедрил Google Authenticator в свой проект без каких-либо проблем. Использую пакет sonata-project/google-authenticator.

    Генерация кода:
    $g = new \Google\Authenticator\GoogleAuthenticator();
    $secret = $g->generateSecret();
    echo '<img src="https://chart.googleapis.com/chart?'
      .'cht=qr&chl=otpauth://totp/mysite%3Fsecret%3D'
      .$secret.'&chs=200x200&chld=L|0" />';
    Значение $secret сохраняется в БД и привязано к конкретному юзеру.

    Проверка OTP:
    $g = new \Google\Authenticator\GoogleAuthenticator();
    if ($g->checkCode($secret, $code)) {
      echo 'Welcome!'
    } else {
      echo 'Wrong code';
    }
    Примеры кода приведены для библиотеки версии 1.0.2.

    Важный момент - время на сервере и на телефонах должно совпадать, чтобы генерируемые OTP совпадали. Поэтому сервер синхронизируется по протоколу NTP. С телефонами сложнее. Можно в настройках системы установить синхронизацию часов телефона с временем из GSM сети (для любых платформ), либо в настройках приложения Google Authenticator (в android версии есть, в iOS версии нет, про другие платформы не в курсе).
    Ответ написан
    Комментировать
  • Как создать связку Forge+Bukkit?

    Vamp
    @Vamp
    Для этого необходимо очень хорошо разбираться как в forge, так и в bukkit. Лучшим вариантом будет поучаствовать в разработке обоих проектов. То есть не в создании модов и плагинов с использованием данных платформ, а именно в развитии их самих. А там уже будет видно как скрестить ежа с ужом. Так же можете посмотреть как это сделано в термосе и попробовать повторить.

    В любом случае, сделать быстро и просто точно не получится. Я однажды попробовал взяться, но когда понял, что это займёт неадекватное количество времени, то бросил и переключился на SpongeForge.
    Ответ написан
    Комментировать
  • Неправильно приходят байты?

    Vamp
    @Vamp
    Раз не склеивается само, значит нужно склеивать самому. Алгоритм довольно прост - читаем из буфера не более readableBytes() байт. Если не хватило данных до полного пакета, то сохраняем прочитанное и ждём следующего пакета, откуда дочитываем недостающее.

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

    Вот эти квадратики в логе почти наверняка и есть маркер окончания пакета или заголовок следующего:
    5b220c3b09f5b774481408.png

    Но я думаю, что движок предоставляет абстракции более высокого уровня, которые сами собирают сырые пакеты в какой-нибудь Packet250CustomPayload, а вам останется только обработать их.
    Ответ написан
    6 комментариев
  • Как команда ping преобразует адреса?

    Vamp
    @Vamp
    Описание алгоритма есть в документации к функции inet_ntoa, используемой командой ping.

    The address supplied in cp can have one of the following forms:

    ....

    a.b.c

    Parts a and b specify the first two bytes of the binary address. Part c is interpreted as a 16-bit value that defines the rightmost two bytes of the binary address. This notation is suitable for specifying (outmoded) Class B network addresses.


    То есть 327 трактуется как двухбайтовое целое. В битовом представлении это число выглядит так:
    0000 0001 0100 0111
    И далее оно делится на два октета - 0000 0001 и 0100 0111, что, соответственно, будет 1 и 71.
    Ответ написан
    3 комментария
  • Как из php работать с sftp с авторизацией по приватному SSH-2 RSA ключу?

    Vamp
    @Vamp
    Метод exec() - это функция SSH2. Так как сервер, к которому вы подключаетесь, не предоставляет вам полноценный SSH доступ, а только лишь SFTP, то и методы вы должны вызывать из класса SFTP:
    $ssh->chdir()/nlist()/stat()/get()/put()/delete() и так далее.
    Ответ написан
    1 комментарий
  • Можно ли заменить объект на новый, вместе с заменой ссылок?

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

    // MyClass - тип объекта, который хотим подменить
    // унаследоваться необходимо, чтобы не сломать совместимость типов
    class MyProxy extends MyClass {
    
      // наш спрятанный объект, который будем заменять в будущем
      private MyClass hidden;
    
      @Override
      public int hashCode() {
        return hidden.hashCode();
      }
    
      @Override
      public boolean equals(Object o) {
        return hidden.equals(o);
      }
      // и так далее заоверрайдить все-все публичные
      // методы наследуемого класса
    
      // правда, с публичными свойствами будет проблема - для
      // их замены необходима более сложная логика:
      @Override
      public int calculate() {
        hidden.var = var; // на случай если поле было изменено извне
        int result = hidden.calculate();
        var = hidden.var; // а это если поле было изменено изнутри
        return result;
      }
    
      public int var;
    
      // а вот и главный заменщик
      void setObject(MyClass new_o) {
        hidden = new_o;
        var = new_o.var;
      }
    }

    Потом останется только найти все места, где создаётся объект и заменить его на прокси объект. Далее настоящий объект подменяется вызовом setObject().

    Если у проксируемого объекта есть final поля или методы, то придётся отказаться от наследования MyClass и заменить во всём проекте тип ссылок MyClass на MyProxy.
    Ответ написан
    Комментировать
  • Почему параметры, выставленные в MySQL, не всегда работают в JAVA?

    Vamp
    @Vamp
    Проблема не в java, а в самом запросе. Если вы не перечисляете колонки, данные для которых вы хотите указать, то mysql считает, что вы собираетесь указать данные для всех существующих в таблице колонок. Независимо от того, есть там default значение или нет.

    Для решения вашей проблемы необходимо явным образом перечислить колонки:
    INSERT = "INSERT INTO users (login, password, email, group, ip, country, city) VALUES (?, ?, ?, ?, ?, ?, ?)";

    Все колонки, явным образом не перечисленные в запросе, получат значение по умолчанию, если оно было определено при создании таблицы для них.
    Ответ написан
    1 комментарий
  • Каким сайтам нужен APCu?

    Vamp
    @Vamp
    В статье рассматривается производительность какой-то абстрактной "обычной CMS" на дешманском VPS хостинге и влияние трех разных видов кеша на общую производительность сайта.

    В разделе "SERVER CACHE TYPES EXPLAINED", в принципе, довольно точно и доходчиво расписано про рассматриваемые типы кеширования.

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

    APCu позволяет закешировать произвольную информацию. Например, результат SQL запроса, чтобы в следующий раз не делать запрос к базе и достать результат сразу из кеша. Необходимость APCu для сайта очень сильно зависит от самого сайта. Обычно об этом написано в документации к движку, на котором написан сайт. Так же возможна ситуация, когда сайт затачивался под другой кеширующий модуль, например, memcache, xcache или даже кеширует свои данные в простые файлы - в этом случае наличие/отсутствие APCu ни на что влиять не будет.

    There is one peculiarity regarding APCu caching, when it is used alone without OpCache enabled. APCu will internally reduce number of MySQL queries between PHP and MySQL, however, to really shine and show it’s full potential, OpCache must be enabled.

    Вот этот замечательный абзац вообще полный бред. APCu не сможет магическим образом уменьшить количество запросов к mysql. Об этом должен позаботиться программист явно - положить результат запроса в кеш и проверить в кеше его наличие перед отправкой запроса. И APCu не станет сиять, если включить OpCache. Эти два модуля никак друг на друга не влияют. Вообще. Совсем.

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

    На таблицу результатов можно не смотреть. Эти цифры не имеют никакого смысла, если не представлен сайт, который был протестирован, так как влияние APCu и static HTML cache очень сильно от этого зависит. Результаты могут быть диаметрально противоположны для тех же самых условий, но для другого сайта.

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

    Отвечая на ваш вопрос - ставить APCu нужно только для сайтов, которые явно его поддерживают. OpCache нужно ставить всегда.
    Ответ написан
    Комментировать