快速入门
需求
假如我们要开发一个商品模块,我们将它命名为 ItemBundle ,其包含了商品、sku、商品类型、标签,可以实现商品相关的增删改查。
环境说明
本文档是用的开发环境是官方的 docker 环境,点此查看,其他环境使用命令时请按需修改。
本文档所使用的 php artisan 命令,都需要进入容器内执行,进入容器的命令为:
docker exec -it ecshopx-dev-docker_espier-bloated-web_1 sh
开发说明
在使用 Doctrine Orm 开发应用程序时,可以使用以下三种方式来启动开发:
Code First:首先要开发 Entity 类,然后将他们映射到数据库中
Model First:需要是用工具对(UML)进行建模,并从改模型生成数据库和 Entity 类
Database First:首先要设计数据表,然后根据数据表生成 Entity 类
Code First
此方式需要根据需求设计 Entity 类,设计时需要熟知 Doctrine Orm 注解,只要熟悉数据库设计即可,所以用这种方式效率更高。此方式具体开发流程如下:
一、设计 Entity 类
二、通过命令生成数据库迁移
php artisan doctrine:migrations:diff
三、生成数据表
php artisan doctrine:migrations:migrate
Ecstore 和 Bbc 和此方式类似
Database First
此方式和 Database First 正好相反,先设计数据表,再生成 Entity 类。由于此方式不需要熟知 Doctrine Orm 注解,只要熟悉数据库设计即可,所以用这种方式效率更高。此方式具体开发流程如下:
一、采用数据库设计软件设计数据库 ER 图。
二、通过 ER 图生成的 SQL 创建数据表
三、通过命令生成实体
php artisan doctrine:mapping:import annotation --filter=DemoUser --namespace=ItemBundle\\Entities\\
--filter=DemoUser 中的 DemoUser 是想要生成的实体名,是数据表的大驼峰写法,不写 --filter 将会把数据库中所有的数据表都生成 Entity 放到 src/ItemBundle/Entities 目录中
本教程将采用 Database First
的方式。
设计数据库
根据需求,我们通过 Mysql Workbench
来设计 ER 图:
设计完 ER 图之后,在 Mysql Workbench
中可以通过 Database-> Forward Engineer
直接将 ER 图直接生成数据表。 为了方便讲解,我们将此 ER 图生成的 SQL 放在此处,方便大家直接创建数据库:
-- -----------------------------------------------------
-- Table `mydb`.`item_type`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `mydb`.`item_type` ;
CREATE TABLE IF NOT EXISTS `mydb`.`item_type` (
`id` BIGINT NOT NULL,
`name` VARCHAR(255) NOT NULL COMMENT '名称',
`code` VARCHAR(64) NOT NULL COMMENT '编码',
`updated` INT(11) NOT NULL,
`created` INT(11) NULL,
PRIMARY KEY (`id`))
COMMENT = '类型表';
-- -----------------------------------------------------
-- Table `mydb`.`item`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `mydb`.`item` ;
CREATE TABLE IF NOT EXISTS `mydb`.`item` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`item_type_id` BIGINT NOT NULL COMMENT '类型',
`name` VARCHAR(255) NOT NULL COMMENT '名称',
`price` DECIMAL(20,3) NOT NULL COMMENT '价格',
`created` INT(11) UNSIGNED NOT NULL COMMENT '创建时间',
`updated` INT(11) NULL COMMENT '最后更新时间',
PRIMARY KEY (`id`),
CONSTRAINT `fk_item_item_type`
FOREIGN KEY (`item_type_id`)
REFERENCES `mydb`.`item_type` (`id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
COMMENT = '商品';
CREATE INDEX `fk_item_item_type_idx` ON `mydb`.`item` (`item_type_id` ASC);
-- -----------------------------------------------------
-- Table `mydb`.`sku`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `mydb`.`sku` ;
CREATE TABLE IF NOT EXISTS `mydb`.`sku` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL COMMENT '名称',
`price` DECIMAL(20,3) NOT NULL COMMENT '价格',
`created` INT(11) NOT NULL,
`updated` INT(11) NULL,
`store` VARCHAR(45) NULL COMMENT '库存',
`spec1` VARCHAR(45) NULL COMMENT '规格 1',
`spec2` VARCHAR(45) NULL COMMENT '规格 2\n',
`item_id` BIGINT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `fk_sku_item1`
FOREIGN KEY (`item_id`)
REFERENCES `mydb`.`item` (`id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
COMMENT = 'SKU';
CREATE INDEX `fk_sku_item1_idx` ON `mydb`.`sku` (`item_id` ASC);
-- -----------------------------------------------------
-- Table `mydb`.`tag`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `mydb`.`tag` ;
CREATE TABLE IF NOT EXISTS `mydb`.`tag` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(60) NOT NULL COMMENT '标签名称',
`updated` INT(11) NOT NULL,
`created` INT(11) NULL,
PRIMARY KEY (`id`))
COMMENT = '标签';
-- -----------------------------------------------------
-- Table `mydb`.`item_tag`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `mydb`.`item_tag` ;
CREATE TABLE IF NOT EXISTS `mydb`.`item_tag` (
`item_id` BIGINT NOT NULL,
`tag_id` BIGINT UNSIGNED NOT NULL,
PRIMARY KEY (`item_id`, `tag_id`),
CONSTRAINT `fk_item_tag_item1`
FOREIGN KEY (`item_id`)
REFERENCES `mydb`.`item` (`id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION,
CONSTRAINT `fk_item_tag_tag1`
FOREIGN KEY (`tag_id`)
REFERENCES `mydb`.`tag` (`id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION);
CREATE INDEX `fk_item_tag_tag1_idx` ON `mydb`.`item_tag` (`tag_id` ASC);
CREATE INDEX `fk_item_tag_item1_idx` ON `mydb`.`item_tag` (`item_id` ASC);
生成实体类
我们可以通过命令行生成 Entity 类
php artisan doctrine:mapping:import annotation --namespace=ItemBundle\\Entities\\
生成 Entity 成功后可以看到以下输出:
Importing mapping information from "mydb" entity manager
> writing /app/src/ItemBundle/Entities/Item.php
> writing /app/src/ItemBundle/Entities/ItemType.php
> writing /app/src/ItemBundle/Entities/Sku.php
> writing /app/src/ItemBundle/Entities/Tag.php
以 ItemType 实体为例,打开 ItemType.php 可以看到以下内容:
<?php
namespace ItemBundle\Entities;
use Doctrine\ORM\Mapping as ORM;
/**
* ItemType
*
* @ORM\Table(name="item_type")
* @ORM\Entity
*/
class ItemType
{
/**
* @var int
*
* @ORM\Column(name="id", type="bigint", nullable=false)
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* @var string
*
* @ORM\Column(name="name", type="string", length=255, nullable=false, options={"comment"="名称"})
*/
private $name;
/**
* @var string
*
* @ORM\Column(name="code", type="string", length=64, nullable=false, options={"comment"="编码"})
*/
private $code;
/**
* @var int
*
* @ORM\Column(name="updated", type="integer", nullable=false)
*/
private $updated;
/**
* @var int|null
*
* @ORM\Column(name="created", type="integer", nullable=true)
*/
private $created;
}
生成实体类的 Getter 和 Setter
根据数据表生成的实体类,只有 private
的属性,我们还需要为其设置 Getter 和 Setter 方法,可以以下命令生成:
php artisan doctrine:generate:entities --filter=ItemType
定义接口
一个接口对一个一条路由,一个路由对应一个控制器方法。
定义控制器
我们先来完成 ItemType 实体的增删改查操作。
先创建一个带有增删改查空方法的控制器:
<?php
//src/ItemBundle/Http/Api/V1/Action/ItemTypesController.php
namespace ItemBundle\Http\Api\V1\Action;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller as BaseController;
use Dingo\Api\Exception\StoreResourceFailedException;
class ItemTypesController extends BaseController
{
public function index(Request $request) {
return $this->response->array([]);
}
public function save(Request $request) {
return $this->response->array([]);
}
public function show(Request $request, $id) {
return $this->response->array([$id]);
}
public function update(Request $request, $id) {
}
public function destroy(Request $request, $id) {
}
}
定义路由
<?php
//routes/api/item.php
$api->version('v1', function($api) {
$api->group(['namespace' => 'ItemBundle\Http\Api\V1\Action','middleware' => [], 'providers' => 'jwt'], function($api) {
$api->get('/item_types', ['name' => '获取商品类型列表', 'as' => 'item_types. list', 'uses'=>'ItemTypesController@index']);
$api->post('/item_types', ['name' => '创建商品类型', 'as' => 'item_types.save', 'uses'=>'ItemTypesController@save']);
$api->get('/item_types/{id}', ['name' => '获取商品类型详情', 'as' => 'item_types.show', 'uses'=>'ItemTypesController@show']);
$api->put('/item_types/{id}/', ['name' => '更新商品类型', 'as' => 'item_types.update', 'uses'=>'ItemTypesController@update']);
$api->delete('/item_types', ['name' => '删除商品类型', 'as' => 'item_types.destroy', 'uses'=>'ItemTypesController@destroy']);
});
});
此时,可以通过浏览器访问 http://127.0.0.1:8080/api/item_types 获取商品类型列表接口。
定义接口文档
一、定义 ItemBundle 接口文档分组
基本信息:
<?php
//src/ItemBundle/Http/Api/V1/Swagger/Info.php
/**
* @SWG\Swagger(
* swagger="2.0",
* schemes={"http"},
* host="localhost",
* basePath="/espier/public/index.php/api/",
* @SWG\Info(
* version="1.0",
* title="商品相关接口",
* description="item api",
* ),
* )
*/
接口标签
<?php
//src/ItemBundle/Http/Api/V1/Swagger/Tags.php
/**
* @SWG\Tag(
* name="ItemCrud",
* description="商品增删改查相关",
* )
*/
/**
* @SWG\Tag(
* name="ItemTag",
* description="商品标签",
* )
*/
接口统一错误响应结构体:
<?php
//src/ItemBundle/Http/Api/V1/Swagger/ItemsErrorRespones.php
namespace src\GoodsBundle\Http\Api\V1\Swagger;
/**
* @SWG\Definition(type="object")
*/
class ItemsErrorRespones
{
/**
* @SWG\Property
* @var string
*/
public $message;
/**
* @SWG\Property
* @var string
*/
public $errors;
/**
* @SWG\Property(format="int32")
* @var int
*/
public $code;
/**
* @SWG\Property(format="int32")
* @var int
*/
public $status_code;
/**
* @SWG\Property
* @var string
*/
public $debug;
}
二、定义具体接口的输入和输出
<?php
//src/ItemBundle/Http/Api/V1/Action/ItemTypesController.php
namespace ItemBundle\Http\Api\V1\Action;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller as BaseController;
use Dingo\Api\Exception\StoreResourceFailedException;
class ItemTypesController extends BaseController
{
/**
* @SWG\Get(
* path="/item_types",
* summary="获取商品类型列表",
* tags={"ItemType"},
* description="获取商品类型列表",
* operationId="index",
* @SWG\Response(
* response=200,
* description="成功返回结构",
* @SWG\Schema(
* @SWG\Property(
* property="data",
* type="array",
* @SWG\Items(
* type="object",
* @SWG\Property(property="status", type="stirng"),
* )
* ),
* ),
* ),
* @SWG\Response( response="default", description="错误返回结构", @SWG\Schema( type="array", @SWG\Items(ref="#/definitions/ItemsErrorRespones") ) )
* )
*/
public function index(Request $request) {
return $this->response->array([]);
}
/**
* @SWG\Post(
* path="/item_types",
* summary="新增商品类型",
* tags={"ItemType"},
* description="新增商品类型",
* operationId="save",
* @SWG\Parameter( name="name", in="formData", description="类型名称", required=true, type="string"),
* @SWG\Parameter( name="code", in="formData", description="类型描述", required=true, type="string"),
* @SWG\Response(
* response=200,
* description="成功返回结构",
* @SWG\Schema(
* @SWG\Property(
* property="data",
* type="array",
* @SWG\Items(
* type="object",
* @SWG\Property(property="id", type="integer"),
* @SWG\Property(property="name", type="string"),
* @SWG\Property(property="code", type="string"),
* )
* ),
* ),
* ),
* @SWG\Response( response="default", description="错误返回结构", @SWG\Schema( type="array", @SWG\Items(ref="#/definitions/ItemsErrorRespones") ) )
* )
*/
public function save(Request $request) {
return $this->response->array([]);
}
/**
* @SWG\Get(
* path="/item_types/{id}",
* summary="获取商品详情",
* tags={"ItemType"},
* description="获取商品类型列表",
* operationId="show",
* @SWG\Parameter( name="id", in="path", description="类型名称", required=true, type="string"),
* @SWG\Response(
* response=200,
* description="成功返回结构",
* @SWG\Schema(
* @SWG\Property(
* property="data",
* type="array",
* @SWG\Items(
* type="object",
* @SWG\Property(property="status", type="stirng"),
* )
* ),
* ),
* ),
* @SWG\Response( response="default", description="错误返回结构", @SWG\Schema( type="array", @SWG\Items(ref="#/definitions/ItemsErrorRespones") ) )
* )
*/
public function show(Request $request,$id) {
return $this->response->array([$id]);
}
/**
* @SWG\Put(
* path="/item_types/{id}",
* summary="更新商品类型",
* tags={"ItemType"},
* description="获取商品类型列表",
* operationId="update",
* @SWG\Parameter( name="id", in="path", description="类型名称", required=true, type="string"),
* @SWG\Parameter( name="name", in="formData", description="类型名称", required=false, type="string"),
* @SWG\Parameter( name="code", in="formData", description="类型描述", required=false, type="string"),
* @SWG\Response(
* response=200,
* description="成功返回结构",
* @SWG\Schema(
* @SWG\Property(
* property="data",
* type="array",
* @SWG\Items(
* type="object",
* @SWG\Property(property="status", type="stirng"),
* )
* ),
* ),
* ),
* @SWG\Response( response="default", description="错误返回结构", @SWG\Schema( type="array", @SWG\Items(ref="#/definitions/ItemsErrorRespones") ) )
* )
*/
public function update(Request $request, $id) {
}
/**
* @SWG\Delete(
* path="/item_types/{id}",
* summary="删除商品类型",
* tags={"ItemType"},
* description="删除商品类型",
* operationId="destroy",
* @SWG\Parameter( name="id", in="path", description="类型名称", required=true, type="string"),
* @SWG\Response(
* response=200,
* description="成功返回结构",
* @SWG\Schema(
* @SWG\Property(
* property="data",
* type="array",
* @SWG\Items(
* type="object",
* @SWG\Property(property="status", type="stirng"),
* )
* ),
* ),
* ),
* @SWG\Response( response="default", description="错误返回结构", @SWG\Schema( type="array", @SWG\Items(ref="#/definitions/ItemsErrorRespones") ) )
* )
*/
public function destroy(Request $request, $id) {
}
}
三、接口文档
接口文档的生成请参阅 [接口文档生成和接口测试]
php artisan api:swagger --output=src/ItemBundle/Http/Api/V1 && php -S 0.0.0.0:8005 -t public/
成功之后,将会看到以下界面:
实现商品类型的 CRUD
我们以 MVC 架构为基础,加入 Repository 模式 和 Service 模式来完成我们的开发。
第一步:新增相关类
新建与 ItemType 相关的 Service 和 Repository 类。
新建 ItemTypeSecvice 类:
//src/ItemBundle/Services/ItemTypeSercice.php
<?php
namespace ItemBundle\Services;
class ItemTypeService
{
}
新建 ItemTypeRepository 类:
//src/ItemBundle/Repositories/ItemTypeRepository.php
<?php
namespace ItemBundle\Repositories;
use Doctrine\ORM\EntityRepository;
class ItemTypeRepository extends EntityRepository
{
}
同时修改 ItemType 类与之相关联:
<?php
//src/ItemBundle/Entities/ItemType.php
namespace ItemBundle\Entities;
use Doctrine\ORM\Mapping as ORM;
/**
* ItemType
*
* @ORM\Table(name="item_type")
* @ORM\Entity(repositoryClass="ItemBundle\Repositories\ItemTypeRepository")
*/
class ItemType
{
//...
}
第二步:确定调用关系
采用 Service 和 Repository 模式,相关类的调用关系为 Controller->Service->Repository 。
我们利用 Laravel 自带的依赖注入的方式,在 Controller 中新增构造方法来初始化 Service 类
<?php
//src/ItemBundle/Http/Api/V1/Action/ItemTypesController.php
namespace ItemBundle\Http\Api\V1\Action;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller as BaseController;
use Dingo\Api\Exception\StoreResourceFailedException;
use ItemBundle\Entities\ItemType;
use Dingo\Api\Exception\ResourceException;
use ItemBundle\Services\ItemTypeService;
class ItemTypesController extends BaseController
{
protected $itemTypeService;
public function __construct(ItemTypeService $itemTypeService)
{
$this->itemTypeService = $itemTypeService;
}
//...
}
因为初始化 Repository 类需要特定的方法,所以在 ItemTypeSecvice 类中,我们需要手动初始化 Repository 类:
//src/ItemBundle/Services/ItemTypeSercice.php
<?php
namespace ItemBundle\Services;
use ItemBundle\Entities\ItemType;
class ItemTypeService
{
protected $itemTypeRepository;
public function __construct()
{
$this->itemTypeRepository = app('registry')->getManager('default')->getRepository(ItemType::class);
}
}
第三步:编写逻辑
ItemTypeRepository 类
//src/ItemBundle/Repositories/ItemTypeRepository.php
<?php
namespace ItemBundle\Repositories;
use ItemBundle\Entities\ItemType;
use Doctrine\ORM\EntityRepository;
class ItemTypeRepository extends EntityRepository
{
public function store(ItemType $itemType)
{
$em = $this->getEntityManager();
$em->persist($itemType);
$em->flush();
return $itemType;
}
public function delete(ItemType $itemType)
{
$em = $this->getEntityManager();
$em->remove($itemType);
$em->flush();
return $itemType;
}
}
ItemTypeSercice 类:
//src/ItemBundle/Services/ItemTypeSercice.php
<?php
namespace ItemBundle\Services;
use ItemBundle\Entities\ItemType;
class ItemTypeService
{
protected $itemTypeRepository;
public function __construct()
{
$this->itemTypeRepository = app('registry')->getManager('default')->getRepository(ItemType::class);
}
public function create($params)
{
$itemType = new ItemType();
$itemType->setName($params['name']);
$itemType->setCode($params['code']);
$itemType->setCreated(time());
return $this->itemTypeRepository->store($itemType);
}
public function update($id,$data)
{
$itemType = $this->itemTypeRepository->find($id);
if( !$itemType ) {
throw new ResourceException("未查询到更新数据");
}
$itemType->setName($data['name']);
$itemType->setCode($data['code']);
$itemType->setUpdated(time());
return $this->itemTypeRepository->store($itemType);
}
public function deleteById($id)
{
$itemType = $this->itemTypeRepository->find($id);
if( !$itemType ) {
throw new ResourceException("未查询到更新数据");
}
return $this->itemTypeRepository->delete($itemType);
}
public function findAll()
{
return $this->itemTypeRepository->findAll();
}
public function find($id)
{
return $this->itemTypeRepository->find($id);
}
public function toArray(ItemType $itemType)
{
return [
'id'=>$itemType->getId(),
'name'=>$itemType->getName(),
'code'=>$itemType->getCode(),
'created'=>$itemType->getCreated(),
'updated'=>$itemType->getUpdated(),
];
}
}
控制器
<?php
//src/ItemBundle/Http/Api/V1/Action/ItemTypesController.php
namespace ItemBundle\Http\Api\V1\Action;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller as BaseController;
use Dingo\Api\Exception\StoreResourceFailedException;
use ItemBundle\Entities\ItemType;
use Dingo\Api\Exception\ResourceException;
use ItemBundle\Services\ItemTypeService;
class ItemTypesController extends BaseController
{
protected $itemTypeService;
public function __construct(ItemTypeService $itemTypeService)
{
$this->itemTypeService = $itemTypeService;
}
public function index(Request $request) {
$list = $this->itemTypeService->findAll();
foreach ($list as $itemType) {
$result[] = $this->itemTypeService->toArray($itemType);
}
return $this->response->array($result);
}
public function save(Request $request) {
$rules = [
'name' => ['required', '类型名称不能为空'],
'code' => ['required', '类型编码不能为空'],
];
$params = $request->all(array_keys($rules));
$error = validator_params($params, $rules);
if($error) {
throw new StoreResourceFailedException($error);
}
$itemType = $this->itemTypeService->create($params);
return $this->response->array(['id'=>$itemType->getId()]);
}
public function show(Request $request,$id) {
$itemType = $this->itemTypeService->find($id);
return $this->response->array($this->itemTypeService->toArray($itemType));
}
public function update(Request $request, $id) {
$rules = [
'name' => ['required', '类型名称不能为空'],
'code' => ['required', '类型编码不能为空'],
];
$params = $request->all(array_keys($rules));
$error = validator_params($params, $rules);
if($error) {
throw new StoreResourceFailedException($error);
}
$itemType = $this->itemTypeService->update($id,$params);
return $this->response->array(['status'=>'succ']);
}
public function destroy(Request $request, $id) {
$itemType = $this->itemTypeService->deleteById($id);
return $this->response->array(['status'=>'succ']);
}
}
未完待续
Last updated