快速入门

需求

假如我们要开发一个商品模块,我们将它命名为 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