donatello-za/rake-php-plus
A dependency-free PHP implementation of RAKE (Rapid Automatic Keyword Extraction) for extracting keywords and key phrases from text. Includes stopword lists, multi-language support, customizable scoring, and straightforward integration into existing projects.
Install with Composer:
composer require donatello-za/rake-php-plus
The package is framework-agnostic, so in Laravel you typically use it directly from a service class, job, or controller rather than through a facade.
Minimal extraction:
use DonatelloZa\RakePlus\RakePlus;
$text = 'Laravel queues make background jobs manageable. '
. 'Queue workers process jobs and retry failed jobs automatically.';
$rake = RakePlus::create($text, 'en_US');
$keywords = $rake->keywords(); // ranked phrases
$scores = $rake->scores(); // phrase => score
First places to look:
RakePlus class for the public API and tunable options.keywords() for a ranked list, and scores() when you need the underlying RAKE score.A good first use case: extracting tags or suggested keywords from support tickets, CMS articles, product descriptions, or uploaded documents.
$tags = RakePlus::create($ticket->body, 'en_US')->keywords(10);
Start with the defaults, inspect the output on real content, then tune stopwords and phrase-length settings.
Wrap extraction in an application service
Keep RAKE configuration in one place so results stay consistent across controllers, jobs, and commands.
namespace App\Services;
use DonatelloZa\RakePlus\RakePlus;
final class KeywordExtractor
{
public function extract(string $text, ?string $locale = null, int $limit = 15): array
{
$locale = $this->normalizeLocale($locale ?? config('app.locale'));
$rake = RakePlus::create($text, $locale);
$rake->setMinWords(1);
$rake->setMaxWords(4);
$rake->setMinLength(3);
return $rake->keywords($limit);
}
private function normalizeLocale(string $locale): string
{
return match ($locale) {
'en' => 'en_US',
'de' => 'de_DE',
'fr' => 'fr_FR',
'es' => 'es_ES',
'it' => 'it_IT',
'nl' => 'nl_NL',
'pt' => 'pt_PT',
default => $locale,
};
}
}
Validate the exact language keys against the bundled stopword files in the installed version.
Return ranked results with scores
Use keywords() when you only need phrases. Use scores() when you want to apply your own threshold, compare relative importance, or combine RAKE with other signals.
$rake = RakePlus::create($article->body, 'en_US');
$scores = $rake->scores();
arsort($scores);
$strong = array_filter(
$scores,
fn (float $score) => $score >= 2.0
);
RAKE scores are relative to the text being processed. A score of 2.0 in one document is not directly comparable to 2.0 in another. Use scores for ranking within a document, not as an absolute quality metric across documents.
Tune phrase boundaries deliberately
The defaults are reasonable, but different content types benefit from different settings.
$rake = RakePlus::create($productDescription, 'en_US');
$rake->setMinWords(1); // allow single-word keywords
$rake->setMaxWords(3); // keep tags concise
$rake->setMinLength(3); // drop very short tokens
$rake->setMaxLength(40); // avoid very long phrases
For tag suggestion, 1–3 words usually works well. For topic modelling or document summarisation, allow longer phrases.
Add domain stopwords without losing defaults
Custom stopwords are especially important for product names, internal jargon, and boilerplate language.
$rake = RakePlus::create($text, 'en_US');
$stopwords = $rake->getStopwords();
$stopwords = array_merge($stopwords, [
'lorem',
'ipsum',
'click',
'here',
]);
$rake->setStopwords($stopwords);
If you replace stopwords rather than merge them, extraction quality will often degrade because common grammatical words start appearing in phrases.
Chunk long documents and aggregate results
RAKE works on sentence-level co-occurrence. Very large documents dilute phrase relationships and increase memory use. Process paragraphs or sections separately, then merge results.
$totals = [];
foreach ($chunks as $chunk) {
$rake = RakePlus::create($chunk, $locale);
foreach ($rake->scores() as $phrase => $score) {
$totals[$phrase] = ($totals[$phrase] ?? 0) + $score;
}
}
arsort($totals);
$top = array_slice($totals, 0, 20, true);
This is a pragmatic aggregation, not a mathematically exact merge of RAKE scores, but it works well for long articles and document collections.
Persist extracted keywords
Store keywords in a JSON column or a pivot table, depending on whether you need filtering and joins.
// Migration
$table->json('keywords')->nullable();
// Model
protected $casts = [
'keywords' => 'array',
];
// Job or observer
$article->update([
'keywords' => app(KeywordExtractor::class)
->extract($article->body, 'en_US', 20),
]);
Run extraction in a queued job when the text is large or when extraction happens on save.
Normalise input before extraction
RAKE expects plain text. Strip HTML, decode entities, collapse whitespace, and remove control characters.
$plain = trim(preg_replace('/\s+/', ' ', strip_tags($html)));
$keywords = RakePlus::create($plain, 'en_US')->keywords(15);
If your content contains Markdown, URLs, code blocks, or emoji-heavy text, remove or replace those constructs first. Otherwise they will influence delimiters and phrase boundaries.
Use custom delimiters and regex for unusual content
The package exposes sentence delimiters, word delimiters, and regex configuration. Adjust these when working with content that does not follow normal prose conventions.
$rake = RakePlus::create($text, 'en_US');
$rake->setSentenceDelimiters(['.', '!', '?', "\n"]);
$rake->setWordDelimiters([' ', "\t", '-', '/']);
Use this sparingly. Over-customising delimiters can merge phrases that should remain separate or split terms that should stay together.
Integrate with search and recommendations
A practical pattern is to use RAKE as one signal among several.
keywords field.RAKE is not semantic
It is a statistical phrase extraction algorithm based on word co-occurrence and stopwords. It does not understand synonyms, entities, or intent. “Laravel queue” and “Laravel queues” may appear as separate phrases. Do not expect stemming or lemmatisation unless you preprocess the text yourself.
Stopword quality dominates output
Most poor results come from stopword mismatches, not from the algorithm itself. Review the bundled list for your language, then add domain-specific boilerplate. Common additions include site names, call-to-action phrases, product category names, and legal disclaimers.
Language codes must match bundled files
Use exact locale keys supported by the installed package. If you pass an unsupported code, verify whether the package falls back to English or fails. Map application locales such as en, pt-BR, or fr_CA to supported values in one place.
setMinWords() and setMinLength() are different
Confusing them leads to either single common words leaking through or useful multi-word phrases being discarded.
Replacing stopwords can remove defaults
setStopwords() replaces the active list. Always merge with getStopwords() first if you want to preserve built-in behaviour. This is the most common cause of sudden quality regressions after adding a few custom terms.
Case and punctuation handling should be verified
Check how your installed version returns phrases, especially for capitalised names and hyphenated terms. If you need stable database keys, normalise consistently before storing or comparing:
$key = mb_strtolower(trim($phrase));
Do not assume output is already normalised across versions.
Empty or short text produces weak results
Very short inputs may return no keywords or only single words. Guard against empty arrays before rendering tag UIs or writing to the database.
$keywords = RakePlus::create($text, 'en_US')->keywords(10);
$keywords = array_values(array_filter($keywords));
Avoid sharing a mutable RAKE instance
Create a fresh instance per text or per request. Setters mutate instance state, so reusing a configured instance across different locales or content types can produce surprising results.
Cache extracted results, not raw text
Extraction is CPU-bound but not usually a bottleneck for small texts. For large documents or batch imports, cache the final keywords in the model or a dedicated table. Re-running extraction on every page load is unnecessary.
Test with fixture text
Add a small set of representative fixtures: short text, long article, non-English text, HTML-heavy content, and text containing only stopwords. Assert on stable properties such as “contains this phrase” or “does not contain stopwords” rather than exact score values, which are sensitive to configuration.
$keywords = app(KeywordExtractor::class)->extract($fixture, 'en_US');
$this->assertContains('queue workers', $keywords);
$this->assertNotContains('the', $keywords);
Tune against your own corpus
Do not copy settings from a blog post. Start with defaults, inspect real output, then adjust max words, min length, and domain stopwords. Keep the configuration in a config file so changes are reviewable and testable.
Use it as a component, not a complete solution
RAKE is excellent for fast, dependency-free keyword extraction. For production search, tagging, or recommendations, pair it with manual review, existing taxonomy rules, or a second ranking signal. Treat its output as candidate keywords, not final labels.
How can I help you explore Laravel packages today?