承接 survos/kit-bundle 相关项目开发

从需求分析到上线部署,全程专人跟进,保证项目质量与交付效率

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

survos/kit-bundle

Composer 安装命令:

composer require survos/kit-bundle

包简介

Base classes and traits for consistent Survos bundle development — auto-registers twig namespaces, asset mapper paths, and doctrine mappings by convention

README 文档

README

Convention-based base class for Symfony 8 bundle development.

The goal is simple: if a Survos bundle has conventional Symfony code, installing the bundle should make that code available without a recipe full of repeated wiring.

  • src/Command/ classes with #[AsCommand] should become console commands.
  • src/Controller/ classes with #[Route] should be routable when the bundle opts into route loading.
  • templates/ should be available as a Twig namespace.
  • assets/ should be available to AssetMapper when the bundle declares an asset package.
  • src/Entity/ can be mapped into Doctrine when the bundle explicitly opts in.

Doctrine deserves special attention: when a bundle maps entities, installing that bundle changes what Doctrine considers part of the application model. That is useful for bundles like key-value-bundle, but it means the app developer must review and apply schema changes.

Two audiences

This document distinguishes two roles:

  • Bundle author — writes the bundle (extends AbstractSurvosBundle or AbstractUxBundle, ships it as a package)
  • App developer — installs and configures the bundle in their Symfony application

For bundle authors

Extend AbstractSurvosBundle

use Survos\Kit\AbstractSurvosBundle;
use Survos\Kit\Traits\HasDoctrineEntities;

final class SurvosMyBundle extends AbstractSurvosBundle
{
    use HasDoctrineEntities;

    protected function doctrineAlias(): string
    {
        return 'My';
    }

    public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void
    {
        parent::loadExtension($config, $container, $builder); // auto-scans Command/, Controller/

        // Only register services that need non-default arguments
        $container->services()
            ->set(MyService::class)
            ->arg('$config', $config);
    }
}

What the base class handles automatically

Convention What is registered
src/Command/ exists Command classes are loaded as services; #[AsCommand] is auto-configured
src/Controller/ exists Controller classes are loaded as services
src/Controller/ exists + HasConfigurableRoutes Controller routes are loaded via attribute scanning
templates/ exists Registered as a Twig namespace — SurvosMyBundle@SurvosMy
assets/ exists + ASSET_PACKAGE const defined Path registered with Symfony AssetMapper
src/Entity/ exists + HasDoctrineEntities ORM mapping is registered; see Doctrine section below

Declaring the bundle dependency

Use #[RequiredBundle] to declare that your bundle needs kit-bundle active. Symfony enforces this automatically — no recipe, no documentation note, no forgotten bundles.php entry:

use Symfony\Component\DependencyInjection\Kernel\RequiredBundle;
use Survos\Kit\SurvosKitBundle;

#[RequiredBundle(SurvosKitBundle::class)]
final class SurvosMyBundle extends AbstractSurvosBundle { ... }

Doctrine Entity Mapping

HasDoctrineEntities opts a bundle into Doctrine ORM mapping. It registers the bundle's src/Entity/ directory as attribute-mapped Doctrine entities during container prepending.

This is not a passive convenience. For app developers, installing a bundle that uses HasDoctrineEntities means Doctrine may discover new mapped classes and therefore new tables, columns, indexes, or relations.

Expected app workflow:

composer require survos/key-value-bundle
php bin/console doctrine:schema:update --dump-sql
php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrate

Bundle author opt-in:

use Survos\Kit\AbstractSurvosBundle;
use Survos\Kit\Traits\HasDoctrineEntities;

final class SurvosMyBundle extends AbstractSurvosBundle
{
    use HasDoctrineEntities;
}

By default, the entity namespace is derived from the bundle namespace:

Survos\MyBundle\SurvosMyBundle
// maps src/Entity as:
Survos\MyBundle\Entity

Override entityNamespace() only when the entities are not in the standard namespace:

protected function entityNamespace(): string
{
    return 'Survos\\MyBundle\\Model\\Doctrine';
}

Override doctrineAlias() only when the default alias, the bundle class name without Bundle, is not what you want.

Route Configuration

Every bundle that uses HasConfigurableRoutes should expose the same two app options:

public function configure(DefinitionConfigurator $definition): void
{
    $children = $definition->rootNode()->children();
    $this->addRouteOptions($children, '/claims');

    // bundle-specific options...
    $children->end();
}

That gives apps parity with traditional config/routes/*.yaml imports:

  • routes_enabled: false means "do not auto-import this bundle's controller attributes"
  • route_prefix: /custom-prefix means "mount the bundle's routes under this URL prefix"

The bundle must then call captureRouteConfig() and registerRouteLoader() from loadExtension(), and addRouteLoaderCompilerPass() from build().

UX / AssetMapper Bundles

Bundles that ship reusable frontend assets should extend AbstractUxBundle. It extends AbstractSurvosBundle, enables AssetMapper registration by default, and registers itself as a no-op compiler pass so asset-heavy bundles can override process() when they need compile time wiring.

use Survos\Kit\AbstractUxBundle;

final class SurvosIiifBundle extends AbstractUxBundle
{
    public const ASSET_PACKAGE = 'iiif';
}

Compiler pass registration on Symfony 8.1+

Symfony 8.1 automatically registers bundles that implement CompilerPassInterface, but it does so at PassConfig::TYPE_BEFORE_OPTIMIZATION with priority -10000. That is later than Survos bundle passes historically ran, and it is too late for passes that prepare service definitions or Twig globals consumed during compilation/rendering.

Do not remove explicit compiler-pass registration just because the bundle implements CompilerPassInterface. Register it deliberately:

use Symfony\Component\DependencyInjection\Compiler\PassConfig;

public function build(ContainerBuilder $container): void
{
    parent::build($container);
    $container->addCompilerPass($this, PassConfig::TYPE_BEFORE_OPTIMIZATION);
}

AbstractUxBundle already does this for UX bundles. Child UX bundles should call parent::build() and add only their extra build-time work, such as route-loader compiler passes. Non-UX bundles that implement CompilerPassInterface should register themselves explicitly with PassConfig::TYPE_BEFORE_OPTIMIZATION unless their pass is truly safe to run at Symfony's late auto-registration priority.

Troubleshooting: a UX controller never loads in the browser

Symptom: a <twig:…> component renders data-controller="survos--foo--bar", but the controller never connects — empty console, no error. This bites every time we add a new UX bundle or symlink one for local dev. There are three independent causes, and you can hit all of them at once. Check them in this order:

1. Stale compiled public/assets/ shadows your changes (the silent one). If public/assets/ exists (left over from a php bin/console asset-map:compile), the dev server serves those frozen files instead of compiling on the fly. Your controllers.json, package.json, and controller-source edits have zero effect and the compiled controllers.js digest never changes — the tell-tale sign. Fix in dev:

rm -rf public/assets var/cache/dev

In prod, re-run asset-map:compile after the bundle changes instead. (Never leave a compiled public/assets/ checked in or sitting in a dev checkout.)

2. The controllers.json key must be the Composer package name, @-prefixed. @symfony/stimulus-bundle's UxPackageReader resolves the key via Composer\InstalledVersions::isInstalled(substr($key, 1)). So the app's assets/controllers.json key must be @survos/iiif-bundle (the composer name survos/iiif-bundle), not @survos/iiif (the npm/asset name). The wrong key throws Could not find package "survos/iiif" referred to from controllers.json.

3. The Stimulus identifier comes from the per-controller "name" in the bundle's assets/package.json — not from the key or the npm name. ControllersMapGenerator builds the identifier from symfony.controllers.<id>.name (slashes → --). Without a name, it falls back to <composer-key>/<controller>, e.g. survos--iiif-bundle--iiif-diva — which will not match a component that references @survos/iiif/iiif-diva (→ survos--iiif--iiif-diva). Always set an explicit name:

// <bundle>/assets/package.json
{
  "name": "@survos/iiif",
  "symfony": {
    "controllers": {
      "iiif-diva": {
        "name": "survos/iiif/iiif-diva",          // → identifier survos--iiif--iiif-diva
        "main": "controllers/diva_viewer_controller.js",
        "enabled": true,
        "fetch": "lazy"
      }
    }
  }
}

Verify the end result without a browser — the compiled map should list your controller:

curl -s "$APP_URL/assets/@symfony/stimulus-bundle/controllers.js" | grep your-controller
# → "survos--iiif--iiif-diva": () => import("../../@survos/iiif/controllers/diva_viewer_controller.js")

Overriding Conventions

Override twigNamespace() to customise or disable Twig path registration:

protected function twigNamespace(): ?string
{
    return 'MyCustomNamespace'; // null to skip entirely
}

Override assetNamespace() or define ASSET_PACKAGE to control AssetMapper registration:

// Option A: constant (preferred)
public const ASSET_PACKAGE = 'claims';  // → @survos/claims

// Option B: method override
protected function assetNamespace(): ?string
{
    return '@survos/claims';
}

For app developers

Most behaviour is automatic. The only knobs exposed are route registration, which apps sometimes need to take over manually.

# config/packages/survos_claims.yaml
survos_claims:
    routes_enabled: true       # default — set false to manage routes yourself
    route_prefix:  /claims     # optional URL prefix for all bundle routes
    list_predicates: []        # bundle-specific options vary per bundle

routes_enabled: false is the escape hatch: the bundle's controllers are still registered as services, but no routes are loaded. Use this when you want to mount the bundle's routes under a custom prefix in your own config/routes/ file, or only expose a subset of them.

Commands, Twig paths, and AssetMapper registration have no on/off toggle — they are unconditional. To suppress a command, use Symfony's built-in console.command tag exclusion or remove the command class from the bundle if you're forking it.

Before / after

A typical bundle before kit-bundle (~90 lines):

// ❌ Before: every class listed, every path hard-coded
public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void
{
    $services = $container->services()->defaults()->autowire()->autoconfigure();
    $services->set(MyRepository::class);
    $services->set(MyImporter::class);
    $services->set(MyProjector::class);
    $services->set(MyService::class)->arg('$config', $config);
    $services->set(MyImportCommand::class);
    $services->set(MyExportCommand::class);
    $services->set(MyTwigExtension::class)->autoconfigure();
    // ...
}

public function prependExtension(ContainerConfigurator $container, ContainerBuilder $builder): void
{
    $builder->prependExtensionConfig('doctrine', [
        'orm' => ['mappings' => [
            'SurvosMyBundle' => [
                'is_bundle' => false,
                'type'      => 'attribute',
                'dir'       => dirname(__DIR__) . '/src/Entity',   // repeated everywhere
                'prefix'    => 'Survos\\MyBundle\\Entity',
                'alias'     => 'My',
            ],
        ]],
    ]);
    $builder->prependExtensionConfig('twig', [
        'paths' => [dirname(__DIR__) . '/templates' => 'SurvosMy'],  // repeated everywhere
    ]);
}

After kit-bundle:

// ✅ After: conventions replace boilerplate
final class SurvosMyBundle extends AbstractSurvosBundle
{
    use HasDoctrineEntities;
    use HasConfigurableRoutes;

    // Doctrine + Twig handled by the base class
    protected function doctrineAlias(): string { return 'My'; }

    public function configure(DefinitionConfigurator $definition): void
    {
        $children = $definition->rootNode()->children();
        $this->addRouteOptions($children, '/my');
        $children->end();
    }

    public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void
    {
        parent::loadExtension($config, $container, $builder); // scans Command/, Controller/
        $this->captureRouteConfig($config);
        $this->registerRouteLoader($builder);

        // Only wire services that need arguments
        $container->services()
            ->set(MyService::class)
            ->arg('$config', $config);
    }

    public function build(ContainerBuilder $container): void
    {
        parent::build($container);
        $this->addRouteLoaderCompilerPass($container);
    }
}

Commands, Twig paths, and entity mappings are handled by convention. Route loading keeps a small explicit hook so each bundle can expose the standard routes_enabled and route_prefix escape hatches.

Requirements

  • PHP 8.4+
  • Symfony 8.1+
  • doctrine/orm — optional, only needed when using HasDoctrineEntities
  • symfony/asset-mapper — optional, only needed for Stimulus / UX bundles

survos/kit-bundle 适用场景与选型建议

survos/kit-bundle 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 478 次下载、GitHub Stars 达 0, 最近一次更新时间为 2026 年 05 月 18 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「symfony」 「bundle」 「traits」 「base-class」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 survos/kit-bundle 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 survos/kit-bundle 我们能提供哪些服务?
定制开发 / 二次开发

基于 survos/kit-bundle 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 478
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 31
  • 依赖项目数: 31
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-05-18