genj/social-feed-bundle
genj/social-feed-bundle is a PHP package that adds social feed functionality to your application. It provides the building blocks for collecting, organizing, and rendering feed content in one place, so you can integrate a unified feed into existing projects without writing the plumbing from scratch.
First, the framing: despite being catalogued alongside Laravel packages, genj/social-feed-bundle is a Symfony bundle (GenjSocialFeedBundle, ~2014–2016 era). There is no service provider, no config/social-feed.php, no publishable assets. If you are a Laravel developer, your "getting started" is really a read-and-port exercise: clone the repo, read Provider/, Model/, and DependencyInjection/Configuration.php, and decide whether to port the abstraction or lift the provider classes into a small in-app service.
If you are in a legacy Symfony 2.8/3.x app where it already installs:
composer require genj/social-feed-bundle (expect resolution failures on modern PHP/Symfony — see Gotchas).AppKernel::registerBundles() (or config/bundles.php on Symfony 3.4+).# app/config/config.yml
genj_social_feed:
providers:
twitter:
consumer_key: "%twitter_ck%"
consumer_secret: "%twitter_cs%"
access_token: "%twitter_at%"
access_token_secret: "%twitter_ats%"
instagram:
access_token: "%instagram_token%"
facebook:
app_id: "%fb_app_id%"
app_secret: "%fb_app_secret%"
(The exact key names live in DependencyInjection/Configuration.php — that file is the source of truth, not the README.)
Where to look first, in order:
DependencyInjection/Configuration.php → the real config schema.Provider/ → one class per network; this is the code you will actually rewrite.Model/ (or Entity/) → the normalized post object; this is the only part worth keeping.Resources/config/services.yml → service IDs and tags (how new providers register).Resources/views/ → the reference rendering, if any.First use case: a "latest from our socials" strip on a marketing page — pull the last 3–5 posts per network for one brand account, merge, sort by date, render identically. That is the single scenario this bundle was built for, and the one whose shape survives even after the network APIs die.
Because the package is archived, never let controllers touch it directly. Put a seam in front of it on day one:
// src/AppBundle/Feed/FeedRepository.php
class FeedRepository
{
private $feed; // the bundle's manager/aggregator service
private $cache;
public function latest(int $limit = 10): array
{
return $this->cache->get('social.latest', function () use ($limit) {
return $this->feed->getPosts(['limit' => $limit]); // normalized Post[]
});
}
}
When (not if) you replace the bundle, only this class changes.
The bundle's value is that Twitter/Instagram/Facebook posts come back as a common object (text, author, avatar, image, permalink, timestamp, network). Exploit that:
{% for post in posts %}
<article class="social-card social-card--{{ post.network }}">
<img src="{{ post.authorAvatar }}" alt="{{ post.authorName }}">
<p>{{ post.text }}</p>
{% if post.image %}<img src="{{ post.image }}" alt="">{% endif %}
<a href="{{ post.url }}" target="_blank" rel="noopener">{{ post.createdAt|date('M j') }}</a>
</article>
{% endfor %}
Resist per-network templates. If you find yourself branching on network in Twig, the model isn't being used as intended.
"Multiple user accounts" is the bundle's headline feature. Configure one named block per account/brand and select at call time rather than instantiating anything yourself:
providers:
twitter_brand_a: { ... }
twitter_brand_b: { ... }
In the controller, loop the configured account keys and collect results. If the bundle only supports one instance per network, the workaround is a small factory that clones the provider service and overwrites credentials before each call — ugly but common in this generation of bundles.
Every one of these networks will rate-limit you, and a homepage hit should never block on three outbound HTTP calls. Two viable patterns:
app/console social:feed:warm), scheduled every 10–15 minutes. Controllers read cache only.Store the normalized post array in cache, not the raw API payload — that keeps the cache portable across provider rewrites.
The extension point is the provider interface. In practice:
class MastodonProvider implements ProviderInterface
{
public function getPosts(array $options = []): array { /* return Post[] */ }
}
# services.yml
app.social.mastodon_provider:
class: AppBundle\Feed\MastodonProvider
tags: [{ name: genj_social_feed.provider, alias: mastodon }]
Check Resources/config/services.yml for the actual tag name and whether the aggregator is a tagged-service compiler pass or a hardcoded switch. If it's a switch, you're editing vendor code — fork it into src/ instead.
Never hit live APIs in CI. Record one JSON response per network into tests/fixtures/, stub the HTTP layer (Guzzle mock handler or a fake ProviderInterface returning hand-built Post objects), and assert on the merged, sorted output. This is also your regression harness when you rewrite providers.
The APIs underneath are dead — this is the whole story. Last release was September 2016. Since then: Twitter killed the v1.1 endpoints the bundle calls and now requires v2 + OAuth2 app-only or user context; Instagram's legacy API was retired in favour of the Instagram Graph API (Facebook-login + business accounts only); Facebook Graph API versions expire roughly every two years. Expect every provider to return 401/403/410 on a fresh install. Budget for a rewrite of Provider/* and keep only the model, config shape, and rendering.
Composer will fight you. The bundle pins Symfony 2.x/3.x and pulls abandoned SDKs (twitteroauth, instagram PHP SDK, old facebook/graph-sdk) with their own PHP constraints and known CVEs. On PHP 8 / Symfony 5+ it will not resolve; on Laravel it is simply not installable. If you must run it, isolate it in a legacy app or consume it as reference source, not as a dependency.
Escaping is inconsistent. Twitter text arrives HTML-entity-encoded; Instagram captions are plain text with emoji and newlines; Facebook can include markup. Normalize once in your own decorator (decode entities, nl2br, strip tags) before it hits Twig — otherwise you'll either double-escape or open an XSS hole by reaching for |raw.
Timestamps are a minefield. Providers return different formats and timezones (Twitter used a specific D M d H:i:s O Y format). Convert to DateTimeImmutable in UTC at the provider boundary and store that in the model; sorting a merged feed by string dates silently produces garbage ordering.
Token expiry and permissions. Long-lived page tokens expire; Instagram/Facebook require app review for anything beyond your own accounts. Log the raw error body on failure — "401" tells you nothing, the API's message/error_subcode tells you whether it's a token, a scope, or an app-review problem.
Cache stampede and empty-cache rendering. With a 15-minute TTL and a busy page, several requests will miss simultaneously and hammer the API. Use a lock (flock, Redis SETNX, or Symfony's Lock component on modern versions) around the refresh, and always render something — an empty social strip looks broken to stakeholders.
Debugging levers, in order: dump the raw HTTP response before it's mapped to Post (decorate the HTTP client the providers use); check Symfony's profiler for the outbound calls; cache:clear after any config change — this generation of bundles reads config into container parameters only at compile time, so edits appear to do nothing until you clear.
Twig extension override. If the bundle ships a Twig function/filter for rendering, prefer your own partial. Overriding a bundle's Twig extension means matching its exact service ID and argument list, and it breaks silently on minor updates.
Verdict for day-to-day work: do not adopt this as a dependency. Read Provider/ and Model/ for ~30 minutes, then write a ~200-line in-app aggregator (interface + one class per network + a cache decorator) using modern SDKs or plain Guzzle. You get the same normalized-post ergonomics, working APIs, and none of the abandoned-dependency debt. The bundle's real contribution — the "one Post model, many providers, cached aggregate" pattern — is worth copying; the code is not.
How can I help you explore Laravel packages today?