<?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[ESK's Tech Garden]]></title><description><![CDATA[ESK's Tech Garden]]></description><link>https://blog.elikem.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1672319985737/vU7NmIYL4.jpg</url><title>ESK&apos;s Tech Garden</title><link>https://blog.elikem.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Mon, 07 Sep 2026 08:43:08 GMT</lastBuildDate><atom:link href="https://blog.elikem.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[AI failed me today.]]></title><description><![CDATA[I'm a developer. I solve problems for a living. In fact, I love to solve problems. I often think I enjoy the process of troubleshooting far more than actually implementing the fix.
So after several mo]]></description><link>https://blog.elikem.dev/ai-failed-me-today</link><guid isPermaLink="true">https://blog.elikem.dev/ai-failed-me-today</guid><category><![CDATA[Artificial Intelligence]]></category><category><![CDATA[#ArtificialIntelligence ]]></category><category><![CDATA[AI]]></category><category><![CDATA[Linux]]></category><category><![CDATA[DevLife]]></category><category><![CDATA[Productivity]]></category><category><![CDATA[Fedora]]></category><dc:creator><![CDATA[Elikem]]></dc:creator><pubDate>Wed, 24 Jun 2026 22:46:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/5f0b925e3fe8405bdb8d8018/30b4fd06-820d-4b0a-982b-e75839e2760c.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I'm a developer. I solve problems for a living. In fact, I <em>love</em> to solve problems. I often think I enjoy the process of troubleshooting far more than actually implementing the fix.</p>
<p>So after several months of ignoring my HP LaserJet M608's many problems, I finally decided to do something about it. I don't think I would have been motivated to do this, but I really wanted to print a copy of Chopin's op 53 polonaise so bad.</p>
<p>I'd previously tried to fix the printer. But that was before AI got good enough. Countless videos and Google searches did little good. I simply couldn't find my way around the problems. Today, that was about to change. "Why not?", I asked myself? With the power of AI, I would finally conquer this HP printer! So I figured this would be my chance to start afresh and just ask Claude instead.</p>
<p>Claude has gotten me through several sticky situations - career issues, legal dilemmas, planning vacations and outings, deciding whether to go to the hospital, you name it.</p>
<p>And if Claude could do those, surely it could fix a simple printer issue, right?</p>
<p><em>Right....?</em></p>
<h3>It Started Reasonably Enough</h3>
<p>The printer had been showing a "data corruption detected" message on boot. Fair enough — Claude diagnosed this quickly, gave me a clean list of steps to try, and sounded very confident. Hard reset, factory defaults, firmware update. Easy. I felt good about this.</p>
<p>Then I asked how to connect the printer to my Lenovo ThinkPad running Fedora. Claude launched into a detailed guide about HPLIP, <code>hp-setup</code>, CUPS. All very authoritative. All formatted in neat little tables and code blocks. Very reassuring.</p>
<p><strong>And this, ladies and gentlemen, is how we went down the rabbit hole.</strong></p>
<h3>The Rabbit Hole</h3>
<p>What followed was nearly three hours of increasingly creative troubleshooting. At one point I was running <code>lpstat</code>, <code>hp-info</code>, <code>hp-firmware</code>, <code>lsusb</code>, <code>dmesg</code>, <code>dd</code>, <code>modprobe</code>, <code>cupsenable</code>, <code>cupsdisable</code> — you name it. Claude had an answer for everything. Every dead end was met with a new suggestion, a new command, a new theory.</p>
<p>The printer threw a <strong>49.50.03 firmware error</strong>. Claude knew what it was. The printer's IP address came back as <strong>0.0.0.0</strong>. Claude had an explanation. Every time I hit a wall, there was a confident response waiting for me on the other side.</p>
<p>The problem is that confidence and correctness are not the same thing.</p>
<h3>The Firmware Flash That Wasn't</h3>
<p>The most painful chapter was the firmware update. Claude told me to download the latest HP FutureSmart firmware — a 103MB file — and flash it to the printer over USB. Reasonable enough. I downloaded it, extracted the <code>.bdl</code> file, and sent it via <code>lpr</code>.</p>
<p>It sat in the queue for twenty minutes. Nothing happened.</p>
<p>We tried <code>cp</code> to <code>/dev/usb/lp0</code>. No such file or directory. We tried <code>/dev/lp0</code>. No such device. We tried <code>dd</code> directly to the USB bus. Invalid argument. We unloaded and reloaded the <code>usblp</code> kernel module. We stopped CUPS entirely. At one point Claude suggested writing a shell script to watch for the device to appear and flash it in the 100-millisecond window before the kernel removed it again.</p>
<p>That's when I asked: <em>"Are you really sure you know what you're doing?"</em></p>
<p>And then the response I saw replaced my smile with a grin of disappointment. "No, not really."</p>
<p>Me and the AI were winging it! Giving me false motivation! With full <em>vim</em> (confidence)! And I was executing those commands with my full chest (like we say in Ghana)! We had drifted into trial-and-error territory and continuing on that tangent would be a big gamble. That moment of honesty was arguably more useful than the previous two hours of confident suggestions.</p>
<p>But wait - if you think this was only a Claude problem, it wasn't. My disappointed self went over to Gemini, like someone seeking consolation in a side chick after the main relationship goes bust. Gemini's first response made such perfect sense - and everything else was soft poison. Commands after commands. Methods upon methods. Explanations about why things were not working that resulted in further commands to modify things some more.</p>
<p>I had enough.</p>
<p>I gave up.</p>
<h2>No, the Printer Still Doesn't Work</h2>
<p>As I write this, my HP LaserJet M608 is sitting on my desk, home screen showing, USB cable connected, completely useless. CUPS is back up and running. The firmware file is still in my Downloads folder. The data corruption error is still there.</p>
<p>The AI was helpful right up until it wasn't. And that's if we count going round in circles as helpfulness.</p>
<h3>What I Learned</h3>
<p>AI assistants are genuinely useful for well-documented, common problems. It's not a conscious machine. It's GIGO. Garbage in, garbage out. It lacks the ability to innovate and create actual solutions. It only tries to find patterns and associations in its dataset. But the moment you step outside that boundary — into obscure hardware errors, device driver conflicts, corrupted firmware — the AI starts to improvise. And it improvises with the same confident tone it uses when it actually knows the answer. And that's the dangerous part. There's no "I'm guessing now" indicator. No warning light. Just more code blocks and more command.</p>
<p>Don't get me wrong - when AI gets it right, it's an awesome tool.</p>
<p>But when it doesn't, you're on your own.</p>
]]></content:encoded></item><item><title><![CDATA[Why?!!!!!!!!!!!!]]></title><description><![CDATA[It will always surprise me how people take the pains to learn web development only to do things like these…

What’s more interesting is that the recipient address is not mine. Yet, it lands in my mail.
Funny how they thought we would have flying cars...]]></description><link>https://blog.elikem.dev/these-spammers-never-give-up</link><guid isPermaLink="true">https://blog.elikem.dev/these-spammers-never-give-up</guid><category><![CDATA[spam]]></category><category><![CDATA[email]]></category><dc:creator><![CDATA[Elikem]]></dc:creator><pubDate>Mon, 20 Oct 2025 00:44:24 GMT</pubDate><content:encoded><![CDATA[<p>It will always surprise me how people take the pains to learn web development only to do things like these…</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760920801929/61d1fce1-ac3b-4f8f-80cc-2c2dd7da06b6.png" alt class="image--center mx-auto" /></p>
<p>What’s more interesting is that the recipient address is not mine. Yet, it lands in my mail.</p>
<p>Funny how they thought we would have flying cars in 2020, but it’s 2025 and this is the best some people can use tech for.</p>
]]></content:encoded></item><item><title><![CDATA[Is Docker slowing down your PC? Try this...]]></title><description><![CDATA[Introduction
When working with Docker on Windows, many developers opt to use Docker Desktop for its convenience and integration. Docker makes it easy to distribute your code to your team members without having to go through the pains of setting up th...]]></description><link>https://blog.elikem.dev/run-docker-in-wsl-without-docker-desktop</link><guid isPermaLink="true">https://blog.elikem.dev/run-docker-in-wsl-without-docker-desktop</guid><category><![CDATA[Docker]]></category><category><![CDATA[WSL]]></category><category><![CDATA[Linux]]></category><category><![CDATA[containers]]></category><dc:creator><![CDATA[Elikem]]></dc:creator><pubDate>Wed, 26 Jun 2024 15:00:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/jOqJbvo1P9g/upload/824769b7d3a7bc651b4d889666a71885.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>When working with Docker on Windows, many developers opt to use Docker Desktop for its convenience and integration. Docker makes it easy to distribute your code to your team members without having to go through the pains of setting up their environment or switching versions of software every time.</p>
<p>However, if you prefer a more streamlined setup or wish to avoid Docker Desktop, you can run Docker directly within Windows Subsystem for Linux (WSL). This guide walks you through setting up Docker in WSL, resolving common issues, and ensuring a smooth Docker experience.</p>
<p>PS: If you're in a hurry, skip over the next two subheadings. But if you do read, you will appreciate the journey more.</p>
<h3 id="heading-quickest-way-of-building-your-software-with-docker">Quickest way of building your software with Docker</h3>
<p>The fastest way to use Docker in Windows is via Docker Desktop. If you have your files sitting at <code>C:\Users\John\projects</code>, you really just need to start the Docker engine, and you can continue developing (Assuming you already have your Dockerfile or docker-compose file set up).</p>
<p>This approach is great for a start but you will most likely run into performance issues. Serious performance issues. <s>And good luck to you if you only have 4GB RAM!</s> This can be a challenge, especially with limited resources. My computer kept freezing and I ultimately uninstalled Docker for a long time.</p>
<p>Eventually, the problem of switching environments too often led me back to Docker. And then I was forced to research. This led me to explore and arrive at the next iteration in my setup.</p>
<h3 id="heading-an-improved-way-docker-with-wsl">An improved way: Docker with WSL</h3>
<p>If you have WSL, you can move your project files into your Linux folder so that they sit at <code>~/projects/</code> instead. Then you just need to fire up Docker Desktop, spin up your WSL instance, and then serve or run your project. You would need to configure Docker Desktop to communicate with the Linux distro.</p>
<p>This is also a very valid way of doing things. But lately, I have been wondering if it was possible to further boost the performance of my apps by completely bypassing the need for Docker Desktop. After all, the desktop app was made for Windows compatibility. But WSL actually functions like a full OS on its own. So what if we could pretend we were running Linux natively and just install Docker there directly?</p>
<h4 id="heading-the-tradeoff">The tradeoff?</h4>
<p>The tradeoff is that we definitely lose the GUI experience. We would need to rely on commands to do everything. But I would rather it be super fast without a GUI than to have it super slow with a GUI.</p>
<p>I have a desktop PC which I wanted to dedicate to frontend work, while my smaller-screen-but-more-powerful machine handles backend work. The desktop struggles to run Docker Desktop, even though it has 16GB RAM and a fast i7 processor (the same specs as the smaller machine). I couldn't figure out why this was happening, but for this reason I decided to go with the approach we're about to unravel.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<ul>
<li><p>Windows 11 (Win 10 users, <a target="_blank" href="https://devblogs.microsoft.com/commandline/wsl-2-support-is-coming-to-windows-10-versions-1903-and-1909/">read this</a>)</p>
</li>
<li><p>WSL installed and running Ubuntu 20.04 or greater. You can follow <a target="_blank" href="https://docs.microsoft.com/en-us/windows/wsl/install">this guide</a>.</p>
</li>
</ul>
<h2 id="heading-installing-docker-engine-on-wsl-2">Installing Docker Engine on WSL 2</h2>
<p>This section will guide you on installing Docker's core engine in your Linux distro within WSL.</p>
<ul>
<li><p><strong>Open your WSL terminal</strong> (e.g., Ubuntu).</p>
</li>
<li><p><strong>Update the Package Index</strong>:</p>
<pre><code class="lang-plaintext">  sudo apt-get update
</code></pre>
</li>
<li><p><strong>Install Required Packages</strong>:</p>
<pre><code class="lang-plaintext">  sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common
</code></pre>
</li>
<li><p><strong>Add Docker’s Official GPG Key</strong>:</p>
<pre><code class="lang-plaintext">  curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
</code></pre>
</li>
<li><p><strong>Add Docker’s APT Repository</strong>:</p>
<pre><code class="lang-plaintext">  sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
</code></pre>
</li>
<li><p><strong>Update the Package Index Again</strong>:</p>
<pre><code class="lang-plaintext">  sudo apt-get update
</code></pre>
</li>
<li><p><strong>Install Docker Engine</strong>:</p>
<pre><code class="lang-plaintext">  sudo apt-get install -y docker-ce docker-ce-cli containerd.io
</code></pre>
</li>
<li><p><strong>Start Docker</strong>:</p>
<pre><code class="lang-plaintext">  sudo service docker start
</code></pre>
</li>
<li><p><strong>Recommended (Optional): Run Docker Without Sudo</strong>:</p>
<p>  If you use this command, you will no longer need to prepend sudo when using docker commands. Therefore, we will not use sudo for the rest of this article. But if you need to run service commands to alter or check the status of docker, you need sudo.</p>
<pre><code class="lang-plaintext">  sudo usermod -aG docker $USER
  newgrp docker
</code></pre>
</li>
</ul>
<h3 id="heading-post-installation-steps">Post-Installation Steps</h3>
<p>Now let's do just a few more things to spice it up. Let's check if we have some basic extensions installed.</p>
<p>Run <code>docker</code> and see if you get any warnings about invalid plugins just after the "commands" section. If you have missing plugins, you should see this output:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1719042866020/0238c7c4-3a87-4b50-a941-76bc7433d201.png" alt class="image--center mx-auto" /></p>
<p>In order to resolve it, we need to install those plugins. Run the following commands based on the plugins you want:</p>
<h4 id="heading-docker-compose-plugin">Docker compose plugin</h4>
<p>To download and install the plugin, run the following commands:</p>
<pre><code class="lang-plaintext">mkdir -p ~/.docker/cli-plugins/
curl -SL https://github.com/docker/compose/releases/download/v2.17.2/docker-compose-linux-x86_64 -o ~/.docker/cli-plugins/docker-compose
</code></pre>
<p>The first command makes the directory that will house all plugins. The second downloads and installs it.</p>
<p>After this, we need to make it executable by changing its permissions. Run <code>chmod +x ~/.docker/cli-plugins/docker-compose</code> to do so.</p>
<p>Verify the installation using the name of the plugin. Run <code>docker compose version</code>. You should get output. Another way to check is by running <code>docker</code> again and ensuring that you no longer see a warning for that plugin. You will follow this step for the other plugins.</p>
<h4 id="heading-docker-buildx-plugin">Docker Buildx plugin</h4>
<pre><code class="lang-plaintext">curl -SL https://github.com/docker/buildx/releases/download/v0.10.5/buildx-v0.10.5.linux-amd64 -o ~/.docker/cli-plugins/docker-buildx
chmod +x ~/.docker/cli-plugins/docker-buildx
</code></pre>
<p>Again, we first install and then make the file executable as we did previously and also check to be sure it's installed properly by running <code>docker buildx version</code> .</p>
<h4 id="heading-docker-scan-plugin">Docker scan plugin</h4>
<pre><code class="lang-plaintext">curl https://github.com/docker/scan-cli-plugin/releases/latest/download/docker-scan_linux_amd64 -L -s -S -o ~/.docker/cli-plugins/docker-scan
chmod +x ~/.docker/cli-plugins/docker-scan
</code></pre>
<h3 id="heading-autostart-docker-when-wsl-is-launched">Autostart Docker when WSL is launched</h3>
<p>Now we need to make our life easier by automatically starting Docker whenever we open WSL. We don't want the stress of typing <code>sudo service docker start</code> every single time. For this step, we need to modify the home directory's shell configuration file. On my distro, there is a <code>~/.bashrc</code> file that is fired whenever WSL is booted. If you don't see that file, look out for <code>~/.zshrc</code> . If neither exists, create the appropriate one.</p>
<p>Head into the Nano editor using <code>nano ~/.bashrc</code>. If it does not work, you need to prepend sudo.</p>
<p>Add <code>sudo service docker start &gt; /dev/null</code> to the file. This command boots up docker but also prevents the output from being sent to the terminal every time you boot. It makes use of Linux's null device. If you want the output, simply use <code>sudo service docker start</code> instead.</p>
<p>Now reboot the WSL distro to be <em>sure</em> it works even though we both know it will (I have trust issues. Must always double-check).</p>
<p>Finally, to check the status of Docker, run <code>sudo service docker status</code>.</p>
<h3 id="heading-optional-add-your-plugins-to-path">(Optional) Add your plugins to PATH</h3>
<p>Add the plugin directory to your PATH to ensure they are accessible. Run <code>nano ~/.bashrc</code> and add:</p>
<pre><code class="lang-plaintext">export PATH=$PATH:~/.docker/cli-plugins/
</code></pre>
<p>Add this line to your shell configuration file (e.g., <code>~/.bashrc</code> or <code>~/.zshrc</code>), then reload your shell configuration: <code>source ~/.bashrc</code></p>
<h3 id="heading-verifying-docker-installation">Verifying Docker Installation</h3>
<p>We can now run a simple Hello world program to make sure Docker works. In your terminal, run <code>docker run hello-world</code>.</p>
<p>If you get this output, you're good to go!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1719045506397/a4241e34-f082-4798-a6df-c78882530a64.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>To wrap things up, we've covered the essential steps to ensure Docker runs smoothly on your WSL setup. From editing configuration files to adding necessary commands and verifying the installation, these steps help streamline your development environment. By following this guide, you can confidently manage Docker services and ensure they are always ready to go. Happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[One simple trick to supercharge your programming skills...]]></title><description><![CDATA[Hello there!
If you've been programming for any reasonable length of time, there's no doubt you've already discovered the secret "KFC recipe" of programming. It's a secret, which when you have learned it, can potentially turn you into a millionaire! ...]]></description><link>https://blog.elikem.dev/one-simple-trick-to-supercharge-your-programming-skills</link><guid isPermaLink="true">https://blog.elikem.dev/one-simple-trick-to-supercharge-your-programming-skills</guid><category><![CDATA[General Programming]]></category><category><![CDATA[General Advice]]></category><dc:creator><![CDATA[Elikem]]></dc:creator><pubDate>Wed, 03 Apr 2024 12:22:02 GMT</pubDate><content:encoded><![CDATA[<p>Hello there!</p>
<p>If you've been programming for any reasonable length of time, there's no doubt you've already discovered the secret "KFC recipe" of programming. It's a secret, which when you have learned it, can potentially turn you into a millionaire! Finally, you get to buy your dream car with a customized number plate and cruise on a billion-dollar yacht, sipping champagne, flying to exotic locations and eating at the fanciest restau------ oh wait... you don't know the secret? Bummer.🤦🏽‍♂️</p>
<p>My bad. This secret does come by experience, but in this article, we will give it to you for free. This trick I'm about to show you is really simple but I assure you that it works.</p>
<p>The trick is to <strong>master keyboard shortcuts.</strong></p>
<p>The time taken to find your way around a GUI and move the cursor to the right position can always quickly be cut in half or reduced by as much as 90% (just an estimate...). This advice may sound simple and probably like a no-brainer, but I'll give an example to fortify the point I'm making.</p>
<p>It wasn't until I started using Ctrl + backspace (equivalent is Cmd + backspace) to clear unwanted words that I realized how much time was being wasted just by simply using backspace on its own.</p>
<p>This is how much time it takes when using backspace on its own:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1712137739928/7eec36e2-26b4-4a1f-bb12-ff263cc70158.gif" alt class="image--center mx-auto" /></p>
<p>Compare that to this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1712137782192/540510cc-07f8-45cc-a8a9-ae68ed27913c.gif" alt class="image--center mx-auto" /></p>
<p>The former takes about 33.19 seconds while the latter takes just under 5.85 seconds!</p>
<p>Even better, using Ctrl + A followed by backspace or delete takes an astonishing 1.63 seconds. You could even speed it up a bit more by first selecting the entire passage with the mouse and pressing backspace or delete. This takes about 1.51 seconds. Obviously, the only reason the GUI method worked the fastest in this example is because the text was not too lengthy. Bulk up the text and the method may slow down exponentially, as compared to using Ctrl + A, which has a time complexity of O(1).</p>
<p>This is, of course, just a rough demonstration but in the real world, these things really do add up and cost you a LOT of time.</p>
<p>I have been complimented by a former coworker about how fast I type. And yet I don't even think I type that fast.</p>
<p>In summary, learning how to use keyboard shortcuts is a secret recipe you must know as a software developer. It will absolutely transform the way you write code.</p>
]]></content:encoded></item><item><title><![CDATA[When the internet goes poof...!]]></title><description><![CDATA[For several years, I've often pondered over what would happen if the internet were to crash, or for some reason, if all computing systems in the world were to suddenly stop working. Well, this past week, I had a little foretaste of that.
As software ...]]></description><link>https://blog.elikem.dev/when-the-internet-goes-poof</link><guid isPermaLink="true">https://blog.elikem.dev/when-the-internet-goes-poof</guid><category><![CDATA[internet]]></category><category><![CDATA[Disaster recovery]]></category><category><![CDATA[crash]]></category><dc:creator><![CDATA[Elikem]]></dc:creator><pubDate>Sun, 17 Mar 2024 18:15:03 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/JyRTi3LoQnc/upload/c816bbd3378a8ac5e4c2dbd73013bcee.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>For several years, I've often pondered over what would happen if the internet were to crash, or for some reason, if all computing systems in the world were to suddenly stop working. Well, this past week, I had a little foretaste of that.</p>
<p>As software devs, we live our entire lives in the cloud. Our repos are on Github, ready to be fetched from any device. We store our personal files in cloud storage. All of our life's hard work exists as bits on a server somewhere we've never been. We have no tangible representation of our work. And when the only network connection we have to our work is severed, there is panic.</p>
<p><em>Panic</em> and <em>frustration</em> are the right words to describe the mood software devs in West Africa faced this past week.</p>
<p>I was in conversation with a client on Thursday, 14th March at 11 in the morning. I was trying to create a new project on GCP. All of a sudden, the internet wouldn't work. Nothing worked. Everything went down for a while. I quickly sent the client an SMS: "My internet is not working". "Same here", came the reply. After a few minutes, WhatsApp messages went through. My groups were filled with people scratching their heads and wondering what was going on. I was equally puzzled. Developers could neither push nor pull. I could not access any sites. A good friend of mine had to miss her class because she couldn't use her bank's app to withdraw money.</p>
<p>Within minutes, it became clear what had happened. The internet went "poof".</p>
<p>More clarity about the outage started to trickle in. At first, there were only speculations. But the truth became plain soon enough. 4 major undersea cables had been cut. The damage, severe. The time to recovery, long.</p>
<p>Being nerds, we began to try and figure out what we could do to save the situation. We tried many things. Some had to switch to networks that typically served lower customer bases. Others had to create VMs and forward all their traffic through those to access all sites. And some others went with commercially available VPN software. As I write this, I am connected to a VPN. I'm serving the internet through my phone's hotspot. My fibre provider is working hard to restore the network, but I currently only have a download speed of about 220 Kbps. At that speed, it would be better to sleep than to bother oneself trying to load anything at all.</p>
<p>While the service providers have been able to race time and provide a solution to help most of us, there are some developers who were not so lucky. There are some posts on social media about developers who have lost their remote jobs within these past 3 days.</p>
<p>Just. Like. That.</p>
<p>I can imagine how hard the engineers working at the ISPs had to work just to get us all back online. My hat goes off for each and every one of these dedicated heroes! We still have a long road to full recovery: a minimum of 5 weeks.</p>
<p>But the bottom line is this: what if the internet went poof one day? Where would my work be? Are our governments and ISPs only concerned with fixing the problem and not preventing future occurrences? Would satellite internet not relieve our over-dependence on fibre optics? Why should critical applications that are only used by the local population be dependent on external fibre connections or servers in other countries?</p>
<p>And if a solar storm and an underwater disaster were to simultaneously strike, destroying both satellite and fibre connections, what would happen to the world of tech?</p>
<p>I ponder and I wonder.</p>
]]></content:encoded></item><item><title><![CDATA[Native PHP Apps (2/∞)]]></title><description><![CDATA[Introduction
In the last post, I mentioned something about being able to trigger desktop notifications right from your NativePHP app. Today, I wish to test it out and see if it'll be easy to do.
Getti]]></description><link>https://blog.elikem.dev/native-php-apps-part-2</link><guid isPermaLink="true">https://blog.elikem.dev/native-php-apps-part-2</guid><category><![CDATA[Laravel]]></category><category><![CDATA[PHP]]></category><category><![CDATA[NativePHP]]></category><category><![CDATA[native]]></category><category><![CDATA[2Articles1Week]]></category><dc:creator><![CDATA[Elikem]]></dc:creator><pubDate>Sat, 26 Aug 2023 06:00:09 GMT</pubDate><content:encoded><![CDATA[<h2>Introduction</h2>
<p>In the last post, I mentioned something about being able to trigger desktop notifications right from your NativePHP app. Today, I wish to test it out and see if it'll be easy to do.</p>
<p>Getting the application set up took quite a while, and I had to dig through the Github issues of the repository in order to find a way to get the app running. Clearly, there is still a lot of work to be done in order to make the set up as smooth and seamless for as many platforms as possible.</p>
<p>For that reason, I will not show how to set up the app at this time. But if you have any issues, do well to consult the repository.</p>
<h2>TLDR: I just need the code</h2>
<p>If you're not interested in reading the full article here, you can <a href="https://github.com/eskayamadeus/try-out-nativephp/commit/dea23f3bf9f0bb5581507a32cd175bffde8f4812">find the necessary code in this commit</a>.</p>
<h2>Running the app</h2>
<p>To get Electron to open up, we just need to build and then run.</p>
<p>So I did <code>php artisan native:build win</code> to build.</p>
<p>And then <code>php artisan native:serve</code> to start the server.</p>
<p>What this did was to open up a native Electron window.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1692612250370/3bdf58ce-616c-45f3-9d55-79f2128838bb.png" alt="" style="display:block;margin:0 auto" />

<p>By default, it opened up my DevTools as well. We'll just go ahead and close that.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1692612292967/95777993-5ebe-459b-8068-121001327d96.png" alt="" style="display:block;margin:0 auto" />

<h2>Modifying UI</h2>
<p>Now we need to add a button that we can click to show the notifications. Since we're working in Laravel, we already have a <code>welcome.blade.php</code> view by default. And the 4 items that appear are actually all just buttons, so we'll just duplicate them.</p>
<pre><code class="language-xml">&lt;div class="mt-16"&gt;
    &lt;div class="grid grid-cols-1 md:grid-cols-2 gap-6 lg:gap-8"&gt;
        &lt;button id="runButton" class="scale-100 p-6 bg-white dark:bg-gray-800/50 dark:bg-gradient-to-bl from-gray-700/50 via-transparent dark:ring-1 dark:ring-inset dark:ring-white/5 rounded-lg shadow-2xl shadow-gray-500/20 dark:shadow-none flex motion-safe:hover:scale-[1.01] transition-all duration-250 focus:outline focus:outline-2 focus:outline-red-500"&gt;
            &lt;h2 class="text-xl font-semibold text-gray-900 dark:text-white"&gt;Show notification&lt;/h2&gt;
        &lt;/button&gt;

        ...

&lt;/div&gt;
</code></pre>
<p>And then when we run it, we get:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1692613012455/47c66c2b-65a8-413e-b5cf-129888b37f5b.png" alt="" style="display:block;margin:0 auto" />

<p>Certainly not the best UI, but just a proof of concept.</p>
<p>Now we want that button to do something when clicked, so we already gave it an ID of <code>runButton</code>. We'll create a script on this same welcome page that sends a POST request to the appropriate controller which will then invoke the notification.</p>
<p>So before our closing body tag, we can create a script like so:</p>
<pre><code class="language-xml">&lt;script&gt;
    document.getElementById('runButton').addEventListener('click', function() {
        // Send an AJAX request to the route
        fetch("{{ route('run.method') }}", {
            method: 'POST',
            headers: {
                'X-CSRF-TOKEN': '{{ csrf_token() }}',
                'Content-Type': 'application/json',
            },
        })
        .then(data =&gt; {
            // Handle the response or update the UI as needed
        })
        .catch(error =&gt; {
            console.error('An error occurred:', error);
        });
    });
&lt;/script&gt;
</code></pre>
<p>The main takeaway from this script is the <code>fetch()</code> method which sends a POST request to the server. We have not yet defined the target endpoint, so let's do that.</p>
<h2>Endpoint and Controller</h2>
<p>Before we add the endpoint to the default routes in <code>web.php</code>, let's go ahead and create the controller.</p>
<p>With Artisan, it's pretty easy to generate. Since we don't need multiple methods, the simplest thing here is to use the shortcut flag to generate an invokable controller:</p>
<p><code>php artisan make:controller -i NotificationButtonController</code></p>
<p>Then in the controller, we can just modify the <code>__invoke</code> class as follows:</p>
<pre><code class="language-php">public function __invoke()
{
    Notification::title('Hello from NativePHP')
        -&gt;message('This is a detail message coming from your Laravel app.')
        -&gt;show();
}
</code></pre>
<p><em>This example was actually lifted straight out of the</em> <a href="https://nativephp.com/docs/1/the-basics/notifications#sending-notifications"><em>documentation</em></a><em>.</em></p>
<p>Now we can link this controller in our <code>web.php</code> routes:</p>
<pre><code class="language-php">&lt;?php

use App\Http\Controllers\NotificationButtonController;
use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider and all of them will
| be assigned to the "web" middleware group. Make something great!
|
*/

Route::get('/', function () {
    return view('welcome');
});

Route::post('/run-method', NotificationButtonController::class)-&gt;name('run.method');
</code></pre>
<h2>Testing the code</h2>
<p>Now we can just refresh the Electron window (using the relevant keyboard shortcut) and then click the button. It will gracefully display a native notification!</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1692614005143/68cbdeb0-335b-4e9d-836f-bd694076de8a.png" alt="" style="display:block;margin:0 auto" />

<h2>Conclusion</h2>
<p>I hope you can see how easy it was!</p>
]]></content:encoded></item><item><title><![CDATA[Native PHP Apps (1/∞)]]></title><description><![CDATA[Since all things begin with an introduction, let's have a chat.
What is NativePHP?
If you've ever needed a way to build desktop apps, you may find that you often need to serve a backend and link it up with an Electron.js app. However, with NativePHP,...]]></description><link>https://blog.elikem.dev/native-php-apps-part-1</link><guid isPermaLink="true">https://blog.elikem.dev/native-php-apps-part-1</guid><category><![CDATA[PHP]]></category><category><![CDATA[Electron]]></category><category><![CDATA[native]]></category><category><![CDATA[NativePHP]]></category><category><![CDATA[2Articles1Week]]></category><dc:creator><![CDATA[Elikem]]></dc:creator><pubDate>Thu, 24 Aug 2023 06:00:09 GMT</pubDate><content:encoded><![CDATA[<p>Since all things begin with an introduction, let's have a chat.</p>
<h2 id="heading-what-is-nativephp">What is NativePHP?</h2>
<p>If you've ever needed a way to build desktop apps, you may find that you often need to serve a backend and link it up with an Electron.js app. However, with NativePHP, you can build your functionalities using PHP. In other words, you can build your Electron desktop application using PHP.</p>
<p>As we speak, much work has already been done on the framework, but it is heavily dependent on Laravel, so you need to know some Laravel.</p>
<p>The framework is still in alpha, so don't expect too much at this point.</p>
<h2 id="heading-what-well-learn">What we'll learn</h2>
<p>I'm no pro when it comes to NativePHP. In fact, I'm writing these as I'm learning it too. I neither have prior experience with it nor have I watched any tutorials about it. But it is my goal to power through the entire documentation and note down what I'm learning. I wish to help boost the publicity of this exciting new framework.</p>
<p>Some of the features currently described in the docs include:</p>
<ul>
<li><p>Sending notifications</p>
</li>
<li><p>Configuring hotkeys</p>
</li>
<li><p>Reading from the clipboard</p>
</li>
<li><p>Popping up dialogs</p>
</li>
<li><p>Working with application windows</p>
</li>
</ul>
<p>...and so much more!</p>
<p>I think the possibilities are endless, and the fact that I can build all these in PHP is frankly awesome.</p>
<h2 id="heading-final-words">Final words</h2>
<p>I have a ton of work and this series will be updated in my leisure time. However, you can follow my page for more updates coming soon.</p>
]]></content:encoded></item><item><title><![CDATA[WhatsApp has failed me...again.]]></title><description><![CDATA[Back in my undergrad days, we were taught about the "CIA" of information. These three key attributes of information give us an important way to think about the kind of information we store digitally. In short, all information on computing systems mus...]]></description><link>https://blog.elikem.dev/whatsapp-has-failed-me-again</link><guid isPermaLink="true">https://blog.elikem.dev/whatsapp-has-failed-me-again</guid><category><![CDATA[General Programming]]></category><category><![CDATA[whatsapp]]></category><category><![CDATA[Cloud]]></category><dc:creator><![CDATA[Elikem]]></dc:creator><pubDate>Thu, 03 Aug 2023 22:13:00 GMT</pubDate><content:encoded><![CDATA[<p>Back in my undergrad days, we were taught about the "CIA" of information. These three key attributes of information give us an important way to think about the kind of information we store digitally. In short, all information on computing systems must be Confidential, Integrous (i.e. have integrity), and Available.</p>
<p>But what happens when the biggest social messaging app in your community fails at one of these? Serious consequences arise.</p>
<p>In my case, information failed to be available when I needed it which led to serious data loss. This post is not a post of praise for WhatsApp. It's a post born out of frustration.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1690366205122/448fcd65-170c-4695-a909-8d1d09f72857.jpeg" alt="Photo by Mourizal Zativa on Unsplash   " class="image--center mx-auto" /></p>
<h2 id="heading-the-problem-with-whatsapp">The problem with WhatsApp</h2>
<p>When WhatsApp launched in November 2009, I do not believe they had scalability in mind. Perhaps, they thought they would only have a few users across their country. Things, of course, changed for them when Facebook went ahead to purchase WhatsApp in 2014 for $19 billion. I still remember it like it was yesterday.</p>
<p>I have used WhatsApp for several years and observed its steady growth and rollout of features. Regardless, the cracks in their architecture were showing up from time to time. Many times, they took several months to roll out features that were already present in Telegram. Even when they finally allowed WhatsApp to be used on laptops, it still needed to be in sync with the mobile version and wouldn't work when the phone was offline or off. It took a few more years of development until we finally got to a point where we could use it on multiple devices simultaneously.</p>
<p>However, even with the ability to use WhatsApp on multiple devices, uninstalling WhatsApp on your phone would terminate the connection to the PC apps. So then I ask: what is the problem?</p>
<p>From my analysis, the problem is that WhatsApp, from the start, was designed to store user messages on their mobile devices rather than on the cloud. The only thing being stored in the cloud were probably undelivered messages and files that could only be retained for a couple of weeks.</p>
<p>Sure, I get it. They were losing money before Facebook came in to purchase them. That would mean that they couldn't possibly afford to keep trillions of messages on their servers, especially when the app itself was already free and devoid of advertisement. The sensible thing, therefore, was to let users keep track of those messages and then store backups of the conversation history.</p>
<p>Now this is where things get really dicey. As the years rolled on, I personally began to record database sizes of 1.3 GB upwards.</p>
<blockquote>
<p>If you'd like to see how much space your WhatsApp backups are consuming, on Android, go to Internal Storage&gt;Android&gt;com.whatsapp&gt;databases</p>
</blockquote>
<p>To add to that, WhatsApp keeps backups of up to 7 days. 1.3 GB multiplied by 7? Approximately 8.45GB!</p>
<p>Add the media files, which in my case, were up to 23 GB and we now have 31.45 GB! Squish this into a 64GB phone with several other apps and you can see the problem. The price difference between a 64GB phone and a 128GB phone is not a cup of coffee.</p>
<p>Meanwhile, my Telegram account, which I've actively used since 2015 or 2016 doesn't even hold a single KB in user data.</p>
<p>Remember, I'm here to rant, not to make excuses for them.</p>
<h2 id="heading-what-went-wrong-on-my-phone">What went wrong on my phone?</h2>
<p>Till date I have no idea. I had enough space in my phone.</p>
<p>I was headed to an event on Tuesday, July 18, and as soon as I arrived at the destination, I opened WhatsApp and saw the dreaded <em>popup of death</em>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1690369839015/1b63ffa7-915c-46ea-adb2-93b06ce596a3.jpeg" alt class="image--center mx-auto" /></p>
<p>Having been a victim of WhatsApp data loss in the past, as soon as I saw this message, I knew something dark was about to take place.</p>
<p><strong>Skip restore</strong> is certainly never the solution.</p>
<p><strong>Restore</strong> is the way forward, but if your backup file is corrupted or something else goes wrong, say goodbye to your chats.</p>
<p>I tapped on Restore, hoping that all would go well. Indeed, at first, it seemed like all was going well. My chats were being restored using the previous day's backup. But then suddenly, the phone's screen timed out and went off. When I unlocked the phone, the restoring process was stuck at 36% and wouldn't move an inch.</p>
<p>I waited a while, but since it wouldn't move, I closed the app and reopened it. It did not take me to the ongoing restoring process but took me straight to the chats section. It was at this point that I realized it wasn't all the chats that had been restored and I figured that I would need to restart the restoring process for the missing chats to be restored.</p>
<p>To do this, I chose to uninstall WhatsApp. But my phone asked if I wanted to keep app data. I choose yes.</p>
<p>After I reinstalled WhatsApp, it didn't ask to initialize the user profile from scratch as I expected. And I realized this was because I didn't choose to clear the app data.</p>
<p>So I uninstalled WhatsApp again, but this time accepted that the app data be deleted.</p>
<h2 id="heading-big-mistake">Big mistake!</h2>
<p>Sure enough, the app opened up after reinstallation, and then I went through the process. It sent an OTP to my email and then started looking for Google Drive backups. It was at this point that I realized WhatsApp had not stored a single Google Drive backup since April 21st.</p>
<p>And then I decided that this would be too far back and that I would use my local backups instead. So I deleted WhatsApp again, reinstalled and turned on airplane mode hoping that it would ask to check the local databases this time.</p>
<p>I waited for a long while and it was still not finding any local backups. It was at this time I realized I had lost the backup while reinstalling! In fact, not only did I lose the backups, but I lost all my media as well. I lost the entire com.whatsapp folder. Come on!</p>
<p>Okay, so to be honest, I only later realized that the option for Google Drive backup in my settings was set to "Only when I tap Back up". I had no idea when that option was selected in the first place.</p>
<p>Although some may choose to blame me because of what I just said, I would like to state that a customer or user should never ever have to worry about data storage.</p>
<p><strong>Ever</strong>.</p>
<p>Remember that most users are not even technically minded. I have met older people who even had no idea that they could turn off automatic media downloads. How then would such people be expected to take full responsibility for data retention?</p>
<h2 id="heading-my-recommendation">My recommendation</h2>
<p>Even if the WhatsApp team cannot at this point migrate all messages to the cloud, they should provide some sort of functionality that allows the second party of an E2E encrypted chat to "share" the entire chat history with the victim of data loss, so that it synchronizes the chat history of the affected person independently of a local/cloud backup.</p>
<p>Also, if a user turns off their Google Drive backups, they should be alerted periodically.</p>
]]></content:encoded></item><item><title><![CDATA[ChatGPT couldn't solve this!]]></title><description><![CDATA[Context
So I was recently trying to debug an application. It was an extremely buggy legacy software that kept giving everyone headaches - from the users to the founders. Several developers had been on the project but each left behind huge amounts of ...]]></description><link>https://blog.elikem.dev/chatgpt-couldnt-solve-this</link><guid isPermaLink="true">https://blog.elikem.dev/chatgpt-couldnt-solve-this</guid><category><![CDATA[chatgpt]]></category><category><![CDATA[AI]]></category><category><![CDATA[PHP]]></category><category><![CDATA[MariaDB]]></category><category><![CDATA[MySQL]]></category><dc:creator><![CDATA[Elikem]]></dc:creator><pubDate>Wed, 12 Apr 2023 12:08:04 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-context">Context</h2>
<p>So I was recently trying to debug an application. It was an extremely buggy legacy software that kept giving everyone headaches - from the users to the founders. Several developers had been on the project but each left behind huge amounts of unfinished work - as well as more and more bugs. Some of these bugs were so bad that the reports being generated were full of incorrect data.</p>
<p>One of the features of the application was to generate terminal reports of all the students and assign positions/rankings to each subject they were tested on, in order to determine which child was first place, second place, and so on.</p>
<p>For confidentiality, I will not post the original code.</p>
<p>However, while trying to rewrite the ranking mechanism, I ran into an interesting problem and thought, "Hey, let me leave this to ChatGPT to do".</p>
<p>ChatGPT has proved very helpful to me in my coding journey. I began with CoPilot last year but signed up for ChatGPT this year when it launched. If you've followed my blog for a while, you know that I like to explore AI and also explore deep questions about the technology. In fact, last year, I mused about the possibility of <a target="_blank" href="https://blog.elikem.dev/is-ai-infinite">AI becoming "infinite"</a> in its capabilities.</p>
<p>CoPilot was great, but it kept generating outdated and incorrect code for me. So I did not continue using it. ChatGPT was even better - with the ability to actually explain things in simple language. However, I never really used ChatGPT for coding until very recently. At first, I gave it simpler queries and it seemed to do very well. But today, I gave it a tougher challenge and it ended up wasting hours of my time. I also noticed that it kept lagging and freezing and I had to refresh the page each time that happened.</p>
<h2 id="heading-the-problem">The problem</h2>
<p>Let's jump right into the code.</p>
<p>The problem is this block of code:</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>
    $students = [
    [
        <span class="hljs-string">"STUDENT_ID"</span> =&gt; <span class="hljs-string">"STD003420"</span>,
        <span class="hljs-string">"TOTAL_SCORE"</span> =&gt; <span class="hljs-string">"62.5"</span> 
    ], 
    [
        <span class="hljs-string">"STUDENT_ID"</span> =&gt; <span class="hljs-string">"STD003421"</span>,
        <span class="hljs-string">"TOTAL_SCORE"</span> =&gt; <span class="hljs-string">"60.0"</span> 
    ], 
    [
        <span class="hljs-string">"STUDENT_ID"</span> =&gt; <span class="hljs-string">"STD003066"</span>,
        <span class="hljs-string">"TOTAL_SCORE"</span> =&gt; <span class="hljs-string">"65.0"</span> 
    ], 
    [
        <span class="hljs-string">"STUDENT_ID"</span> =&gt; <span class="hljs-string">"STD003064"</span>,
        <span class="hljs-string">"TOTAL_SCORE"</span> =&gt; <span class="hljs-string">"70.5"</span> 
    ], 
    [
        <span class="hljs-string">"STUDENT_ID"</span> =&gt; <span class="hljs-string">"STD003041"</span>,
        <span class="hljs-string">"TOTAL_SCORE"</span> =&gt; <span class="hljs-string">"69.0"</span> 
    ], 
    [
        <span class="hljs-string">"STUDENT_ID"</span> =&gt; <span class="hljs-string">"STD003062"</span>,
        <span class="hljs-string">"TOTAL_SCORE"</span> =&gt; <span class="hljs-string">"74.0"</span> 
    ], 
    [
        <span class="hljs-string">"STUDENT_ID"</span> =&gt; <span class="hljs-string">"STD003425"</span>,
        <span class="hljs-string">"TOTAL_SCORE"</span> =&gt; <span class="hljs-string">"66.5"</span> 
    ], 
    [
        <span class="hljs-string">"STUDENT_ID"</span> =&gt; <span class="hljs-string">"STD003055"</span>,
        <span class="hljs-string">"TOTAL_SCORE"</span> =&gt; <span class="hljs-string">"72.5"</span> 
    ], 
    [
        <span class="hljs-string">"STUDENT_ID"</span> =&gt; <span class="hljs-string">"STD003069"</span>,
        <span class="hljs-string">"TOTAL_SCORE"</span> =&gt; <span class="hljs-string">"74.5"</span> 
    ], 
    [
        <span class="hljs-string">"STUDENT_ID"</span> =&gt; <span class="hljs-string">"STD003068"</span>,
        <span class="hljs-string">"TOTAL_SCORE"</span> =&gt; <span class="hljs-string">"63.0"</span> 
    ], 
    [
        <span class="hljs-string">"STUDENT_ID"</span> =&gt; <span class="hljs-string">"STD003045"</span>,
        <span class="hljs-string">"TOTAL_SCORE"</span> =&gt; <span class="hljs-string">"63.0"</span> 
    ], 
    [
        <span class="hljs-string">"STUDENT_ID"</span> =&gt; <span class="hljs-string">"STD003115"</span>,
        <span class="hljs-string">"TOTAL_SCORE"</span> =&gt; <span class="hljs-string">"57.0"</span> 
    ], 
    [
        <span class="hljs-string">"STUDENT_ID"</span> =&gt; <span class="hljs-string">"STD003427"</span>,
        <span class="hljs-string">"TOTAL_SCORE"</span> =&gt; <span class="hljs-string">"69.5"</span> 
    ], 
    [
        <span class="hljs-string">"STUDENT_ID"</span> =&gt; <span class="hljs-string">"STD003002"</span>,
        <span class="hljs-string">"TOTAL_SCORE"</span> =&gt; <span class="hljs-string">"72.5"</span> 
    ], 
    [
        <span class="hljs-string">"STUDENT_ID"</span> =&gt; <span class="hljs-string">"STD003536"</span>,
        <span class="hljs-string">"TOTAL_SCORE"</span> =&gt; <span class="hljs-string">"60.5"</span> 
    ], 
    [
        <span class="hljs-string">"STUDENT_ID"</span> =&gt; <span class="hljs-string">"STD003122"</span>,
        <span class="hljs-string">"TOTAL_SCORE"</span> =&gt; <span class="hljs-string">"87.0"</span> 
    ], 
    [
        <span class="hljs-string">"STUDENT_ID"</span> =&gt; <span class="hljs-string">"STD003534"</span>,
        <span class="hljs-string">"TOTAL_SCORE"</span> =&gt; <span class="hljs-string">"79.0"</span> 
    ], 
    [
        <span class="hljs-string">"STUDENT_ID"</span> =&gt; <span class="hljs-string">"STD003049"</span>,
        <span class="hljs-string">"TOTAL_SCORE"</span> =&gt; <span class="hljs-string">"77.0"</span> 
    ], 
    [
        <span class="hljs-string">"STUDENT_ID"</span> =&gt; <span class="hljs-string">"STD003121"</span>,
        <span class="hljs-string">"TOTAL_SCORE"</span> =&gt; <span class="hljs-string">"66.0"</span> 
    ], 
    [
        <span class="hljs-string">"STUDENT_ID"</span> =&gt; <span class="hljs-string">"STD003047"</span>,
        <span class="hljs-string">"TOTAL_SCORE"</span> =&gt; <span class="hljs-string">"85.0"</span> 
    ], 
    [
        <span class="hljs-string">"STUDENT_ID"</span> =&gt; <span class="hljs-string">"STD003043"</span>,
        <span class="hljs-string">"TOTAL_SCORE"</span> =&gt; <span class="hljs-string">"86.5"</span> 
    ], 
    [
        <span class="hljs-string">"STUDENT_ID"</span> =&gt; <span class="hljs-string">"STD003185"</span>,
        <span class="hljs-string">"TOTAL_SCORE"</span> =&gt; <span class="hljs-string">"70.5"</span> 
    ], 
    [
        <span class="hljs-string">"STUDENT_ID"</span> =&gt; <span class="hljs-string">"STD003001"</span>,
        <span class="hljs-string">"TOTAL_SCORE"</span> =&gt; <span class="hljs-string">"82.5"</span> 
    ], 
    [
        <span class="hljs-string">"STUDENT_ID"</span> =&gt; <span class="hljs-string">"STD003003"</span>,
        <span class="hljs-string">"TOTAL_SCORE"</span> =&gt; <span class="hljs-string">"86.5"</span> 
    ], 
    [
        <span class="hljs-string">"STUDENT_ID"</span> =&gt; <span class="hljs-string">"STD003538"</span>,
        <span class="hljs-string">"TOTAL_SCORE"</span> =&gt; <span class="hljs-string">"70.0"</span> 
    ], 
    [
        <span class="hljs-string">"STUDENT_ID"</span> =&gt; <span class="hljs-string">"STD003537"</span>,
        <span class="hljs-string">"TOTAL_SCORE"</span> =&gt; <span class="hljs-string">"70.0"</span> 
    ], 
    [
        <span class="hljs-string">"STUDENT_ID"</span> =&gt; <span class="hljs-string">"STD003535"</span>,
        <span class="hljs-string">"TOTAL_SCORE"</span> =&gt; <span class="hljs-string">"80.0"</span> 
    ], 
    [
        <span class="hljs-string">"STUDENT_ID"</span> =&gt; <span class="hljs-string">"STD003053"</span>,
        <span class="hljs-string">"TOTAL_SCORE"</span> =&gt; <span class="hljs-string">"73.5"</span> 
    ], 
    [
        <span class="hljs-string">"STUDENT_ID"</span> =&gt; <span class="hljs-string">"STD003120"</span>,
        <span class="hljs-string">"TOTAL_SCORE"</span> =&gt; <span class="hljs-string">"77.0"</span> 
    ], 
    [
        <span class="hljs-string">"STUDENT_ID"</span> =&gt; <span class="hljs-string">"STD003059"</span>,
        <span class="hljs-string">"TOTAL_SCORE"</span> =&gt; <span class="hljs-string">"70.0"</span> 
    ], 
    [
        <span class="hljs-string">"STUDENT_ID"</span> =&gt; <span class="hljs-string">"STD003426"</span>,
        <span class="hljs-string">"TOTAL_SCORE"</span> =&gt; <span class="hljs-string">"61.0"</span> 
    ], 
    [
        <span class="hljs-string">"STUDENT_ID"</span> =&gt; <span class="hljs-string">"STD003424"</span>,
        <span class="hljs-string">"TOTAL_SCORE"</span> =&gt; <span class="hljs-string">"60.0"</span> 
    ], 
    [
        <span class="hljs-string">"STUDENT_ID"</span> =&gt; <span class="hljs-string">"STD003422"</span>,
        <span class="hljs-string">"TOTAL_SCORE"</span> =&gt; <span class="hljs-string">"50.0"</span> 
    ], 
    [
        <span class="hljs-string">"STUDENT_ID"</span> =&gt; <span class="hljs-string">"STD003118"</span>,
        <span class="hljs-string">"TOTAL_SCORE"</span> =&gt; <span class="hljs-string">"68.0"</span> 
    ]
  ];

  usort($students, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">$a, $b</span>) </span>{
      <span class="hljs-keyword">return</span> $b[<span class="hljs-string">'TOTAL SCORE'</span>] - $a[<span class="hljs-string">'TOTAL SCORE'</span>];
  });

  var_dump($students);
<span class="hljs-meta">?&gt;</span>
</code></pre>
<p>I wished to sort this class of 34 students based on their total scores, in descending order. The easiest way, of course, was to pass a callback to PHP's <code>usort()</code> method and sit back.</p>
<p>When I ran it, however, it seemed to have trouble getting the first value correct. I already know <a target="_blank" href="https://www.php.net/manual/en/function.usort.php">how <code>usort()</code> works under the hood</a>, so that's not the issue. And you can see that my callback is correct. But instead of returning <code>87.0</code> as the correct answer, it returned <code>86.5</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681299303643/4d5bee82-194e-46dd-9f7c-32234166ef9e.png" alt class="image--center mx-auto" /></p>
<p>This was puzzling, because <code>86.5</code> can never be greater than <code>87.0</code>.</p>
<p>So I decided to ask our new best friend - ChatGPT.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681299690304/1ad7ca3e-4e05-45fc-b705-3b44af2320c3.png" alt class="image--center mx-auto" /></p>
<p><em>Skipping to the response...</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681299716693/e8dd5dd6-e0c0-4326-ab3c-69e722ecc27b.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681299732296/ace8d840-e3c4-49e1-ab60-9af11d61cf1f.png" alt class="image--center mx-auto" /></p>
<p>Alright, so I decided to copy the new code and use it.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681299808696/6229a0b6-d1ef-46d3-983f-dab12c8c61e1.png" alt class="image--center mx-auto" /></p>
<p>As I expected, it did not work. So I went back to ChatGPT to complain about it, and even pasted the new code in there.</p>
<p>And just like that, it left me hanging:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681299965789/01caa44f-e6cb-46dc-b036-f4ddac72790f.png" alt class="image--center mx-auto" /></p>
<p>But I still needed an answer and so I regenerated the response:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681300062386/b44661ca-998c-412d-97d3-e19befaf5a7c.png" alt class="image--center mx-auto" /></p>
<p>But interestingly, the answer it got was also not correct. Here's a snippet of it:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681300300415/16492edb-97f3-4a3d-b1db-67c4d4ee7219.png" alt class="image--center mx-auto" /></p>
<p>Looool... how on earth is 85.0 greater than 86.5?</p>
<p>Someone may say, "but why are you comparing strings?" Well, it turns out that if you compare these same strings "85.0" and "86.5" in PHP, you get the right answer.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681300494978/9c8db270-9f84-4fd2-93f5-5bd1957d2702.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681300545807/06e60266-ba22-4199-a37b-1d384d706e76.png" alt class="image--center mx-auto" /></p>
<p>So it's not about the usage of strings at all.</p>
<p>I then complained once more and this time, ChatGPT was just so overwhelmed that it gave up midway. (And even got the programming language wrong.)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681300810616/42dee272-071d-45c2-a65e-9d60323f9a1a.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681300862889/c9aa782f-5638-49f0-a630-63b59c2bc93e.png" alt class="image--center mx-auto" /></p>
<p><em>To anyone who may be confused, the reason why the sorting didn't work is that the</em> <code>usort()</code> <em>method, after performing the subtraction, converts the result to an integer. And if the difference is something as small as -0.5, it assumes the result is 0, and so it sees both 87 and 86.5 as being equivalent. The solution, of course, is to use a different sorting method. I expected ChatGPT to notice this and warn me, but it failed to do so.</em></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Is AI going to replace programmers? Not anytime soon.🥱</p>
]]></content:encoded></item><item><title><![CDATA[How to upload real files in Laravel Tests]]></title><description><![CDATA[Introduction
When building an application in Laravel, it is often the case that files would have to be uploaded.
Running an endpoint in Postman or designing a view in order to test that the file is being processed properly can be a cumbersome task. T...]]></description><link>https://blog.elikem.dev/how-to-upload-real-files-in-laravel-tests</link><guid isPermaLink="true">https://blog.elikem.dev/how-to-upload-real-files-in-laravel-tests</guid><category><![CDATA[Laravel]]></category><category><![CDATA[PHPUnit]]></category><category><![CDATA[PHP]]></category><category><![CDATA[Testing]]></category><category><![CDATA[unit testing]]></category><dc:creator><![CDATA[Elikem]]></dc:creator><pubDate>Sat, 18 Mar 2023 14:19:11 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>When building an application in Laravel, it is often the case that files would have to be uploaded.</p>
<p>Running an endpoint in Postman or designing a view in order to test that the file is being processed properly can be a cumbersome task. This guide will teach you how to upload real files in Laravel tests, instead of using fakes, so that we can actually read the content of the file and be sure that it's processing properly.</p>
<p>Using a fake file in your test is convenient when you only need to assert that a file has been uploaded and/or saved. However, it is impractical when the content of the given file matters to you.</p>
<p>In this guide, we will upload an Excel spreadsheet so that we can use and test the rows and columns contained therein.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>These are important prerequisite knowledge needed for this tutorial:</p>
<ul>
<li><p>Familiarity with Laravel 5.5+</p>
</li>
<li><p>Laravel testing with PHPUnit. The latest versions of PHPUnit, such as version 9 or 10, should be sufficient.</p>
</li>
</ul>
<h2 id="heading-overview">Overview</h2>
<p>In this article, we'll work on a feature that extracts data from a row in a spreadsheet and persists it in the database.</p>
<p>For the sake of this tutorial, we'll deal with spreadsheets in a common format such as XLSX or CSV. Also, we are going to use the <a target="_blank" href="https://github.com/SpartnerNL/Laravel-Excel">Spartner Laravel Excel package (formerly Maatwebsite/Excel)</a>.</p>
<p>Then we'll write the tests and controllers needed. The application we'll be working on is one that takes financial data from a spreadsheet and processes it to create metrics.</p>
<h2 id="heading-installation">Installation</h2>
<p>To install the package, run <code>composer require maatwebsite/excel</code> in a terminal.</p>
<p>No further configuration is needed, but you can still explore the options available. Just run <code>php artisan vendor:publish --provider="Maatwebsite\Excel\ExcelServiceProvider" --tag=config</code> to publish the configuration file, which contains a long list of options.</p>
<h2 id="heading-creating-spreadsheet">Creating spreadsheet</h2>
<p>Next, we need to create a spreadsheet. You may use any software of your choice but I have already created a spreadsheet in Google Sheets with just a heading row and one single row of data.</p>
<p>Your spreadsheet could look like this:</p>
<table><tbody><tr><td><p><strong>Year</strong></p></td><td><p><strong>Month</strong></p></td><td><p><strong>Total issued shares</strong></p></td><td><p><strong>Dividends Paid</strong></p></td><td><p><strong>Weighted Average Cost of Capital</strong></p></td><td><p><strong>Capital expenditure (additions)</strong></p></td><td><p><strong>Depreciation</strong></p></td><td><p><strong>Accumulated depreciation</strong></p></td><td><p><strong>Historical cost of assets</strong></p></td><td><p><strong>Cash flow from operations</strong></p></td></tr><tr><td><p>2022</p></td><td><p>07</p></td><td><p>1000000</p></td><td><p>0</p></td><td><p>18%</p></td><td><p>2</p></td><td><p>4</p></td><td><p>6</p></td><td><p>9</p></td><td><p>590</p></td></tr></tbody></table>

<h2 id="heading-testing">Testing</h2>
<h3 id="heading-where-should-we-save-the-spreadsheet">Where should we save the spreadsheet?</h3>
<p>Let's begin by creating a folder in our Tests directory. It may be a good idea to save the spreadsheet in a folder called "Assets" within your "tests" directory.</p>
<p>My directory looks like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1679146308925/f37f25c5-261d-473e-b30e-07ae115b5142.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-basic-structure-of-test-class">Basic structure of test class</h3>
<p>Next, let's create the test class. You can create it in the <code>tests/Feature</code> namespace.</p>
<p>When it comes to creating test classes, I often like to do it without explicitly naming the classes. I've explained my reasons <a target="_blank" href="https://blog.elikem.dev/a-helpful-tip-for-your-laravel-tests">in a previous article</a>. If you want to use that approach, though, just be aware that it'll affect the way you use the <code>php artisan test --filter</code> command. For example, you will no longer be able to selectively run only tests in that class by passing the name of the class to the filter argument. However, you will still be able to call individual classes one by one.</p>
<p>That being said, let's create an anonymous class like this:</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">Tests</span>\<span class="hljs-title">Feature</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">Tests</span>\<span class="hljs-title">TestCase</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Foundation</span>\<span class="hljs-title">Testing</span>\<span class="hljs-title">LazilyRefreshDatabase</span>;

<span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">TestCase</span>
</span>{
    <span class="hljs-keyword">use</span> <span class="hljs-title">LazilyRefreshDatabase</span>;

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">test_that_excel_details_are_saved</span>(<span class="hljs-params"></span>)
    </span>{
        <span class="hljs-comment">//</span>
    }
};
</code></pre>
<p>We'll return to this class in a moment, but we need to examine the work so far.</p>
<p>First, we have set up the file as an anonymous PHP class by just returning a new class.</p>
<p>Our new class then extends the default TestCase class, which is responsible for bootstrapping the application, and setting up and tearing down tests.</p>
<p>Next, we have imported the <code>LazilyRefreshDatabase</code> trait. This trait cleans out your database in between tests only if the next test to be run actually makes use of the database. This is an improvement over the <code>RefreshDatabase</code> trait in older versions of Laravel. However, you are free to use the latter if you wish.</p>
<p>After using the trait, we have defined the test function and called it <code>test_that_excel_is_processed</code>. Prepending <code>test</code> to the function's name will cause PHPUnit to recognize it as a valid test and run it.</p>
<p>If we need to run this test, all we need to do is to pass its name to the filter flag:</p>
<p><code>php artisan test --filter test_that_excel_is_processed</code></p>
<p>Let us move on to add a bit more flesh to the class. You can skip this and move to the next example, but we need to do it this way in order to make things easier to understand:</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">Tests</span>\<span class="hljs-title">Feature</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Imports</span>\<span class="hljs-title">FinancialNoteImport</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">UploadedFile</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Tests</span>\<span class="hljs-title">TestCase</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Foundation</span>\<span class="hljs-title">Testing</span>\<span class="hljs-title">LazilyRefreshDatabase</span>;

<span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">TestCase</span>
</span>{
    <span class="hljs-keyword">use</span> <span class="hljs-title">LazilyRefreshDatabase</span>;

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">test_that_excel_details_are_saved</span>(<span class="hljs-params"></span>)
    </span>{
        <span class="hljs-comment">// We will remove this. Don't worry about it.</span>
        <span class="hljs-keyword">$this</span>-&gt;markTestIncomplete();
    }
};
</code></pre>
<p>At this stage, we still haven't written any actual testing code. However, we just added two new imports:</p>
<ul>
<li><p>The <code>FinancialNoteImport</code> will point to an <code>Import</code> class that will handle the processing of the document. Read about Imports <a target="_blank" href="https://docs.laravel-excel.com/3.1/imports/">here</a>.</p>
</li>
<li><p>The framework's <code>UploadedFile</code> import helps us to fake files while testing. We will populate this with the content of our actual file.</p>
</li>
</ul>
<p>We have also marked the test as incomplete so that it will not be run when we run all other tests.</p>
<h3 id="heading-using-uploadedfile">Using UploadedFile</h3>
<p>Let's talk a bit more about why we need to use the <code>Illuminate\Http\UploadedFile</code> class and its limitations.</p>
<p>Basically, the whole point of this test - or what we aim to achieve - is to upload a file as though we were a user and then test that the exact content of the file is what is being saved in the database.</p>
<p>Sounds easy, but the question is <em>how do we do this</em>?</p>
<p>If we navigate to the Laravel documentation section on Testing File Uploads, we see a lot of examples about faking an UploadedFile instance. In those examples, we also see that we are encouraged to fake the Storage Facade as well with <code>Storage::fake()</code>. What this means is that we can pretend to create a file of any extension and then proceed to perform a bunch of assertions - like testing that the file has been uploaded and exists in storage, etc., etc., etc.</p>
<p>But the documentation really doesn't help if we need to make use of the <em>contents</em> of the file. What we want is a way to use an existing file. <strong>If you try to just get a real file from your storage disk and use it anyway, the test class will never be able to access the file.</strong></p>
<p>I once did this in a project and had to scrap it, because I couldn't figure out why it never worked. The Assets directory contained three files. I split up a file in 3 parts and named them <code>chunk1</code>, <code>chunk2</code>, and <code>chunk3</code>. Here, I was trying to iterate through the Assets directory and upload each chunk to an endpoint that was responsible for taking these chunks one by one and merging them afterward:</p>
<pre><code class="lang-php">$chunks = [<span class="hljs-string">'chunk1'</span>, <span class="hljs-string">'chunk2'</span>, <span class="hljs-string">'chunk3'</span>];

<span class="hljs-comment">// Loop sending of three chunks in the Assets/ directory</span>
<span class="hljs-keyword">for</span> ($i = <span class="hljs-number">0</span>; $i &lt; <span class="hljs-number">3</span>; $i++) {
    $chunk = file_get_contents(base_path(<span class="hljs-string">'tests/Assets'</span>).<span class="hljs-string">'/'</span>.$chunks[$i]);

    <span class="hljs-keyword">$this</span>-&gt;actingAs(<span class="hljs-keyword">$this</span>-&gt;user)
        -&gt;patchJson(route(<span class="hljs-string">'api.media.upload-chunk'</span>));
}
</code></pre>
<p>That was a long time ago and I never needed to do it again... until recently.</p>
<p>This time, however, I decided to take the bull by the horns and dig deep into the Laravel source code for answers.</p>
<p>Fortunately, I found something. This is from the <code>Illuminate\Http\Testing\FileFactory</code> class:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693064785851/0878c70e-fb65-4360-873c-7c7715bbbe08.png" alt class="image--center mx-auto" /></p>
<p>Observe that the <code>createWithContent()</code> method appears nowhere in the official documentation:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693064423450/7b702dad-0423-497c-b7e5-d61a8a190774.png" alt class="image--center mx-auto" /></p>
<p>Finding it in the source code was a big relief. This made everything easy for me.</p>
<p>As a rule of thumb, I recommend digging through source code as a last resort if you need to do something that hasn't been documented yet. Sometimes, creators of frameworks write a lot of methods without documenting them. So dig first, and if you don't find anything, come up with your own solution.</p>
<p>Who knows? You might even be able to contribute your solution to the original repo!</p>
<p>Anyway, finding this method was significant as it meant I only needed to use it in my test and I'd be good to go. Therefore. I only had to modify my test class.</p>
<p>Since Laravel tests only recognize fake uploaded files, this is the way to go if you really need to use your own data within the files:</p>
<pre><code class="lang-php"><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">test_that_excel_is_processed</span>(<span class="hljs-params"></span>)
</span>{
    $fileContent = file_get_contents(base_path(<span class="hljs-string">'tests/Assets/My Spreadsheet.xlsx'</span>));

    <span class="hljs-comment">// We can either use the same name or another. Doesn't matter.</span>
    <span class="hljs-comment">// I chose to use "sample"</span>
    $file = UploadedFile::fake()-&gt;createWithContent(<span class="hljs-string">'sample.xlsx'</span>, $fileContent);

    <span class="hljs-comment">// This will process and save the contents of the file.</span>
    <span class="hljs-comment">// we don't need to go into detail here.</span>
    (<span class="hljs-keyword">new</span> FinancialNoteImport)-&gt;import($file);

    <span class="hljs-keyword">$this</span>-&gt;assertDatabaseCount(<span class="hljs-string">'financials'</span>, <span class="hljs-number">1</span>);
    <span class="hljs-keyword">$this</span>-&gt;assertDatabaseHas(<span class="hljs-string">'financials'</span>, [
        <span class="hljs-string">'total_issued_shares'</span> =&gt; <span class="hljs-number">1000000</span>,
        <span class="hljs-string">'dividends_paid'</span> =&gt; <span class="hljs-number">0</span>,
        <span class="hljs-string">'weighted_average_cost_of_capital'</span> =&gt; <span class="hljs-number">0.18</span>,
        <span class="hljs-string">'capital_expenditure'</span> =&gt; <span class="hljs-number">2</span>,
        <span class="hljs-string">'depreciation'</span> =&gt; <span class="hljs-number">4</span>,
        <span class="hljs-string">'accumulated_depreciation'</span> =&gt; <span class="hljs-number">6</span>,
        <span class="hljs-string">'historical_cost_of_assets'</span> =&gt; <span class="hljs-number">9</span>,
        <span class="hljs-string">'cash_flow_from_operations'</span> =&gt; <span class="hljs-number">590</span>,
    ]);
}
</code></pre>
<p>In this example, we simply retrieve the content of our spreadsheet into memory. (Make sure the file is not too large)</p>
<p>Next, we create a faked UploadedFile instance which we pre-populate with the content of the spreadsheet.</p>
<p>Then we go ahead to run the import using our custom <code>Import</code> class.</p>
<p>Lastly, we run two assertions: the first to ensure that the database contains only one record; the latter to check the values. We use the same values here that we configured in the spreadsheet.</p>
<p>Now we can run the aforementioned command: <code>php artisan test --filter test_that_excel_is_processed</code>.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Our test passes with flying colours!🎉🎉🎉</p>
<p>If we'd like to be extra sure that this is working, we can add <code>$this-&gt;assertDatabaseCount('financials', 0);</code> at the beginning of the test function to ensure that the database table is truly empty before running the test.</p>
<p>In this article, we have explored how to upload real files into Laravel tests. We only needed to find the right method for the operation, manually store a predefined file, load the file during the test, and check the database to make sure the file's contents were truly captured.</p>
<p>Also note that aside from manually digging through the source code to find the right method, Laravel has its entire <a target="_blank" href="https://laravel.com/api/master/">API documentation</a> available online. It requires a bit of digging as well but may be preferable to using the source code.</p>
<p>If there are any questions, don't hesitate to type them out in the comment section.</p>
]]></content:encoded></item><item><title><![CDATA[You may not need Putty anymore if you try this...]]></title><description><![CDATA[If you're familiar with managing Virtual machine instances and servers on GCP, AWS, DigitalOcean and others, you most likely already know about SSH.
Usually, you'd create an SSH key pair, save the private key on your machine and store the public key ...]]></description><link>https://blog.elikem.dev/you-may-not-need-putty-anymore</link><guid isPermaLink="true">https://blog.elikem.dev/you-may-not-need-putty-anymore</guid><category><![CDATA[General Programming]]></category><category><![CDATA[ssh]]></category><category><![CDATA[ssh-keys]]></category><category><![CDATA[DigitalOcean]]></category><category><![CDATA[Windows]]></category><dc:creator><![CDATA[Elikem]]></dc:creator><pubDate>Sat, 24 Dec 2022 01:15:09 GMT</pubDate><content:encoded><![CDATA[<p>If you're familiar with managing Virtual machine instances and servers on GCP, AWS, DigitalOcean and others, you most likely already know about SSH.</p>
<p>Usually, you'd create an SSH key pair, save the private key on your machine and store the public key in the authorized_keys file of your Linux server. Having these keys make it easy for you to be authenticated without a password when you try to access your server remotely.</p>
<p>For those who use Linux and Mac, you usually have the SSH utility already installed. But when you're in the Windows ecosystem, a lot of things aren't handed to you on a silver platter.</p>
<p>Most tutorials would teach you to install an app called Putty and use it when you need to connect to your server but behold... I teach you a new way today.</p>
<h3 id="heading-tools-needed">Tools needed</h3>
<ol>
<li><p>First, you need a Windows machine. Connected to the internet.</p>
</li>
<li><p>Next, you need the <code>ssh</code> utility. So go ahead and open command prompt and type <code>ssh</code></p>
<p> If you have it already, you should get something like this:</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1671840326200/7a9bb3ba-5915-48ab-8955-1b5dcfac1412.png" alt class="image--center mx-auto" /></p>
<p> If you do not see that, try to <a target="_blank" href="https://www.howtogeek.com/336775/how-to-enable-and-use-windows-10s-built-in-ssh-commands/">install or enable SSH.</a></p>
</li>
</ol>
<h3 id="heading-create-a-custom-command">Create a custom command</h3>
<p>Now make sure you have your private key to the server. If you already had Putty, then it means you already had a private key stored somewhere on your computer. You just need to locate it. Make sure to use the original private key, not Putty's .ppk format.</p>
<p>Now, before I proceed, I wish to give credit to <a target="_blank" href="https://stackoverflow.com/a/65823225/8722948">this StackOverflow answer</a> for guiding me in the right direction.</p>
<p>Let's proceed. Please note that you can do the following through Windows Explorer if you wish.</p>
<ol>
<li><p>Open cmd.exe (Command Prompt)</p>
</li>
<li><p>Navigate to your user directory. For example, <code>cd C:\Users\john</code>. In my case, the username is "user", but I will stick with "john" for this post in order not to confuse anyone.</p>
</li>
<li><p>Run: <code>mkdir commands</code></p>
</li>
<li><p>Run: <code>cd commands</code></p>
</li>
<li><p>Run the following one after the other to create and populate a file where we'd house our commands. You can name it anything you want, but the extension must be cmd:</p>
<ol>
<li><p>echo @echo off &gt;&gt; init.cmd</p>
</li>
<li><p>echo doskey open-my-server=ssh -i "C:\Users\john\.ssh\id_secret_key" root@xxx.xxx.xxx.xxx &gt;&gt; init.cmd</p>
<p> <em>make sure to replace xxx.xxx.xxx.xxx with your server's IP address. Also, the -i flag points to our identity file (private key).</em></p>
</li>
</ol>
</li>
<li><p>Verify the content of the new file with <code>TYPE init.cmd</code> :</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1671842113050/2d0d1e3f-0fdc-49cc-83c1-498710e43ce5.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Make your script automatically available to Command Prompt by adding the path to your file in the registry: <code>reg add "HKCU\Software\Microsoft\Command Processor" /v AutoRun /t REG_EXPAND_SZ /d "%"USERPROFILE"%\commands\init.cmd" /f</code></p>
</li>
<li><p>Restart command prompt</p>
</li>
<li><p>Now you can run <code>open-my-server</code> and it should just connect in the terminal beautifully.</p>
</li>
</ol>
<h3 id="heading-conclusion">Conclusion</h3>
<p>So that's it! I hope you enjoyed this. Your comments, questions and feedback are most welcome!</p>
<p>Merry Christmas!</p>
]]></content:encoded></item><item><title><![CDATA[Is AI infinite?]]></title><description><![CDATA[TLDR: The answer is no.
Before you read, I want to state emphatically that I am not an AI researcher. I'm not a professor of any sorts...
A few moments ago, I saw a notification on Notion - new AI capabilities in the famous note-taking application:

...]]></description><link>https://blog.elikem.dev/is-ai-infinite</link><guid isPermaLink="true">https://blog.elikem.dev/is-ai-infinite</guid><category><![CDATA[Artificial Intelligence]]></category><category><![CDATA[AI]]></category><category><![CDATA[notion]]></category><category><![CDATA[marketing]]></category><category><![CDATA[sales]]></category><dc:creator><![CDATA[Elikem]]></dc:creator><pubDate>Thu, 15 Dec 2022 00:05:22 GMT</pubDate><content:encoded><![CDATA[<p><em>TLDR: The answer is no.</em></p>
<p>Before you read, I want to state emphatically that I am not an AI researcher. I'm not a professor of any sorts...</p>
<p>A few moments ago, I saw a notification on Notion - <a target="_blank" href="https://www.notion.so/product/ai?autoplay=1">new AI capabilities</a> in the famous note-taking application:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1671057122788/Vb7nERPP-.png" alt class="image--center mx-auto" /></p>
<p>According to this headline, you can now suggest to Notion what you'd like to see, and it will be generated for you. Computers have now inched closer to the "let there be <em>this</em> or <em>that</em>" point. Basically, you can now tell a computer what you want to see, and it will be done for you.</p>
<p>This isn't exactly new, of course. Many of you have already experimented with ChatGPT at this point. It can solve homework, act as a terminal, tell jokes, write cover letters, and even defend wrong answers with solid arguments.</p>
<p>So you already know that we are at such a point in time as has never been in the history of mankind.</p>
<p>However, with computers gaining such strong abilities even with our currently limited classical computing power, one can only ponder on that headline, and begin to think about it seriously. <em>Is AI infinite?</em></p>
<p>Well, for starters, let's look at our own biological makeup as humans. We aren't infinite. Not even close.</p>
<p>What does it mean to be finite? Oxford dictionary tells us it is to be "limited in size or extent". This is not only talking about physical limitations in our growth or size but also about our mental capabilities. We forget. We make silly mistakes. We fall sick. We die.</p>
<p>But are we really finite? This would need to be considered across two main areas: physiologically and psychologically.</p>
<p>Physiologically, we are plagued with a billion diseases. We cannot lift a car with a single finger. We are severely limited. We cannot breathe underwater. We cannot fly. We cannot go to space without special suits. We cannot survive extreme temperatures. The list is endless, really.</p>
<p>Psychologically, we cannot handle heavy mental processing. We cannot think about two things at once. The multitaskers among you are definitely rolling their eyes right now, but it's true. At each given split-second, you are focused on only one thing at a time. You may be able to switch between topics so fast that you experience the illusion of multitasking, but in reality, I do not believe it is possible. As you age, it gets even worse. You forget several things. You take more and more time to figure out solutions to problems. You begin to wonder where the brain that could once understand complex algebra has hidden itself. Need I say more? The truth is right before us.</p>
<p>Now that we have established the fact that we aren't infinite, let us ask: can finite beings create infinite beings?</p>
<p>The short answer is no. A big and resounding NO.</p>
<h3 id="heading-can-any-machine-be-infinite">Can any machine be infinite?</h3>
<p>The reason we have AI in the first place is that we realized there were many things we couldn't trust our <strong>minds</strong> to do. I must emphasize that we are talking about our minds here. We needed something that could think faster, learn faster, and know so much more than we could ever keep in our individual brains. This is the same reason we began to build mechanical machines. It's the same pattern. Are machines infinite? Not a chance.</p>
<p>Machines are more efficient. That's true. But they are not infinite. They themselves require maintenance, frequent oiling here and there, occasional replacement of parts and the like. Therefore, man is still greater than machine.</p>
<p>But what about AI? It doesn't necessarily need maintenance. It could be trained to train itself. It can even be trained to train and create other AI. That's something mechanical machines cannot do. AI is an attempt to create a machine that can reason like a human being. But is it infinite?</p>
<p>Let's look at a single case. We have seen reports of ChatGPT giving incorrect answers. It's so bad in fact, that StackOverflow quickly jumped up to ban answers given by it. Let me quote a section of the <a target="_blank" href="https://meta.stackoverflow.com/questions/421831/temporary-policy-chatgpt-is-banned">official notice given by StackOverflow</a>:</p>
<blockquote>
<p>Overall, because the average rate of getting <em>correct</em> answers from ChatGPT is too low, the posting of answers created by ChatGPT is <em>substantially harmful</em> to the site and to users who are asking or looking for <em>correct</em> answers.</p>
<p>The primary problem is that while the answers which ChatGPT produces have a high rate of being incorrect, they typically <em>look like</em> they <em>might</em> be good and the answers are <em>very</em> easy to produce.</p>
</blockquote>
<p>And this is not only a ChatGPT problem. If any of you tried out Github's Copilot, as I did, then you know that out of the 10 auto-generated code blocks, most were incorrect. This was due to various reasons, but I personally observed that sometimes, the answers were given based on an older version of the language or framework, with slightly different syntax. It's basically GIGO. Garbage in, garbage out. If we fed it with pure garbage code, it would generate pure garbage code, because it had no way of autonomously checking the code for correctness.</p>
<p>This is the fundamental issue! AI cannot be said to be infinite if it gives 99% correct answers and still has 1% false answers. In order to be infinite, it must prove itself to be the ultimate source of truth. You should be able to ask it a deliberately extreme question like "what did the president tell his wife last afternoon", and it would search the database and tell you exactly what it was. It should be able to predict, with super accuracy, the outcome of a soccer match.</p>
<p>For it to be <strong>infinite</strong>, it MUST KNOW EVERYTHING.</p>
<p>If it does not know EVERYTHING, then it is obviously <strong>limited in knowledge</strong>. This is common logic. This is common sense.</p>
<blockquote>
<p><strong>For deep thinkers only:</strong></p>
<p>To be infinite is to be "limitless or endless in space, extent, or size". That includes mental capability. It must not be in doubt about any subject matter. It must not be ignorant about any detail. If you begin to think of the universe as a set of mathematical and physical laws, then it stands to reason that if we destroy this universe and recreate it with the exact same laws, we would arrive at the same conclusion every single time, because 1 + 1 can never be anything other than 2. Therefore, your AI, if it is truly infinite, should be able to understand the laws of the universe with such precision that it knows everything. I may have lost 80% of you here, but all I'm saying is that your AI needs to have a mental capacity that is out of this world, and that is able to encompass all the knowledge ever known to this universe.</p>
</blockquote>
<p>Remember that we are dealing with the mental faculty here. We cannot judge it on the basis of strength, health, wealth, and so on. We can only judge based on mental indicators: speed of thought, accuracy of information, reliability, conscience, ability to predict future occurrences, and so on. AI was created to assist the human mind. However, to call it infinite in your marketing is too strong of a claim. The technology still has a long way to go.</p>
<h3 id="heading-will-it-ever-be-infinite">Will it ever be infinite?</h3>
<p>As far as I am concerned, AI will never be infinite. Why is this so? Very simply put, AI relies on existing human knowledge in order to be useful. Even if you train it with 100% correct data, you will still require it to learn from its interaction with users. And from there, you and I can clearly see how flaws will be introduced. If the beings providing the input are not 100% correct all the time, there is no way on this planet that the machine learning from these beings will be 100% correct.</p>
<h3 id="heading-the-conclusion">The conclusion</h3>
<p>Now that you've read this, you can rest easy, knowing that while it may take some of your jobs, just as mechanical and electronic machines took away many manual jobs, it will never end up becoming your ruler.</p>
<p>Thank you for reading, and I'd love to hear your comments.</p>
]]></content:encoded></item><item><title><![CDATA[A Helpful Tip for your Laravel tests]]></title><description><![CDATA[Hey there.
If you're already using Laravel, I just want to share a very quick tip you can use to structure your test classes.
If you're a little clumsy with your nomenclature, when writing tests, you will often run into a stern warning from Composer:...]]></description><link>https://blog.elikem.dev/a-helpful-tip-for-your-laravel-tests</link><guid isPermaLink="true">https://blog.elikem.dev/a-helpful-tip-for-your-laravel-tests</guid><category><![CDATA[PHPUnit]]></category><category><![CDATA[PHP]]></category><category><![CDATA[Testing]]></category><dc:creator><![CDATA[Elikem]]></dc:creator><pubDate>Fri, 17 Jun 2022 20:10:45 GMT</pubDate><content:encoded><![CDATA[<p>Hey there.</p>
<p>If you're already using Laravel, I just want to share a very quick tip you can use to structure your test classes.</p>
<p>If you're a little clumsy with your nomenclature, when writing tests, you will often run into a stern warning from Composer:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1655494918549/Evlo2iYoC.png" alt="Screenshot 2022-06-17 193515.png" /></p>
<p>Ouch. </p>
<p>We are facing this issue because the name of our class does not match the name of our PHP file. Our class is named "AnExampleTest" but the file itself is named "ExampleTest". We could also face this issue if the namespace does not match the folder structure.</p>
<p>Sure enough, fixing the file name and the namespace can solve the issue. However, I have found that a better way to declare such tests is to do so as anonymous classes. I was inspired to use this technique by observing the default migration files in Laravel 9.</p>
<p>So do something like this instead:</p>
<pre><code class="lang-php"><span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">TestCase</span>
</span>{
    <span class="hljs-comment">/**
     * A basic test example.
     *
     * <span class="hljs-doctag">@return</span> void
     */</span>
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">test_the_application_returns_a_successful_response</span>(<span class="hljs-params"></span>)
    </span>{
        $response = <span class="hljs-keyword">$this</span>-&gt;get(<span class="hljs-string">'/'</span>);

        $response-&gt;assertStatus(<span class="hljs-number">200</span>);
    }
};
</code></pre>
<p>Since we do not need to import this class anywhere else, this should be a great fit for us.</p>
<p>That's all for now. Thank you for reading!</p>
]]></content:encoded></item><item><title><![CDATA[Why you still suck at programming after so long, and what to do about it.]]></title><description><![CDATA[So you've been programming for a few months or maybe years. You get excited at every new release of a language or framework. You watch tons and tons of tutorials. You read countless lines of documentation. You buy every new course that is published. ...]]></description><link>https://blog.elikem.dev/why-you-still-suck-at-programming-after-so-long-and-what-to-do-about-it</link><guid isPermaLink="true">https://blog.elikem.dev/why-you-still-suck-at-programming-after-so-long-and-what-to-do-about-it</guid><category><![CDATA[General Advice]]></category><category><![CDATA[advice]]></category><category><![CDATA[Productivity]]></category><category><![CDATA[Learning Journey]]></category><dc:creator><![CDATA[Elikem]]></dc:creator><pubDate>Sun, 06 Feb 2022 23:41:36 GMT</pubDate><content:encoded><![CDATA[<p>So you've been programming for a few months or maybe years. You get excited at every new release of a language or framework. You watch tons and tons of tutorials. You read countless lines of documentation. You buy every new course that is published. </p>
<p>Except....you're still almost as bad as you were several years ago.</p>
<p>You've tried everything, but you can't seem to understand why your abilities never seem to match up to the market standard. It begins to affect your mind. You feel inferior. You want to give up entirely and become maybe a taxi driver or something. </p>
<p>But hang on! This really isn't about you. It's got nothing to do with you as a person. </p>
<p>I too was stuck in this viscious cycle for really long. That's why I think I'm qualified to talk about this. </p>
<p>Most likely, the problem you have is the same one I had.</p>
<p>In my opinion, the BIGGEST growth killer in this profession is the desire for perfection. You compare yourself to others way too much. You write code, but quickly abandon it when you see someone else do the very same thing in a seemingly better way (or with fewer lines of code).</p>
<p>But you need to realize that code doesn't need to be perfect. At least, not on your first attempt. We all write code and vehemently deny writing such terrible code after a couple of years. My point is - you need to allow yourself to <em>progress</em>.</p>
<p>Beautiful, optimized and efficient code is a journey, not a destination. You need to allow yourself to fail in order to enjoy this journey.</p>
<p>This has been a very short article, but all I want you to remember is this: there isn't just one way of doing something. There isn't "that one perfect approach" to solving a problem with code. Often, there are many different ways. And the more time you spend fantasizing over whether your code is perfect or not, the huger the chunk of time you will waste!</p>
<p>As a beginner, or intermediate programmer, DO NOT worry about perfection. When you can stand on your own two feet confidently, then you can begin to worry about those fine details.</p>
<p>I wish I had heard these words from the beginning of my journey. They would have saved me the trauma of thinking I simply wasn't good enough. </p>
<p>Now, I commit such knowledge into your bosom. Happy coding! </p>
]]></content:encoded></item></channel></rss>