PHP 怎么生成物料清单

wen PHP项目 1

PHP怎么生成物料清单?从递归算法到实战封装的完整指南


📚 目录导读

  1. 为什么用PHP生成物料清单(BOM)?
  2. 物料清单的数据结构设计(数据库表与字段)
  3. 核心算法:递归遍历与层级计算(附代码)
  4. 循环引用与死循环的防御策略
  5. 性能优化:预加载与缓存机制
  6. 实战封装:一个可复用的BOMGenerator类
  7. 常见问题解答(FAQ)
  8. SEO关键词策略与总结建议

为什么用PHP生成物料清单(BOM)?

在制造业、电商ERP或进销存系统中,物料清单(Bill of Materials)是描述产品与子件、原材料层级关系的核心数据模型,尽管Python或Java常被推荐,但PHP凭借其部署简单、生态成熟(Laravel/ThinkPHP),依然是中小型企业内部系统的首选,通过PHP生成BOM,不仅能动态计算成本、库存,还能为生产计划提供实时依据。

PHP 怎么生成物料清单

物料清单的数据结构设计(数据库表与字段)

为了高效生成BOM,至少需要两张表:

-- 产品/物料主档表
CREATE TABLE `item` (
  `id` INT PRIMARY KEY AUTO_INCREMENT,
  `sku` VARCHAR(32) UNIQUE,
  `name` VARCHAR(100),
  `is_assembly` TINYINT(1) DEFAULT 0 -- 1为父件,0为子件
);
-- 物料关系表(核心BOM表)
CREATE TABLE `bom_relation` (
  `id` INT PRIMARY KEY AUTO_INCREMENT,
  `parent_item_id` INT NOT NULL,  -- 父件ID
  `child_item_id` INT NOT NULL,   -- 子件ID
  `quantity` DECIMAL(10,2) NOT NULL DEFAULT 1, -- 用量
  `scrap_rate` DECIMAL(5,2) DEFAULT 0, -- 损耗率%
  UNIQUE KEY `uk_parent_child` (`parent_item_id`,`child_item_id`)
);

设计关键点:通过is_assembly判断是否继续向下展开,避免全表扫描。

核心算法:递归遍历与层级计算(附代码)

生成BOM的本质是多叉树遍历,以下是一个简化的递归实现:

<?php
class BomService {
    private $relations = [];
    // 预加载所有关系,避免重复查询(见性能优化)
    public function loadAllRelations() {
        $db = new PDO('...');
        $stmt = $db->query("SELECT * FROM bom_relation");
        while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
            $this->relations[$row['parent_item_id']][] = $row;
        }
    }
    public function buildBom($itemId, $qty = 1, &$result = [], $depth = 0) {
        $children = $this->relations[$itemId] ?? [];
        foreach ($children as $child) {
            $needQty = $qty * $child['quantity'] * (1 + $child['scrap_rate']/100);
            $name = $this->getItemName($child['child_item_id']); // 从缓存获取
            $result[] = [
                'item_id' => $child['child_item_id'],
                'name' => $name,
                'quantity' => round($needQty, 2),
                'depth' => $depth,
                'is_assembly' => $this->isAssembly($child['child_item_id']) // 判断是否继续
            ];
            // 若为组装件,递归
            if ($this->isAssembly($child['child_item_id'])) {
                $this->buildBom($child['child_item_id'], $needQty, $result, $depth+1);
            }
        }
        return $result;
    }
}
?>

输出示例

  • 深度0:产品A 数量1
  • 深度1:子件B 数量2子件C 数量3
  • 深度2:原材料D 数量4(B的下一层)

循环引用与死循环的防御策略

生产环境中,物料关系可能出现A→B→A的循环错误,使用路径追踪数组

function buildBomSafe($itemId, $qty, &$result, &$path, $depth) {
    if (in_array($itemId, $path)) {
        throw new Exception("循环引用检测到: ".implode('->', $path)."->".$itemId);
    }
    $path[] = $itemId;
    // ... 递归逻辑
    array_pop($path);
}

数据库层应建立触发器禁止插入导致循环的关系(需使用WITH RECURSIVE提前校验)。

性能优化:预加载与缓存机制

  • 一次性加载:如上述代码所示,使用loadAllRelations()把全部关系放入内存,替代递归中N次查询。
  • Redis缓存:将构建好的BOM树序列化存入Redis,键名bom_成品ID_版本号,生产变更时主动失效。
  • 分页/懒加载:如果BOM层级极深(如航空业),改用迭代式栈遍历,避免PHP栈内存溢出。

实战封装:一个可复用的BOMGenerator类

<?php
class BOMGenerator {
    private $itemCache = []; // 产品信息缓存
    private $relationMap = [];
    public function __construct($pdo) {
        // 预加载所有item和relation
        $this->loadItems($pdo);
        $this->loadRelations($pdo);
    }
    // 对外统一入口
    public function generate($finishedGoodsId, $quantity) {
        $path = [];
        $result = [];
        $this->recursiveBom($finishedGoodsId, $quantity, $result, $path, 0);
        return ['tree' => $result, 'total_cost' => $this->calcTotalCost($result)];
    }
    private function recursiveBom($id, $qty, &$result, &$path, $depth) {
        if (isset($path[$id])) { die("循环依赖"); }
        $path[$id] = true;
        foreach ($this->relationMap[$id] ?? [] as $rel) {
            $childQty = $qty * $rel['quantity'];
            // 计算净需求量并保存
            $result[] = ['id'=>$rel['child_id'], ...];
            if ($this->isAssembly($rel['child_id'])) {
                $this->recursiveBom($rel['child_id'], $childQty, $result, $path, $depth+1);
            }
        }
        unset($path[$id]);
    }
}
?>

常见问题解答(FAQ)

Q1:递归次数太多导致PHP报错(超过100层)怎么办?
A1:改用循环+栈数据结构,因为PHP默认递归上限是100-256层(取决于xdebug.max_nesting_level),将递归转换为while循环并手动维护栈数组,可无限扩展。

Q2:BOM中如果同一个子件出现在不同层级,如何汇总数量?
A2:在递归时以item_id为键,累加quantity到哈希表中,最后输出时判断is_assembly标记是否合并。

Q3:如何防止修改BOM时误删父件?
A3:数据库外键设为ON DELETE RESTRICT,并在业务逻辑层检查是否有子件引用。

SEO关键词策略与总结建议

本文核心关键词PHP BOM生成物料清单递归算法Laravel BOM模块多级BOM展开
内链建议:在文中自然嵌入“PHP多层级数组处理”、“MySQL递归查询”相关链接(例如指向站内教程)。
:本文通过完整的表结构设计、递归防死循环代码、全局缓存优化,给出了一个可直接落地的PHP BOM生成方案,重点在于数据预加载而非每条查询,以及路径哨兵机制,建议读者根据自身框架(Laravel/Lumen)封装成服务提供者,并添加单元测试。


扩展阅读:如果你需要输出带“最低层级码”(LLC)的BOM用于MRP计算,请在递归返回时增加后序遍历逻辑,将子件的最小层级向上传递,这是一个高阶优化点,欢迎在评论区留言讨论实现细节。

抱歉,评论功能暂时关闭!