netbull/core-bundle
Composer 安装命令:
composer require netbull/core-bundle
包简介
Symfony utility bundle: entity-reference and phone-number form types, three-query Doctrine paginator, spatial/range/phone DBAL types, JS route export and Twig helpers
README 文档
README
A Symfony utility bundle bundling the pieces NetBull apps share: entity-reference (AJAX/Select2) and phone-number form types, a three-query Doctrine paginator with sortable-column Twig helpers, custom DBAL types (spatial, integer range, phone number), a route exporter for frontend JavaScript, and assorted Twig filters.
- PHP: >= 8.3
- Symfony: 7.4 (LTS)
Installation
composer require netbull/core-bundle
Register the bundle
With Symfony Flex this is automatic. Otherwise add it to config/bundles.php:
return [ // ... NetBull\CoreBundle\NetBullCoreBundle::class => ['all' => true], ];
Configuration
Everything is optional — the bundle works with an empty netbull_core: config. Defaults shown:
# config/packages/netbull_core.yaml netbull_core: # JS routing export (bin/console netbull:core:js-routing) js_routes_path: null # output file, relative to the project dir, e.g. 'assets/js/router.js' js_type: 'js' # 'js' (window.Netbull.Router) or 'es6' (ES module) # Defaults for the AjaxType / Select2Type widgets form_types: ajax: minimum_input_length: 1 page_limit: 10 allow_clear: false delay: 250 language: 'en' cache: true # Classes/icons used by the pagination_sortable() Twig function (FontAwesome by default) paginator: sortable: icons: none: 'fa fa-sort' asc: 'fa fa-sort-up' desc: 'fa fa-sort-down' active_class: 'text-success' not_active_class: 'text-primary'
To use the bundle's form widgets, register its form themes:
# config/packages/twig.yaml twig: form_themes: - '@NetBullCore/Form/forms.html.twig' - '@NetBullCore/Form/phone_number.html.twig' # or phone_number_bootstrap.html.twig
The markup is Bootstrap-flavored and uses FontAwesome icons; the select widgets emit Select2 data attributes — the app ships and initializes the JS itself.
Pagination
The Paginator service runs three queries — a COUNT, an IDs query (LIMIT/OFFSET + ORDER BY on
primary keys only), and a data query restricted to those IDs — so large offsets stay fast and
joins never break the limit. Page (page/currentPage), page size (perPage/pageSize, the
literal all disables the limit) and sorting (field + direction) are sniffed from the
request automatically.
Have the repository implement PaginatorRepositoryInterface:
class AccountRepository extends ServiceEntityRepository implements PaginatorRepositoryInterface { public function getPaginationCount(array $params = []): QueryBuilder { return $this->createQueryBuilder('a')->select('COUNT(a.id)'); } public function getPaginationIds(array $params = []): QueryBuilder { return $this->createQueryBuilder('a')->select('a.id'); } public function getPaginationQuery(array $params = []): QueryBuilder { return $this->createQueryBuilder('a')->select('a', 'p')->leftJoin('a.profile', 'p'); } }
Wire the three builders in the controller (the service is autowirable):
public function index(AccountRepository $repo, Paginator $paginator): JsonResponse { $params = []; // your filters // default sort — guard it, setSorting() replaces the sorting sniffed from the request if (!$paginator->getSorting()) { $paginator->setSorting(new Sorting('a.createdAt', Sorting::DIRECTION_DESC)); } $paginator ->setCountQuery($repo->getPaginationCount($params)) ->setIdsQuery($repo->getPaginationIds($params)) ->setQuery($repo->getPaginationQuery($params)); return $this->json($paginator->paginateShort()); // { items: [...], pagination: { currentPage, pageSize, totalItems } } }
paginate() returns a richer pagination array (page ranges, route, sorting, ...) for classic
server-rendered lists; setItemNormalizer(fn (array $row) => ...) maps every item before output.
PaginatorSimple is a one-query variant for when you already hold the filtered ID list
(setIds([['id' => 1], ...]) + setQuery(...)).
FIELD() requirement — the data query re-orders rows with MySQL's
FIELD(), which Doctrine doesn't know natively. Register it once (the bundle shipsbeberlei/doctrineextensionsfor this):# config/packages/doctrine.yaml doctrine: orm: dql: string_functions: FIELD: DoctrineExtensions\Query\Mysql\Field
Sortable column headers in Twig
{# `pagination` is the array from paginator.paginate() #} <th>{{ pagination_sortable(pagination, 'Name', 'a.name') }}</th>
Clicking cycles ascending → descending → cleared. A default Sorting must be set on the
paginator for the links to render. query_inputs('q') emits hidden inputs preserving the other
query parameters inside a GET filter form.
Form types
| Type | What it does |
|---|---|
DynamicType |
Entity <select> that renders only the selected option(s); the app's JS loads the rest |
AjaxType |
DynamicType + remote search: generates an endpoint URL from a route, emits Select2 AJAX data attributes |
Select2Type |
AjaxType + full Select2 config (allow_clear, delay, language, cache, tags) |
EntityHiddenType |
Entity reference in a hidden input (id ↔ entity via model transformer) |
AutoCollectionType |
Core CollectionType rendered with a data-prototype + "Add new" button widget |
UnorderedCollectionType |
Collection keyed/matched by an identifier property instead of array position |
CompoundRangeType |
min/max integer pair stored as a "min-max" string |
MoneyType |
Core MoneyType with fixed 1.234,56-style separators regardless of locale (localize: true opts back out) |
PhoneNumberType |
libphonenumber-backed input — single text or country-select + national number |
PointType / PointTextType |
"lat, lng" hidden/text input mapped to the Point value object |
$builder->add('customer', AjaxType::class, [ 'class' => Customer::class, 'text_property' => 'name', 'remote_route' => 'app_customers_search', // generated with ?perPage=<page_limit> 'minimum_input_length' => 2, 'placeholder' => 'Search customers...', 'multiple' => false, ]);
The endpoint receives the Select2 request and should return the stock Select2 shape
{ results: [{ id, text }], pagination: { more } } (or the app's JS overrides
processResults). Submitted values are entity primary keys (primary_key option, default
id).
UnorderedCollectionType matches submitted rows to existing items by an identifier instead of
their index, so reordered/partial submissions behave predictably; duplicate identifiers are
rejected with a form error:
$builder->add('contacts', UnorderedCollectionType::class, [ 'entry_type' => ContactType::class, 'property' => 'id', 'allow_add' => true, 'allow_delete' => true, ]);
$builder->add('phone', PhoneNumberType::class, [ 'widget' => PhoneNumberType::WIDGET_COUNTRY_CHOICE, 'country_choices' => ['BG', 'DE', 'GB'], 'preferred_country_choices' => ['BG'], ]);
Doctrine utilities
DBAL column types
Register the ones you use:
# config/packages/doctrine.yaml doctrine: dbal: types: point: NetBull\CoreBundle\ORM\Types\Point range: NetBull\CoreBundle\ORM\Types\Range phone_number: NetBull\CoreBundle\ORM\Types\PhoneNumber geometry: NetBull\CoreBundle\ORM\Types\Geometry linestring: NetBull\CoreBundle\ORM\Types\Linestring multilinestring: NetBull\CoreBundle\ORM\Types\MultiLinestring multipolygon: NetBull\CoreBundle\ORM\Types\Multipolygon
use NetBull\CoreBundle\ORM\Objects\Point; #[ORM\Column(type: 'point', nullable: true)] private ?Point $coordinates = null; // new Point($latitude, $longitude) #[ORM\Column(type: 'phone_number', nullable: true)] private ?\libphonenumber\PhoneNumber $phone = null; // stored as E.164, VARCHAR(35)
pointmaps to a MySQLPOINTcolumn viaST_PointFromText/ST_AsText.rangestoresNetBull\CoreBundle\ORM\Objects\Rangeas a"min-max"string — create the column manually (string), schema generation does not emit a type for it.- The four spatial types (
geometry,linestring,multilinestring,multipolygon) validate WKT through geoPHP —composer require itamair/geophpto use them. All spatial SQL targets MySQL/MariaDB. Note thatgeometryaccepts POLYGON/MULTIPOLYGON WKT only, despite the generic name.
GREATEST() in DQL
doctrine: orm: dql: numeric_functions: GREATEST: NetBull\CoreBundle\Query\Mysql\Greatest
SELECT GREATEST(p.updatedAt, p.createdAt) FROM App\Entity\Page p
Phone number validation
use NetBull\CoreBundle\Validator\Constraints\PhoneNumber; #[PhoneNumber(defaultRegion: 'BG', type: PhoneNumber::MOBILE)] private ?string $phone = null;
Accepts strings, stringables or libphonenumber\PhoneNumber objects; with the default
defaultRegion (ZZ) the value must be in international format (+359...). Type-specific
messages out of the box (mobile, fixed_line, toll_free, ...). Pair with the
phone_number DBAL type and PhoneNumberType form type.
In Twig:
{{ user.phone|phone_number_format }} {# INTERNATIONAL by default #}
{% if user.phone is phone_number_of_type(constant('libphonenumber\\PhoneNumberType::MOBILE')) %}...{% endif %}
JS routing
Export routes to the frontend without FOSJsRoutingBundle. Only routes marked with the
expose option are dumped:
#[Route('/api/items/{id}', name: 'app_item', options: ['expose' => true])]
netbull_core: js_routes_path: 'assets/js/router.js' js_type: 'js' # or 'es6'
bin/console netbull:core:js-routing # writes <project>/assets/js/router.js bin/console netbull:core:js-routing --target=/tmp/router.js
The js flavor defines window.Netbull.Router with one function per route:
const url = window.Netbull.Router.app_item(42); // "/api/items/42"
The es6 flavor default-exports a router module:
import Router from './router'; const url = Router.get('app_item', 42); // "/api/items/42"
Parameters are substituted positionally as-is (no URL-encoding).
Twig helpers
| Helper | Kind | Example |
|---|---|---|
pagination_sortable(pagination, label, field) |
function | sortable <th> link (see Pagination) |
query_inputs(currentField) |
function | hidden inputs preserving current query params |
helperText(text) |
function | FontAwesome question-mark tooltip icon |
lipsum(length = 30) |
function | lorem-ipsum filler text |
inflect(count = 0) |
filter | {{ 'item'|inflect(items|length) }} → "item"/"items" |
titleize |
filter | {{ 'first_name'|titleize }} → "First Name" |
country(locale = '') |
filter | {{ 'BG'|country }} → "Bulgaria" |
strip_tags_super |
filter | extracts the <body> text of a full HTML document |
phone_number_format(format?) |
filter | formats a libphonenumber\PhoneNumber |
phone_number_of_type(type) |
test | checks the number type |
Utilities
Inflect— English pluralize/singularize/titleize/underscore/humanize (powers the Twig filters).PrintLabels— TCPDF subclass that prints text labels onto A4 sticker sheets (built-inLabels5×13 andOrderLabels3×7 grids). Legacy; requirescomposer require tecnickcom/tcpdf.
Development
composer test # PHPUnit composer phpstan # static analysis composer cs-check # coding standards (php-cs-fixer, dry-run) composer check # all of the above
License
netbull/core-bundle 适用场景与选型建议
netbull/core-bundle 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 16.73k 次下载、GitHub Stars 达 0, 最近一次更新时间为 2017 年 03 月 08 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「symfony」 「paginator」 「bundle」 「doctrine」 「Forms」 「select2」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 netbull/core-bundle 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 netbull/core-bundle 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 netbull/core-bundle 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
2lenet/EasyAdminPlusBundle
The bundle for easy using json-rpc api on your project
Provide a way to secure accesses to all routes of an symfony application.
DMS Meetup API Bundle, enables Meetup API clients in services
Symfony DataGridBundle
MongoDB Adapter to be used with Laminas Paginator
统计信息
- 总下载量: 16.73k
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 25
- 依赖项目数: 1
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2017-03-08