<?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"><channel><title><![CDATA[Untitled Publication]]></title><description><![CDATA[Untitled Publication]]></description><link>https://blog.medunes.net</link><generator>RSS for Node</generator><lastBuildDate>Tue, 14 Apr 2026 09:05:18 GMT</lastBuildDate><atom:link href="https://blog.medunes.net/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Don't blame the devs]]></title><description><![CDATA[Smelly code, not a function or a module, but an overall design. You know what? do not start by WTFing the $!f*# developers who wrote it.

There are good enough cases where developers can't be found guilty, and the "criminal" in such cases are: stakeh...]]></description><link>https://blog.medunes.net/dont-blame-the-devs</link><guid isPermaLink="true">https://blog.medunes.net/dont-blame-the-devs</guid><category><![CDATA[clean code]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[software development]]></category><dc:creator><![CDATA[medunes]]></dc:creator><pubDate>Tue, 12 Dec 2023 22:44:36 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1702420948889/a8711df3-a92a-4849-8d8e-10245678e8fe.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<ul>
<li><p>Smelly code, not a function or a module, but an overall design. You know what? do not start by W<em>T</em>F<em>ing the </em>$!f*# developers who wrote it.</p>
</li>
<li><p>There are good enough cases where developers can't be found guilty, and the "criminal" in such cases are: stakeholders.</p>
</li>
<li><p>Genius developers might only reduce the ugliness level from horrible to bad, but can't make it look good by any means.</p>
</li>
<li><p>There are situations where the dev teams from tech-leads, engineering managers down to the juniors do their best and put most of pressure on stakeholders, PMs, POs &amp; co.. trying to get an authentic answer of the most valuable question for any code designer: "towards which direction(s) the project might evolve, mid term and long term?".</p>
</li>
<li><p>Good developers might fight hard to refine the stakeholders mind and get an acceptable answer of the question..
Very good developers might add on top of that some guessing or stakeholder's intentions "reverse-engineer" armed with "soft psychological skills" and couple previous experiences..</p>
</li>
<li><p>However, what can we do if the project was initially announced to be an "internal single user CLI-only file compression tool" that magically turned into a "large-scale secure OAuth based data flow compression service" ?</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Combining 2FA and Public Key Authentication for a better Linux SSH security]]></title><description><![CDATA[What are we trying to improve?
SSH (Secure Shell Connection) is a secure way to login to a Linux server and remotely work with it. However there might be a good margin of further improvements to secure even more this.
1- Substitute password authentic...]]></description><link>https://blog.medunes.net/combining-2fa-and-public-key-authentication-for-a-better-linux-ssh-security</link><guid isPermaLink="true">https://blog.medunes.net/combining-2fa-and-public-key-authentication-for-a-better-linux-ssh-security</guid><category><![CDATA[2FA]]></category><category><![CDATA[Security]]></category><category><![CDATA[Linux]]></category><category><![CDATA[Devops]]></category><category><![CDATA[ssh]]></category><dc:creator><![CDATA[medunes]]></dc:creator><pubDate>Wed, 06 Dec 2023 00:41:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1701858907645/6ef7165f-8408-4960-8eb8-a8b9cd1c287a.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-what-are-we-trying-to-improve">What are we trying to improve?</h2>
<p>SSH (Secure Shell Connection) is a secure way to login to a Linux server and remotely work with it. However there might be a good margin of further improvements to secure even more this.</p>
<p>1- <strong>Substitute password authentication with public key authentication</strong>: * <strong>Security</strong>: <a target="_blank" href="https://en.wikipedia.org/wiki/Public-key_cryptography">Public key authentication</a> is more secure than passwords. Passwords can be brute-forced or guessed, while a cryptographic key pair (public and private keys) is practically impossible to crack with current technology. * <strong>No Replay Attacks</strong>: Since the private key is never transmitted over the network, there's no risk of interception and replay attacks, unlike passwords. * <strong>Automation</strong>: Public keys are great for automated processes. You can use SSH without entering a password each time, which is essential for scripts and system administration tasks. * <strong>Management</strong>: It's easier to manage and revoke access with public keys. Instead of changing a password (which might be shared or used on multiple systems), you just remove the public key from the server. * <strong>Phishing Resistant</strong>: Users are less susceptible to phishing attacks since they're not entering a password that could be captured.</p>
<p>2- <strong>Disable root login from the SSH configuration</strong>: * <strong>Mitigate Brute Force Attacks</strong>: Root is a known username and often targeted by brute force attacks. Disabling root login means attackers can't directly access the most privileged account. * <strong>Limit Privilege Escalation</strong>: Even if an attacker compromises a regular user account, they still need to find a way to escalate privileges to root, adding an extra layer of security. * <strong>Audit Trails</strong>: Using <code>sudo</code> from a regular account to perform administrative tasks leaves a trail, helping in auditing and monitoring activities. * <strong>Reduced Risk of Accidental Damage</strong>: Preventing direct root access reduces the risk of accidental high-impact changes by system administrators. * <strong>Compliance</strong>: Many security standards and best practices advise against using root accounts for routine administration.</p>
<p>3- <strong>Force 2FA authentication with TOTP</strong>: Adding Time-based One-Time Password (TOTP) as an additional layer to SSH authentication significantly boosts security: * <strong>Two-Factor Authentication:</strong> TOTP introduces a second factor of authentication. Even if an attacker steals a user's SSH key or password, they still need the TOTP, which is typically generated on a device the user possesses. * <strong>Dynamic Codes</strong>: TOTPs change every 30-60 seconds. This makes stolen codes useless after a very short time, countering replay attacks. * <strong>Mitigates Phishing:</strong> TOTPs are harder to phish than static passwords because they are constantly changing and are only valid for a short period. * <strong>No Network Dependency:</strong> TOTPs are generated by an algorithm based on time and a secret key, so they don't require network connectivity, making them reliable and efficient. * <strong>Harder to Intercept:</strong> Unlike static passwords, intercepting a TOTP doesn't compromise future logins, as each password is valid for only one login session.</p>
<p>4- <strong>Change SSH server port from default 22 to a less common port number</strong> * <strong>Reduce Automated Attacks</strong>: Many bots and scripts target port 22 for SSH attacks. Changing the port can reduce the volume of these automated attacks. * <strong>Lower Profile</strong>: By not using the default port, your server becomes less obvious to casual scanners who are looking for easy targets. * <strong>Filter Out Noise</strong>: Changing the port can reduce log noise from repeated login attempts, making it easier to spot genuine security threats.</p>
<h2 id="heading-configuring-your-local-machine">Configuring your local machine</h2>
<p>1- <strong>Setup Public Key Authentication</strong>: * <strong>Generate Key Pair:</strong> To make the public key authentication work, we need to generate the called "key-pair": the public key which you share with the remote server, and the private key which must be kept <strong>private</strong> and secure on your local machine. For enhancing even more security, you can add a passphrase to your private key so that is won't be possible to use it until the passphrase is entered. Thanks some commonly used tools you can achieve all the burden above with the simple commands below:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># replace my_server_name here eventually</span>
<span class="hljs-comment"># when prompted with passphrase, enter it with keyboard</span>
ssh-keygen -t ed25519 -f ~/.ssh/my_server_name
</code></pre>
<pre><code class="lang-bash"><span class="hljs-comment"># replace my_server_name here eventually</span>
ssh-copy-id -i ~/.ssh/my_server_name.pub user@remote_host
</code></pre>
<p>If <code>ssh-copy-id</code> is not an option, you still can manually copy the public key to the remote server. First display and copy your public key: (should be available after the generation steps above)</p>
<pre><code class="lang-bash">cat ~/.ssh/my_server_name.pub
</code></pre>
<p>Then open the <code>authorized_keys</code> file on the remote server and paste the copied key to its end.</p>
<pre><code class="lang-bash">nano ~/.ssh/authorized_keys
</code></pre>
<p>Now an important step, do not forget to apply necessary permissions</p>
<pre><code class="lang-bash">chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
</code></pre>
<p>2- <strong>Add SSH configuration (optional)</strong>: This will improve the UX while logging into your remote server making it more convenient. For this we suppose that your remote host IP is <code>192.168.12.34</code> and we will suppose the SSH port has been set on the remote server to, for example, <code>4898</code>. Based on these parameters, you should add this section to your <code>~/.ssh/config</code> file.</p>
<pre><code class="lang-bash"><span class="hljs-comment">#~/.ssh/config</span>
Host my_server_name
    HostName 192.168.12.34
    User my_username
    IdentityFile /home/my_username/.ssh/my_server_name
    Port 4898
</code></pre>
<p>3- <strong>Have a TOTP client</strong>: You will need this to be able to generate the TOTP number once prompted from the remote server. You have couple options here: Mobile apps like <a target="_blank" href="play.google.com/store/apps/details?id=com.google.android.apps.authenticator2">Google Authenticator</a> or <a target="_blank" href="https://authy.com/download/">Authy</a>, Desktop applications like <a target="_blank" href="https://www.linux.org/threads/in-depth-tutorial-how-to-set-up-2fa-totp-with-keepassxc-aegis-and-authy.36577/">Keepass TOTP</a> or even more advanced tools like <a target="_blank" href="https://support.yubico.com/hc/en-us/articles/360013789259-Using-Your-YubiKey-with-Authenticator-Codes">YubiKey</a></p>
<h2 id="heading-configuring-your-remote-server">Configuring your remote server</h2>
<p>Instructions here are tested on Debian based Linux distributions, but should work on other distros as well without "big" differences. 1- <strong>Install google authenticator</strong>: Here you run the authenticator installation then you execute it, and we will answer "y" for all the prompts.</p>
<pre><code class="lang-bash">sudo apt install libpam-google-authenticator
...
...
...
</code></pre>
<pre><code class="lang-plaintext">google-authenticator
Do you want authentication tokens to be time-based (y/n) y
█████████████████████████
██ ▄▄▄▄▄ █▀▄█▄█ █ ▄▄▄▄▄ ██
██ █   █ █▀▄▀█▄█ █   █ ██
██ █▄▄▄█ █ ▀ ▀ ▄ █▄▄▄█ ██
██▄▄▄▄▄▄▄█▄█ ▀ █▄▄▄▄▄▄▄██
██▄▀▄▀▄█ ▀▄█▀▄█▄█▀█▀██▀██
██▄█▄██▄▀▄▄▀█▄▀ ▀▄█▄▀▀▄██
██▄▄▄▄▄▄▄█▄▄█▄▄███▄█▄▄▄███
█████████████████████████

Your new secret key is: 4D3FPZ2W6Y7IOMV 
Your verification code is 123456
Your emergency scratch codes are:
81234567
89123456
78123456
69123456
58123456
</code></pre>
<p>Here, pay attention to the generated codes: Use The ASCII made QR code (yay, terminal!) to add the TOTP to your smartphone app by adding new entry and scanning the picture. And use the secret key to add a TOTP setup to your keepass application and/or YubiKey. The backup codes should be saved for eventual emergency cases.</p>
<p>2- <strong>Configure SSH server</strong>: Here we will act on two configs: the <code>SSHD</code> and the <code>PAM</code>. As we explained in the first section, we will edit the SSH daemon config file to disable password authentication and root user, keep interactive keyboard for <code>TOTP</code> prompting, enable the Public Key authentication and change the SSH port number. The resulting config file <code>/etc/ssh/sshd_config</code> should look like this:</p>
<pre><code class="lang-bash">sudo nano /etc/ssh/sshd_config
</code></pre>
<pre><code class="lang-bash"><span class="hljs-comment"># This one we changed from default Port 22</span>
Port 4898

<span class="hljs-comment"># Add this entry or edit it if it is already existing</span>
PermitRootLogin no

<span class="hljs-comment"># We can explicitly set this although it should be the default value even if omitted. </span>
PubkeyAuthentication yes

<span class="hljs-comment"># This setting will disable the password based authentication. </span>
PasswordAuthentication no

<span class="hljs-comment"># This entry will allow both publickey authentication and keyboard-interactive which # we need to enter our TOTP code</span>
AuthenticationMethods publickey,keyboard-interactive

<span class="hljs-comment"># PAM (Pluggable Authentication Modules) is also required to allow google </span>
<span class="hljs-comment"># authenticator #integration</span>
UsePAM yes

<span class="hljs-comment"># Depending on your Debian version, if you find the entry </span>
<span class="hljs-comment"># KbdInteractiveAuthentication existing in the config file, then use it, otherwise you </span>
<span class="hljs-comment"># might find the deprecated entry which is ChallengeResponseAuthentication which</span>
<span class="hljs-comment"># in that case should be set to yes instead.</span>
KbdInteractiveAuthentication yes
</code></pre>
<p>3- <strong>Configure PAM</strong>: for this we need to enter some changes to the <code>/etc/pam.d/sshd</code> file:</p>
<pre><code class="lang-bash">sudo nano /etc/pam.d/sshd
</code></pre>
<p>First, find the following three lines and comment them out</p>
<pre><code class="lang-bash"><span class="hljs-comment">#@include common-auth</span>
<span class="hljs-comment"># account  required     pam_access.so</span>
<span class="hljs-comment">#@include common-password</span>
</code></pre>
<p>Then add the following line to the end of the file:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># two-factor authentication via Google Authenticator</span>
auth required pam_google_authenticator.so
</code></pre>
<p>Now we only have one last step to go: reststart the SSH server</p>
<pre><code class="lang-plaintext"> sudo systemctl restart sshd
</code></pre>
<p>Server setup should be complete.</p>
<h2 id="heading-ssh-like-a-boss">SSH like a boss</h2>
<ul>
<li>Given all the steps above correctly achieved, you should by now be able to ssh to your remote server as easy as the following:</li>
</ul>
<pre><code class="lang-bash"> ssh my_server_name
(my_username@192.168.12.34) Verification code: 
Linux raspberrypi 6.1.0-rpi6-rpi-v8 <span class="hljs-comment">#1 SMP PREEMPT Debian 1:6.1.58-1+rpt2 (2023-10-27) aarch64</span>

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms <span class="hljs-keyword">for</span> each program are described <span class="hljs-keyword">in</span> the
individual files <span class="hljs-keyword">in</span> /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Wed Dec  6 01:16:51 2023 from 192.168.12.33
my_username@my_server_name:~ $
</code></pre>
<p>You will be of course prompted for the TOTP code (4 digits) which you should use your smartphone, keepass or YubiKey to get them. So that was pretty match it. I will be happy to support for any further questions, rectifications or simple likes :)</p>
]]></content:encoded></item><item><title><![CDATA[Github copilot: will you be jobless?]]></title><description><![CDATA[Github Copilot.. Now this is the new beast right ?
Everybody is scared or at least worried about its capabilities..
Junior developers are thinking about a career shift ?
Ok, I personally only see it much like an x86 programmer in the 70's astonished ...]]></description><link>https://blog.medunes.net/github-copilot-will-you-be-jobless</link><guid isPermaLink="true">https://blog.medunes.net/github-copilot-will-you-be-jobless</guid><category><![CDATA[coding]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[freeCodeCamp.org]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Python]]></category><dc:creator><![CDATA[medunes]]></dc:creator><pubDate>Sat, 23 Apr 2022 20:44:44 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1650746592722/zpLALjWoX.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Github Copilot.. Now this is the new beast right ?
Everybody is scared or at least worried about its capabilities..
Junior developers are thinking about a career shift ?</p>
<p>Ok, I personally only see it much like an x86 programmer in the 70's astonished by a C compiler turning his "human words C code" into a working x86 ASM instructions..</p>
<p>So at the end of the day, nothing changes.. still the same game.. just re-arrangement of what the human should manage and what the machine should manage.</p>
<p>People often think if the machine becomes able to fully accomplish one of their tasks, it means they are becoming jobless.. false.. it means they have more new kind of jobs and problems to solve.</p>
<p>And even if we take apart the story of skills adaption, and assume you wanna stay at your exact current field of expertise, do you really think tools like copilot will "kill" the need for developers who know how to write high quality code? I really really doubt it, I rather think it will force them to fasten the belts and grow a bit more towards their best level of expertise.. and, ok, I would agree it will eliminate those with low capabilities who refuse to grow and stay at their mediocre level.</p>
<p>Back to our assembly example (sorry that was my first love and will apparently stay forever), although compilers almost killed hundred of thousands of job positions requiring x86 programmers, look how rare it is today to fall on a skilled developer who masters the art of writing, debugging, or optimizing a compiler.. or reverse engineering malicious code..</p>
<p>Very very few ones right ? because they all rely on compilers and automated tools to do the stuff. Probably half of developers nowadays might not even know what happens to their source code when they press the "run" button.</p>
<p>So, do you think those highly skilled x86 programmers are today jobless.. or rather being spammed day and night by recruiters of giant IT companies?</p>
<p>Embrace the change and don't oppose it.
“The Only Constant in Life Is Change.”- Heraclitus</p>
<p>#github #copilot #javascript #career #people #php</p>
]]></content:encoded></item><item><title><![CDATA[How to log your life easier in Symfony?]]></title><description><![CDATA[Hi coderz, how you doing? I know you are fed-up with blogposts and have tons of ToDos

So let's go for a quick blogpost and as usual I'll try to bring something useful (I hope) with as few words as I can (I hope as well).
The goal:

Turning any class...]]></description><link>https://blog.medunes.net/how-to-log-your-life-easier-in-symfony</link><guid isPermaLink="true">https://blog.medunes.net/how-to-log-your-life-easier-in-symfony</guid><category><![CDATA[Symfony]]></category><category><![CDATA[PHP]]></category><category><![CDATA[logging]]></category><category><![CDATA[dependency injection]]></category><category><![CDATA[clean code]]></category><dc:creator><![CDATA[medunes]]></dc:creator><pubDate>Tue, 01 Mar 2022 22:14:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1646172649196/j-5OqWLmq.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hi coderz, how you doing? I know you are fed-up with blogposts and have tons of ToDos</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646171224195/2-XHtKriD.png" alt="image.png" /></p>
<p>So let's go for a quick blogpost and as usual I'll try to bring something useful (I hope) with as few words as I can (I hope as well).</p>
<h2 id="heading-the-goal">The goal:</h2>
<ul>
<li>Turning any class in your project "logging-able", yet with the least possible changes and boilerplate verbosity. </li>
</ul>
<h2 id="heading-the-classical-way">The classical way:</h2>
<ul>
<li><p>So knowing that monolog's logger (however you configure it) is a tagged service at the end, the "only" way to "cleanly" use it is to DI/inject it to the needed service</p>
</li>
<li><p>Injecting services in Symfony can be done with multiple approaches and strategies. But in best cases you'll have at least to touch two things:</p>
<ul>
<li><p>The class needing the logger by defining a private property to receive the logger service instance, and an assignement in the constructor to actually receive the service.</p>
</li>
<li><p>The <code>services.yaml</code> if you choose to go with setter injection rather than constructor injection (as the latter can benefit from autowiring)</p>
</li>
</ul>
</li>
<li><p>Now as logging is a "nice and wanted" feature, your urge to log stuff here and there can grow over and over, and you might feel silly polluting your code with one more line of properties and one other in the constructor. Sometimes you'd only write a constructor for the sake eyes of the DI injection of the logger. Having this exact snippet copy/pasted over a dozen of classes might really make you feel unhappy.</p>
</li>
</ul>
<h2 id="heading-the-shortcut">The shortcut:</h2>
<ul>
<li><p>If you use the autocomplete feature of PHPStorm, you'd notice a pair of interface/trait having the same prefix.</p>
<ul>
<li><p><code>Psr\Log\LoggerAwareInterface</code></p>
</li>
<li><p><code>Psr\Log\LoggerAwareTrait</code></p>
</li>
</ul>
</li>
<li>Can we use them altogether to solve the issue above? → yes.</li>
<li>In general the SomethingAwareInterface naming pattern, means the class is supposed to have a method named "setSomething()".</li>
<li>And following conventions as well, having that setter means your class should have a "something" property that setter will modify, and here comes the SomethingAwareTrait to define that property for you.</li>
<li>Now implementing/using that interface/trait makes your class having a property named "something" and a setter for it. Nice thing here is that all that code verbosity is totally hidden in the backyard.</li>
<li>Still one single obstacle: How will the trait actually get the service instance.</li>
<li>One solution we might think about is the "@required" annotation above the setLogger() method, but in our case the setter is defined in the used trait, and we can't modify it.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646171806528/2LJZ8NnoV.png" alt="image.png" /></p>
<ul>
<li>A once and for all solution is to slightly modify the application's kernel, so that it loops over all services before the container is built, and check if any service is a "somethingAware", then make him really aware of it by explicitly injecting the service.
Here is a showcase to make any class of your project that implement/use the pair above, being able to use the logger straight away without any further overhead:</li>
</ul>
<pre><code><span class="hljs-meta">&lt;?php</span>

<span class="hljs-comment">// src/Kernel.php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">DateTime</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Psr</span>\<span class="hljs-title">Log</span>\<span class="hljs-title">LoggerAwareInterface</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Bundle</span>\<span class="hljs-title">FrameworkBundle</span>\<span class="hljs-title">Kernel</span>\<span class="hljs-title">MicroKernelTrait</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">DependencyInjection</span>\<span class="hljs-title">Compiler</span>\<span class="hljs-title">CompilerPassInterface</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">DependencyInjection</span>\<span class="hljs-title">ContainerBuilder</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">DependencyInjection</span>\<span class="hljs-title">Definition</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">HttpKernel</span>\<span class="hljs-title">Kernel</span> <span class="hljs-title">as</span> <span class="hljs-title">BaseKernel</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Kernel</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">BaseKernel</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">CompilerPassInterface</span>
</span>{
    <span class="hljs-keyword">use</span> <span class="hljs-title">MicroKernelTrait</span>;

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">process</span>(<span class="hljs-params">ContainerBuilder $container</span>): <span class="hljs-title">void</span>
    </span>{
        $definitions = $container-&gt;getDefinitions();
        <span class="hljs-keyword">foreach</span> ($definitions <span class="hljs-keyword">as</span> $definition) {
            <span class="hljs-keyword">if</span> (!<span class="hljs-keyword">$this</span>-&gt;isAware($definition, LoggerAwareInterface::class)) {
                <span class="hljs-keyword">continue</span>;
            }
            $definition-&gt;addMethodCall(
                 <span class="hljs-string">'setLogger'</span>,
                [$container-&gt;getDefinition(<span class="hljs-string">'monolog.logger'</span>)]
             );
        }
    }

 <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">isAware</span>(<span class="hljs-params">Definition $definition, <span class="hljs-keyword">string</span> $awarenessClass</span>): <span class="hljs-title">bool</span>
 </span>{
     $serviceClass = $definition-&gt;getClass();
     <span class="hljs-keyword">if</span> ($serviceClass === <span class="hljs-literal">null</span>) {
         <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
     }
     $implementedClasses = @class_implements($serviceClass, <span class="hljs-literal">false</span>);
     <span class="hljs-keyword">if</span> (<span class="hljs-keyword">empty</span>($implementedClasses)) {
         <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
     }
     <span class="hljs-keyword">if</span> (\array_key_exists($awarenessClass, $implementedClasses)) {
         <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
     }

     <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
 }

}
</code></pre><p>Now Just use implement the interface and use the trait in your command, for example, and you are ready to go!</p>
<pre><code><span class="hljs-meta">&lt;?php</span>

<span class="hljs-comment">// src/Command/MyCommand.php</span>

<span class="hljs-keyword">declare</span>(strict_types=<span class="hljs-number">1</span>);

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Command</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">Psr</span>\<span class="hljs-title">Log</span>\<span class="hljs-title">LoggerAwareInterface</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Psr</span>\<span class="hljs-title">Log</span>\<span class="hljs-title">LoggerAwareTrait</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">Console</span>\<span class="hljs-title">Attribute</span>\<span class="hljs-title">AsCommand</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">Console</span>\<span class="hljs-title">Command</span>\<span class="hljs-title">Command</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">Console</span>\<span class="hljs-title">Input</span>\<span class="hljs-title">InputInterface</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">Console</span>\<span class="hljs-title">Output</span>\<span class="hljs-title">OutputInterface</span>;

<span class="hljs-comment">#[AsCommand(</span>
    name: <span class="hljs-string">'app:my-command'</span>,
    description: <span class="hljs-string">'test!'</span>,
)]
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyCommand</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Command</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">LoggerAwareInterface</span>
</span>{
    <span class="hljs-keyword">use</span> <span class="hljs-title">LoggerAwareTrait</span>;

    <span class="hljs-keyword">protected</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">execute</span>(<span class="hljs-params">InputInterface $input, OutputInterface $output</span>): <span class="hljs-title">int</span>
    </span>{
        <span class="hljs-keyword">$this</span>-&gt;logger-&gt;info(<span class="hljs-string">'I can log!'</span>);

        <span class="hljs-keyword">return</span> Command::SUCCESS;
    }
}
</code></pre><ul>
<li>You can clone/download the code snippets above from this <a target="_blank" href="https://gist.github.com/MedUnes/5f57a004c59dacd2fe19cf56ea2c18f9">gist on github</a> as well</li>
</ul>
<p><strong>Enough talk for today, hope it helped and see you soon!
</strong></p>
]]></content:encoded></item><item><title><![CDATA[How to make two docker containers from two different projects communicate with each others ?]]></title><description><![CDATA[The problem:
You have a container under a docker-compose project, that you want to communicate or access another container which exists under a second docker-compose project
A real-life situation?

For some security policy, your SysOp or hosting prov...]]></description><link>https://blog.medunes.net/how-to-make-two-docker-containers-from-two-different-projects-communicate-with-each-others</link><guid isPermaLink="true">https://blog.medunes.net/how-to-make-two-docker-containers-from-two-different-projects-communicate-with-each-others</guid><category><![CDATA[Docker]]></category><category><![CDATA[containers]]></category><category><![CDATA[elasticsearch]]></category><category><![CDATA[kibana]]></category><category><![CDATA[networking]]></category><dc:creator><![CDATA[medunes]]></dc:creator><pubDate>Mon, 07 Feb 2022 12:15:51 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1644235057030/P8EoHalpl.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-the-problem">The problem:</h2>
<p>You have a container under a docker-compose project, that you want to communicate or access another container which exists under a second docker-compose project</p>
<h2 id="heading-a-real-life-situation">A real-life situation?</h2>
<ul>
<li><p>For some security policy, your SysOp or hosting provider blocks all external traffic, except those for port 80 (HTTP) or port 443 (HTTPS). However, you want to set up and manage several web (or non-web) services on your machine, basically using docker containers and managed by docker-compose.</p>
</li>
<li><p>To handle this you set-up a reverse-proxy using a dedicated docker-compose project and container, called proxy.</p>
</li>
<li><p>The proxy container/project listens on port 80, for all incoming external requests. (The nginx config can distinguish the called domain with the <em>server_name</em> directive)</p>
</li>
<li><p>On the other side, on another docker-compose project, you have a Kibana service that serves request on kibana.exemple.com, but under port 5601 (still HTTP traffic).</p>
</li>
<li><p>The Kibana service is on a dedicated container called <em>kibana</em>, among three other containers in the ELK docker-compose porject (different from the reverse-proxy project).</p>
</li>
<li><p>The solution as you would guess, is that the <em>reverse-proxy</em> forwards the external http://kibana.exemple.com:80 to an internal http://kibana:5601, to solve the port restriction issue.</p>
</li>
<li><p>The kibana domain name used when we want to hit the http://kibana:5601 endpoint, is actually unknown to the outside, or even to the host machine (in principle), it is only internally resolvable by docker built-in DNS to map it to the kibana container.
The problem here is, the network to which the kibana container belongs to is internal and only visible within the ELK docker compose projcet, to which kibana container belongs.</p>
</li>
<li><p>Yet, we want the <em>reverse-proxy</em> container that lives in a separated project to be able to hit that endpoint without much boilerplate work.</p>
</li>
</ul>
<h2 id="heading-a-solution">A solution?</h2>
<ul>
<li><p>Fortunately, docker-compose provides an easy way to solve this kind of situations, which is the external networks.</p>
</li>
<li><p>An external network must be defined, outside from the docker-compose config (one way through a docker-compose command)</p>
</li>
<li><p>Both networks should join this network, and, that's all.</p>
</li>
<li><p>Now you can just refer the container by its name from one or the other side as if they were on the same project</p>
</li>
<li><p>Here it how the official docker-compose defines the feature:</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1644235584012/2UiKBnhmb.png" alt="docker-compose -docs.png" /></p>
<h2 id="heading-how-to-make-it-work">How to make it work?</h2>
<ul>
<li>Create an external network from command line, here we suppose you name it proxy_external</li>
</ul>
<pre><code class="lang-bash">$ docker network create proxy_external
</code></pre>
<ul>
<li>Add the proxy_external under the services → proxy→ networks level of docker-compose.yml of the <em>reverse-proxy</em> project </li>
<li>Add the proxy_external straight under the networks level of docker-compose.yml of the elk project . Define this as an external network</li>
</ul>
<pre><code class="lang-yaml"><span class="hljs-comment"># reverse-proxy project</span>

<span class="hljs-attr">version:</span> <span class="hljs-string">"3.8"</span>
<span class="hljs-attr">services:</span>
  <span class="hljs-attr">proxy:</span>
       <span class="hljs-comment"># ..</span>
    <span class="hljs-attr">networks:</span>

      <span class="hljs-comment"># This is the internal network for the reverse-proxy project</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">proxy</span>

      <span class="hljs-comment"># This is the external proxy</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">proxy_external</span>
<span class="hljs-attr">networks:</span>

  <span class="hljs-comment"># This is the server's network, defined by the service</span>
  <span class="hljs-attr">proxy:</span> <span class="hljs-string">~</span>

  <span class="hljs-comment"># This is an external network. It serves as a bridge between reverse-proxy and other projects.</span>
  <span class="hljs-attr">proxy_external:</span>
    <span class="hljs-attr">external:</span>
      <span class="hljs-attr">name:</span> <span class="hljs-string">proxy_external</span>
</code></pre>
<ul>
<li><p>Add the proxy_external under the services → kibana→ networks  level of docker-compose.yml of the reverse-proxy project </p>
</li>
<li><p>Add the proxy_external straight under the networks level of docker-compose.yml of the elk project . Define this as an external network</p>
</li>
</ul>
<pre><code class="lang-yaml"><span class="hljs-comment"># elk project</span>

<span class="hljs-attr">version:</span> <span class="hljs-string">"3.8"</span>
<span class="hljs-attr">services:</span>
  <span class="hljs-string">elk</span>
       <span class="hljs-comment"># ..</span>
    <span class="hljs-attr">networks:</span>

      <span class="hljs-comment"># This is the internal network for the elk project</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">elk</span>

      <span class="hljs-comment"># This is the external proxy</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">proxy_external</span>
<span class="hljs-attr">networks:</span>

  <span class="hljs-comment"># This is the server's network, defined by the service</span>
  <span class="hljs-attr">elk:</span> <span class="hljs-string">~</span>

  <span class="hljs-comment"># This is an external network. It serves as a bridge between reverse-proxy and other projects.</span>
  <span class="hljs-attr">proxy_external:</span>
    <span class="hljs-attr">external:</span>
      <span class="hljs-attr">name:</span> <span class="hljs-string">proxy_external</span>
</code></pre>
<h2 id="heading-further-readings">Further readings:</h2>
<ul>
<li><p>https://docs.docker.com/compose/compose-file/compose-file-v3/#network-configuration-reference</p>
</li>
<li><p>https://odysee.com/@the-digital-life:a/docker-networking-tutorial-all-network:1</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[I created my own SOLID principles]]></title><description><![CDATA[A week ago, I twitted:
"SOLID of uncle Bob, How I see them?"
And got a nice like from Uncle Bob :)

I am a big fan of SOLID!
Not only that, time after time, I elaborated my own words to express SOLID principles. And as you can see below, I noticed it...]]></description><link>https://blog.medunes.net/i-created-my-own-solid-principles</link><guid isPermaLink="true">https://blog.medunes.net/i-created-my-own-solid-principles</guid><category><![CDATA[General Programming]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[SOLID principles]]></category><category><![CDATA[clean code]]></category><dc:creator><![CDATA[medunes]]></dc:creator><pubDate>Mon, 29 Mar 2021 22:32:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1617059228278/_ROOV2d1O.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A week ago, I twitted:</p>
<p>"SOLID of uncle Bob, How I see them?"</p>
<p>And got a nice like from Uncle Bob :)</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m6ewo5s9nj0qv4tjg4dq.png" alt="Alt Text" /></p>
<p>I am a big fan of SOLID!
Not only that, time after time, I elaborated my own words to express SOLID principles. And as you can see below, I noticed it is almost all about embracing the #change, mastering the #change and anticipating the #change..</p>
<p><strong>[S]: a Unique decider of #change!</strong></p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/plb4reg6xn4oigtg12jt.png" alt="image" /></p>
<p>Whenever you pack some of your code into a module (i.e: function, class, package..), there must be a unique thing(often a business department or tech dependency), that will <strong>force</strong> you to #change the code of that module. </p>
<p>One, and not two, or twenty, only one!</p>
<p><strong>[O]: Minimize touched code if you HAVE to #change</strong></p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t5e5g85siq160vtvzq8l.png" alt="image" /></p>
<p>"Open for extension / closed for modification", right?</p>
<p>Well, there is nothing which is open AND closed at the same time, you know! Your code will be ALWAYS open for modification, otherwise, let's apply TripleDES encryption on each of our classes and then delete the key. Thus we have a really closed module. </p>
<p>That's why I believe "closed to modification" should be suffixed with: "except the very tiny piece that will be used to connect the old code with the newly added one"</p>
<p><strong>[L]:Your problem is not mine</strong></p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xoz8d7y69xvtym3qundj.png" alt="image" /></p>
<p>Ok this principle has the scariest words, but from my point of view what it wants us to avoid is writing subclasses which add rules to the ones it inherits. (that's rude right?)</p>
<p>Hence my way to see it as a "Your problem is not mine". </p>
<p>Suppose you ship a package to your customers, who are developers in this case. And one day you provide them a new version consisting of a submodule of the old one, but with a bunch of silent restrictions and rules. You only did that to solve some of your own code problems some where..</p>
<p>How would you imagine the face of that poor developer trying to figure out WHY are you doing this? Don't you think that "Your problem is not his problem" ?</p>
<p><strong>[I]: Don't pull all your eggs in one basket</strong></p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/orxbdslkvekc1ket6hob.png" alt="image" /></p>
<p>The official version of the principle is:
“Clients should not be forced to depend upon interfaces that they do not use.”
Again, my intuitive question here is: Nice, but why? or, what does it try to troubleshoot?</p>
<p>And my answer is: it clearly fights those hasty guys (including me sometimes :)) who write those allknowing interfaces having a dozen of methods. Some ultimate interface and called IReadWriteCompress or ReadWriteCompressInterface.. </p>
<p>Without even looking into the code, we feel the instinct and the urge to split that BigTank into three small speed-boats, for instance: WriteInterface, ReadInterface and CompressInterface. </p>
<p>So one should not pull all his eggs in one basket, or all his methods in one interface!</p>
<p><strong>[D]: Rely on the least #changing dependency</strong></p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gyligrcag4ascssv5d3f.png" alt="image" /></p>
<p>"High-level modules should not depend on low-level modules. Both should depend on abstractions"</p>
<p>Ok, but why?
The answer I could come up with is: "no one wants to depend on loosely changing element right?"</p>
<p>And this makes it obvious to go for dependency on abstractions rather than dependency on implementations (aka low-level modules).</p>
<p>Low-Level usually has way more details and information to tell than abstractions. Abstractions only expose the big picture which is supposed to last.</p>
<p>Abstractions have less details, which means adapting to their #changes is always easier.</p>
<p>So, that was it, and I would be glad if you could also share your own SOLID versions with me :)
Happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[It is an argument, not a trojan horse!]]></title><description><![CDATA[Let's talk about something cool: trojan horse parameters.
This is how I call this fancy technique which many of us, developers, might have used (or still doing). Ok, so what is it?
As a daily work, you face a problem, think about a solution and start...]]></description><link>https://blog.medunes.net/it-is-an-argument-not-a-trojan-horse</link><guid isPermaLink="true">https://blog.medunes.net/it-is-an-argument-not-a-trojan-horse</guid><category><![CDATA[PHP]]></category><category><![CDATA[PHP7]]></category><category><![CDATA[clean code]]></category><category><![CDATA[refactoring]]></category><category><![CDATA[Object Oriented Programming]]></category><dc:creator><![CDATA[medunes]]></dc:creator><pubDate>Thu, 17 Dec 2020 01:39:00 GMT</pubDate><content:encoded><![CDATA[<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1608163500900/g6-OckHFj.png" alt="image.png" /></p>
<p>Let's talk about something cool: trojan horse parameters.
This is how I call this fancy technique which many of us, developers, might have used (or still doing). Ok, so what is it?</p>
<p>As a daily work, you face a problem, think about a solution and start writing a piece of code for it, then you pack it into a [hopefully] well-named function.</p>
<p>Once you're done, a bright idea comes to your mind telling you: Hey! why not extend your solution so that it also includes<strong> "case B"</strong>? You think a bit and answer: Yes! why not?</p>
<p>Let's illustrate this through an example: You work on a class which is responsible for data persistence, and there are two ways of doing this: either local storage or remote/cloud storage. </p>
<p>And as you appreciated the pop-up idea that asked you to extend your solution so that it covers both cases, you ended up writing this nice code:</p>
<pre><code><span class="hljs-meta">&lt;?php</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FileStorage</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">store</span>(<span class="hljs-params">File $file, $remote = <span class="hljs-literal">false</span></span>): <span class="hljs-title">void</span>
    </span>{
        <span class="hljs-keyword">if</span> ($remote) {
            $compressedFile = <span class="hljs-keyword">$this</span>-&gt;compress($file);
            $fileParts = <span class="hljs-keyword">$this</span>-&gt;splitFile($compressedFile);
            <span class="hljs-keyword">$this</span>-&gt;logger-&gt;info(<span class="hljs-string">'Started uploading to AWS/S3 started.'</span>);
            <span class="hljs-keyword">foreach</span> ($fileParts <span class="hljs-keyword">as</span> $filePart) {
                <span class="hljs-keyword">$this</span>-&gt;awsClient-&gt;upload($filePart);
            }
            <span class="hljs-keyword">$this</span>-&gt;logger-&gt;info(<span class="hljs-string">'Finished uploading to AWS/S3 started.'</span>);
        } <span class="hljs-keyword">else</span> {
            $fp = fopen(<span class="hljs-keyword">$this</span>-&gt;getStoragePath() . $file-&gt;getName(), <span class="hljs-string">'w'</span>);
            fwrite($fp, $file-&gt;getContent());
            fclose($fp);
            <span class="hljs-keyword">$this</span>-&gt;logger-&gt;info(<span class="hljs-string">'File successfully saved on local storage.'</span>);
        }
    }
}
</code></pre><p>Ok, can you tell me what's wrong with that thing above? I can tell you my point of view:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1608164726244/RAn832VSJ.png" alt="image.png" /></p>
<p>If you have a look at it twice, you'll realise the <em>$remote</em> parameter is actually acting as a trigger or switch button, responsible for a complete change of the function's behavior. You can conclude that in case of <em>$remote = true</em> we have a totally standalone logic pushing data to S3/AWS, and in case <em>$remote = false</em> a second different logic is writing data to local disk.</p>
<p>Which rule are we violating here?  </p>
<ul>
<li><p><a target="_blank" href="https://en.wikipedia.org/wiki/Single-responsibility_principle">Single Responsibility Principle</a> ?</p>
</li>
<li><p><a target="_blank" href="https://en.wikipedia.org/wiki/Separation_of_concerns">Separation of Concerns</a> ? </p>
</li>
<li><p><a target="_blank" href="https://en.wikipedia.org/wiki/Modular_programming">Modularity</a> ?  </p>
</li>
<li><p><a target="_blank" href="https://notherdev.blogspot.com/2013/07/code-smells-rigidity.html">Rigidity</a>  ?</p>
</li>
</ul>
<p>This tiny little optional parameter turned into a trojan horse that changes the logic of the function 180 degrees, in a totally silent way.</p>
<p>So, why not be transparent, modular, flexible and single responsible?
Besides, no big effort must to be taken here, just <strong>split and rename ! </strong>
The only thing we have to do is to get rid of the optional/default parameter and move each block under the if statement into a separated new function, which name should reflect the actual task being achieved; something like this:</p>
<pre><code> <span class="hljs-meta">&lt;?php</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FileStorage</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">saveToDisk</span>(<span class="hljs-params">File $file</span>): <span class="hljs-title">void</span>
    </span>{
        $fp = fopen(<span class="hljs-keyword">$this</span>-&gt;getStoragePath() . $file-&gt;getName(), <span class="hljs-string">'w'</span>);
        fwrite($fp, $file-&gt;getContent());
        fclose($fp);
        <span class="hljs-keyword">$this</span>-&gt;logger-&gt;info(<span class="hljs-string">'File successfully saved on local storage.'</span>);
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">upload</span>(<span class="hljs-params">File $file</span>): <span class="hljs-title">void</span>
    </span>{
        $compressedFile = <span class="hljs-keyword">$this</span>-&gt;compress($file);
        $fileParts = <span class="hljs-keyword">$this</span>-&gt;splitFile($compressedFile);
        <span class="hljs-keyword">$this</span>-&gt;logger-&gt;info(<span class="hljs-string">'Started uploading to AWS/S3.'</span>);
        <span class="hljs-keyword">foreach</span> ($fileParts <span class="hljs-keyword">as</span> $filePart) {
            <span class="hljs-keyword">$this</span>-&gt;awsClient-&gt;upload($filePart);
        }
        <span class="hljs-keyword">$this</span>-&gt;logger-&gt;info(<span class="hljs-string">'Finished uploading to AWS/S3.'</span>);

    }
}
</code></pre><p>You can even go a step further towards clean-code by coding to an interface, not a concrete implementation (class),  and moving each logic to a separated, ensuring even more modular parts and being closer to the  <a target="_blank" href="https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle">Open/Closed Principle</a> </p>
<pre><code><span class="hljs-meta">&lt;?php</span>

<span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">StorageInterface</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">save</span>(<span class="hljs-params">File $file</span>): <span class="hljs-title">void</span></span>;
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AwsStorage</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">StorageInterface</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">save</span>(<span class="hljs-params">File $file</span>): <span class="hljs-title">void</span>
    </span>{
        $compressedFile = <span class="hljs-keyword">$this</span>-&gt;compress($file);
        $fileParts = <span class="hljs-keyword">$this</span>-&gt;splitFile($compressedFile);
        <span class="hljs-keyword">$this</span>-&gt;logger-&gt;info(<span class="hljs-string">'Started uploading to AWS/S3 started.'</span>);
        <span class="hljs-keyword">foreach</span> ($fileParts <span class="hljs-keyword">as</span> $filePart) {
            <span class="hljs-keyword">$this</span>-&gt;awsClient-&gt;upload($filePart);
        }
        <span class="hljs-keyword">$this</span>-&gt;logger-&gt;info(<span class="hljs-string">'Finished uploading to AWS/S3 started.'</span>);
    }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LocalStorage</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">StorageInterface</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">save</span>(<span class="hljs-params">File $file</span>): <span class="hljs-title">void</span>
    </span>{
        $fp = fopen(<span class="hljs-keyword">$this</span>-&gt;getStoragePath().$file-&gt;getName(), <span class="hljs-string">'w'</span>);
        fwrite($fp, $file-&gt;getContent());
        fclose($fp);
        <span class="hljs-keyword">$this</span>-&gt;logger-&gt;info(<span class="hljs-string">'File successfully saved on local storage.'</span>);
    }
}
</code></pre><p>And that was it!</p>
<p>I would also be glad to know your opinion about the topic if you consider it from  a different point of view.</p>
<p>Happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[Top 10 Senior Developers Skills]]></title><description><![CDATA[Junior, Senior, Medior, Professional and similar grades and classification labels that developers might have as stickers alongside their careers.

To follow the path, look to the master, follow the master, walk with the master, see through the master...]]></description><link>https://blog.medunes.net/top-10-senior-developers-skills</link><guid isPermaLink="true">https://blog.medunes.net/top-10-senior-developers-skills</guid><category><![CDATA[problem solving skills]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[Software Engineering]]></category><dc:creator><![CDATA[medunes]]></dc:creator><pubDate>Thu, 22 Oct 2020 20:20:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1603399572557/JoHTkveDC.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Junior, Senior, Medior, Professional and similar grades and classification labels that developers might have as stickers alongside their careers.</p>
<blockquote>
<p>To follow the path, look to the master, follow the master, walk with the master, see through the master, become the master. ~Zen Proverb.</p>
</blockquote>
<p><img src="https://cdn.pixabay.com/photo/2017/01/30/03/41/owl-2020011_1280.jpg" alt="image.png" /></p>
<p>I believe these are relative and probably subjective estimations, that might specially change from an environment to another and from an ecosystem to another.</p>
<p>But If we want to gather the common aspects that might depict a senior developer regardless their stack or professional surroundings, we might come up with the following hints:</p>
<ol>
<li><p>Never argues this language is <strong>STRONGER</strong> than this or this framework is <strong>BETTER</strong> than this. He knows they are tools to achieve goals depending on the context.</p>
</li>
<li><p>Spends MORE time on analysing the problem and discussing it with the client than time on implementing the feature.</p>
</li>
<li><p>Has the reflex to check as much as possible if the problem can be solved without  writing a line of code.</p>
</li>
<li><p>Is able to estimate in which direction the code will evolve in the future and writes code accordingly.</p>
</li>
<li><p>Strongly believes development is about solving problems and not writing code that runs.</p>
</li>
<li><p>Can switch between languages and frameworks without needing months to learn everything, just the needed knowledge to implement the feature.</p>
</li>
<li><p>Knows his stack <strong>PERFECTLY</strong>, keeps up-to-date with releases, best practices, bug fixes, tools, and so on. For the rest of stacks, he doesn't sink into details.</p>
</li>
<li><p>Knows about different point of views in code design, but doesn't spend much time in worthless battles.</p>
</li>
<li><p>Courageous enough to write his own package, and humble enough to use other developer's packages. Also has the ability to estimate which alternative is more relevant.</p>
</li>
<li><p>Optionally, in his free time, writes &amp; ships code that people really use.</p>
</li>
</ol>
]]></content:encoded></item></channel></rss>