cachethq/twitter
Twitter integration for Cachet status pages. Connect a Twitter account and automatically share incident and maintenance updates with followers, keeping them informed while preserving your existing Cachet workflow and simple Laravel-based configuration.
This is a single-responsibility package: it turns plain text containing @handle mentions into HTML with links to the corresponding Twitter profiles. Treat it as a text formatter, not a Twitter API client — it makes no network calls and requires no OAuth credentials.
Install and register
composer require cachethq/twitter
On Laravel 5.5+ the service provider is auto-discovered. On Laravel 5.0–5.4, add the provider and facade manually in config/app.php:
'providers' => [
CachetHQ\Twitter\TwitterServiceProvider::class,
],
'aliases' => [
'Twitter' => CachetHQ\Twitter\Facades\Twitter::class,
],
No config file needs publishing and there is nothing to migrate. If the provider isn't discovered, run composer dump-autoload and confirm the package is listed under vendor/cachethq/twitter.
Where to look first
The package is tiny — read src/ end to end before using it (10–15 minutes). The relevant surface is a parser class and, usually, a facade or container binding. Confirm the exact method name from the source before wiring it in; the README is thin and the API has changed between minor versions.
First use case
The canonical scenario this was built for is Cachet-style status/incident text: an operator writes Database failover complete — thanks @oncall_team, and the rendered status page should show a clickable profile link rather than raw text.
// routes/web.php — smoke test
Route::get('/parse', function () {
return app(\CachetHQ\Twitter\Parser::class)->parse('Thanks @cachethq for the quick fix');
});
If you get <a href="https://twitter.com/cachethq">@cachethq</a> back, the package is wired correctly. If not, check class name and method signature in vendor/cachethq/twitter/src/ — that is faster than debugging the facade.
Linkify at render time, store raw text
Keep the raw string in the database and parse on output. Parsing on write means a future change to link format (or a switch to x.com URLs) requires a data migration.
// Blade component
{!! app(\CachetHQ\Twitter\Parser::class)->parse(e($incident->message)) !!}
Wrap it behind your own helper
Because the package is small and lightly maintained, insulate your codebase with one boundary so swapping it later is a one-file change:
namespace App\Support;
class MentionParser
{
public function link(string $text): string
{
return app(\CachetHQ\Twitter\Parser::class)->parse($text);
}
}
Then inject MentionParser everywhere. If the package dies, you replace the body with a regex and never touch call sites.
Extracting handles vs. rendering links
Two distinct needs show up in practice:
['cachethq', 'oncall_team'] to fire notifications, build a mention index, or resolve user IDs.For extraction, do not round-trip through the HTML output. Either run your own preg_match_all('/@([A-Za-z0-9_]{1,15})/', $text, $m) or strip tags from the parser output. Extraction is the more common need in modern apps, and it is a five-line job — factor that in when deciding whether the dependency earns its place.
Integration points
$incident->message_html that composes parsing plus escaping. Cache the attribute on the model instance so a list view doesn't re-parse the same row repeatedly.message (raw) and message_html (parsed) so clients choose.@Name → @name) at the boundary, since Twitter handles are case-insensitive but your links should be consistent.Pipeline order matters
Escape first, parse second. If you parse then escape, your generated <a> tags get neutralised. If you escape then parse, a malicious "><script> payload in the surrounding text is already inert while the parser only ever sees the @handle token.
It is effectively unmaintained. Two stars, zero dependents, and a Laravel 5/6 target means the composer.json almost certainly pins php: ^7.x and illuminate/support: 5.*|6.*. On Laravel 9/10/11 you will hit dependency resolution failures. Options: fork and widen the constraints (the code is a few dozen lines), or drop the dependency and inline a parser. For a package this small, inlining is usually the right call — the "opportunity" score is low precisely because the functionality is trivially reproducible.
Regex edge cases you will hit
@cachethq. or @cachethq, should link the handle, not the period. Check the parser's output on sentence-ending mentions; many naive implementations swallow the punctuation into the link.[email protected] contains a @. A correct parser requires a word boundary or start-of-string before the @. Verify this explicitly with a unit test.[A-Za-z0-9_]. A loose parser will happily link @some_really_long_thing that can never be a real handle.<a href="...">@foo</a> produces nested anchors. Parse once, at one layer only.Debugging approach
The package has no logging. Isolate failures with a three-input test matrix: plain text, text with punctuation, text with an email address. If output looks wrong, dump preg_last_error() from your own comparison regex — the bug is nearly always in the pattern, not the Laravel wiring.
Extension points are minimal
There is likely no config file or driver system, so "extending" means either subclassing the parser and rebinding it in the container, or forking. If you need custom behaviour (link to x.com, add rel="nofollow noopener", use a different handle pattern), assume you're writing it yourself rather than configuring it.
XSS is your responsibility. The parser emits raw HTML. Any output path must be {!! !!} on escaped input, never on user-supplied text directly. If a future contributor "simplifies" the Blade by removing the e(), you have a stored XSS vector on your status page — add a test that asserts a script payload stays escaped.
Recommendation for a day-to-day workflow: use it if you're already on Cachet or a Laravel 5/6 app that needs mention linkification today and you want the behaviour off your plate. Otherwise, budget an hour to write a 20-line MentionParser with tests, skip the dependency, and avoid the framework-version dead end.
How can I help you explore Laravel packages today?