<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sarfraz Ahmed</title>
    <description>The latest articles on DEV Community by Sarfraz Ahmed (@sarfraznawaz2005).</description>
    <link>https://dev.to/sarfraznawaz2005</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F49230%2F6605a722-3f12-43e9-b052-4fdb97129885.png</url>
      <title>DEV Community: Sarfraz Ahmed</title>
      <link>https://dev.to/sarfraznawaz2005</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sarfraznawaz2005"/>
    <language>en</language>
    <item>
      <title>How a Web-Framework Really Should be Today</title>
      <dc:creator>Sarfraz Ahmed</dc:creator>
      <pubDate>Sat, 20 Sep 2025 15:08:19 +0000</pubDate>
      <link>https://dev.to/sarfraznawaz2005/how-a-web-framework-really-should-be-today-2o05</link>
      <guid>https://dev.to/sarfraznawaz2005/how-a-web-framework-really-should-be-today-2o05</guid>
      <description>&lt;p&gt;Let’s assume imaginary web-framework called Lattice — a from-scratch, full-stack system that treats your app as a live, distributed dataflow rather than a pile of routes, controllers, and glue (MVC is fucking old). Think “reactive graph of capabilities” that runs on servers, at the edge, and in the browser with the same mental model. No sacred cows, just steak. 🥩&lt;/p&gt;

&lt;p&gt;Full Article: &lt;a href="https://medium.com/@sarfraznawaz2005/how-a-web-framework-really-should-be-today-0dcf6a3d729e" rel="noopener noreferrer"&gt;How a Web-Framework Really Should be Today&lt;/a&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>webdev</category>
      <category>web</category>
    </item>
    <item>
      <title>PHP RAG: A Vector Store-Free Approach</title>
      <dc:creator>Sarfraz Ahmed</dc:creator>
      <pubDate>Thu, 22 Aug 2024 12:48:55 +0000</pubDate>
      <link>https://dev.to/sarfraznawaz2005/php-rag-a-vector-store-free-approach-47c4</link>
      <guid>https://dev.to/sarfraznawaz2005/php-rag-a-vector-store-free-approach-47c4</guid>
      <description>&lt;p&gt;I envisioned creating a simple desktop RAG application, a "chat with docs" tool, using PHP. However, I wanted to avoid relying on online vector store services. My aim was to utilize locally-stored or file-based vector store solutions. Unfortunately, PHP lacked suitable options in this regard. I considered using an SQLite database, but again, it proved insufficient.&lt;/p&gt;

&lt;p&gt;Undeterred, I explored the possibility of storing embeddings locally within JSON files. While this approach wouldn't match the speed and capabilities of dedicated vector stores, I was eager to see how it performed. This led to the creation of a PHP class designed to enable the construction of basic RAG applications without external dependencies.&lt;/p&gt;

&lt;p&gt;The class leverages cosine similarity and conventional text search, merging them into a hybrid approach to maximize the number of matches. In my initial tests, the performance with smaller files was quite satisfactory. A 20MB file yielded responses within a few seconds, which met my requirements. The picture of actual app built is in header ☺ &lt;/p&gt;

&lt;p&gt;While the code has potential for improvement, especially in terms of performance, it served its purpose well. Concurrent file processing, queues, PHP fibers, generators, caching, etc could enhance its speed.&lt;/p&gt;

&lt;p&gt;Feel free to use this class, but you may need to make adjustments as needed. The LlmProvider interface dictates the signature of the embedding and chat methods:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public function embed(array $texts, string $embeddingModel): array|string;

public function chat(string $message, bool $stream = false): mixed;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll need to wrap your LLM calls within these methods. The $fileIdentifier is a suffix that prevents overwriting previous JSON files.&lt;/p&gt;

&lt;p&gt;I decided to share this code to garner feedback and suggestions from the community. Any improvements or modifications are welcome, and I encourage you to share them in the comments.&lt;/p&gt;

&lt;p&gt;Here is the class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php

namespace App\Services;

use App\LLM\LlmProvider;
use Exception;
use Illuminate\Support\Facades\Log;
use Smalot\PdfParser\Parser;

class DocumentSearchService
{
    private static ?DocumentSearchService $instance = null;

    protected Parser $parser;
    protected array $embeddings = [];
    private array $embeddingsCache = [];
    protected array $textSplits = [];

    private function __construct(protected LlmProvider $llm,
                                 protected string      $fileIdentifier,
                                 protected string      $embdeddingModel,
                                 protected int         $embdeddingsBatchSize = 100,
                                 protected int         $chunkSize = 500,
                                 protected float       $similarityThreshold = 0.6,
                                 protected int         $maxResults = 3)
    {
        $this-&amp;gt;parser = new Parser();
    }

    public static function getInstance(
        LlmProvider $llm,
        string      $fileIdentifier,
        string      $embdeddingModel,
        int         $embdeddingsBatchSize = 100,
        int         $chunkSize = 500,
        float       $similarityThreshold = 0.6,
        int         $maxResults = 3
    ): DocumentSearchService
    {
        if (self::$instance === null) {
            self::$instance = new self($llm, $fileIdentifier, $embdeddingModel, $embdeddingsBatchSize, $chunkSize, $similarityThreshold, $maxResults);
        }

        return self::$instance;
    }

    /**
     * @throws Exception
     */
    public function searchDocuments(array $files, string $query): array
    {
        $results = $this-&amp;gt;performCosineSimilaritySearch($files, $query);

        if (!empty($results)) {
            if (app()-&amp;gt;environment('local')) {
                Log::info('Resutls found via cosine similarity');
            }

            return $this-&amp;gt;getTopResults($results);
        }

        $results = $this-&amp;gt;performTextSearch($files, $query);

        if (!empty($results)) {
            if (app()-&amp;gt;environment('local')) {
                Log::info('Resutls found via text search');
            }

            return $this-&amp;gt;getTopResults($results);
        }

        if (app()-&amp;gt;environment('local')) {
            Log::info('No results found, giving suggested topics');
        }

        return $this-&amp;gt;getListOfIdeas($files);
    }

    /**
     * @throws Exception
     */
    protected function performCosineSimilaritySearch(array $files, string $query): array
    {
        $results = [];

        $queryEmbeddings = $this-&amp;gt;llm-&amp;gt;embed([$this-&amp;gt;getCleanedText($query, true)], $this-&amp;gt;embdeddingModel);

        $this-&amp;gt;setTextEmbeddingsFromFiles($files);

        $results = array_merge($results, $this-&amp;gt;compareEmbeddings($queryEmbeddings));

        usort($results, fn($a, $b) =&amp;gt; $b['similarity'] &amp;lt;=&amp;gt; $a['similarity']);

        return $results;
    }

    /**
     * @throws Exception
     */
    protected function performTextSearch(array $files, string $query): array
    {
        $results = [];
        $cleanedQuery = $this-&amp;gt;getCleanedText($query, true);

        foreach ($files as $file) {
            foreach ($this-&amp;gt;textSplits[$file] as $chunks) {
                foreach ($chunks as $index =&amp;gt; $chunk) {
                    $exactMatchScore = $this-&amp;gt;calculateExactMatchScore($cleanedQuery, $chunk['text']);
                    $fuzzyMatchScore = $this-&amp;gt;calculateFuzzyMatchScore($cleanedQuery, $chunk['text']);

                    $maxScore = max($exactMatchScore, $fuzzyMatchScore);

                    if ($maxScore &amp;gt;= $this-&amp;gt;similarityThreshold) {
                        $results[] = [
                            'similarity' =&amp;gt; $maxScore,
                            'index' =&amp;gt; $index,
                            'matchedChunk' =&amp;gt; ['text' =&amp;gt; $chunk['text'], 'metadata' =&amp;gt; $chunk['metadata']],
                        ];
                    }
                }
            }
        }

        usort($results, fn($a, $b) =&amp;gt; $b['similarity'] &amp;lt;=&amp;gt; $a['similarity']);

        return $results;
    }

    protected function getListOfIdeas(array $files): array
    {
        $prompt = &amp;lt;&amp;lt;&amp;lt;EOF
        Please convert following piece of text into brief list of topics user can ask questions about.
        Do not mention anything else except for providing list of topics in following format:

        - TOPIC 1
        - TOPIC 2
        - TOPIC 3

        {{TEXT}}
        EOF;

        $result = '';
        foreach ($files as $file) {
            foreach ($this-&amp;gt;textSplits[$file] as $chunks) {
                foreach ($chunks as $chunk) {
                    $result .= $chunk['text'] . "\n";
                }
            }
        }

        $prompt = str_replace('{{TEXT}}', $this-&amp;gt;getCleanedText($result), $prompt);

        $llmResult = $this-&amp;gt;llm-&amp;gt;chat($prompt);

        return [[
            'similarity' =&amp;gt; $this-&amp;gt;similarityThreshold,
            'index' =&amp;gt; 0,
            'matchedChunk' =&amp;gt; ['text' =&amp;gt; $llmResult, 'metadata' =&amp;gt; []],
        ]];
    }

    protected function calculateExactMatchScore(string $query, string $text): float
    {
        return stripos($text, $query) !== false ? $this-&amp;gt;similarityThreshold : 0.0;
    }

    protected function calculateFuzzyMatchScore(string $query, string $text): float
    {
        $distance = levenshtein($query, $text);
        $maxLength = max(strlen($query), strlen($text));

        return $maxLength === 0 ? $this-&amp;gt;similarityThreshold : 1 - ($distance / $maxLength);
    }

    public function isEmbdeddingDone(string $file, string $fileIdentifier): bool
    {
        $fileName = basename($file);
        $path = storage_path("app/$fileName-" . $fileIdentifier . '.json');

        return file_exists($path);
    }

    protected function getEmbeddingsOrLoadFromCache(string $file, array $chunks): array
    {
        $fileName = basename($file);
        $path = storage_path("app/$fileName-" . $this-&amp;gt;fileIdentifier . '.json');
        $cacheKey = "$fileName-" . $this-&amp;gt;fileIdentifier;

        if (array_key_exists($cacheKey, $this-&amp;gt;embeddingsCache)) {
            //Log::info("Loaded embeddings from cache for $fileName");
            return $this-&amp;gt;embeddingsCache[$cacheKey]['embeddings'];
        }

        if (file_exists($path)) {
            $data = json_decode(file_get_contents($path), true);
            //Log::info("Loaded embeddings from file for $fileName");
            return $data['embeddings'];
        }

        $textSplits = array_map(function ($chunk) {
            return trim($chunk['text']);
        }, $chunks);

        $textSplits = array_filter($textSplits);

        $embeddings = $this-&amp;gt;llm-&amp;gt;embed($textSplits, $this-&amp;gt;embdeddingModel);

        $data = [
            'embeddings' =&amp;gt; $embeddings,
            'chunks' =&amp;gt; $chunks
        ];

        $this-&amp;gt;embeddingsCache[$cacheKey] = $data;

        file_put_contents($path, json_encode($data));

        return $embeddings;
    }


    /**
     * @throws Exception
     */
    protected function setTextEmbeddingsFromFiles(array $files): void
    {
        foreach ($files as $file) {

            // already set
            if (isset($this-&amp;gt;textSplits[$file]) &amp;amp;&amp;amp; $this-&amp;gt;textSplits[$file]) {
                continue;
            }

            $textWithMetadata = $this-&amp;gt;extractTextFromFile($file);
            $chunks = $this-&amp;gt;splitTextIntoChunks($textWithMetadata);

            // Chunk the text based on $embdeddingsBatchSize
            $chunkedTextArray = array_chunk($chunks, $this-&amp;gt;embdeddingsBatchSize);

            $chunkedEmbeddings = [];
            $chunkedTextSplits = [];
            foreach ($chunkedTextArray as $chunkIndex =&amp;gt; $chunkedText) {
                $embeddings = $this-&amp;gt;getEmbeddingsOrLoadFromCache($file, $chunkedText);
                $chunkedEmbeddings[$chunkIndex] = $embeddings;
                $chunkedTextSplits[$chunkIndex] = $chunkedText;
            }

            $this-&amp;gt;textSplits[$file] = $chunkedTextSplits;
            $this-&amp;gt;embeddings[$file] = $chunkedEmbeddings;

            // Free memory after processing each file
            unset($textWithMetadata, $chunks, $chunkedTextArray, $chunkedEmbeddings, $chunkedTextSplits);
        }
    }

    /**
     * @throws Exception
     */
    protected function extractTextFromFile(string $file): array
    {
        $extension = pathinfo($file, PATHINFO_EXTENSION);

        switch (strtolower($extension)) {
            case 'pdf':
                $pdf = $this-&amp;gt;parser-&amp;gt;parseFile($file);
                $pages = $pdf-&amp;gt;getPages();
                $text = [];

                foreach ($pages as $pageNumber =&amp;gt; $page) {
                    $text[] = [
                        'text' =&amp;gt; $this-&amp;gt;getCleanedText($page-&amp;gt;getText()),
                        'metadata' =&amp;gt; ['source' =&amp;gt; basename($file), 'page' =&amp;gt; $pageNumber + 1]
                    ];
                }

                return $text;
            case 'txt':
            case 'md':
            case 'html':
            case 'htm':
                $content = file_get_contents($file);
                $lines = explode("\n", $content);
                $text = [];

                foreach ($lines as $lineNumber =&amp;gt; $line) {
                    $text[] = [
                        'text' =&amp;gt; $this-&amp;gt;getCleanedText($line),
                        'metadata' =&amp;gt; ['source' =&amp;gt; basename($file), 'line' =&amp;gt; $lineNumber + 1]
                    ];
                }

                return $text;
            default:
                throw new Exception("Unsupported file type: $extension");
        }
    }

    /**
     * @throws Exception
     */
    protected function compareEmbeddings(array $queryEmbeddings): array
    {
        $results = [];
        $alreadyAdded = [];

        if (count($this-&amp;gt;textSplits) !== count($this-&amp;gt;embeddings)) {
            throw new Exception("Splits and embeddings count mismatch!");
        }

        foreach ($this-&amp;gt;embeddings as $file =&amp;gt; $fileEmbeddings) {
            foreach ($fileEmbeddings as $mainIndex =&amp;gt; $embeddings) {

                if (isset($embeddings['embeddings'])) {
                    // Gemini structure: 'embeddings' =&amp;gt; array of arrays with 'values'
                    $embeddingValues = array_column($embeddings['embeddings'], 'values');
                } else {
                    // OpenAI structure: direct array of embedding values
                    $embeddingValues = [$embeddings];
                }

                foreach ($embeddingValues as $index =&amp;gt; $embedding) {
                    // Gemini structure for queryEmbeddings
                    if (isset($queryEmbeddings['embeddings'])) {
                        $queryEmbeddingValues = $queryEmbeddings['embeddings'][0]['values'];
                    } else {
                        // OpenAI structure for queryEmbeddings
                        $queryEmbeddingValues = $queryEmbeddings;
                    }

                    $similarity = $this-&amp;gt;cosineSimilarity($embedding, $queryEmbeddingValues);

                    if ($similarity &amp;gt;= $this-&amp;gt;similarityThreshold) {

                        if (isset($this-&amp;gt;textSplits[$file][$mainIndex][$index])) {
                            $matchedText = $this-&amp;gt;textSplits[$file][$mainIndex][$index];
                            $hash = md5($matchedText['text']);

                            if (!isset($alreadyAdded[$hash])) {
                                $alreadyAdded[$hash] = true;

                                $results[] = [
                                    'similarity' =&amp;gt; $similarity,
                                    'index' =&amp;gt; $index,
                                    'matchedChunk' =&amp;gt; $matchedText,
                                ];
                            }
                        }

                    }
                }
            }
        }

        return $results;
    }

    protected function getTopResults(array $results): array
    {
        return array_slice($results, 0, $this-&amp;gt;maxResults);
    }

    protected function splitTextIntoChunks(array $textWithMetadata): array
    {
        $chunks = [];
        $overlapPercentage = 30; // 30% overlap, adjust as needed
        $overlapSize = max(1, (int)($this-&amp;gt;chunkSize * ($overlapPercentage / 100)));

        $fullText = implode("\n", array_column($textWithMetadata, 'text'));
        $totalLength = strlen($fullText);

        $chunkStart = 0;
        while ($chunkStart &amp;lt; $totalLength) {
            $chunkEnd = min($chunkStart + $this-&amp;gt;chunkSize, $totalLength);
            $chunk = substr($fullText, $chunkStart, $chunkEnd - $chunkStart);

            $chunks[] = [
                'text' =&amp;gt; trim($chunk),
                'metadata' =&amp;gt; $this-&amp;gt;getMetadataForChunk($textWithMetadata, $chunkStart, $chunkEnd - 1)
            ];

            if ($chunkEnd == $totalLength) {
                break;
            }

            $chunkStart += max(1, $this-&amp;gt;chunkSize - $overlapSize);
        }

        return $chunks;
    }

    protected function getMetadataForChunk(array $textWithMetadata, int $start, int $end): array
    {
        $metadata = [];
        $currentPosition = 0;

        foreach ($textWithMetadata as $item) {
            $length = strlen($item['text']);

            if ($currentPosition + $length &amp;gt;= $start &amp;amp;&amp;amp; $currentPosition &amp;lt;= $end) {
                $metadata[] = $item['metadata'];
            }

            if ($currentPosition &amp;gt; $end) {
                break;
            }

            $currentPosition += $length + 1; // +1 for the newline
        }

        return $metadata;
    }

    protected function cosineSimilarity(array $u, array $v): float
    {
        try {
            $dotProduct = 0.0;
            $uLength = 0.0;
            $vLength = 0.0;

            foreach ($u as $i =&amp;gt; $value) {
                $dotProduct += $value * $v[$i];
                $uLength += $value * $value;
                $vLength += $v[$i] * $v[$i];
            }

            return $dotProduct / (sqrt($uLength) * sqrt($vLength));
        } catch (Exception $e) {
            return 0;
        }
    }

    protected function getCleanedText(string $text, bool $removeStopWords = false): string
    {
        $text = strtolower($text);
        $text = strip_tags($text);
        $text = preg_replace('/&amp;lt;br\s*\/?&amp;gt;/i', "\n", $text);
        $text = preg_replace('/&amp;lt;\/p&amp;gt;/i', "\n\n", $text);
        $text = preg_replace('/\r\n|\r/', "\n", $text);
        $text = preg_replace('/(\s*\n\s*){3,}/', "\n\n", $text);
        $text = preg_replace('/\s+/', ' ', $text);
        $text = preg_replace('/[^\w\s\-_.&amp;amp;*$@]/', '', $text);

        if ($removeStopWords) {
            $text = $this-&amp;gt;removeStopwords($text);
        }

        return trim($text);
    }

    protected function removeStopwords(string $text): string
    {
        $stopwords = [
            'the', 'a', 'an', 'and', 'but', 'if', 'or', 'because', 'as', 'until',
            'while', 'of', 'at', 'by', 'for', 'with', 'about', 'against', 'between',
            'into', 'through', 'during', 'before', 'after', 'above', 'below', 'to',
            'from', 'up', 'down', 'in', 'out', 'on', 'off', 'over', 'under', 'again',
            'further', 'then', 'once', 'here', 'there', 'when', 'where', 'why',
            'how', 'all', 'any', 'both', 'each', 'few', 'more', 'most', 'other',
            'some', 'such', 'no', 'nor', 'not', 'only', 'own', 'same', 'so', 'than',
            'too', 'very', 'can', 'will', 'just', 'don', 'should', 'now', 'what',
            'is', 'am', 'are', 'was', 'were', 'be', 'been', 'being', 'has', 'have',
            'had', 'do', 'does', 'did', 'having', 'he', 'she', 'it', 'they', 'them',
            'his', 'her', 'its', 'their', 'my', 'your', 'our', 'we', 'you', 'who',
            'whom', 'which', 'this', 'that', 'these', 'those', 'I', 'me', 'mine',
            'yours', 'ours', 'himself', 'herself', 'itself', 'themselves'
        ];

        $words = explode(' ', $text);
        $filteredWords = array_diff($words, $stopwords);

        return implode(' ', $filteredWords);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Usage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$searchService = DocumentSearchService::getInstance($llmInstance, 'unique resource id', 'embedding-001', 100, 2000);
$results = $searchService-&amp;gt;searchDocuments($files, 'some query');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The returned results can then further be fed into LLM prompt with results context, user query and conversation history.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Kids Whiteboard Drawing App</title>
      <dc:creator>Sarfraz Ahmed</dc:creator>
      <pubDate>Fri, 02 Apr 2021 17:00:06 +0000</pubDate>
      <link>https://dev.to/sarfraznawaz2005/kids-whiteboard-drawing-app-18b3</link>
      <guid>https://dev.to/sarfraznawaz2005/kids-whiteboard-drawing-app-18b3</guid>
      <description>&lt;p&gt;Very easy to use and simple whiteboard and drawing app for kids. It comes with simple interface and no bloated drawing screen. Helps your child grow their learning as well as drawing skills.&lt;/p&gt;

&lt;p&gt;No more need for expensive physical whiteboard products.&lt;/p&gt;

&lt;p&gt;Features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Draw with varying thickness&lt;/li&gt;
&lt;li&gt;Eraser&lt;/li&gt;
&lt;li&gt;Unlimited undo&lt;/li&gt;
&lt;li&gt;16 colors to choose from&lt;/li&gt;
&lt;li&gt;Save drawings&lt;/li&gt;
&lt;li&gt;Share with friends and family&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;External storage permission is needed to save drawings to device.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://play.google.com/store/apps/details?id=com.mahamsoft.kidsdrawingapp"&gt;View On PlayStore&lt;/a&gt;&lt;/p&gt;

</description>
      <category>android</category>
    </item>
    <item>
      <title>Cute Me Up Android App</title>
      <dc:creator>Sarfraz Ahmed</dc:creator>
      <pubDate>Fri, 02 Apr 2021 16:58:34 +0000</pubDate>
      <link>https://dev.to/sarfraznawaz2005/cute-me-up-android-app-24lk</link>
      <guid>https://dev.to/sarfraznawaz2005/cute-me-up-android-app-24lk</guid>
      <description>&lt;p&gt;Free, easy-to-use yet powerful photo app provides a set of stunning filters you can use to bring best of you.&lt;/p&gt;

&lt;p&gt;Awesome Features:&lt;/p&gt;

&lt;p&gt;✴ HD photo processing with fast image rendering&lt;br&gt;
✴ Photo vignette &amp;amp; sharpness effects&lt;br&gt;
✴ Gorgeous photo filters with one-tap auto enhance&lt;br&gt;
✴ Fun stickers &amp;amp; text over the photo&lt;br&gt;
✴ Adjust exposure, contrast, color temperature &amp;amp; saturation&lt;br&gt;
✴ Round corner&lt;br&gt;
✴ Photo crop with chosen aspect ratio&lt;br&gt;
✴ Many bulit-in photo frames&lt;br&gt;
✴ Draw over image&lt;br&gt;
✴ Blur photo backgrounds with a smart selection tool&lt;br&gt;
✴ Share your creativity with friends and family&lt;br&gt;
✴ Support both phones &amp;amp; tablets&lt;br&gt;
✴ And many more&lt;/p&gt;

&lt;p&gt;Just make yourself awesome!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://play.google.com/store/apps/details?id=com.mahamsoft.cutemephotoeditor"&gt;View On PlayStore&lt;/a&gt;&lt;/p&gt;

</description>
      <category>android</category>
    </item>
    <item>
      <title>JSNews for JavaScript Developers</title>
      <dc:creator>Sarfraz Ahmed</dc:creator>
      <pubDate>Fri, 05 Mar 2021 05:37:52 +0000</pubDate>
      <link>https://dev.to/sarfraznawaz2005/jsnews-for-javascript-developers-1bmd</link>
      <guid>https://dev.to/sarfraznawaz2005/jsnews-for-javascript-developers-1bmd</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--H1t0vvR9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sqqem9cg2bd4enapr8it.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--H1t0vvR9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sqqem9cg2bd4enapr8it.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is nice app for all JavaScript developers they can use to keep updated with latest JavaScript stuff from difference sources in one application.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://play.google.com/store/apps/details?id=io.codeinphp.jsnews"&gt;View JSNews on Android PlayStore&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
    </item>
    <item>
      <title>PHPNews for PHP Developers</title>
      <dc:creator>Sarfraz Ahmed</dc:creator>
      <pubDate>Sat, 27 Feb 2021 18:52:38 +0000</pubDate>
      <link>https://dev.to/sarfraznawaz2005/phpnews-for-php-developers-256l</link>
      <guid>https://dev.to/sarfraznawaz2005/phpnews-for-php-developers-256l</guid>
      <description>&lt;p&gt;This is nice app for all PHP developers they can use to keep updated with latest PHP stuff from difference sources in one application.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uvfNGK9U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0p29jfxicinkhlkbgx0g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uvfNGK9U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0p29jfxicinkhlkbgx0g.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://play.google.com/store/apps/details?id=io.codeinphp.phpnews"&gt;View PHPNews on Android PlayStore&lt;/a&gt;&lt;/p&gt;

</description>
      <category>php</category>
    </item>
    <item>
      <title>Composer Vendor Cost</title>
      <dc:creator>Sarfraz Ahmed</dc:creator>
      <pubDate>Wed, 12 Feb 2020 13:49:05 +0000</pubDate>
      <link>https://dev.to/sarfraznawaz2005/composer-vendor-cost-1lkg</link>
      <guid>https://dev.to/sarfraznawaz2005/composer-vendor-cost-1lkg</guid>
      <description>&lt;p&gt;Simple composer plugin that displays size of each folder under &lt;code&gt;vendor&lt;/code&gt; to help you with which package is taking the most disk space. It will run automatically when you use &lt;code&gt;composer install&lt;/code&gt; or &lt;code&gt;composer update&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/sarfraznawaz2005/composer-cost"&gt;Checkout Plugin&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>php</category>
    </item>
    <item>
      <title>LaraFeed</title>
      <dc:creator>Sarfraz Ahmed</dc:creator>
      <pubDate>Sun, 02 Feb 2020 10:42:12 +0000</pubDate>
      <link>https://dev.to/sarfraznawaz2005/larafeed-5cca</link>
      <guid>https://dev.to/sarfraznawaz2005/larafeed-5cca</guid>
      <description>&lt;p&gt;Laravel package for providing visual feedback via screenshots.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/sarfraznawaz2005/larafeed"&gt;Checkout LaraFeed&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
    </item>
    <item>
      <title>Laravel Meter</title>
      <dc:creator>Sarfraz Ahmed</dc:creator>
      <pubDate>Mon, 27 Jan 2020 07:15:33 +0000</pubDate>
      <link>https://dev.to/sarfraznawaz2005/laravel-meter-2e0b</link>
      <guid>https://dev.to/sarfraznawaz2005/laravel-meter-2e0b</guid>
      <description>&lt;p&gt;Laravel Meter monitors application performance for different things such as requests, commands, queries, events, etc and presents results in tables/charts. Think of it like Laravel Telescope but for performance monitoring.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/sarfraznawaz2005/meter"&gt;Checkout Laravel Meter&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
    </item>
    <item>
      <title>PHPNews App</title>
      <dc:creator>Sarfraz Ahmed</dc:creator>
      <pubDate>Thu, 23 Jan 2020 07:39:27 +0000</pubDate>
      <link>https://dev.to/sarfraznawaz2005/phpnews-app-de0</link>
      <guid>https://dev.to/sarfraznawaz2005/phpnews-app-de0</guid>
      <description>&lt;p&gt;Being able to keep oneself updated with latest trends, best practices and news is very crucial especially with the web evolving so rapidly today. This is equally important to become better or what they say modern developer as opposed to obsolete developer. As the technology stack evolves, the client requirements also evolve pushing us as developers to remain abreast of latest stuff as much as we possibly can.&lt;/p&gt;

&lt;p&gt;Two of the most common methods to stay up-to-date with latest stuff is to use some RSS/Atom Aggregator or subscription via emails at various sites. These work fine but I was personally overwhelmed by the amount of new updates and posts I used to get, it was kind of hard to keep the track of it all with that many subscriptions from various sources on different technologies. I could have minimized the subscriptions but that wouldn't save the actual purpose.&lt;/p&gt;

&lt;p&gt;Therefore being a developer, often times, you come up with your own solution which fits your needs. I wanted just a simple and focused app on just one technology eg PHP that would give me updates from various sources in one central place without bloating me away with too many or stale posts. Since idea was simple, I didn't want to spend too much time on Android so I took the route of using JavaScript to create this app. It turned out there are quite some ways to create Native apps today using one technology that can run across devices and platforms. I found NativeScript quick and easy to get started with although there are other solutions too to make native apps such as ReactNative, Xamarin, Flutter, etc.&lt;/p&gt;

&lt;p&gt;Over the weekend, I went on to creating this app called PHPNews and I believe end result is exactly what I wanted as it serves the purpose.&lt;/p&gt;

&lt;p&gt;It collects information from various sources including official blogs of PHP frameworks such as Laravel, Symfony, Zend Framework and CMSs like WordPress and last but not the least our old friend planet-php.net.&lt;/p&gt;

&lt;p&gt;Definitely give it a try and also share any suggestions or improvements.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://play.google.com/store/apps/details?id=io.codeinphp.phpnews"&gt;View PHPNews on Android PlayStore&lt;/a&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>android</category>
    </item>
    <item>
      <title>Laravel Indexer</title>
      <dc:creator>Sarfraz Ahmed</dc:creator>
      <pubDate>Wed, 25 Dec 2019 11:50:14 +0000</pubDate>
      <link>https://dev.to/sarfraznawaz2005/laravel-indexer-3h8d</link>
      <guid>https://dev.to/sarfraznawaz2005/laravel-indexer-3h8d</guid>
      <description>&lt;p&gt;Laravel package to monitor SELECT queries and offer best possible INDEX fields.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/sarfraznawaz2005/indexer"&gt;Checkout Laravel Indexer&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
    </item>
    <item>
      <title>Laravel Actions</title>
      <dc:creator>Sarfraz Ahmed</dc:creator>
      <pubDate>Wed, 20 Nov 2019 08:27:26 +0000</pubDate>
      <link>https://dev.to/sarfraznawaz2005/laravel-actions-2bj9</link>
      <guid>https://dev.to/sarfraznawaz2005/laravel-actions-2bj9</guid>
      <description>&lt;p&gt;Laravel package as an alternative to single action controllers with support for web and api in single class. You can use single class to send appropriate web or api response automatically. It also provides easy way to validate request data.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/sarfraznawaz2005/actions"&gt;Checkout Laravel Actions&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
    </item>
  </channel>
</rss>
