martin-georgiev/social-post
Social Post is a Laravel package for publishing content to multiple social networks from a single API. Compose a post once and share it to Facebook, Twitter/X, LinkedIn and more, with a clean, framework-friendly interface for managing cross-platform social updates.
composer require martin-georgiev/social-post — but read the archived state first. The last release was 2018-08-02, so pin it in a scratch project on PHP 7.1/7.2 to explore it, and treat it as a blueprint you vendor or fork, not a dependency you ship to production on PHP 8.
Where to look first (in the repo, in this order):
src/Publisher.php — the single entry point; it fans a message out to a set of drivers and aggregates results.src/Driver/ — one class per network (Facebook, LinkedIn, Twitter) plus a DriverInterface with roughly publish(string $message, array $options = []) and getMaxMessageLength(): int.src/Exception/ — the failure types the publisher aggregates.publish() call.First use case — post one status update to all three networks and see the truncation behaviour:
use SocialPost\Publisher;
use SocialPost\Driver\Facebook;
use SocialPost\Driver\Twitter;
use SocialPost\Driver\LinkedIn;
$publisher = new Publisher([
'facebook' => new Facebook($fbAppId, $fbAppSecret, $pageAccessToken),
'twitter' => new Twitter($consumerKey, $consumerSecret, $token, $tokenSecret),
'linkedin' => new LinkedIn($clientId, $clientSecret, $accessToken),
], $logger); // PSR-3 LoggerInterface
$publisher->publish('Shipping v2.0 of the API today. https://example.com/blog/v2');
The library deliberately does not do the OAuth dance. You obtain access tokens yourself (or with a dedicated SDK) and hand them in. That is the first thing to internalise: half your integration work lives outside this package.
Wire it as a Laravel singleton with config, not inline news. Put credentials in config/services.php under social.*, build the driver map in a SocialServiceProvider, and bind Publisher as a singleton. Keep one config flag per network so you can disable a broken channel without a deploy.
Fan out per network, not per message. The biggest architectural mistake is calling publish() with all drivers in one request: one Twitter 429 blocks the LinkedIn post and the user waits for three HTTP round-trips. Instead resolve the publisher once, and dispatch one queued job per enabled network:
foreach (config('social.enabled') as $network) {
PublishToNetwork::dispatch($network, $post->id)->onQueue('social');
}
Each job builds a single-driver publisher (or calls the driver directly), catches failures, and records the outcome on the posts row.
Keep a social_posts + social_deliveries table. Store the canonical message once, and one row per network with status, remote_id, error, attempts, published_at. Without remote_id you cannot delete, edit, or de-duplicate after a retry — and retries will happen.
Override the message per network when truncation is unacceptable. A 400-character announcement will be silently chopped on Twitter. Prefer storing body, body_twitter (≤280), body_linkedin and falling back to the publisher's truncation only for incidental content.
Test against fakes, not the network. Implement DriverInterface in a FakeDriver that records calls and optionally throws; assert your publisher/job logic against it. The real drivers are thin Guzzle wrappers, so integration tests are better done as one manual smoke test per network per quarter.
Extension point: the driver map is keyed by network name, so adding Mastodon/Bluesky/Threads is a new class implementing the same two methods plus a config entry — no changes to Publisher. This is the package's genuinely good idea and the reason to copy the abstraction into your own App\Social\ namespace.
publish_actions (posting to a user's timeline) was removed in 2018 — right around this release — and facebook/graph-sdk is abandoned. You must post as a Page with pages_manage_posts and app review, using /{page-id}/feed with a long-lived Page token. Expect to rewrite this driver entirely.getMaxMessageLength() to 140 and may use byte-based substr. Check for mb_substr and bump to 280 (and account for t.co's 23-char URL counting). Twitter/X v1.1 statuses/update also now sits behind paid tiers; v2 requires a different endpoint and payload.people/~/shares with w_share was retired; the modern path is the UGC/Posts API with w_member_social, an author URN (urn:li:person:...), and headers like X-Restli-Protocol-Version: 2.0.0 plus a LinkedIn-Version header. Any 2018 driver will 403/404 immediately.facebook/graph-sdk, abraham/twitteroauth ^1.0, zoonman/linkedin-api-php-client, and php: ^7.0. Installation on PHP 8 needs --ignore-platform-reqs and will still hit removed functions. Don't fight it — fork the two files you need.Http::retry() or Laravel's job $backoff/$tries, and honour Retry-After on 429 yourself.getPrevious(). Log $e->getMessage() and iterate the chain, or you'll see "Could not publish" with no cause for hours.Bottom line for a Laravel team: don't require this in 2024+. Spend an hour reading Publisher and DriverInterface, then write ~200 lines of App\Social\{Publisher, Contracts\Driver, Drivers\*} using the current Graph API, X v2, and LinkedIn Posts API. You keep the one good interface, lose the abandoned dependencies, and gain the retry/queue/idempotency behaviour you'd have built on top anyway.
How can I help you explore Laravel packages today?