Banner相关表分析
banner指banner位,banner_item是具体的banner项
一个banner位有多个banner_item,一个banner_item是能属于一个banner,所以是一对多的关系
两者通过banner_item的banner_id外键关联
banner表
banner_item表
banner_item 还有一个字段img_id与image表关联
image表
模型关联
applicationapimodelBanner.php
<?php
namespace appapimodel;
class Banner extends Model
{
public function items()
{
//参数:1.关联的模型BannerItem2.关联BannerItem的外键3.当前模型的主键
return $this->hasMany('BannerItem', 'banner_id', 'id');
}
}
使用
applicationapicontrollerv1Banner.php
<?php
namespace appapicontrollerv1;
use appapivalidateIDMustBePositiveInt;
use appapimodelBanner as BannerModel;
/**
* Banner资源
*/
class Banner
{
public function getBanner($id)
{
$validate = new IDMustBePositiveInt();
$validate->goCheck();
$banner = BannerModel::with('items')->find($id);
if (!$banner) {
throw new BannerMissException();
}
return $banner;
}
}
查看返回结果
使用命令新建Image模型
php think make:model api/Image
<?php
namespace appapimodel;
use thinkModel;
class Image extends Model
{
}
关联到Image模型
applicationapimodelBannerItem.php
<?php
namespace appapimodel;
use thinkModel;
class BannerItem extends Model
{
public function img()
{
//一对一关系使用
//参数:1.关联模型名2.关联外键3.关联模型主键
return $this->belongsTo('Image', 'img_id', 'id');
}
}
使用
applicationapicontrollerv1Banner.php
class Banner
{
public function getBanner($id)
{
$validate = new IDMustBePositiveInt();
$validate->goCheck();
//嵌套关联查询
$banner = BannerModel::with(['items', 'items.img'])->find($id);
return $banner;
}
}
返回的数据
从规范性来看,把查询的代码封装下更好
applicationapimodelBanner.php
class Banner extends Model
{
public function items()
{
return $this->hasMany('BannerItem', 'banner_id', 'id');
}
public static function getBannerById($id)
{
$banner = self::with(['items','items.img'])
->find($id);
return $banner;
}
}
controller/v1/banner.php
class Banner
{
public function getBanner($id)
{
$validate = new IDMustBePositiveInt();
$validate->goCheck();
$banner = BannerModel::getBannerById($id);
if (!$banner) {
throw new BannerMissException();
}
return $banner;
}
}
隐藏模型字段
返回的因为是受保护的类型,不能直接访问其成员,要先转成数组
class Banner
{
public function getBanner($id)
{
$validate = new IDMustBePositiveInt();
$validate->goCheck();
$banner = BannerModel::getBannerById($id);
$data = $banner->toArray();
unset($data['dalete_time']); //删除delete_time字段
if (!$banner) {
throw new BannerMissException();
}
return $data;
}
}
上面的方法不推荐使用,建议用模型的方法
class Banner
{
public function getBanner($id)
{
$validate = new IDMustBePositiveInt();
$validate->goCheck();
$banner = BannerModel::getBannerById($id);
if (!$banner) {
throw new MissException([
'msg' => '请求banner不存在',
'errorCode' => 40000
]);
}
//传进一个要隐藏的数组
$banner->hidden(['delete_time', 'update_time'])
//只显示数组内的字段
// $banner->visible(['id'])
return $banner;
}
}
查看返回结果
在模型内部隐藏字段
applicationapimodelBanner.php
class Banner extends Model
{
protected $hidden = ['id']; // visible方法也可用
}
查看返回结果
图片资源url配置
如果图片存在本地服务器上,在数据库url字段用相对路径,可以适应不同的根目录配置。
在application新建文件夹extra,这里面的配置文件会被tp5自动加载
applicationextrasetting.php
<?php
return [
//将图片资源images文件夹放在zergpublic文件夹下
'img_prefix' => 'http://127.0.0.1:8000/public/images'
];
读取器的妙用
applicationapimodelImage.php
<?php
namespace appapimodel;
use thinkModel;
class Image extends Model
{
protected $hidden = ['delete_time', 'id', 'from'];
//这个命名是特定的,表示是读取器,get和attr是固定式,url是字段名
public function getUrlAttr($value, $data) {
$finalUrl = $value;
if ($data['from'] == 1) {
//本地图片
//使用了助手函数config这里可以成功拿到图片的基地址
$finalUrl = config('setting.img_prefix').$value;
}
//如未通过以上判断说明是网络图片
return $finalUrl;
}
}
查看返回结果
自定义模型基类
提取一个基类优化结构
命令
php think make:model api/BaseModel
applicationapimodelBaseModel.php
<?php
namespace appapimodel;
use thinkModel;
class BaseModel extends Model
{
protected $hidden = ['delete_time'];
protected function prefixImgUrl($value, $data){
$finalUrl = $value;
if($data['from'] == 1){
$finalUrl = config('setting.img_prefix').$value;
}
return $finalUrl;
}
}
重新定义读取器
Img.php
<?php
namespace appapimodel;
use thinkModel;
class Image extends BaseModel
{
protected $hidden = ['delete_time', 'id', 'from'];
public function getUrlAttr($value, $data)
{
return $this->prefixImgUrl($value, $data);
}
}
让所有的模型都继承自己写的BaseModel
定义API版本号
能更好适应业务变化
复制applicationapicontrollerv1Banner.php到
applicationapicontrollerv2Banner.php (仅用于测试)
新增路由
application
oute.php
Route::get('api/:version/banner/:id', 'api/:version.Banner/getBanner');
此时可以通过更改url调用接口的不同版本
专题接口模型分析
一个theme下面有多个商品product,如果用一个字段表示拓展性就差了,这里可以新增中间表theme_product表示
theme表
product表
theme_product表
创建Theme和Product模型类
applicationapimodelTheme.php
class Theme extends BaseModel
{
//
}
class Product extends BaseModel
{
//
}
没必要建立中间表的模型类,tp5已经自动处理
一对一关系
applicationapimodelTheme.php
class Theme extends BaseModel
{
public function topicImg()
{
return $this->belongsTo('Image', 'topic_img_id', 'id');
}
public function headImg()
{
return $this->belongsTo('Image', 'head_img_id', 'id');
}
}
一对一也有主从关系
主题Theme有关联Image表的外键,而Image表中并没有此类字段,如果用Image关联Theme则要用hasOne
theme表
image表
theme接口验证与重构
定义路由
application
oute.php
Route::get('api/:version/theme', 'api/:version.Theme/getSimpleList');
applicationapicontrollerv1Theme.php
class Theme
{
//返回一组theme模型
public function getSimpleList($ids='') {
}
}
applicationapivalidateIDCollection.php
<?php
namespace appapivalidate;
class IDCollection extends BaseValidate
{
// 千万不要在require|checkIDS中加空格
// 不然你会哭的
// 源码中是没有去处多余空格的判断的
// 这将导致验证不执行
protected $rule = [
'ids' => 'require|checkIDs'
];
//这个message和rule一样也是内置的,如果规则未通过则用此信息通知
protected $message = [
'ids' => 'ids参数必须为以逗号分隔的多个正整数,仔细看文档啊'
];
protected function checkIDs($value)
{
$values = explode(',', $value);
if (empty($values)) {
return false;
}
foreach ($values as $id) {
if (!$this->isPositiveInteger($id)) {
// 必须是正整数
return false;
}
}
return true;
}
}
上面的如果要生效还要更改下验证器基类
applicationapivalidateBaseValidate.php
class BaseValidate extends Validate
{
// 检测所有客户端发来的参数是否符合验证类规则
public function goCheck()
{
$request = Request::instance();
$params = $request->param();
//同时校验
$result = $this->batch()->check($params);
if (!$result) {
$e = new ParameterException([
'msg' => $this->error,
'code' => 400,
'errorCode' => 10002
]);
throw $e;
} else {
return true;
}
}
//把applicationapivalidateIDMustBePositiveInt.php的方法移动到这
protected function isPositiveInteger($value, $rule='', $data='', $field='')
{
if (is_numeric($value) && is_int($value + 0) && ($value + 0) > 0) {
return true;
}
//不能返回字符串,否则会被认为通过验证
//return $field . '必须是正整数';
return false;
}
}
applicationapicontrollerv1Theme.php
class Theme
{
public function getSimpleList($ids='')
{
(new IDCollection())->goCheck();
return 'success'
}
}
测试,如果返回success表示通过验证
完成Theme简要信息接口
applicationapicontrollerv1Theme.php
class Theme
{
public function getSimpleList($ids='') {
(new IDCollection())->goCheck();
$ids = explode(',', $ids);
$result = ThemeModel::with('topicImg,headImg')->select($ids);
if (!$result) {
throw new ThemeException();
}
return $result;
}
}
applicationlibexceptionThemeException.php
<?php
namespace applibexception;
class ThemeException extends BaseException
{
public $code = 404;
public $msg = '指定主题不存在,请检查主题ID';
public $errorCode = 30000;
}
把不需要返回给客户端的字段隐藏
applicationapimodelTheme.php
class Theme extends BaseModel
{
protected $hidden = ['delete_time', 'topic_img_id', 'head_img_id'];
//...
}
theme详情接口
多对多关系
applicationapimodelTheme.php
class Theme extends BaseModel
{
public function products()
{
//1.关联模型名2.中间表名3.中间表的当前模型外键4.中间表的当前模型关联键
return $this->belongsToMany(
'Product', 'theme_product', 'product_id', 'theme_id');
}
}
applicationapicontrollerv1Theme.php
class Theme
{
public function getSimpleList($ids='') {
(new IDCollection())->goCheck();
$ids = explode(',', $ids);
$result = ThemeModel::with('topicImg,headImg')->select($ids);
if (!$result) {
throw new ThemeException();
}
return $result;
}
//theme详情接口
public function getComplexOne($id)
{
return 'success';
}
}
定义路由
application
oute.php
Route::get('api/:version/theme/:id', 'api/:version.Theme/getComplexOne');
使用时发现路由不能正常使用,会被上一个theme路由匹配成功
路由使用完整匹配
applicationconfig.php
return [
//...
'route_complete_match' => true,
//...
];
测试,返回success
Theme详情
applicationapicontrollerv1Theme.php
class Theme
{
public function getSimpleList($ids='') {
(new IDCollection())->goCheck();
$ids = explode(',', $ids);
$result = ThemeModel::with('topicImg,headImg')->select($ids);
if (!$result) {
throw new ThemeException();
}
return $result;
}
public function getComplexOne($id)
{
(new IDMustBePositiveInt())->goCheck();
}
}
applicationapimodelTheme.php
class Theme extends BaseModel
{
public static function getThemeWithProducts($id)
{
$themes = self::with('products,topicImg,headImg')
->find($id);
return $themes;
}
}
applicationapicontrollerv1Theme.php
public function getComplexOne($id)
{
(new IDMustBePositiveInt())->goCheck();
$theme = ThemeModel::getThemeWithProducts($id);
if(!$theme){
throw new ThemeException();
}
return $theme;
}
REST的合理利用
新建一个读取器
applicationapimodelProduct.php
class Product extends BaseModel
{
//pivot如果是多对多关系会tp5自动带上的中间表属性,这里不需要
protected $hidden = [
'delete_time', 'main_img_id', 'pivot', 'from', 'category_id',
'create_time', 'update_time'];
public function getMainImgUrlAttr($value, $data)
{
return $this->prefixImgUrl($value, $data);
}
}
查看返回结果
最近新品
使用模型插入数据库,update_time、create_time、delete_time会自动更新
新建路由
application
oute.php
Route::get('api/:version/product/recent', 'api/:version.Product/getRecent');
applicationapicontrollerv1Product.php
class Product
{
public function getRecent($count = 15)
{
(new Count())->goCheck();
return 'success';
}
}
新建验证器
applicationapivalidateCount.php
<?php
namespace appapivalidate;
class Count extends BaseValidate
{
protected $rule = [
'count' => 'isPositiveInteger|between:1,15',
];
}
applicationapimodelProduct.php
class Product extends BaseModel
{
public static function getMostRecent($count)
{
$products = self::limit($count)
->order('create_time desc') //排序
->select();
return $products;
}
}
applicationapicontrollerv1Product.php
<?php
namespace appapicontrollerv1;
use appapimodelProduct as ProductModel;
use appapivalidateCount;
use applibexceptionProductException;
class Product
{
public function getRecent($count = 15)
{
(new Count())->goCheck();
$products = ProductModel::getMostRecent($count);
if (!$products)
{
throw new ProductException();
}
return $products;
}
}
applicationlibexceptionProductException.php
<?php
namespace applibexception;
class ProductException extends BaseException
{
public $code = 404;
public $msg = '指定商品不存在,请检查商品ID';
public $errorCode = 20000;
}
使用数据集还是数组?
观察接口的返回结果,有一个summary字段是不需要的
applicationapimodelProduct.php
class Product extends BaseModel
{
//这里不能直接加summary,否则所有与Product相关联的接口都不会显示这个字段
protected $hidden = [
'delete_time', 'main_img_id', 'pivot', 'from', 'category_id',
'create_time', 'update_time'];
}
在接口中对summer做隐藏
applicationapicontrollerv1Product.php
class Product
{
public function getRecent($count = 15) {
(new Count())->goCheck();
$products = ProductModel::getMostRecent($count);
if (!$products)
{
throw new ProductException();
}
//将数组转成数据集
$collection = collection($products)
$products = $collection->hidden(['summary'])
return $products
}
}
数据集包含了许多对数据的有用操作,可以在配置文件配置,让数据集不再以二维数组的方式返回而是以数据集类的方式返回
applicationdatabase.php
return [
//...
'resultset_type' => 'collection',
//...
];
applicationapicontrollerv1Product.php
class Product
{
(new Count())->goCheck();
$products = ProductModel::getMostRecent($count);
//返回数据集类,原来的判空方式不再适用,这里使用数据集类的isEmpty方法判空
//其他接口使用了模型的select方法返回数据集的地方也要改
if ($products->isEmpty())
{
throw new ProductException();
}
$products = $products->hidden(['summary']);
return $products;
}
查看结果
分类接口
application oute.php
Route::get('api/:version/category/all', 'api/:version.Category/getAllCategories');
applicationapimodelCategory.php
class Category extends BaseModel
{
public function img()
{
return $this->belongsTo('Image', 'topic_img_id', 'id');
}
}
applicationapicontrollerv1Category.php
class Category
{
public function getAllCategories()
{
//[]代表查询所有
//以下语句相当于CategoryModel::with('img')->select()
$categories = CategoryModel::all([], 'img');
if($categories -> isEmpty()){
throw new CategoryException();
}
return $categories;
}
}
定义异常
applicationlibexceptionCategoryException.php
<?php
namespace applibexception;
class CategoryException extends BaseException
{
public $code = 404;
public $msg = '指定类目不存在,请检查商品ID';
public $errorCode = 20000;
}
分类商品接口
新建路由
application
oute.php
Route::get('api/:version/product/by_category', 'api/:version.Product/getAllInCategory');
applicationapicontrollerv1Product.php
class Product
{
public function getAllInCategory($id)
{
(new IDMustBePositiveInt())->goCheck();
$products = ProductModel::getProductsByCategoryID(
$id);
if ($products->isEmpty()) {
throw new ProductException();
}
$products = $products->hidden(['summary']);
return $products;
}
}
applicationapimodelProduct.php
class Product extends BaseModel
{
public static function getProductsByCategoryID($categoryID)
{
$products = self::where('category_id', '=', $categoryID) -> select();
return $products;
}
}