8.1. Service使用

在目录 protected/Core/Service 下有很多预先提供的服务,我们建议你尽可能使用这些服务以保证业务逻辑的正确性。bzfshop 系统本身也是使用这些业务逻辑构造而成的。

8.1.1. 普通Service

我们以 Service/Goods/Goods.php 为例说明 Service 的基本用法:

GoodsBasicService 的用法. 

// 定义一个我们自己喜欢的名字,比如 GoodsBasicService
use Core\Service\Goods\Goods as GoodsBasicService;

// 建立一个服务实例
$goodsBasicService = new GoodsBasicService();

// 取得商品 42107 的数据
$goods = $goodsBasicService->loadGoodsById(42107);

// 取得商品 42107 的关联商品 信息
$linkGoodsArray = $goodsBasicService->fetchLinkGoodsArray(42107);

从上面的方法我们可以看到,所谓服务,其实就是一些 "预定义" 的方法,里面封装了一系列的数据访问。这些方法考虑到了数据之间的关联性,会自动处理数据关联的问题,这就是所谓 业务逻辑。

这里只是一个很简单的例子,更多的业务逻辑就需要你自己去读 Core\Service 中的代码了,所有 Service 都按照目录进行了分类,所以单纯从目录的名字上你应该就能猜出它的用途。

在看这些 Service 的时候你会注意到它们的方法命名有些特别,一些方法以 load 开头,比如 loadGoodsById()loadUserById(),一些方法的名字以 fetch 开头,比如 fetchGoodsArrayByGoodsIdArray()fetchOrderGoodsArray()。这些命名不是随意取的,而是有它特别的意义。

load 只返回一条记录,这个记录其实是 Core\Modal\SqlMapper 对象,这个对象实现了 CRUD 操作,也就是常见的 PHP 框架中说的 ActiveRecord 操作模式(不懂的请自行 Google)。所以 load 返回的对象你是可以直接执行 CRUD 操作的,例如:

load返回的数据对象可以直接做 CRUD 操作. 

// 定义一个我们自己喜欢的名字,比如 GoodsBasicService
use Core\Service\Goods\Goods as GoodsBasicService;

// 建立一个服务实例
$goodsBasicService = new GoodsBasicService();

// 取得商品 42107 的数据
$goods = $goodsBasicService->loadGoodsById(42107);

// load 返回的对象可以直接做 CRUD 操作,实现了 ActiveRecord 的访问方式

// 直接做数据表字段的更新
$goods->goods_name = 'this is a new namge';
#goods->goods_price = 1000;
$goods->save();


// 删除数据库的这条记录
$goods->erase();

fetch 一般返回的是多条记录的数组,这些只是普通的 PHP 数组(PHP 中称为 Associate Array,也就是普通的 array),和数据库已经没有什么关系了。所以 fetch 回来的数据你只能简单作为 array 来操作,不能像 load 的结果一样做数据更新操作。

[注意]

load 方法返回的数据可以支持 CRUD 操作,fetch 方法返回的数据只能当做普通的 array 来使用

8.1.2. BaseService

BaseService 是所有 Service 的父类,它实现了基本的数据库查询操作,大部分的 Service 其实就是根据业务逻辑调用 BaseService 的查询操作而已。所以如果你要扩充自己的 Service 也可以从这里派生。

简单查询

下面是一个单表查询的例子:

单表查询. 

// new 一个 BaseService 对象
$baseService = new BaseService();

// 单表 load,后面带查询条件
$user = $baseService->_loadById('users', 'user_id = ?', 103);

// 单表,多条件查询
$user = $baseService->_loadById('users', 'user_id = ? and age > ? and age < ?', 103, 18, 50);

// 单表,查询一组数据
$suppliersArray =  $baseService->_fetchArray(
            'suppliers', // 表名
            '*', // 字段
            array(
                array('suppliers_name = ?', $supplier_name),
                array('age > ? and age < ?', 18, 50),
            ), // 查询条件
            array('order' => 'suppliers_id desc'), // 排序
            10,   // 偏移 10
            30    // 取 30 条记录
        );

可以看到单表查询很简单,提供 表名 + 查询 条件就可以了,具体的参数意义请自行查看 BaseService 中的代码注释,这里就不多说了。

[警告]

新手 很喜欢自己去拼接 SQL 语句,比如 $sql = 'select * from users where user_name = '.$user_name; 这是一种极端错误的做法。你永远不知道 $user_name 变量会是什么东西,如果是一些特殊字符串比如 $user_name ='%; delete * from users;'; ,你这一拼接就会是灾难的结果。所以不要去拼接 SQL 语句,应该采用 ('user_name = ?',$user_name) 这样的查询方式,最终 bzfshop 会把它转变为 PDO 的 bindParam() 调用,这才是安全的查询。

多表查询

有时候我们需多个表的查询,比如 order_info 表和 order_refer 表做联合查询,如下:

多表联合查询. 

        $baseService->_fetchArray(
            array('order_refer' => 'orf', 'order_info' => 'oi'), // table
            '*',
            array(
                array('orf.order_id = oi.order_id'),
                array('oi.order_id = ?', 1004)
            ), //查询过滤条件
            array('order' => 'oi.order_id desc'),
            $offset,
            $limit,
        );

从代码可以看到,table 参数我们支持传递一个数组 array('order_refer' ⇒ 'orf', 'order_info' ⇒ 'oi') 分别是两个表名,然后在查询条件中传递 array('orf.order_id = oi.order_id') 这样就能实现两个表的联合查询了。同样,如果是 三个、四个、… 更多的表联合查询,无非是数组多几个表名,然后查询条件多一些条件就可以了。

复杂查询

有些时间我们需要写一些更加复杂的查询,不是简单的几个表明就可以了,这些也能支持,如下面复杂的 LEFT JOIN 查询:

复杂的多表查询. 

        // 定义多表的 left join
        $tableLeftJoinStr =
            DataMapper::tableName('order_goods') . ' as og LEFT JOIN ' . DataMapper::tableName('order_refer')
            . ' as orf ON og.order_id = orf.order_id LEFT JOIN ' . DataMapper::tableName('order_info')
            . ' as oi ON og.order_id = oi.order_id';

        // 做复杂查询
        $baseService->_fetchArray(
            $tableLeftJoinStr, //table
            'og.*, oi.user_id, oi.pay_time, oi.add_time', // fields
            array('oi.order_id = ?', 42107),
            array('order' => 'og.order_id desc, og.rec_id desc'),
            $offset,
            $limit,
            $ttl
        );

从上面的代码可以看到, table 参数可以是一个非常复杂的 字符串,里面包含多表的 join 定义,这样我们就能实现非常复杂的查询。

[注意]

注意 DataMapper::tableName() 的使用,由于 bzfshop 支持 表名前缀(例如 bzf_),所以我们通过这个方法来取得真实的表名。具体见 第 8.2 节 “Modal使用”