PHPackages                             tourze/wechat-work-external-contact-bundle - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. tourze/wechat-work-external-contact-bundle

ActiveSymfony-bundle

tourze/wechat-work-external-contact-bundle
==========================================

企业微信外部联系人管理组件包

0.0.2(11mo ago)0912MITPHPPHP ^8.1CI failing

Since Jun 4Pushed 4mo ago1 watchersCompare

[ Source](https://github.com/tourze/wechat-work-external-contact-bundle)[ Packagist](https://packagist.org/packages/tourze/wechat-work-external-contact-bundle)[ RSS](/packages/tourze-wechat-work-external-contact-bundle/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (2)Dependencies (42)Versions (3)Used By (2)

WeChat Work External Contact Bundle
===================================

[](#wechat-work-external-contact-bundle)

[![PHP Version](https://camo.githubusercontent.com/6518db1335bf20fdff07253dc6d6d0cec955b5fb6a8ef1382ac6d73687ecc07f/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068702d253345253344382e312d626c7565)](https://www.php.net/)[![License](https://camo.githubusercontent.com/f8df3091bbe1149f398a5369b2c39e896766f9f6efba3477c63e9b4aa940ef14/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d677265656e)](LICENSE)[![Build Status](https://camo.githubusercontent.com/7d4414cbb75c840ddb7265e181d65668c1dce61ba985d79d80d4eefa06159878/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6275696c642d70617373696e672d73756363657373)](https://github.com/tourze/php-monorepo)[![Code Coverage](https://camo.githubusercontent.com/e372444a0f578fd50651457b3b96bd8e764d24445d695fb260c0fa4bf7548e59/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f636f7665726167652d39352532352d73756363657373)](https://codecov.io)

[English](README.md) | [中文](README.zh-CN.md)

Table of Contents
-----------------

[](#table-of-contents)

- [Overview](#overview)
- [Features](#features)
- [Installation](#installation)
- [Configuration](#configuration)
- [Dependencies](#dependencies)
- [Usage](#usage)
- [Advanced Usage](#advanced-usage)
- [Events](#events)
- [Commands](#commands)
- [Entities](#entities)
- [Testing](#testing)
- [License](#license)

Overview
--------

[](#overview)

WeChat Work External Contact management bundle for Symfony applications. This bundle provides comprehensive management capabilities for external contacts (customers) in WeChat Work ecosystem including synchronization, event handling, and automated processing.

Features
--------

[](#features)

- External contact (customer) management
- External contact list synchronization
- Welcome message sending for new contacts
- Avatar downloading and storage
- OpenID conversion for payment integration
- Event-driven architecture with Symfony Messenger
- Audit logging for external API interactions
- Cron job automation for background tasks

Installation
------------

[](#installation)

```
composer require tourze/wechat-work-external-contact-bundle
```

Configuration
-------------

[](#configuration)

The bundle uses automatic service configuration. Add the bundle to your `config/bundles.php`:

```
return [
    // ...
    WechatWorkExternalContactBundle\WechatWorkExternalContactBundle::class => ['all' => true],
];
```

Dependencies
------------

[](#dependencies)

### Required Dependencies

[](#required-dependencies)

- PHP &gt;= 8.1
- Symfony &gt;= 6.4
- Doctrine ORM &gt;= 3.0
- WeChat Work Bundle &gt;= 0.1
- Carbon &gt;= 2.72
- League Flysystem &gt;= 3.10

### Dev Dependencies

[](#dev-dependencies)

- PHPUnit &gt;= 10.0
- PHPStan &gt;= 2.1

Usage
-----

[](#usage)

### Commands

[](#commands)

#### Sync External Contact List

[](#sync-external-contact-list)

Synchronizes the external contact list from WeChat Work API:

```
php bin/console wechat-work:sync-external-contact-list
```

This command runs automatically via cron at 4:30 AM daily.

#### Check User Avatar

[](#check-user-avatar)

Downloads and stores external contact avatars:

```
php bin/console wechat-work:external-contact:check-user-avatar
```

This command runs automatically every 8 hours (at :25 minutes).

### API Requests

[](#api-requests)

The bundle provides several request classes for WeChat Work API:

```
use WechatWorkExternalContactBundle\Request\GetExternalContactRequest;
use WechatWorkBundle\Service\WorkService;

// Get external contact details
$request = new GetExternalContactRequest();
$request->setExternalUserId('external_user_id');
$request->setCursor('optional_cursor');

$response = $workService->request($agent, $request);
```

### Entities

[](#entities)

#### ExternalUser

[](#externaluser)

Represents an external contact (customer):

```
use WechatWorkExternalContactBundle\Entity\ExternalUser;

$externalUser = new ExternalUser();
$externalUser->setExternalUserId('wm_xxx');
$externalUser->setName('Customer Name');
$externalUser->setType(1); // 1: WeChat user, 2: Enterprise WeChat user
```

#### ExternalServiceRelation

[](#externalservicerelation)

Manages the relationship between internal users and external contacts:

```
use WechatWorkExternalContactBundle\Entity\ExternalServiceRelation;

$relation = new ExternalServiceRelation();
$relation->setCorp($corp);
$relation->setUser($user);
$relation->setExternalUser($externalUser);
$relation->setAddExternalContactTime(new \DateTimeImmutable());
```

### Procedures

[](#procedures)

#### GetWechatWorkExternalUserDetail

[](#getwechatworkexternaluserdetail)

JSON-RPC procedure to get external user details:

```
// Request
{
    "method": "GetWechatWorkExternalUserDetail",
    "params": {
        "corpId": 123,
        "externalUserId": "wm_xxx"
    }
}

// Response
{
    "id": 1,
    "name": "Customer Name",
    "type": 1,
    "avatar": "https://...",
    "gender": 1
}
```

#### SaveWechatWorkExternalUser

[](#savewechatworkexternaluser)

JSON-RPC procedure to save external user data:

```
// Request
{
    "method": "SaveWechatWorkExternalUser",
    "params": {
        "corpId": 123,
        "agentId": 456,
        "userId": "user123",
        "externalUserId": "wm_xxx",
        "externalContact": {
            "name": "Customer Name",
            "avatar": "https://...",
            "type": 1,
            "gender": 1
        }
    }
}
```

### Events

[](#events)

#### GetExternalUserDetailEvent

[](#getexternaluserdetailevent)

Dispatched when external user details are requested:

```
use WechatWorkExternalContactBundle\Event\GetExternalUserDetailEvent;

// Listen to the event
class ExternalUserListener
{
    public function onGetExternalUserDetail(GetExternalUserDetailEvent $event)
    {
        $externalUser = $event->getExternalUser();
        $corpId = $event->getCorpId();

        // Process external user data
    }
}
```

### Welcome Messages

[](#welcome-messages)

Send welcome messages to new external contacts:

```
use WechatWorkExternalContactBundle\Request\SendWelcomeMessageRequest;
use WechatWorkExternalContactBundle\Request\Attachment\Image;
use WechatWorkExternalContactBundle\Request\Attachment\Link;

$request = new SendWelcomeMessageRequest();
$request->setWelcomeCode('WELCOME_CODE_FROM_WEBHOOK');
$request->setText('Welcome to our service!');

// Add image attachment
$image = new Image();
$image->setMediaId('MEDIA_ID');
$request->addAttachment($image);

// Add link attachment
$link = new Link();
$link->setTitle('Learn More');
$link->setUrl('https://example.com');
$link->setPicUrl('https://example.com/image.jpg');
$link->setDesc('Click to learn more about our services');
$request->addAttachment($link);

$response = $workService->request($agent, $request);
```

Advanced Usage
--------------

[](#advanced-usage)

### Custom Event Listeners

[](#custom-event-listeners)

You can create custom event listeners to extend the bundle's functionality:

```
use WechatWorkServerBundle\Event\WechatWorkServerMessageRequestEvent;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;

#[AsEventListener]
class CustomExternalContactListener
{
    public function onServerMessageRequest(WechatWorkServerMessageRequestEvent $event): void
    {
        $message = $event->getMessage()->getRawData();

        if (!isset($message['ExternalUserID'])) {
            return;
        }

        // Custom processing logic
        $this->processExternalContact($message);
    }

    private function processExternalContact(array $message): void
    {
        // Your custom logic here
    }
}
```

### Extending Entities

[](#extending-entities)

Add custom fields to external contacts:

```
use WechatWorkExternalContactBundle\Entity\ExternalUser;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
class CustomExternalUser extends ExternalUser
{
    #[ORM\Column(type: 'string', nullable: true)]
    private ?string $customField = null;

    public function getCustomField(): ?string
    {
        return $this->customField;
    }

    public function setCustomField(?string $customField): self
    {
        $this->customField = $customField;
        return $this;
    }
}
```

### Message Queue Configuration

[](#message-queue-configuration)

Configure Symfony Messenger for async processing:

```
# config/packages/messenger.yaml
framework:
    messenger:
        transports:
            external_contact:
                dsn: 'doctrine://default'
                options:
                    queue_name: external_contact
        routing:
            'WechatWorkExternalContactBundle\Message\SaveExternalContactListItemMessage': external_contact
```

### Custom Commands

[](#custom-commands)

Create custom commands extending the base functionality:

```
use WechatWorkExternalContactBundle\Command\SyncExternalContactListCommand;

class CustomSyncCommand extends SyncExternalContactListCommand
{
    protected function configure(): void
    {
        parent::configure();
        $this->setName('app:custom-sync-external-contacts');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        // Pre-processing
        $this->customPreProcessing();

        // Run original sync
        $result = parent::execute($input, $output);

        // Post-processing
        $this->customPostProcessing();

        return $result;
    }
}
```

Events
------

[](#events-1)

The bundle dispatches several events that you can listen to:

### WechatWorkServerMessageRequestEvent

[](#wechatworkservermessagerequestevent)

Triggered when a WeChat Work server message is received:

```
use WechatWorkServerBundle\Event\WechatWorkServerMessageRequestEvent;

public function onServerMessage(WechatWorkServerMessageRequestEvent $event): void
{
    $message = $event->getMessage();
    $rawData = $message->getRawData();

    // Handle different event types
    match ($rawData['ChangeType'] ?? null) {
        'add_external_contact' => $this->handleAddContact($rawData),
        'del_external_contact' => $this->handleDeleteContact($rawData),
        'add_half_external_contact' => $this->handleHalfContact($rawData),
        default => null,
    };
}
```

Commands
--------

[](#commands-1)

### Available Commands

[](#available-commands)

CommandDescriptionSchedule`wechat-work:sync-external-contact-list`Sync external contact listDaily 04:30`wechat-work:external-contact:check-user-avatar`Download and store avatarsEvery 8 hours### Command Examples

[](#command-examples)

```
# Manual sync
php bin/console wechat-work:sync-external-contact-list

# Check specific avatar
php bin/console wechat-work:external-contact:check-user-avatar

# Run with verbose output
php bin/console wechat-work:sync-external-contact-list -v
```

Entities
--------

[](#entities-1)

### ExternalUser

[](#externaluser-1)

Main entity representing external contacts:

```
use WechatWorkExternalContactBundle\Entity\ExternalUser;

$externalUser = new ExternalUser();
$externalUser->setNickname('Customer Name');
$externalUser->setExternalUserId('wm_external_123');
$externalUser->setAvatar('https://example.com/avatar.jpg');
$externalUser->setGender(1); // 1=male, 2=female, 0=unknown
```

### ExternalServiceRelation

[](#externalservicerelation-1)

Represents the relationship between internal users and external contacts:

```
use WechatWorkExternalContactBundle\Entity\ExternalServiceRelation;

$relation = new ExternalServiceRelation();
$relation->setUser($internalUser);
$relation->setExternalUser($externalUser);
$relation->setAddExternalContactTime(new \DateTimeImmutable());
```

Architecture
------------

[](#architecture)

The bundle follows Symfony best practices:

- **Commands**: Console commands for batch operations
- **Controllers**: REST API endpoints
- **Entities**: Doctrine entities for data persistence
- **Events**: Event-driven communication
- **Message Handlers**: Async processing with Symfony Messenger
- **Procedures**: JSON-RPC procedures for internal APIs
- **Repositories**: Data access layer
- **Requests**: WeChat Work API request objects

Testing
-------

[](#testing)

Run the test suite:

```
./vendor/bin/phpunit packages/wechat-work-external-contact-bundle/tests
```

The test suite includes:

- ✅ **Unit Tests**: 358 tests covering entities, requests, events, and messages
- ✅ **PHPStan Analysis**: Level 9 static analysis with zero errors
- ⚠️ **Integration Tests**: Temporarily disabled due to complex service dependencies

**Note**: Complex integration tests requiring HttpClientBundle\\Service\\SmartHttpClient are tracked in [GitHub Issue #931](https://github.com/tourze/php-monorepo/issues/931). All unit tests and core functionality tests pass successfully (358/358 tests).

License
-------

[](#license)

This bundle is part of the Tourze PHP Monorepo and follows the same license terms.

Documentation
-------------

[](#documentation)

For more information about WeChat Work External Contact API, see:

- [Official Documentation](https://developer.work.weixin.qq.com/document/path/92109)

###  Health Score

31

—

LowBetter than 68% of packages

Maintenance64

Regular maintenance activity

Popularity9

Limited adoption so far

Community11

Small or concentrated contributor base

Maturity36

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

2

Last Release

340d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/e354fdb316da535dfa8ba2e9193a473c403b6bc6fb9170778d1dc50e304c6e9d?d=identicon)[tourze](/maintainers/tourze)

---

Top Contributors

[![tourze](https://avatars.githubusercontent.com/u/13899502?v=4)](https://github.com/tourze "tourze (26 commits)")

---

Tags

wechat

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/tourze-wechat-work-external-contact-bundle/health.svg)

```
[![Health](https://phpackages.com/badges/tourze-wechat-work-external-contact-bundle/health.svg)](https://phpackages.com/packages/tourze-wechat-work-external-contact-bundle)
```

###  Alternatives

[sulu/sulu

Core framework that implements the functionality of the Sulu content management system

1.3k1.3M152](/packages/sulu-sulu)[sylius/sylius

E-Commerce platform for PHP, based on Symfony framework.

8.4k5.6M651](/packages/sylius-sylius)[contao/core-bundle

Contao Open Source CMS

1231.6M2.4k](/packages/contao-core-bundle)[shopware/platform

The Shopware e-commerce core

3.3k1.5M3](/packages/shopware-platform)[ec-cube/ec-cube

EC-CUBE EC open platform.

78527.0k1](/packages/ec-cube-ec-cube)[open-dxp/opendxp

Content &amp; Product Management Framework (CMS/PIM)

7310.3k29](/packages/open-dxp-opendxp)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
