<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Victor Bolshov on Medium]]></title>
        <description><![CDATA[Stories by Victor Bolshov on Medium]]></description>
        <link>https://medium.com/@crocodile2u?source=rss-f66e90ffda6a------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/0*GaotYuKjja9ehfEl.jpg</url>
            <title>Stories by Victor Bolshov on Medium</title>
            <link>https://medium.com/@crocodile2u?source=rss-f66e90ffda6a------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Mon, 18 May 2026 06:31:37 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@crocodile2u/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Laravel + FusionAuth, the Laravel way]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/@crocodile2u/laravel-fusionauth-the-laravel-way-5e57a55ae403?source=rss-f66e90ffda6a------2"><img src="https://cdn-images-1.medium.com/max/1237/1*PBpprBmWiAA_qOJKiXPvwQ.png" width="1237"></a></p><p class="medium-feed-snippet">If you&#x2019;re trying to find out how to add user management, authentication and all, to your Laravel app, you probably already came across a&#x2026;</p><p class="medium-feed-link"><a href="https://medium.com/@crocodile2u/laravel-fusionauth-the-laravel-way-5e57a55ae403?source=rss-f66e90ffda6a------2">Continue reading on Medium »</a></p></div>]]></description>
            <link>https://medium.com/@crocodile2u/laravel-fusionauth-the-laravel-way-5e57a55ae403?source=rss-f66e90ffda6a------2</link>
            <guid isPermaLink="false">https://medium.com/p/5e57a55ae403</guid>
            <category><![CDATA[fusionauth]]></category>
            <category><![CDATA[php]]></category>
            <category><![CDATA[laravel]]></category>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[docker-compose]]></category>
            <dc:creator><![CDATA[Victor Bolshov]]></dc:creator>
            <pubDate>Mon, 30 Jan 2023 11:25:33 GMT</pubDate>
            <atom:updated>2023-01-30T11:25:33.952Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Docker setup for a Laravel+Vue project]]></title>
            <link>https://medium.com/@crocodile2u/docker-setup-for-a-laravel-vue-project-90e4fd3acc7a?source=rss-f66e90ffda6a------2</link>
            <guid isPermaLink="false">https://medium.com/p/90e4fd3acc7a</guid>
            <category><![CDATA[laravel]]></category>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[vue]]></category>
            <category><![CDATA[docker-compose]]></category>
            <dc:creator><![CDATA[Victor Bolshov]]></dc:creator>
            <pubDate>Thu, 19 Mar 2020 12:53:13 GMT</pubDate>
            <atom:updated>2020-03-19T12:53:13.271Z</atom:updated>
            <content:encoded><![CDATA[<p>&gt; originally posted in my Gitlab+Jekyll powered blog: <a href="https://crocodile2u.gitlab.io/docker/laravel/vue/2020/03/17/docker-setup-laravel-vue-project">https://crocodile2u.gitlab.io/docker/laravel/vue/2020/03/17/docker-setup-laravel-vue-project</a></p><p>Here I want to publish a simple yet functional setup for a dockerized app with Laravel backend and Vue.JS frontend.</p><h3>What did I want?</h3><ul><li>Simple setup for development with docker-compose</li><li>With Laravel acting as pure backend</li><li>XDebug</li><li>Vue to take over all the frontend needs</li><li>Hot reload for the Vue app</li><li>No SSL in local development environment</li></ul><h3>What did I come up with?</h3><ol><li><a href="https://crocodile2u.gitlab.io/docker/laravel/vue/2020/03/17/docker-setup-laravel-vue-project#nginx-router">Nginx acting as web trafic router</a>, proxying to frontend or backend, depending on request URI</li><li><a href="https://crocodile2u.gitlab.io/docker/laravel/vue/2020/03/17/docker-setup-laravel-vue-project#backend">Backend service</a>: Laravel running artisan serve</li><li><a href="https://crocodile2u.gitlab.io/docker/laravel/vue/2020/03/17/docker-setup-laravel-vue-project#frontend">Frontend service</a>: Vue-cli running npm run serve</li></ol><h3>Directory/repository structure</h3><p>Legend: d: directory, f: file</p><pre>d: backend -&gt; git submodule, points to laravel API repo<br>d: frontend -&gt; git submodule, points to Vue app repo<br>d: etc<br>  d: nginx<br>    d: conf.d<br>      f: default.conf.nginx<br>  d: php<br>    f: .gitignore<br>d: dockerize<br>  d: backend<br>    f: Dockerfile<br>f: docker-compose.yml<br>f: Makefile</pre><p>As you can see, this project itself mainly serves as an infrastructure project which connects our services and glues them all together. Developer may clone a single repo and work in it, if that’s sufficient. Normally, though, everyone would want to clone this infra repo and start docker-compose stack to have all the project services running locally.</p><h3>Nginx router</h3><p>docker-compose part:</p><pre>www:<br>  image: nginx:alpine<br>  volumes:<br>    - ./etc/nginx/conf.d/default.conf.nginx:/etc/nginx/conf.d/default.conf<br>  ports:<br>    - 80:80<br>  depends_on:<br>    - backend<br>    - frontend</pre><p>…and the configuration, of course.</p><pre><strong>server</strong> {<br>    listen 80;<br>    server_name frontend;</pre><pre>    error_log  /var/log/nginx/error.log debug;</pre><pre>    location / {<br>        proxy_pass http://frontend:8080;<br>    }</pre><pre>    location /sockjs-node {<br>        proxy_pass http://frontend:8080;<br>        proxy_set_header Host $host;<br>        <em># below lines make ws://localhost/sockjs-node/... URLs work, enabling hot-reload<br></em>        proxy_http_version 1.1;<br>        proxy_set_header Upgrade $http_upgrade;<br>        proxy_set_header Connection &quot;Upgrade&quot;;<br>    }</pre><pre>    location /api/ {<br>        <em># on the backend side, the request URI will _NOT_ contain the /api prefix,<br></em>        <em># which is what we want for a pure-api project<br></em>        proxy_pass http://backend:8080/;<br>        proxy_set_header Host localhost;<br>    }<br>}</pre><h3>Backend</h3><p>I added a Dockerfile for the backend, because official PHP images don’t include XDebug. Also, later on, I wanted to add redis extension:</p><pre>FROM php:fpm-alpine</pre><pre>RUN apk add --no-cache $PHPIZE_DEPS oniguruma-dev libzip-dev curl-dev \<br>    &amp;&amp; docker-php-ext-install pdo_mysql mbstring zip curl \<br>    &amp;&amp; pecl install xdebug redis \<br>    &amp;&amp; docker-php-ext-enable xdebug redis</pre><pre>RUN mkdir /app<br>VOLUME /app<br>WORKDIR /app</pre><pre>EXPOSE 8080<br>CMD php artisan serve --host=0.0.0.0 --port=8080</pre><p>And of course, we build this image using docker-compose, that is, we automate and simultaneously document this process with a declarative approach:</p><pre>backend:<br>  build:<br>    context: dockerize/backend<br>  <em># this way container interacts with host on behalf of current user.</em><br>  <em># !!! NOTE: $UID is a _shell_ variable, not an environment variable!</em><br>  <em># To make it available as a shell var, make sure you have this in your ~/.bashrc (./.zshrc etc):</em><br>  <em># export UID=&quot;$UID&quot;</em><br>  user: ${UID}:${UID}<br>  <br>  volumes:<br>    - ./backend:/app<br>    <em># custom adjustments to php.ini</em><br>    <em># i. e. &quot;xdebug.remote_host&quot; to debug the dockerized app</em><br>    - ./etc/php:/usr/local/etc/php/local.conf.d/<br>  environment:<br>    <em># add our custom config files for the php to scan</em><br>    PHP_INI_SCAN_DIR: &quot;/usr/local/etc/php/conf.d/:/usr/local/etc/php/local.conf.d/&quot;<br>  command: &quot;php artisan serve --host=0.0.0.0 --port=8080&quot;</pre><p>The laravel app itself is beyond the scope of this post, I will simply assume it’s a standard app built with composer create-project laravel/laravel or alike.</p><p>Take a note of the comment above the user: ${UID} directive</p><h3>Frontend</h3><p>Again, the Vue app itself is out of scope. I created mine with @vue/cli, using the vue-cli-service.</p><p>Docker-compose:</p><pre>frontend:<br>  image: node:current-alpine<br>  user: ${UID}:${UID}<br>  working_dir: /home/node/app<br>  volumes:<br>  - ./frontend:/home/node/app<br>  environment:<br>    NODE_ENV: development<br>  command: &quot;npm run serve&quot;</pre><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=90e4fd3acc7a" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Laravel with docker-compose]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/swlh/laravel-with-docker-compose-de2190569084?source=rss-f66e90ffda6a------2"><img src="https://cdn-images-1.medium.com/max/661/1*rAT_uHBoPMbyiRKqklKzHA.png" width="661"></a></p><p class="medium-feed-snippet">So, I&#x2019;ve been using Laravel with docker-compose for development for a while, and now I&#x2019;m going to make a simple yet functional project&#x2026;</p><p class="medium-feed-link"><a href="https://medium.com/swlh/laravel-with-docker-compose-de2190569084?source=rss-f66e90ffda6a------2">Continue reading on The Startup »</a></p></div>]]></description>
            <link>https://medium.com/swlh/laravel-with-docker-compose-de2190569084?source=rss-f66e90ffda6a------2</link>
            <guid isPermaLink="false">https://medium.com/p/de2190569084</guid>
            <category><![CDATA[docker-compose]]></category>
            <category><![CDATA[laravel]]></category>
            <category><![CDATA[docker]]></category>
            <dc:creator><![CDATA[Victor Bolshov]]></dc:creator>
            <pubDate>Fri, 30 Aug 2019 09:25:56 GMT</pubDate>
            <atom:updated>2019-09-09T11:51:45.195Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[K8S: matchLabels. Грабельки.]]></title>
            <link>https://medium.com/@crocodile2u/k8s-matchlabels-%D0%B3%D1%80%D0%B0%D0%B1%D0%B5%D0%BB%D1%8C%D0%BA%D0%B8-c9bd20553e8b?source=rss-f66e90ffda6a------2</link>
            <guid isPermaLink="false">https://medium.com/p/c9bd20553e8b</guid>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[for-dummies]]></category>
            <dc:creator><![CDATA[Victor Bolshov]]></dc:creator>
            <pubDate>Fri, 19 Jul 2019 15:54:58 GMT</pubDate>
            <atom:updated>2019-07-19T15:54:58.933Z</atom:updated>
            <content:encoded><![CDATA[<p>Был у нас в облаке Google проект на PHP. Nginx, PHP-FPM, вот это все. Потом прикрутили API для работы с картинками, тоже на Nginx и PHP-FPM. У этого API тоже были docker images, я по факту скопировал спеку сервиса и деплоймента, поменял поле image, еще что-то, специфическое для этого API, задеплоил. Удивился. Запросы к сайту и к API случайным образом стали выдавать ответы от разных бэкендов. То есть, запрашивая сайт, ты получаешь в 50% случаев нормальный ответ от сайта, а в 50% получаешь JSON от сервера API.</p><p>Структура сервисов была такая:</p><p><strong>Сайт</strong>: nginx (fastcgi_pass fpm:9000); fpm</p><p><strong>API</strong>: api-nginx (fastcgi_pass api-fpm:9000); api-fpm</p><p>А фишка была в том, что я по незнанке сделал обоим сервисам одинаковые <em>labels</em>:</p><pre>Service fpm, service api-fpm<br>...</pre><pre><strong>labels</strong>:<br>  <strong>app</strong>: fpm<br>  <strong>tier</strong>: backend</pre><p>И в спеке деплоймента тоже указал их же:</p><pre>Deployment fpm, deployment api-fpm<br>...</pre><pre><strong>selector</strong>:<br>  <strong>matchLabels</strong>:<br>    <strong>app</strong>: fpm<br>    <strong>tier</strong>: backend</pre><p>Я, если честно, не очень понимал, зачем нужны эти лейблы и селекторы и занимался копипастой. В результате в облачной консоли, если зайти на любой их сервисов, там можно было увидеть оба деплоймента для каждого сервиса. Заходишь в <em>fpm</em> — видишь деплойменты <em>fpm</em>, <em>api-fpm</em>. И то же самое, если зайти в сервис <em>api-fpm. </em>Как результат, получалось, что два разных сервиса “обслуживались” двумя разными контейнерами, и выбор производился фактически случайно.</p><p>Я изменил лейблы, оставив для простоты один:</p><p><strong>Сайт:</strong></p><pre><strong>labels</strong>:<br>  <strong>app</strong>: fpm</pre><pre>...(deployment)...<br><strong>selector</strong>:<br>  <strong>matchLabels</strong>:<br>    <strong>app</strong>: fpm</pre><p><strong>API:</strong></p><pre><strong>labels</strong>:<br>  <strong>app</strong>: api-fpm</pre><pre>...(deployment)...<br><strong>selector</strong>:<br>  <strong>matchLabels</strong>:<br>    <strong>app</strong>: api-fpm</pre><p>Теперь все стало ровно, у каждого сервиса осталось по одному деплойменту, запросы к сервису “сайт” стали выдавать HTML, к сервису “API” стали выдавать JSON.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c9bd20553e8b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[GKE: Cloud SQL Proxy as a standalone service]]></title>
            <link>https://medium.com/@crocodile2u/gke-cloud-sql-proxy-as-a-standalone-service-56fddc132934?source=rss-f66e90ffda6a------2</link>
            <guid isPermaLink="false">https://medium.com/p/56fddc132934</guid>
            <category><![CDATA[google-cloud-platform]]></category>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[cloud-sql-proxy]]></category>
            <category><![CDATA[gke]]></category>
            <dc:creator><![CDATA[Victor Bolshov]]></dc:creator>
            <pubDate>Fri, 19 Jul 2019 08:43:33 GMT</pubDate>
            <atom:updated>2019-07-19T08:43:33.802Z</atom:updated>
            <content:encoded><![CDATA[<p><em>[see below for Russian translation]</em></p><p><strong>FOR THE IMPATIENT</strong>: the <a href="https://cloud.google.com/sql/docs/mysql/connect-kubernetes-engine">manual page</a> gives you a config with which SQLProxy listens only to connections from localhost (127.0.0.1). You have to change TCP config for the proxy to listen to connections from outside:</p><pre>command: [&quot;/cloud_sql_proxy&quot;,<br>            &quot;-instances=&lt;INSTANCE_CONNECTION_NAME&gt;=tcp:<strong>0.0.0.0:</strong>3306&quot;,<br>            &quot;-credential_file=/secrets/cloudsql/credentials.json&quot;]</pre><p>Now, in a lil bit more detail ;-)</p><p>Cloud SQL in Google Cloud Platform is a nice utility — a managed database, with good performance, automatic backups, fully production-ready.</p><p>On <a href="https://cloud.google.com/sql/docs/mysql/connect-kubernetes-engine">this page of the manual</a> you can find out how to connect to Cloud SQL from Kubernetes engine. Of all options, Cloud SQL Proxy is said to be the most secure &amp; robust. The manual describes the so-called “sidecar” approach, that is, they start a SQLProxy container in the same deployment as the application, then the app can connect as “mysql:host=127.0.0.1;…”</p><p>What if you want to connect to the same DB instance from several apps/services? Yes, it doesn’t look right from the first glance. However, CloudSQL is somewhat expensive, and smaller projects would benefit from using one CloudSQL instance (maybe with multiple databases) for all application services. Also, you may want to start Jobs that connect to the same DB. Having a new SQLProxy container next to every app service/job results in both running multiple instances of sql-proxy and duplicating the sql-proxy deployment configuration.</p><p>That is, you might want to have SQLProxy started as a standalone service, not as a “sidecar” container. However, if you simply create another service+deployment using the very same config from the manual, you won’t be able to connect to SQLProxy! It happens because of this line in deployment config:</p><pre>command: [&quot;/cloud_sql_proxy&quot;,<br>            &quot;-instances=&lt;INSTANCE_CONNECTION_NAME&gt;=tcp:3306&quot;,<br>            &quot;-credential_file=/secrets/cloudsql/credentials.json&quot;]</pre><p>With this, proxy will only listen to connections from 127.0.0.1, and you may check it with <em>kubectl logs &lt;sqlproxy pod name&gt;</em></p><p>Solution is simple:</p><pre>command: [&quot;/cloud_sql_proxy&quot;,<br>            &quot;-instances=&lt;INSTANCE_CONNECTION_NAME&gt;=tcp:<strong>0.0.0.0:</strong>3306&quot;,<br>            &quot;-credential_file=/secrets/cloudsql/credentials.json&quot;]</pre><p>Notice the tcp configuration change to <strong>0.0.0.0:3306</strong>. Now proxy listens to connections from everywhere (but don’t worry, unless you specifically want it and take appropriate steps, you won’t be able to connect from outside of the cluster).</p><p>Hope that can save a bit of research to fellow readers!</p><p><strong>Русский вариант</strong></p><p>ДЛЯ НЕТЕРПЕЛИВЫХ: <a href="https://cloud.google.com/sql/docs/mysql/connect-kubernetes-engine">мануал</a> приводит конфиг, с которым SQLProxy слушает только соединения с localhost (127.0.0.1). Надо изменить конфиг TCP, чтобы заставить его слушать соединения извне:</p><pre>command: [&quot;/cloud_sql_proxy&quot;,<br>            &quot;-instances=&lt;INSTANCE_CONNECTION_NAME&gt;=tcp:<strong>0.0.0.0:</strong>3306&quot;,<br>            &quot;-credential_file=/secrets/cloudsql/credentials.json&quot;]</pre><p>Ну и немного деталей ;-)</p><p>Cloud SQL отличная штука — управляемая БД, с хорошей производительностью, автоматическими бэкапами, готова для продакшн.</p><p>На этой странице <a href="https://cloud.google.com/sql/docs/mysql/connect-kubernetes-engine">мануала</a> вы узнаете, как подключиться к Cloud SQL из Kubernetes engine. Из всех вариантов самый безопасный и вообще лучший — это Cloud SQL Proxy. Там описан так называемый подход коляска мотоцикла”, в котором контейнер с SQLProxy запускается в том же деплойменте, что и приложение, и тогда из приложения можно коннектиться примерно так: “mysql:host=127.0.0.1;…”</p><p>Что если нужно подключаться к одному инстансу Cloud SQL из разных сервисов приложения? На первый взгляд кажется, что это как-то неправильно. Однако, CloudSQL довольно дороная штука, и небольшие проекты могут выиграть от того, чтобы использовать один инстанс CloudSQL (возможно, с несколькими БД) для всех сервисов приложения. К тому же, может возникнуть законное желание запускать Задачи (Jobs), которые подключаются к той же БД. Если запускать контейнер с SQLProxy рядом с каждым сервисом/задачейприложения приводит к дублированию как инстансов sql-proxy, так и конфигов, необходимых для его запуска.</p><p>В общем, может возникнуть желание запускать SQLProxy как отдельный сервис. Однако, если тупо создать еще один Service+Deployment и использовать тот же конфиг из мануала, подключиться к SQLProxy не выйдет. Вот из-за этой строчки:</p><pre>command: [&quot;/cloud_sql_proxy&quot;,<br>            &quot;-instances=&lt;INSTANCE_CONNECTION_NAME&gt;=tcp:3306&quot;,<br>            &quot;-credential_file=/secrets/cloudsql/credentials.json&quot;]</pre><p>Прокси слушает тольео соединения с 127.0.0.1, это видно, если посмотреть логи: <em>kubectl logs &lt;sqlproxy pod name&gt;</em></p><p>Решение:</p><pre>command: [&quot;/cloud_sql_proxy&quot;,<br>            &quot;-instances=&lt;INSTANCE_CONNECTION_NAME&gt;=tcp:<strong>0.0.0.0:</strong>3306&quot;,<br>            &quot;-credential_file=/secrets/cloudsql/credentials.json&quot;]</pre><p>Мы изменили конфиг TCP на <strong>0.0.0.0:3306</strong>. Теперь прокси слушает коннекты отовсюду (но не беспокойтесь, законнектиться из-за пределов вашего кластера не получится, если только вы специально не откроете такую возможность).</p><p>Надеюсь, кому-то это сэкономит час-другой!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=56fddc132934" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Docker Swarm + XDebug: problem and solution]]></title>
            <link>https://medium.com/@crocodile2u/docker-swarm-xdebug-problem-and-solution-196b098ba351?source=rss-f66e90ffda6a------2</link>
            <guid isPermaLink="false">https://medium.com/p/196b098ba351</guid>
            <category><![CDATA[xdebug]]></category>
            <category><![CDATA[php]]></category>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[swarm]]></category>
            <dc:creator><![CDATA[Victor Bolshov]]></dc:creator>
            <pubDate>Sat, 14 Apr 2018 06:46:03 GMT</pubDate>
            <atom:updated>2018-04-14T06:46:03.151Z</atom:updated>
            <content:encoded><![CDATA[<p>Recently, I started playing with Docker Swarm and set up a small project with two containers: Nginx and PHP-FPM. When I tried to debug with XDebug from PHPStorm, I wasn’t able to do so, because of this bug: <a href="https://github.com/moby/moby/issues/25526">https://github.com/moby/moby/issues/25526</a>. In essence, Docker Swarm is using network stack that differs from that of standalone docker, is more complicated and all. The REMOTE_ADDR that php-fpm sees, becomes an internal docker network IP: <strong>10.255.0.X</strong>. My Xdebug has <strong>remote_connect_back=On</strong>, and then it tries to connect to 10.255.0.X and obviously fails, because there’s no-one listening for debugging connections.</p><p>What I did was that in development I created a <em>fastcgi.conf</em> file for the <strong>Nginx</strong> container, where I simply set remote addr to 192.168.1.67 (my computer’s IP in internal network):</p><pre>...<br>fastcgi_param  <strong>REMOTE_ADDR        192.168.1.67;<br>...</strong></pre><p>This solution only solves the particular problem of XDebug. It is neither super-elegant nor scalable. But it worked for me and made life easier. Hope it will help you as well.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=196b098ba351" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Сцыкун Гаврила]]></title>
            <link>https://medium.com/@crocodile2u/%D1%81%D1%86%D1%8B%D0%BA%D1%83%D0%BD-%D0%B3%D0%B0%D0%B2%D1%80%D0%B8%D0%BB%D0%B0-c085b08cf4b9?source=rss-f66e90ffda6a------2</link>
            <guid isPermaLink="false">https://medium.com/p/c085b08cf4b9</guid>
            <category><![CDATA[epicfail]]></category>
            <category><![CDATA[fun]]></category>
            <dc:creator><![CDATA[Victor Bolshov]]></dc:creator>
            <pubDate>Thu, 30 Nov 2017 13:35:50 GMT</pubDate>
            <atom:updated>2017-11-30T13:35:50.163Z</atom:updated>
            <content:encoded><![CDATA[<p><em>Вчера нам прислали эти видосики, и я не могу не поделиться. На видео, собсно, темнота да и только, но очень выразительный голос. Снято с московского балкона. Чувак заехал в тихий дворик вечером, чтобы там по-тихому поссать. Не закрыл дверь, не поставил на ручник, машина поехала назад, треснулась дверью обо что-то там, дверь выломана к ***м, чувак в панике. На основе этих реальных событий я написал по дороге на работу небольшой стишок, который и предлагаю вашему вниманию. Лайк, шер, репост — приветствуется ;-)</em></p><p><em>English version: this videos were sent to us yesterday, filmed from a balcony in Moscow. A guy drove away from the road to the houses, because he wanted to pee. He did not close the door and did not use the hand brake. The car started moving backwards and hit an obstacle. As a result, the door was broken and the guy was in a pure panic. And I wrote a small poem based on these event (Russian only, sorry ;-) )</em></p><p>-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-</p><p><strong>Гаврила ехал на машине,<br>Поссать Гаврила захотел.<br>Свернул во дворик. Уж светила<br>Луна в межзвездной пустоте.</strong></p><p><strong>Гаврила лихо парканулся,<br>Дверь распахнул, исчез в ночи…<br>И только тихо улыбнулся,<br>Почуяв аромат мочи.</strong></p><p><strong>Он был предельно безмятежен,<br>И пиздеца никак не ждал…<br>Машина тронулась, понеже<br>Ручник Гаврила не поднял.</strong></p><p><strong>Он резво побежал по лужам -<br>Остановить фатальный рок.<br>Хозяйство все торчит наружу,<br>Кусты щекочут между ног.</strong></p><p><strong>Гаврилмобиль достиг забора.<br>А дверь открыта, вот беда…<br>Такого слабого упора<br>Хватило ненадолго, да.</strong></p><p><strong>Прижалась дверь к передней арке,<br>Со скрипом лопнула петля,<br>Повисла дверь. Гаврила жалко<br>Почти что про себя шепнул: “ну бля…”</strong></p><p><strong>Потом Гаврила матерился,<br>Орал: “За что??!!”, рвал волоса.<br>Но голос Кармы все ж пробился<br>Сквозь этот крик: “Зато поссал!”</strong></p><p>-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-</p><p>Всем хорошего настроения, оставайтесь цивилизованными людьми в любой ситуации!</p><iframe src="https://cdn.embedly.com/widgets/media.html?url=http%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DPm00stA6498&amp;src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FPm00stA6498&amp;type=text%2Fhtml&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/2d5c0714cc290820eb6005a015f1e72e/href">https://medium.com/media/2d5c0714cc290820eb6005a015f1e72e/href</a></iframe><iframe src="https://cdn.embedly.com/widgets/media.html?url=http%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DjKGJZdNSjmI&amp;src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FjKGJZdNSjmI&amp;type=text%2Fhtml&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/9221e31e3c42bf6c7550c883b9343519/href">https://medium.com/media/9221e31e3c42bf6c7550c883b9343519/href</a></iframe><iframe src="https://cdn.embedly.com/widgets/media.html?url=http%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DAY7RO-85DRU&amp;src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FAY7RO-85DRU&amp;type=text%2Fhtml&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/b67370726c888c9717db7f3fbe7696e3/href">https://medium.com/media/b67370726c888c9717db7f3fbe7696e3/href</a></iframe><iframe src="https://cdn.embedly.com/widgets/media.html?url=http%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DVdlhtwcNHEs&amp;src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FVdlhtwcNHEs&amp;type=text%2Fhtml&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/097000c7080dbb42c7dcefa48a5a0129/href">https://medium.com/media/097000c7080dbb42c7dcefa48a5a0129/href</a></iframe><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c085b08cf4b9" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Записки программиста]]></title>
            <link>https://medium.com/@crocodile2u/%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%BA%D0%B8-%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%81%D1%82%D0%B0-6be2bdad5c37?source=rss-f66e90ffda6a------2</link>
            <guid isPermaLink="false">https://medium.com/p/6be2bdad5c37</guid>
            <category><![CDATA[badoo]]></category>
            <category><![CDATA[истории]]></category>
            <dc:creator><![CDATA[Victor Bolshov]]></dc:creator>
            <pubDate>Fri, 11 Aug 2017 09:20:22 GMT</pubDate>
            <atom:updated>2017-08-11T09:20:22.543Z</atom:updated>
            <content:encoded><![CDATA[<p>Решил рассказать несколько историй, случившихся со мной или с моими знакомыми и коллегами. Некоторые произошли уже довольно давно, а некоторые совсем недавно. Кроме того, что все они связаны с вашим покорным слугой, они так же связаны так или иначе с информационными технологиями (ну ладно — почти все, кое-что притянуто за уши). Надеюсь, мои друзья, и не только они, получат удовольствие от прочтения, а некоторые, надо думать, сами вспомнят эти дела минувших дней и улыбнутся.</p><p><strong>I. День дурака</strong></p><p>В 2001 году я работал в одной компании в родном Обнинске. Меня туда наняли безо всяких знаний о программировании или технологиях, там я делал первые шаги в HTML и PHP. Штат состоял из студентов, уровень идиотии зашкаливал, а стрелка раздолбометра постоянно торчала в красной зоне. И надо же такому случиться — к нам, как и ко всей остальной стране, пришел День Дурака! Как будто мало было обычных дней, но нет! Надо же получить официальный повод устроить беспредел и шабаш… Не буду останавливаться на стандартных шутках вроде перевернутых рабочих столов и скачущих кнопок “Start” в винде. Паре человек заклеили датчик мыши, у одного мышь оказалась с шариком, но это никого не остановило. Наш Биг Босс Костя вышел на несколько минут поговорить по телефону и оставил незалоченным свой комп. Немедленно его файл <em>hosts</em> подвергся обработке, после чего в течение 20 минут была создана страничка с неприличными лошадьми и большой бегущей строкой “YA HAV BEEN HACKD BY COOL HACKERZZZ!!!”, и все домены наших проектов резолвились на Костиной машине на эту милую поделку. То есть, открыв сайт ООО “Рога и копыта”, который мы разрабатывали, Костя увидел бы лошадей и мессадж от злобных хакеров. Через полчаса шило вылезло из мешка, и Костя поднял крик. Разработчики отреагировали незамедлительно:</p><p>- Я понимаю, что это важно. Но у меня тут турнир по “Сапёру”, я не могу! Пусть Ян займется!</p><p>- Ян тут один за вас трудится, охламоны! И Ян уходит на час пить кофе! <em>(это, конечно же, сказал человек, который собственноручно переписывал hosts)</em></p><p>Костя был в бешенстве примерно с полчаса. Потом шутку открыли, он нервно поржал и успокоился. Он еще не знал, что в его <em>Microsoft Word</em> тоже покопались и поставили невиннейшую автозамену, а именно: каждая запятая превращалась в “, *ля”. Через пару часов, когда он едва не отправил нашему крупнейшему клиенту полный матюков документ, он снова стал как бы немного не в себе и переустановил ворд “потому что он словил какой-то вирус”. У меня есть подозрение, что Костя до сих пор не в курсе про автозамену…</p><p><strong>II. Потому что это неудобно!</strong></p><p>Перенесемся в 2006г. У меня еще не было аккаунта на Facebook, твиттера вообще еще не было, слова “Web 2.0” произносились с блаженным трепетом. Я читал умные книжки, про всякие там забугорные бороздящие все подряд космические корабли, имел Webmoney-кошелек, банковскую карту и вообще был продвинутым чуваком. И тем не менее, каждый будний день я садился на электричку-экспресс и ехал из Обнинска в Москву на работу, а вечером обратно. А кому щас легко? — думал я вместе с целой шумной компанией друзей, которые точно так же ехали на том же экспрессе. Билеты на экспресс продавались в кассах дальнего следования, туда нужно было стоять очередь, и у нас была своя очередность — каждую неделю кто-то один из 4 человек закупал билеты на всех на всю следующую неделю. РЖД, как истые староверы, принимали только наличность. Странно, что они бумажным-то деньгам доверяли, я бы не удивился, если бы кассирша попросила золотом рассчитываться. Или там, морскими раковинами. Но вот в один прекрасный день свершилось чудо: открыли отдельную (одну) кассу, где принимали Банковские Карты. Карт этих у мирян не было, и касса была совершенно свободна. Настала моя очередь закупать билеты, и я устремился. Кассирша была настоящая, советской закалки, с отбеленными, завитыми в мелкие кудряшки волосами. Ее лицо понравилось бы художникам: его можно было смело использовать как палитру. Просто обмакивать кисти в макияж. Образ дополняла чарующая улыбка мексиканской жабы и тяжёлый взгляд, в котором сквозила затаенная грусть. Это потом я понял, что причиной этой грусти было то, что ее посадили сюда, где вместо привычных бумажек ей нужно было возиться с карточками, нажимать на кнопки, вызывая таким образом таинственного и непостижимого Deus ex machina. Я отдал ей листок с паспортными данными, она начала выстукивать морзянку одним пальцем на клавиатуре. “Давайте вашу карту” — в лоток отправилась карта, карта ушла в считыватель, в лотке появилась мини-клавиатура с цифрами — надо ввести ПИН. Лоток закрывается, печатается чек, отправляется в лоток. Открываю лоток, расписываюсь на чеке, закрываю лоток. Жужжит принтер, из него появляется билет. Ура! Щас все распечатается и дело в шляпе! Мысленно начинаю хвалить РЖД (да-да, вот до чего дошло). Из мечтательного настроения меня выводят слова кассирши: “Ну что вы уснули-то? Давайте вашу карту!” Зачем карту? Почему карту? Я же уже же заплатил же ж! “Вы за один билет заплатили!”</p><p>Не сразу я осознал глубину падения. Только что я радовался успехам страны, применяющей передовые технологии для удобства своих граждан… И вдруг — сорок билетов! Сорок раз передавать через лоток карту, терминал, чек, билет… Позвольте… а можно все билеты разом пробить, распечатать, и я их все оплачу? “Нееет. Так нельзя. Может, у вас там денег не хватит на вашей карточке, а с меня потом штраф за 40 билетов возьмут! Не положено!”</p><p>40 минут продолжалась экзекуция. Моя рука писать устала, дважды ломался лоток, собралась небольшая очередь карточных горемык, которые бросали на меня выразительные взгляды.</p><p>Получив пачку билетов, я пошел к администратору. Администраторша была настоящая, советской закалки, с отбеленными, завитыми в мелкие кудряшки волосами… постойте, это дежавю какое-то. Она меня выслушала и сказала строго, как говорит матерая воспитательница детям из младшей группы: “А что вы думаете? Вы думаете, почему по этим вашим… карточкам так мало берут? А? Да потому, что это неудобно!”</p><p><strong>III. Восстание кофе-машин</strong></p><p>Вот вы все говорите “интернет вещей”, “умный дом”… А между тем, как вы сейчас узнаете, еще в 2006 году кофеварки обладали интеллектом, достаточным для нападения на человека. Прямо а-ля Стивен Кинг, ни больше ни меньше.</p><p>В офисе компании “ХроноПэй” была кухня. Даже две, поскольку было два этажа. В каждой кухне было по кофеварке, однажды одна из них сломалась и по какой-то причине была перенесена на кухню нижнего этажа, куда затем пришел специально обученный человек, чтобы ее отремонтировать. Ремонт, насколько я могу судить, не увенчался успехом, но человек оставил кофеварку включенной в сеть. Мы с Лешей стояли возле холодильника и мирно беседовали о модульных тестах и парном программировании. Мимо прошел Дима и, взяв кружку, направился к столу с кофе-машинами. Был погожий летний день, и, как говорится в таких случаях, ничто не предвещало беды.</p><p>В полном соответствии с законами Мерфи, Дима выбрал сломанную кофеварку. Мы с Лехой переглянулись и стали наблюдать. Нажатие кнопки, тарахтение… Кофеварка задрожала, запыхтела, угрожающе заскакала на месте, из всех ее отверстий повалил пар. Дима испуганно отпрянул и с широко раскрытыми глазами смотрел на разбушевавшийся аппарат. Затем он обернулся и глянул на нас, ища поддержки и сочувствия, но увидел лишь неподдельное любопытство и первобытную жажду зрелищ. В этот момент я почувствовал, что ситуация вышла из-под Диминого контроля настолько, что ее можно превратить в полный хаос одним элегантным движением. Я громко крикнул: “Спасайся!!!” - и спрятался за холодильник. Дима изменился в лице, быстро развернулся, схватил стоявший у стола офисный стул и заслонился им от врага. Кофеварка дрыгалась и скакала, кружка с непонятной коричневой жидкостью разбилась на куски от падения на пол, стул оказался сломанным и развалился на части, так что в руках у Димы осталась только спинка, а все остальное рухнуло ему же на ногу. Через минуту опасность была ликвидирована путем выдергивания вилки из розетки. Дима с ненавистью посмотрел на кофеварку, на меня, и ушел. Видимо, стал разрабатывать машину времени, чтобы ликвидировать меня до того, как все вышеописанное произошло.</p><p><strong>IV. Здравствуй, Мир!</strong></p><p>В бытность мою разработчиком в компании Badoo я услышал несколько историй, участником которых не был, но тем не менее хочу их записать.</p><p>Однажды в офисе, под мерный стук клавиш, Серж(TM), который спокойно сидел за своим монитором и никого не трогал, вдруг произнес:</p><p>— А что это у нас баду.ком выдает “халоу ворлд”?</p><p>Такие вещи, которые настолько на виду, что их никто не замечает, только Серж может углядеть.</p><p>— Данунах. Какой “халоу ворлд”?! Первое апреля кончилось, Серега!</p><p>— Небось опять Хасан* чего-то выложил…</p><p>Тут где-то рядом, отчетливо, хоть и с надрывом, прозвучало Ля. Примерно первой октавы. Затем было Фа, а дальше ранее неизвестные ноты Ма и Ху. Вслед за музыкой сфер, сбивая стулья и роняя чашки с кофе, промчался Фишер. Обладая недюжинным интеллектом, он быстро сопоставил новость с тем фактом, что 10 минут назад он раскладывал скрипт следующего содержания:</p><p><em>echo “Hello world!”; exit;</em></p><p>Скрипт был написан для теста одного из сишных сервисов, и то ли он был разложен не в ту папку, то ли не на ту группу серверов — но уехал он на продакшн и заменил собой индексный файл.</p><p>Триста нод**. Триста пытливых CPU, несколько тысяч ядер, в течение 20 минут раздавали людям по всему миру “Hello world!” Такого унижения не испытывал даже робот-меланхолик Марвин из “Автостопом по Галактике”. Возможно, небольшим утешением для них послужит тот факт, что возможно, это был самый высокопроизводительный Helloworld в истории.</p><p><strong>V. Во всем виноват Хасан*</strong></p><p>Однажды Хасану* поручили интеграцию с Фейсбуком. Лайк, шер, репост — вот это все. Плюс сущие несколько миллионов новых пользователей. Хасан* принялся за работу, тут же нагуглил блог некоего разработчика, в котором объяснялось, что, куда и как подключать, все по правде и даже с картинками. И с примерами кода, что и сгубило блог. В HTML, который автор приводил на своей страничке, была в том числе ссылка на Javascript-файл, который располагался по адресу хттп://блогчувака/фейсбук.жс. В таком виде этот код и выехал на продакшн badoo (с тех пор много воды утекло, и сейчас подобное было бы практически невозможно).</p><p>Сначала прилег сам блог. Спустя немного времени больше юзеров узнало про новую фичу, и в результате слег весь хостинг-провайдер. Конечно, 150млн пользователей поперлись на свежачок. А обнаружили, ясное дело, не сразу: у нас-то все работало после релиза, только позже где-то JS-ошибки посыпались…</p><p><strong>VI. Unplug-n-pray</strong></p><p>В былые времена админы Badoo выезжали лично устанавливать систему на новую партию серверов, когда они прибывали в датацентры в Европе или Америке.</p><p>Вот однажды делегация суровых российских админов отправилась покорять Майами. В поте лица они валялись на пляжах, засучив рукава шлепали горячих пуэрториканок и пили пина-коладу. Днём вся команда в серверной, там прохладно и облачно, относительная влажность самое оно. Обжимаются провода, подключаются разъемы, жизнь бьёт SSH-ключом.</p><p>А тем временем в Москве, в Центре Управления Полетами админов в Майами, загорелась красная лампочка. Потом другая, потом третья. Графики упали до отметок, близких к нулевым, настал апокалипсис и третья мировая.</p><p>— Короче, полная опа, — ёмко подытожил кто-то, даже не подозревая, насколько близко он подобрался к печальной истине. Это действительно была опа. И принадлежала она Жене Петренко***, который одним ловким движением таза, словно танцуя, выдернул питание из сервера, на котором работал авторайзер****.</p><p>Сейчас такое было бы уже невозможно. Новые сервера устанавливаются в стойки работниками ДЦ, и сразу после подключения к сети попадают в заботливые руки (именно руки!) сотрудников</p><p><strong>VII. Русский взломщик</strong></p><p>Это случилось в июле 2015 года. Первые недели жизни в Голландии, новая работа, много удивительных открытий, в основном приятных, хоть и не всегда. Я работал в городе Almere, компания была, скажем так, странноватая. Начать хотя бы с того, что мне — программисту — в течение месяца не давали доступа к исходным кодам. Вместо этого я изучал какой-то монструозный проект на Java, без документации и вразумительных инструкций по установке и использованию — это было задание на ресёрч от CTO. В течение двух недель после моего трудоустройства три программиста, составлявшие отдел разработки, уволились. Впрочем, в офисе они все равно не появлялись. Я понес к CTO результаты двухнедельного исследования, он слушал меня примерно минуту, после чего произнес “OK” и кивнул головой, выразительно посмотрев в сторону двери: аудиенция была окончена. На следующий день он уволился. Из программистов в компании остались четыре фронтендера, и те в Дубае. Я сидел один в айтишной части этажа, где могла бы свободно разместиться дюжина разработчиков, и читал хабрахабр.</p><p>Офис был монументальный. Трехэтажный, с высоченными потолками. Наш отдел был отгорожен от остального этажа матовым стеклом. Каждый вечер один из топ-менеджеров, у кого был доступ к сигнализации, обходил здание убеждался, что там никого нет, и закрывал входы и выходы на ночь.</p><p>В тот день я зачитался чего-то интересного и уходил чуть позже обычного. Сигнализация сработала, когда я вызвал лифт. Честно говоря, я слегка опешил, если не сказать — офигел. Я был один в огромном офисном здании, куда скоро прибудет наряд полиции, в чужой стране… Ощущения были не самые приятные. Слава богу, у меня был телефон Робина, который закрывал офис, я объяснил ему ситуацию, и он отправился обратно на работу вызволять меня (да, собственно, исправлять свой же косяк , ведь это он проверял, что сотрудников в здании не осталось).</p><p>Полиции приехало аж три машины, я даже загордился. Они, хоть и отнеслись к моей истории с пониманием, не спускали с меня глаз до тех пор, пока не появился Робин, и я чувствовал, что где-то подспудно они думают что-то вроде “Чертов русский хакер, что ты задумал?”</p><p><strong>Примечания.</strong></p><p>* — во избежание репутационных потерь имя Русана изменено на Хасан</p><p>** — на самом деле, черт его знает, сколько нод. Много.</p><p>*** — страшный демон.</p><p>**** —добродушный великан. Однажды развалил стол для 12 персон, просто оперевшись на него.</p><p><strong>Краткий словарь Badoo. Оставлю здесь, чтобы было.</strong></p><p>“<em>За приезд</em>” — тост. Говорится по любому поводу, обычно Демиургом. Чаще всего других тостов не произносится.</p><p>“<em>Как ты испытательный срок прошел?</em>” (вариант: “<em>Так ты испытательный срок не пройдешь…</em>”) — говорится Рушбой со снисходительной интонацией, в те редкие моменты, когда ему лень выдумать что-то более свежее и оригинальное.</p><p>“<em>У меня метровый иридиевый</em>” — говорится Антохой в качестве аргумента в споре.</p><p>“<em>Во всем виноваты админы и Русан</em>” — святая правда.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6be2bdad5c37" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Купил билет по карточке]]></title>
            <link>https://medium.com/@crocodile2u/%D0%BF%D1%80%D0%BE%D1%87%D0%B8%D1%82%D0%B0%D0%BB-%D0%BA%D0%B0%D0%BA-%D0%B2%D0%B0%D1%80%D0%BB%D0%B0%D0%BC%D0%BE%D0%B2%D0%B0-%D0%BE%D1%88%D0%B0%D1%80%D0%B0%D1%88%D0%B8%D0%BB%D0%B8-%D1%8D%D0%BB%D0%B5%D0%BA%D1%82%D1%80%D0%B8%D1%87%D0%BA%D0%B8-%D0%B8-%D0%B2%D1%81%D0%BF%D0%BE%D0%BC%D0%BD%D0%B8%D0%BB-%D1%81%D0%B2%D0%BE%D0%B8-%D0%BF%D1%80%D0%B8%D0%BA%D0%BB%D1%8E%D1%87%D0%B5%D0%BD%D0%B8%D1%8F-%D1%81-%D1%80%D0%B6%D0%B4-%D0%B4%D0%BE%D0%BA%D0%BB%D0%B0%D0%B4%D1%8B%D0%B2%D0%B0%D1%8E-b0e6828cb6f4?source=rss-f66e90ffda6a------2</link>
            <guid isPermaLink="false">https://medium.com/p/b0e6828cb6f4</guid>
            <category><![CDATA[маразм]]></category>
            <category><![CDATA[ржд]]></category>
            <dc:creator><![CDATA[Victor Bolshov]]></dc:creator>
            <pubDate>Thu, 29 Jun 2017 18:34:35 GMT</pubDate>
            <atom:updated>2017-06-29T18:36:37.550Z</atom:updated>
            <content:encoded><![CDATA[<p><strong>Купил билет по карточке</strong></p><p>Прочитал, как Варламова ошарашили электрички, и вспомнил свои приключения с РЖД. Докладываю.</p><p>Было это лет десять назад. Я тогда мотался каждый день из Обнинска в Москву на экспрессе. Билеты можно было купить только за наличку. И вот в один прекрасный день РЖД открывает продажу билетов по карте! Нас ездила из Обнинска целая компания, человек пять, и как раз была моя очередь покупать на всех билеты (делать это нужно заранее, ибо в последнюю минуту ничего не выйдет). Я прихожу на Киевский вокзал, смотрю — лепота! Отдельная касса выделена для желающих, стало быть, вкусить плодов цивилизации и расплатиться заморским чудом — банковской картой. И эта касса, одна во всем зале, совершенно свободна! Потому как граждане, в массе своей люди несознательные, всех этих карточных фокусов до конца не оценили, а которые попроще, так и вовсе эти карточки… в общем, не доверяли карточкам.</p><p>И вот я, гордый от собственной причастности к прогрессу и высоким технологиям, подхожу к окошку. Мне, говорю, билеты на экспресс, Обнинск-Москва и обратно, на 4 человека, туда и обратно, на всю следующую неделю. Вот вам паспортные данные всех пассажиров!</p><p>За окошком долго печатают номера паспортов, имена и фамилии.</p><p>- карту вашу давайте…</p><p>Даю карту. В лоток ее засовываю. Карта вставляется в терминал, терминал отправляется в лоток, я открываю лоток, ввожу пин-код, лоток закрывается, я жду в нетерпении.</p><p>Карта моя тоже отправляется в лоток, с ней — чек. В чеке надо расписаться. Расписываюсь, забираю карту, отправляю обратно чек.</p><p>Жужжит принтер, из него появляется билет, и отправляется а лоток. Ничего не понимаю. Забираю билет. Говорю:</p><p>- Э-э-э… мне же много билетов надо…</p><p>- Ну много так много.. щас следующий оформим…</p><p>- А можно я за все билеты заплачу один раз? И вы из все сразу мне напечатаете и отдадите.</p><p>- Один раз? А может, у тебя денег столько нет на карте? А? А с меня за неиспользованный билет штраф возьмут! Не положено!</p><p>Сорок билетов надо было мне оформить. Сорок транзакций по карте. Сорок раз лоток мотался туда и сюда. Ёжика из известного анекдота уже бы давно послали куда подальше, а я все расписывался и расписывался на чеках…</p><p>После этого, я пошел к администратору. Нашел. Говорю, так мол и так, работа с картами не тово.. плохо все. Сорок раз расписывался, тудыть ее в качель… Администраторша, дородная тетка, улыбнулась и сказала:</p><p>- А вы что же вот это тут себе думаете? Вы думаете, почему этими вашими… картами! Почему ими так мало расплачиваются? Да потому, что это неудобно!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b0e6828cb6f4" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[PHPUnit worst practices]]></title>
            <link>https://medium.com/@crocodile2u/phpunit-worst-practices-4e3fe3b66fd7?source=rss-f66e90ffda6a------2</link>
            <guid isPermaLink="false">https://medium.com/p/4e3fe3b66fd7</guid>
            <category><![CDATA[phpunit]]></category>
            <category><![CDATA[php]]></category>
            <category><![CDATA[testing]]></category>
            <dc:creator><![CDATA[Victor Bolshov]]></dc:creator>
            <pubDate>Mon, 19 Jun 2017 08:05:47 GMT</pubDate>
            <atom:updated>2017-06-19T08:05:47.674Z</atom:updated>
            <content:encoded><![CDATA[<p><em>How to shoot your leg off with…</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/529/1*WsEnddd5Y4EgEHsT054kUQ.jpeg" /></figure><p>One of my recent larger projects at work was to improve our pretty large PHPUnit test suite, which consists of nearly <strong>7000</strong> test cases. While accomplishing this useful but exhausting work, I made my notes of the most common bad coding, architecture and setup patterns, and this compilation I’d like to offer to you. Mostly, the examples refer to test code, although some were a consequence of poor design decisions. The order in which I list the bad practices and errors, is not the order of their importance. Mostly, it is the order of occurences.</p><ol><li><strong>Lower <em>error_reporting</em> level for unit tests</strong>. The first stage of improvements was a task that sounded fairly simple: turn on <em>E_WARNING</em>s for unit tests. Yes, we used to have them off, as well as <em>E_NOTICE</em> and <em>E_STRICT</em>. And you know what? <strong>Never. Repeat. This. Error.</strong> I mean it. We have a tough code review process established, and the engineers are quite experienced. That’s why I did not expect too much trouble with this. I was wrong. In <em>test/bootstrap.php</em> I changed error reporting, ran the test suite, and I was totally frustrated by the number of warnings in the report: seven damn hundred! Fixing those was mostly a long and boring job of filling in the missing arguments for function/method calls. Yet I felt really bad about this: fixing a missing argument could introduce another error, and probably even harder to track. If we had <em>error_reporting</em> of <em>E_ALL</em> from the very beginning everywhere, this situation could never happen. What is worse is that a warning or even a notice is somewhat likely to be just the top of the iceberg, with any sort of weird logical error lying deeper. A particular such case is listed below under number 2. In the end, after long days of work, I managed to eliminate all warnings. However, <em>E_NOTICE</em> and <em>E_STRICT</em> are still hidden, and I can only hope it will not be my job to deal with them…</li><li><strong>Forgotten <em>dataProvider</em> annotation</strong>. In several test methods, the <em>dataProvider</em> annotation was missing. Method has signature like this: function <em>testSomeBehavior($input, $expectation)</em>, and there is a method below called <em>testSomeBehaviorProvider</em>, but PHPUnit is not told about data provider. Hence, PHPUnit calls method as usual, without arguments, which in PHP is equivalent to passing <em>NULL</em>s, though an <em>E_WARNING</em> is raised. Also, those test methods were only executed one time, instead of several times with different arguments. Those tests were written in such way that giving all <em>NULL</em>s as input was actually <em>OK</em> and all the assertions passed. When writing, developer probably ran a whole test folder, saw the green line and moved on. It is also easy to not pay attention to such a mistake during code review. However, simply turning <em>E_WARNING</em>s on would have made this impossible.</li><li><strong>Static state manipulation</strong>. Some people say globals are evil but don’t care about singletons and alike. When using (and especially when modifying) static state, you better ask yourself twice if you really need it. If there is no other way, be extremely careful. You change the contents of a static class member — it’s left there for all the future users of it. You have to make it 100% sure that you restore the member to its initial state after you’re done, no matter how your test method is terminated, be it an exception or a return. Consider this: <br><strong><em>DatabaseConnection::$instance</em></strong> is a <strong><em>DB</em></strong> object. <br>In your test, you change it: <br><strong><em>DatabaseConnection::$instance = new DBMock();</em></strong><em><br></em>Then, all the other test methods, those that will be called later in the test suite and that expect a <em>DB</em> instance, will meet something unexpected, a mock! Maybe you thought about this, so before return in the test method you restore the value:<br><strong><em>DatabaseConnection::$instance = $originalDB;<br></em></strong>However, it might happen that an Exception is thrown before you restore original value, and those situations must be taken care of, as well. The consequence of this particular error is hard-to-debug weird behavior. What makes it even worse is that the weirdness is observed later in a completely different place. The conclusion: don’t mess with statics. If you have to, be <strong>extremely</strong> careful!</li><li><strong>Using implicit fixtures in functional tests</strong>. In our terms, functional test is one that talks to real database, not to a mock. It happened (yes, “historical reasons”, ya know) that for unit tests we use the same DB snapshot as for development. That is, it contains a small portion of real data with sensitive information obfuscated. I found a lot of functional tests follow this pattern: 1) get an object by ID; 2) apply certain actions; 3) verify the resulting DB structure. For example:<br><strong>$order = find_order(3456789);<br>cancel_order($order);<br>assert(order_is_cancelled(3456789));<br></strong>The motivation behind this would be like “For my test, I need an order of type X with 2+ products in it. Let me find that in our DB snapshot… Ahh, here you are, order ID 3456789”. There are two major problems with this approach. First, those numbers don’t say anything to anyone. Much better would be to create a new order in setUp() or in the test method. Learn <em>DbUnit</em>, it’s a handy way to test against a real database. Second, your DB snapshot is not really stable, and you may find your test failing without any of the relevant code changed. This can be frustrating and take long time to find out what’s happening. Also, if you don’t have <em>BEGIN</em> … <em>ROLLBACK</em> in your <em>setUp()</em> and <em>tearDown()</em>, then it’s even worse. For that, see point 5:</li><li><strong>Not cleaning up after your dog</strong>. Functional tests read and write to the real database. For proper isolation, the DB state after each test should always remain the same. Unchanged. This should be easily achievable by <em>BEGIN</em> … <em>ROLLBACK</em> in your <em>setUp()</em> and <em>tearDown() </em>respectively. In our case, we did have this in place… Occasionally, it used to be broken in certain cases (see point 3: <strong>mangling with statics</strong>). This stuff caused me a lot of trouble when I was to introduce parallel test execution in order to speedup things. The reason being that if your tests modify the database, it is obvious that they rely on a certain order of test execution. I introduced concurrent test runner and got a lot of failures. Test <strong>A</strong> that asserted to have X rows in some table started to fail because of test <strong>B</strong> doing <em>INSERT</em>s to that table. Before concurrency, test <strong>A</strong> used to always run before <strong>B</strong>, and it all seemed OK. I ran the test suite with several workers — and it screwed everything up. In the end, the cure for pains 4 &amp; 5 is: make your functional tests isolated by running every test case in a transaction which is then rolled back; create explicit fixtures (hello <em>DBUnit!</em>).</li></ol><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4e3fe3b66fd7" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>