微信小程序电商实战(三)

数据库访问与ORM

TP5数据库操作三种方式

1原生SQL
2查询器Query
3ORM模型
2和3最终还是翻译成原生SQL语句的形式来执行

TP5数据库操作三种方式之原生SQL

model下的Banner.php

class Banner
{
    public static function getBannerByID($id){
        //TODO:根据Banner ID号 获取Banner信息
        $result = Db::query('select * from banner_item where banner_id=?',[$id]);
        return $result;
    }
}

controller下的Banner.php

class Banner
{
    /**
     * 获取指定id的banner信息
     * @url /banner/:id 访问接口的路径
     * @http GET
     * @id banner的id号
     */
    public  function getBanner($id){
        (new IDMustBePositiveint())->goCheck();//$this换成了IDMustBePositiveint()实例
         $banner = BannerModel::getBannerByID($id);
         if(!$banner){//空(不存在)也是异常
             throw new BannerMissException();
         }
         return json($banner);
    }
}

Postman
在这里插入图片描述

TP5数据库中间层架构解析

在这里插入图片描述
Db:1是操作数据库的入口对象(增删改查都能通过DB来实现的) 2根据配置文件(选择不同的驱动drivers)区分数据库。
Collection数据库连接器,并不是真正的连接数据库,而是起一种待命的状态,连接器是惰性连接,好处是能够节约服务器的资源,它是执行sql语句的。
Query查询器其实最后也会被转化为原生的sql语句,相当于封装了sql,它翻译成原生的sql语句就是利用Builder生成器来实现的,然后返回给Collection执行。查询器一个大的作用就是隐藏差异,支持不同数据库的查询,体现了面向对象思想和设计模式的思想。
drivers驱动提供了几个不同的类 每一个类负责了一个不同的连接

TP5数据库操作三种方式之查询构造器

查询数据器封装了对不同数据库的操作,提供给开发者统一的SQL操作语法,不需要关心原生SQL的差异性

 $result = Db::table('banner_item')->where('banner_id','=',$id)//result并不是真正的返回数据,而是一个Query对象
$result = Db::table('banner_item')->where('banner_id','=',$id)->find();//find()返回的是一个一维数组,只有一条数据库记录
$result = Db::table('banner_item')->where('banner_id','=',$id)->select();/select()返回的是一个二维数组,含有所有数据库记录

Db就相当于一个实现接口,我们不需要关心它的实现细节,只要操作数据库就会使用Db类。Db类使用的table、where等都是辅助方法或称为链式方法,而select、find、update、delete和insert等方法才是真正的执行数据操作方法,不管使用了多少辅助方法都不会进行数据查询等操作,只有使用了select、find、update、delete和insert等方法才能执行数据操作方法。

为什么链式方法并不会执行真正的SQL语句和可以通过链条的方式来调用?
每个链式方法都会返回一个Query对象,所以才可以添加任意多的链式方法,无论添加多少,最终都只会得到一个Query对象,Query对象只有使用select等方法才能生成SQL语句。链式方法只能在真正的SQL执行方法(select方法等)之前调用,不同的链式方法没有先后顺序,相同的链式方法的先后顺序是有可能对最后产生的结果有影响的(比如order方法)。

当使用完Db类执行过select等方法后,Db的状态就会被清除。但是如果不是按照常规编写的话Db状态会保留(如下编写),这只是被称为非链式操作的写法,同样也是执行完select方法后才会被清除.

Db::table('banner_item');
Db:where('banner_id','=',$id);
$result = Db:find();
return $result;

where三种方法:
1表达式法
2数组法
3闭包(代码如下)

$result = Db::table('banner_item')
     ->where(function($query) ues ($id){
           $query->where('banner_id','=',$id)
      })->select();
开启SQL日志记录

1使用fetchSql()方法
2在入口文件index.php中加上如下代码,初始化全局的日志记录

\think\Log::init([
    'type'=>'File',
    'path'=>LOG_PATH,
    'level'=>['sql']
]);
ORM与模型

模型的作用是处理复杂的业务逻辑,ORM是将一个表当作一个对象,模型是可以对应多个对象或者对应多个表,模型和对象或者表之间没有必然联系,模型是根据自己业务逻辑,即功能划分的,模型的主要作用是用来处理比较负责的业务逻辑。模型不止有model这一层,通过业务逻辑划分多层。TP5有model,service,logical三层。
1不要把模型当作数据库的查询
2不要把模型和数据库表一一对应起来,简单的模型和业务逻辑会造成误导,以为是一一对应。

初识模型

1、Db属于数据库访问层,而model不是,它是建立在数据库访问层上的一个更加抽象的业务逻辑的模型层
2、返回一个模型对象的优势在于,我们可以使用模型中大量已经定义好的方法和属性。
代码修改:
model下的Banner.php

class Banner extends Model

修改config.php

  // 默认输出类型 html改为json
    'default_return_type'    => 'json',

controller下的Banner.php

        $banner = BannerModel::get($id); //返回的是模型对象 get方法是在BannerModel继承的Mode类里面定义的 我们不用手动去写getBannerByID
//        $banner = BannerModel::getBannerByID($id);

Postman
在这里插入图片描述

模型定义总结

1模型都要继承thinkphp的model这个类.

2业务足够简单,所以会有一个数据表对应一个模型的假象。不是在所有情况下,表和模型是一一对应的,有可能一个模型对应多个表,比如关联模型就是和多张表都有关联的。关联模型相当于数据库的主表和从表的概念。

3当BannerModel调用get方法时,是怎么知道需要去banner表查询数据的?是如何找到数据库表名和类名之间的对应关系的?默认情况下,数据库表名和类名一一对应,不如模型Banner与数据库表banner对应。

补充1:可以在model中自定义对应哪张表,添加代码:

protected $table = '表名';

补充2:快捷创建模型的方法
在这里插入图片描述

静态调用还是实例对象调用

静态调用

$banner = BannerModel::get($id); 

实例对象调用

$banner = new BannerModel(); 具体数据记录
$banner = $banner->get($id);

两者应该用哪个?
需要先理解类与对象的关系,类是泛指,是抽象的,而对象是具体的。
TP5建议使用静态调用,第一个原因,静态方法调用更为简洁;第二个原因从模型的本质意义上来讨论,BannerMode类l对应的是数据库的一张表,而new 出来的实例对象对应的是表内的一条记录,这里呢,如果我们先实例化一个对象其实就是一条数据了,然后又使用get方法去获取数据,逻辑上不是很通。而静态调用,就是把模型类当做一张表,然后调用BannerMode类的get方法去获取数据。这里也只是为了编程逻辑的完整性和合理性,可以任意选择一种。

几种查询动词的总结与ORM性能问题的探讨

get和find:只能查询一条数据库的记录或只能返回一个模型对象
all和select:返回一组数据库的记录或返回一组模型对象
get和all:模型所特有的方法,Db无法使用
find和select:Db的方法,模型可以调用,因为Db是模型的基石

我们编写代码很多时候就是解决现实世界所遇到的问题,编码很多时候就是现实世界事务的抽象.所以我要使用面向对象的思维去解决问题

模型和数据库访问层是不同的2个概念,它们的职责是不同的.
模型主要是用来处理业务的.
而Db数据库访问层是用来查询数据库的.
但是模型是建立在Db的基础上的.
不要因为模型的性能较差就放弃使用模型
要用面向对象的思维去设计模型
模型的底层仍然是数据库访问抽象层,模型的强大可以自动地调用数据库访问抽象层

好的代码第一原则是什么?
不是代码的性能,而是代码的可读性.
只有设计框架它们的性能就会超微差点,不能像c c++ 汇编语言一样,不过这种性能可以忽略,比如说我们的TP5,虽然性能是有损耗的,如果你使用一些非高级语言,以及不使用框架.那么你的开发效率和周期是有多长?那么你的性能损耗的价值和你的开发效率所损失的时间相比较起来,孰轻孰重?
如果你发现你的产品访问的很慢,真的是ORM引起的吗?
不要把访问速度慢直接就归因于ORM上,在经验来看,一般慢的原因都是sql语句写的不够好.ORM其实没做什么,就是把原生的sql语句封装了一下.
如果项目比较大的时候,需要考虑的高并发的时候建议还是需要考虑使用原生的sql语句.但是绝大多情况的时候ORM就够了.

Banner相关表分析(数据表关系分析)

banner表和banner_item表的关系:本项目为一对多,外键banner_id
banner_item表和image表的关系:本项目为一对一,外键img_id

模型关联

区分当前模型与关联模型
当前模型:Banner
关联模型:BannerItem
目的:banner能够包含banner_item
model下的Banner.php:

public function items(){//关联BannerItem
        return $this->hasMany('BannerItem','banner_id','id');//$this指代Banner 一对多
    }

controller下的Banner.php:

 $banner = BannerModel::with('items')->find($id);

Postman
在这里插入图片描述
嵌套关联查询 :
banner和banner_item关联,而banner_item和image表也关联
目录
在这里插入图片描述
BannerItem.php

class BannerItem extends Model
{
    public function img(){
        return $this->belongsTo('Image','img_id','id');//一对一
    }
}

controller下的Banner.php:

 $banner = BannerModel::with(['items','items.img'])->find($id);

Postman
在这里插入图片描述
将本来在控制器执行的代码放到模型里面的方法(我们自己封装的方法),控制器只是调用:
model下的Banner.php:

 public static function getBannerByID($id){
        //TODO:根据Banner ID号 获取Banner信息
        $banner = self::with(['items','items.img'])->find($id);
        return $banner;
    }

控制器调用

 $banner = BannerModel::getBannerByID($id);//$banner 返回的是一个模型对象
隐藏模型字段

1在模型外部(控制器)隐藏模型字段
利用banner是对象的便利,直接调用hidden方法

<?php


namespace app\api\controller\v1;

use app\api\validate\IDMustBePositiveint;
use app\api\model\Banner as BannerModel;
use app\lib\exception\BannerMissException;

class Banner
{
    /**
     * 获取指定id的banner信息
     * @url /banner/:id 访问接口的路径
     * @http GET
     * @id banner的id号
     */
    public  function getBanner($id){
        (new IDMustBePositiveint())->goCheck();//$this换成了IDMustBePositiveint()实例
         //$banner = BannerModel::with(['items','items.img'])->find($id);//$banner 返回的是一个模型对象
         $banner = BannerModel::getBannerByID($id);//$banner 返回的是一个模型对象  如果getBannerByID()方法是自己写的 返回的是数组
         $banner->hidden(['update_time','delete_time']);
         if(!$banner){//空(不存在)也是异常
             throw new BannerMissException();
         }
         //return json($banner);
         return $banner;//TP5自动序列化 不再需要json
    }
}

2在模型内部隐藏模型字段
外部隐藏方法只是隐藏了banner模型的字段,而banner_item和image的却没有隐藏,所以需要采用模型内部隐藏方式。
根据业务要求来隐藏或显示指定模型字段了,一般我们客户端不需要使用的,不想返回给客户端的我们都可以隐藏起来
方法:在相应的模型中添加属性即可 隐藏hidden 显示visible

protected $hidden = ['update_time','delete_time'];

隐藏后现实的json结构:
在这里插入图片描述

图片资源URL配置

image数据表中的url是一个相对路径,不是一个绝对路径,from字段如果为1表示图片存储在本地服务器上的,如果from字段为2,则表示存储在网络上的(云端存储)。如果from为2,数据表中的url就是一个完整的url路径。如果from为1,则存储在本地上,建议为相对路径。为什么要写相对路径呢?因为我们配置虚拟域名的时候暂时固定了为z.cn,如果把url写完整的话,即包括域名和目录等,那数据表的数据就容易写死,以后如果域名和名录出现变更就不好修改了,如果我们写相对路径的话,图片路径也可以根据相应域名和目录来写出适合的完整url路径。
如何设置图片url的完整路径呢?
首先我们需要配置好域名和一系列目录,我们就要配置配置文件,一般我们如果有自己的业务就自己建立一个配置文件来为我们的业务服务。在application建立一个extra就是TP5会自动加载的一个配置文件目录,然后创建一个setting配置文件,在setting中模仿系统配置文件config写入:

return [
    'img_prefix'  =>'http://z.cn/images'
];

注意,这里我们是把z.cn这个域名指向项目根目录下的public目录,而我们的图片目录images也是需要放在public目录下。这样我们图片的前缀地址就配置好了。
如何读取这个配置文件?
在接口(controller下的Banner)下使用config方法即可

$c=config('setting.img_prefix');
获取器应用

获取器
如何将上面的前缀配置值与图片的url拼接起来,从而获取一个完整的url路径?
利用读取器getUrlAttr(),在Image模型里写入:

public function getUrlAttr($value,$data){//$value是url的取值 $data获取了该条记录的所有字段数据
        $finalUrl = $value;
        if($data['from']==1){//图片存储在本地
            $finalUrl = config('setting.img_prefix').$value;//拼接
        }
        return $finalUrl;//图片存储在云端
    }

获取器也是AOP思想的一种小型应用

自定义模型基类

在Image模型中直接定义了获取器来获取url,那么其他模型也要获取url怎么办?
有两种思路,把读取器里的代码封装成一个函数,需要用到的地方我们用这个函数即可;第二种是我们不把第一种思路里的函数放到其它文件或类里,而是用面向对象的方法,作一个Model的基类,让其他模型继承这个基类,该基类(BaseModel)继承think的Model类。
BaseModel:

<?php
namespace app\api\model;
use think\Model;

class BaseModel extends Model
{
    protected function prefixImgUrl($value,$data){//$value是url的取值 $data获取了该条记录的所有字段数据
        $finalUrl = $value;
        if($data['from']==1){//图片存储在本地
            $finalUrl = config('setting.img_prefix').$value;
        }
        return $finalUrl;//图片存储在云端
    }
}

其他模型(如Image模型)想要获取url只需要调用父类的prefixImgUrl方法:

 public function getUrlAttr($value,$data){//$value是url的取值 $data获取了该条记录的所有字段数据
        return $this->prefixImgUrl($value,$data);
    }

注意:不能直接在BaseModel中直接用获取器,因为如果是读取器那么所有含有url的字段都会被处理,我们只需要在用的时候再子类调用,所以需要改个名字。

定义API版本号

开闭原则:对扩展是开放的,对修改是封闭的。如果你要修改一个代码,最好就是通过扩展的形式来修改,例如Exception想要添加一个新功能,添加一个相关的Exception类即可,尽量不要去破坏以前的代码。
在这里插入图片描述
路由改为:

Route::get('api/:version/banner/:id','api/:version.Banner/getBanner');//三段式(模块/控制器/方法)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章