apie/count-words
Count word frequencies in strings or streams/files with Apie\CountWords\WordCounter. Returns an array of lowercase words mapped to their occurrence count. Supports processing large files via resources (as long as the index fits in memory).
Installation Add the package via Composer:
composer require apie/count-words
No additional Laravel-specific configuration is required.
First Use Case
Import and use WordCounter directly in a Laravel controller or service:
use Apie\CountWords\WordCounter;
// Example: Analyze a user's comment
$comment = "This is a sample comment with some repeated words like like and and.";
$wordCounts = WordCounter::countFromString($comment);
// Output: ['this' => 1, 'is' => 1, 'a' => 1, 'sample' => 1, 'comment' => 1, 'with' => 1, 'some' => 1, 'repeated' => 1, 'words' => 1, 'like' => 2, 'and' => 2]
Where to Look First
WordCounter methods (countFromString, countFromResource).app/Services/WordCounterService.php).Service Layer Abstraction
Wrap WordCounter in a Laravel service to add caching, logging, or validation:
namespace App\Services;
use Apie\CountWords\WordCounter;
use Illuminate\Support\Facades\Cache;
class WordCounterService
{
public function countWords(string $text, string $cacheKey = null): array
{
if ($cacheKey && Cache::has($cacheKey)) {
return Cache::get($cacheKey);
}
$result = WordCounter::countFromString($text);
if ($cacheKey) {
Cache::put($cacheKey, $result, now()->addMinutes(10));
}
return $result;
}
}
Use Case: Cache word counts for repeated analyses (e.g., blog posts).
File Handling with Laravel Storage Process files stored in Laravel’s filesystem (e.g., uploaded documents):
use Illuminate\Support\Facades\Storage;
$path = 'uploads/document.txt';
$file = Storage::disk('public')->open($path);
$wordCounts = WordCounter::countFromResource($file);
$file->close();
Use Case: Analyze user-uploaded documents (e.g., essays, reports).
Artisan Command for Batch Processing Create a command to process multiple files (e.g., CSV exports):
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Apie\CountWords\WordCounter;
use Illuminate\Support\Facades\File;
class ProcessDocuments extends Command
{
protected $signature = 'documents:process {directory}';
protected $description = 'Count words in all text files in a directory';
public function handle()
{
foreach (File::files($this->argument('directory')) as $file) {
$resource = fopen($file->getPathname(), 'r');
$counts = WordCounter::countFromResource($resource);
fclose($resource);
$this->info("Processed {$file->getFilename()}: " . count($counts) . " words");
}
}
}
Use Case: Run via cron to analyze logs or documents nightly.
Request Validation and API Endpoint Expose word counting as an API endpoint with validation:
namespace App\Http\Controllers;
use App\Services\WordCounterService;
use Illuminate\Http\Request;
class WordCountController extends Controller
{
public function __construct(private WordCounterService $counter) {}
public function count(Request $request)
{
$request->validate(['text' => 'required|string|max:10000']);
$wordCounts = $this->counter->countWords($request->text);
return response()->json($wordCounts);
}
}
Use Case: Public API for frontend applications or third-party integrations.
Combine with Laravel Collections Use the result with Laravel’s collection methods for further analysis:
$wordCounts = WordCounter::countFromString($text);
$topWords = collect($wordCounts)
->sortByDesc('value')
->take(5)
->keys()
->all();
Use Case: Highlight top keywords in a blog post.
Queue Long-Running Tasks Offload file processing to a queue job to avoid timeouts:
namespace App\Jobs;
use Apie\CountWords\WordCounter;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
class CountWordsJob implements ShouldQueue
{
use Queueable;
public function handle()
{
$file = fopen(storage_path('large_file.txt'), 'r');
$wordCounts = WordCounter::countFromResource($file);
fclose($file);
// Store or process results...
}
}
Use Case: Process large files asynchronously (e.g., user uploads).
Extend for Custom Tokenization
Subclass WordCounter to add preprocessing (e.g., stopword removal):
namespace App\Services;
use Apie\CountWords\WordCounter as BaseWordCounter;
class CustomWordCounter extends BaseWordCounter
{
protected static function preprocess(string $text): string
{
$stopwords = ['the', 'and', 'is', 'in', 'it'];
$words = explode(' ', strtolower($text));
return implode(' ', array_diff($words, $stopwords));
}
public static function countFromString(string $text): array
{
return parent::countFromString(self::preprocess($text));
}
}
Use Case: Filter out common words for SEO or readability metrics.
Memory Limits with Large Files
countFromResource loads the entire file into memory, causing OutOfMemory errors for files >100MB.Storage facade with streaming:
$file = Storage::disk('public')->readStream('large_file.txt');
$chunkSize = 1024 * 1024; // 1MB chunks
$wordCounts = [];
while (!feof($file)) {
$chunk = fread($file, $chunkSize);
$chunkCounts = WordCounter::countFromString($chunk);
foreach ($chunkCounts as $word => $count) {
$wordCounts[$word] = ($wordCounts[$word] ?? 0) + $count;
}
}
Case Sensitivity and Punctuation
"world!" becomes "world").preg_replace:
$cleanText = preg_replace('/[^\p{L}\p{N}\s]/u', ' ', $text);
$wordCounts = WordCounter::countFromString(mb_strtolower($cleanText));
Resource Leaks
countFromResource can lead to memory leaks.$file = fopen('file.txt', 'r');
$wordCounts = WordCounter::countFromResource($file);
fclose($file); // Critical!
Non-Text Files
countFromResource will throw errors or produce garbage output.$mime = mime_content_type($filePath);
if (strpos($mime, 'text/') === 0) {
$file = fopen($filePath, 'r');
$wordCounts = WordCounter::countFromResource($file);
fclose($file);
}
Verify Input Log or dump input text/files to ensure they match expectations:
\Log::debug('Input text:', ['text' => $text]);
Memory Usage Monitor memory consumption for large files:
$memoryBefore = memory_get_usage();
$wordCounts = WordCounter::countFromResource($file);
$memoryAfter = memory_get_usage();
\Log::info('Memory used:', ['bytes' => $memoryAfter - $memoryBefore]);
Edge Cases Test with:
How can I help you explore Laravel packages today?