微信小程序電商實戰(三)

數據庫訪問與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');//三段式(模塊/控制器/方法)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章