tourze/wechat-official-account-material-bundle
A Symfony bundle for managing WeChat Official Account materials. It offers services, entities, and config to upload, list, and sync images, voice, video, and news items via the WeChat API, with DI integration that simplifies persistent material storage and retrieval in PHP apps.
This is a Symfony bundle for managing WeChat Official Account (公众号) materials — temporary media, permanent media, and news/图文 articles — published by the Tourze collective. Version 0.1.0 is the first release with a real changelog diff against 0.0.1, but the notes are just a compare link: treat the commit history as the documentation.
composer require tourze/wechat-official-account-material-bundle:0.1.*
Because it is a bundle and not a Laravel package, there is no package auto-discovery. Wire it yourself:
vendor/tourze/wechat-official-account-material-bundle/composer.json — check for "type": "symfony-bundle" and any symfony/framework-bundle requirement. If the latter is present, expect container/event-dispatcher baggage and prefer a thin adapter over full registration.src/ for the *Bundle.php class and DependencyInjection/Configuration.php. That config tree is the only accurate reference for options — no README, no config docs.tests/ is the second place to look; tourze bundles usually have functional tests that show the expected service wiring.WechatMaterialServiceProvider (bind the config array, tag the bundle class), or instantiate the manager classes directly with new if the bundle is thin.First use case: upload a temporary image and use the returned media_id to reply to a follower message. Get appid/secret from 微信公众平台 → 开发 → 基本配置, whitelist your server egress IP, and store them in config/wechat.php:
return [
'official_account' => [
'app_id' => env('WECHAT_APP_ID'),
'secret' => env('WECHAT_SECRET'),
],
];
Token handling is the core pattern. Every material call needs access_token. Do not let the bundle fetch it per request — cache it and refresh on 40001/42001:
$token = Cache::remember('wechat:token:'.$appId, 7000, fn () => $client->fetchToken());
$result = $material->uploadTemp('image', $path);
if (in_array($result['errcode'] ?? 0, [40001, 42001])) {
Cache::forget('wechat:token:'.$appId);
// retry once with a refreshed token
}
Wrap the retry in Cache::lock("wechat:token:{$appId}") so a burst of queue workers doesn't stampede the token endpoint and invalidate each other's tokens.
Persist media_id — don't treat WeChat as your database. Add a local model:
Schema::create('wechat_materials', function (Blueprint $t) {
$t->id();
$t->string('media_id')->unique();
$t->string('type'); // image|voice|video|thumb|news
$t->boolean('permanent');
$t->string('url')->nullable();
$t->timestamp('expires_at')->nullable(); // temp media: ~3 days
$t->json('meta')->nullable();
$t->timestamps();
});
This gives you listing/search/permissions that the WeChat batchget API does not (it paginates at 20 items and is slow).
Typical flows you'll implement:
uploadTemp(type, file) → auto-reply media, expires in ~3 days.uploadPermanent(type, file, title, introduction) → image/voice/video, then deletePermanent(media_id) when replaced.uploadNews([...articles]) → 图文; each article needs a thumb_media_id that is itself a permanent thumb upload.batchGet(type, offset, count) → sync job that mirrors counts into your DB nightly.Use jobs for anything bulk. Importing a folder of images should be one job per file with ->onQueue('wechat') and rate limiting (Redis::throttle) — WeChat's per-app quotas are strict and a synchronous loop will hit 45009.
Testing: fake the HTTP layer (Guzzle mock handler or Http::fake if the bundle uses the Laravel client) and assert on the request body, especially multipart boundaries for uploads. Snapshot the JSON error envelope so a bundle upgrade that changes error shape fails loudly.
extra.laravel providers, no publish tags, no facades. Budget half a day for wiring and expect to write a thin WechatMaterial adapter of your own rather than exposing bundle classes to your domain code.0.1.0 has no human-readable release notes. Pin 0.1.* and read the GitHub compare diff before bumping. Pre-1.0 tourze packages rename classes freely between minors.media_ids expire (3 days) and silently break auto-replies; permanent materials are capped (roughly 5,000 images / 1,000 news items) and users can delete them from the admin UI, orphaning your DB rows. Store the flag explicitly and reconcile.errcode; 0 or missing means success. Common codes: 40007 invalid media_id, 40009 invalid image, 45009 API quota, 48001 API unauthorised (feature not enabled on the account type).40009 wastes quota.media_ids and two quota charges. Hash files locally and check your table first.errmsg — it is more specific than any mapping you'll write.40164, not a helpful message.How can I help you explore Laravel packages today?