这个其实很简单啊。需求类似于一个文章可以添加10中field中的任意几种,支持多个。
那你创建一个文章表,然后再创建10张field表,每张表都跟文章表关联即可。
后续扩展也很容易,添加一个 field 类型只要新增一张表即可。
xcwong 回答了问题 · 2020-06-19
这个其实很简单啊。需求类似于一个文章可以添加10中field中的任意几种,支持多个。
那你创建一个文章表,然后再创建10张field表,每张表都跟文章表关联即可。
后续扩展也很容易,添加一个 field 类型只要新增一张表即可。
关注 4 回答 4
xcwong 赞了回答 · 2020-02-25
直接调用 route 方法
$this->route('id');
关注 2 回答 1
xcwong 提出了问题 · 2020-02-25
例如我们有个更新 article 的 api:
PUT articles/{id}
同时我们也定义了一个ArticleUpdateRequest
类来校验表单。那么在该Request类中如何获取到 url 中的 id 呢?
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class ArticleUpdateRequest extends FormRequest
{
public function authorize()
{
return false;
}
public function rules()
{
return [];
}
}
想获取到 id 是想覆写 authorize()
函数。
关注 2 回答 1
xcwong 赞了文章 · 2019-08-22
在前后端分离大行其道的今天,有一个统一的返回值格式不仅能使我们的接口看起来更漂亮,而且还可以使前端可以统一处理很多东西,避免很多问题的产生。
比较通用的返回值格式如下:
public class Result<T> {
// 接口调用成功或者失败
private Integer code = 0;
// 失败的具体code
private String errorCode = "";
// 需要传递的信息,例如错误信息
private String msg;
// 需要传递的数据
private T data;
...
}
最原始的接口如下:
@GetMapping("/test")
public User test() {
return new User();
}
当我们需要统一返回值时,可能会使用这样一个办法:
@GetMapping("/test")
public Result test() {
return Result.success(new User());
}
这个方法确实达到了统一接口返回值的目的,但是却有几个新问题诞生了:
所幸Spring Boot已经为我们提供了更好的解决办法,只需要在项目中加上以下代码,就可以无感知的为我们统一全局返回值。
/**
* 全局返回值统一封装
*/
@EnableWebMvc
@Configuration
public class GlobalReturnConfig {
@RestControllerAdvice
static class ResultResponseAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
if (body instanceof Result) {
return body;
}
return new Result(body);
}
}
}
而我们的接口只需要写成最原始的样子就行了。
@GetMapping("/test")
public User test() {
return new User();
}
将返回值统一封装时我们没有考虑当接口抛出异常的情况。当接口抛出异常时让用户直接看到服务端的异常肯定是不够友好的,而我们也不可能每一个接口都去try/catch进行处理,此时只需要使用@ExceptionHandler
注解即可无感知的全局统一处理异常。
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger LOG = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 全局异常处理
*/
@ExceptionHandler
public JsonData handleException(HttpServletRequest request, HttpServletResponse response, final Exception e) {
LOG.error(e.getMessage(), e);
if (e instanceof AlertException) {//可以在前端Alert的异常
if (((AlertException) e).getRetCode() != null) {//预定义异常
return new Result(((AlertException) e).getRetCode());
} else {
return new Result(1, e.getMessage() != null ? e.getMessage() : "");
}
} else {//其它异常
if (Util.isProduct()) {//如果是正式环境,统一提示
return new Result(RetCode.ERROR);
} else {//测试环境,alert异常信息
return new Result(1, StringUtils.isNotBlank(e.getMessage()) ? e.getMessage() : e.toString());
}
}
}
}
其中的AlertException
为我们自定义的异常,因此当业务中需要抛出错误时,可以手动抛出AlertException
。
以上就是统一处理返回值和统一处理异常的两步。
查看原文赞 74 收藏 46 评论 13
xcwong 发布了文章 · 2019-08-08
Macos Catalina 10.15 Beta (19A526h)升级导致 JetBrain 系列无法选择文件夹打开项目是已知 bug,可以在 jetbrain 家的 bug 追踪网站查看:
https://youtrack.jetbrains.co...
该 bug 将在后续更新中修复。Jetbrain 软件包括 PhpStrom,GoLand,Intellij Idea 等。
临时,我们可以通过关闭原生的文件选择器使用 JetBrain 自带的文件选择器即可。
步骤:
在打开的文件末尾添加:
Dide.mac.file.chooser.native=false
赞 0 收藏 0 评论 0
xcwong 回答了问题 · 2019-07-13
搜索到这里,我回答一下:
我的 store/index.js
中将 store 载在 Vue
上
import Vue from 'vue'
import Vuex from 'vuex'
import survey from './modules/survey'
import user from './modules/user'
import env from './modules/env'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
survey, user, env
}
})
export default store
如果在某一个 .js
文件中想要使用只要将该文件 import 即可。例如:
import store from '../store'
// 如果是
const isWeixin = store.getters.isWeixin
关注 4 回答 3
xcwong 发布了文章 · 2019-07-03
FO 是官方在 FibOS 发行的数字货币,也在多个交易所上交易,作者在写文章的时候,大概报价是 0.05 元。官方也推出了一个简洁易用的钱包 APP 供大家管理自己的 FO 资产,有兴趣的同学也可以下载使用(https://wallet.fo/zh-cn)。有 FO 账户还没有 FO 的小伙伴不妨留下 FO 账户,说不定我会抽几个给大家打 FO 呢。
但是怎么打 FO 呢,很简单,大家可以 FO 钱包 APP 中点击转帐,输入收款账户和转账数量后点确定一气呵成。
但是例如我们在一些需要大批量转账或自动化转账的情况下,我们如何通过代码实现呢?接下来我带领大家一起来体验一下:
我们创建一个文件夹叫 transfer,并且进入该文件夹:
mkdir transfer
cd transfer
初始化项目。我们可以执行 fibos 命令是因为我们已经安装了 fibos 命令行工具。如果不清楚如何安装的可以阅读我本专栏的前一篇文章。
fibos --init
安装 fibos 依赖包。这里可以 使用 fibos 安装也可以选择 npm 命令安装。当然如果您安装特别慢,可以使用 cnpm 或者淘宝 npm 镜像。
fibos --install fibos.js
到此为止,准备工作就做完了。此时你的 transfer 文件夹的目录结构是这样的:
.
├── node_modules
└── package.json
这里需要解释一下的是,我们在开发过程中经常碰到的链主要有三类:
上述三个链或者网完全隔离。大家不要搞混了。
主链:
ID:6aa7bd33b6b45192465afa3553dedb531acaaff8928cf64b70bd4c5e49b7ec6a
EndPoint: http://api.fibos.me
# 当然还有其他,可以在官方文档中查询:节点介绍 — Dev.fo
测试链:
ID:68cee14f598d88d340b50940b6ddfba28c444b46cd5f33201ace82c78896793a
EndPoint:http://api.testnet.fo
这里我们为了快速开始,建议使用测试网络,减少工作量和不必要的坑打击积极性。
我们可以在测试网的网站上创建几个测试账户并给几个账户空投几个 FO,创建方法:
目前我申请了两个账户,并且空投了一些测试链的 FO。分别是:
账户: gtcmpcrcm2h5
私钥: 5JVtt2nHcr52xfULsRCAKGAFDuCazWs5Z1RcvZmerE8W3Fr66UL
公钥: FO7zZJJdfTvGKpdudAcYQp1y97wP9cC9vG2McmeWzvgLWVnkVy9m
账户: cmvdkvfgedjg
私钥: 5JBoQimR9kDB4922S7BaDjQnm8zaD5D2qNXRbbVKZryRCdUxhfc
公钥: FO7wwaYC7r1z1sP7xyBBa9prEt7dFNoMXY9ZMREEyRUYxKgx4KD4
⚠️ 注意:此处是测试链,且为了方便大家才列在这里。千万千万不要在任何地方泄露你的私钥,泄露代表资产丢失。
一切准备就绪,我们要实现转账,我们需要获取一个客户端。我们在目录下创建一个 initClien.js 文件,编写以下代码:
var FIBOS = require('fibos.js');
function initClient(_keyProvider) {
return FIBOS({
chainId: '68cee14f598d88d340b50940b6ddfba28c444b46cd5f33201ace82c78896793a',
keyProvider: _keyProvider,
httpEndpoint: 'http://api.testnet.fo',
logger: {
log: null,
error: null
}
});
}
module.exports = initClient;
其中 chainId 即链的 ID,这里我们接入测试链,所以输入的是测试链的 ID。httpEndpoint 即为接入点,也是测试链的接入点。_keyProvider 为要创建 Client 的那个账户的私钥,我们后续会以参数的形式传入。
此时目录结构为:
.
├── initClient.js
├── node_modules
└── package.json
我们在目录中新建一个 transfer.js
文件,在文件中编码:
var FIBOS = require('./initClient.js')
var config = {
'public-key': 'FO7zZJJdfTvGKpdudAcYQp1y97wP9cC9vG2McmeWzvgLWVnkVy9m', // FO 转出方私钥
'private-key': '5JVtt2nHcr52xfULsRCAKGAFDuCazWs5Z1RcvZmerE8W3Fr66UL' // FO 转入方私钥
};
console.log(config['private-key'])
var fibos = FIBOS(config['private-key']);
let ctx = fibos.contractSync('eosio.token');
var r = ctx.transferSync(
'gtcmpcrcm2h5', // FO 转出方
'cmvdkvfgedjg', // FO 转入方
'0.1000 FO', // FO数量
'Hello Fibos', // 附言
{
authorization: 'gtcmpcrcm2h5' // FO 转出方账户
});
console.log(r);
此时目录结构为:
.
├── initClient.js
├── node_modules
├── package.json
└── transfer.js
执行以下命令进行转账:
fibos transfer.js
成功后会输出这次转账的详情:
{
"broadcast": true,
"transaction": {
"compression": "none",
"transaction": {
"expiration": "2019-07-03T11:49:58",
"ref_block_num": 52798,
"ref_block_prefix": 459488456,
"max_net_usage_words": 0,
"max_cpu_usage_ms": 0,
"delay_sec": 0,
"context_free_actions": [],
"actions": [
{
"account": "eosio.token",
"name": "transfer",
"authorization": [
{
"actor": "gtcmpcrcm2h5",
"permission": "active"
}
],
"data": "509a90e8a22a5166c05e526c6d98b644e80300000000000004464f00000000000b48656c6c6f204669626f73"
}
],
"transaction_extensions": []
},
"signatures": [
"SIG_K1_K46edAAzozP1bThRBGnkNX5M11z4kVBzAAt4qrLZfFVcqaUBhsww3rBdiYgxvhdy3K39bZso1uZGMTp6PEMiQQiDWZATnx"
]
},
"transaction_id": "5dc6a856f8821a395993a083945e919eb19c0dc277c2a88e60ea31655cc07fba",
"processed": { ... }
Provided keys, permissions, and delays do not satisfy declared authorizations
{
"code":500,
"message":"Internal Service Error",
"error":{
"code":3090003,
"name":"unsatisfied_authorization",
"what":"Provided keys, permissions, and delays do not satisfy declared authorizations",
"details":[
{
"message":"transaction declares authority '{"actor":"cmvdkvfgedjg","permission":"active"}', but does not have signatures for it under a provided delay of 0 ms, provided permissions [], provided keys ["FO7zZJJdfTvGKpdudAcYQp1y97wP9cC9vG2McmeWzvgLWVnkVy9m"], and a delay max limit of 3888000000 ms",
"file":"authorization_manager.cpp",
"line_number":520,
"method":"check_authorization"
}
]
}
}
检查 transfer.js 文件中的公钥、私钥和 authorization 是否正确,而且都是 FO 转出方的。这里官网的示例代码注释写错了:https://dev.fo/zh-cn/guide/to...
symbol precision mismatch
{
"code":500,
"message":"Internal Service Error",
"error":{
"code":3050003,
"name":"eosio_assert_message_exception",
"what":"eosio_assert_message assertion failure",
"details":[
{
"message":"assertion failure with message: symbol precision mismatch",
"file":"wasm_interface.cpp",
"line_number":924,
"method":"eosio_assert"
},
{
"message":"pending console output: ",
"file":"apply_context.cpp",
"line_number":72,
"method":"exec_one"
}
]
}
}
FO 的精度错误,FO 数量一定要写四位小数。多了少了都会报这个错误。
missing contract
/Users/charlie/Code/fibos/transfer/node_modules/_eosjs@16.0.2@eosjs/lib/structs.js:546:7
assert(contract != null, 'missing contract');
^
Error: missing contract extransfer.quantity action.data transaction.actions
at Object.fromObject (/Users/charlie/Code/fibos/transfer/node_modules/_eosjs@16.0.2@eosjs/lib/structs.js:546:7)
at Object.fromObject (/Users/charlie/Code/fibos/transfer/node_modules/_fcbuffer@2.2.0@fcbuffer/lib/struct.js:151:34)
at actionDataFromObject (/Users/charlie/Code/fibos/transfer/node_modules/_eosjs@16.0.2@eosjs/lib/structs.js:753:29)
at Object.fromObject (/Users/charlie/Code/fibos/transfer/node_modules/_fcbuffer@2.2.0@fcbuffer/lib/struct.js:148:15)
at Object.fromObject (/Users/charlie/Code/fibos/transfer/node_modules/_fcbuffer@2.2.0@fcbuffer/lib/types.js:384:28)
at Object.fromObject (/Users/charlie/Code/fibos/transfer/node_modules/_fcbuffer@2.2.0@fcbuffer/lib/struct.js:151:34)
at _callee2$ (/Users/charlie/Code/fibos/transfer/node_modules/_eosjs@16.0.2@eosjs/lib/write-api.js:657:38)
at tryCatch (/Users/charlie/Code/fibos/transfer/node_modules/_regenerator-runtime@0.11.1@regenerator-runtime/runtime.js:62:40)
at Generator.invoke [as _invoke] (/Users/charlie/Code/fibos/transfer/node_modules/_regenerator-runtime@0.11.1@regenerator-runtime/runtime.js:296:22)
at Generator.forEach.prototype.(anonymous function) [as next] (/Users/charlie/Code/fibos/transfer/node_modules/_regenerator-runtime@0.11.1@regenerator-runtime/runtime.js:114:21)
这个使用错了 transfer 和 extransfer 函数。其中 transfer 函数表示的是与 EOS 主网兼容的转账方法,函数原型为:
其中 transfer 函数表示的是与 EOS 主网兼容的转账方法,函数原型为:
void transfer(account_name from, account_name to, asset quantity, string memo)
示例:
transfer('accountfrom', 'accountto', '1.0000 FO', 'memo field')
其中 extransfer 函数表示的是与 FIBOS 主网扩展的转账方法,该方法支持在 FIBOS 上发行的所有通证的转账,函数原型为:
void extransfer(account_name from, account_name to, extended_asset quantity, string memo)
示例:
extransfer('accountfrom', 'accountto', '1.0000 FO@eosio', 'memo field')
其中,@ 字符后表示的是该通证的发行方,系统通证 FO 为 FO@eosio,eosio 表示的是该通证是系统原生发行的通证。
转账操作相关的内容就介绍完了。有什么问题留言。也可以在知乎上关注我:https://zhuanlan.zhihu.com/p/...
查看原文赞 1 收藏 0 评论 1
xcwong 发布了文章 · 2019-07-03
FIBOS 开发环境配置非常简单,只需要安装 FIBOS 即可。不过值得一提的是,目前 FIBOS 只支持 UNIX 系统,比如 Mac OSX,Linux 和 FreeBSD,但是不支持 Window 系统。不过 Window 用户装一个虚拟机就可以造起来啦!
curl -s https://fibos.io/download/installer.sh | sh
一行代码搞定,So easy!
赶紧试试 FIBOS 装好没。
fibos --version
正常输出,棒!
理论上任何编辑器都可以,但是我选择了宇宙第一编辑器 VSCODE。因为 FIBOS DApp 基本上都是 js 文件,所以VSCODE 代码高亮基本上不用怎么配置就可以。
唯一需要配置的是 .abi文件。我们可以在 VSCODE 菜单栏中点击 preferences
,搜索 files.associations
,打开settings.json
文件在最后添加:
"files.associations": {
"*.abi": "json"
}
这几行的代码意思就是把 .abi
看做 json
文件进行代码高亮。
今天就分享到这里,下一篇文章进入正题。大家有兴趣也可以关注我的知乎专栏:https://www.zhihu.com/people/...
查看原文赞 1 收藏 0 评论 1
xcwong 发布了文章 · 2019-07-03
一直保持着如儿童般的好奇心和技术的热情,挺早就关注区块链和加密货币。当然并没有发财,这可能跟自己知识结构和本身比较穷有关。
在读大学的时候第一次听到了比特币,那时候可能几美元吧记不清了。后来接触到了以太坊,第一次听到了 DApp 这个词。可能当时各种资料比较少,只是看了看文档并没有真正尝试去写一个 DApp。
真正想着去编写一个 DApp 应该是在 2018 年,当时一句话感触至深:
你想真正了解一件事情最好的方法是参与其中。
于是乎在 2018 年花了一点点钱买了一点点加密币。大家不用猜也知道,买在了山顶上,哈哈哈。也就是从那时候开始,在 Udemy 买了课程 Ethereum and Solidity: The Complete Developer's Guide 和 参加了 LoomNetWork 的在线互动课程。这两个课程质量非常好,强烈推荐,算是我的 DApp 入门课程。但是但是,我并没有后续自己独立开发出一个 DApp。
再后来,通过一直在微博上关注的西祠胡同响马,了解到了 FIBOS,也把自己为数不多的 EOS 兑换成了 FO。
为什么会关注 FIBOS 呢?经历了 Solidity 写 DApp 的确发现 Solidity 其实也有一定的学习成本,虽然说 Solidity 跟 Javascript 类似,但是还是有自己不同的语法。另外 DApp 编程,跟中心化应用你的大脑回路需要不一样。另外 ETH 一个缺点就是性能比较差,而且跑你的 DApp 非常贵。大家应该也听说过一个云养猫的 DApp 就让 ETH 拥堵不堪了。
当然 ETH 的开发团度肯定也意识到了这些问题,但是有号称区块链 3.0 的 EOS 出来想要解决这些问题。更快的运行速度,更大的通量,更便宜的运行成本,但是似乎并没有解决开发门槛的问题。目前 EOS 仅仅支持 C 和 C ++ 编写合约。EOS 比 ETH 开发门槛更高了。
想必无数个开发工程师都会在咆哮,就不能来一个热门语言例如 Javascript 就能开发的智能合约呀!毕竟:
一个凡是真理:凡是能用 Javascript 写的,终将被 Javascript 改写。
最终历史重任似乎落到了 FIBOS 身上,一个结合 FIBJS 以及 EOS 的 JavaScript 的运行平台,它使得 EOS 提供可编程性,并允许使用 JavaScript 编写智能合约。JavaScript 开发 + BANCOR 协议智能通证 + 开发者服务,FIBOS 平台实现了快速开发、快速部署和稳定且流动的通证体系,帮助开发者一步进入区块链时代。
官方文档中仔细罗列了其与 EOS 相比的优势:
EOS 的编译环境依赖性强,编译过程时常遇到很多问题,对于普通一个开发者来说,大多数面对 CMake 的情况是束手无策的。
而 FIBOS 提供一套预编译开发环境,开发者可以快速实现部署,把更多的时间用在编写智能合约上。
编写 EOS 智能合约需要掌握 C++ 语言,这对于一名开发者来说学习成本非常高,并且我们认为正确的写出编译合约的 CMAKELISTS.TXT 才是刚刚开始!
而对于 FIBOS 来说,开发者可以使用 JavaScript 脚本语言进行编写智能合约,而这门语言学习成本很低。
对于一名开发者来说,如果一件事情简单容易,我们认为他们会更容易接受,并渴望了解 FIBOS。
EOS 的测试用例编写也必须使用 C++,高难度的语言学习,高难度的编译,使得测试这件事在 EOS 上面变得复杂、困难。
FIBOS 集成 FIBJS 服务端开发平台,拥有成熟的测试套件,在 FIBOS 平台上编写的用例,开发者可以使用 JavaScript 编写测试用例,这一切看起来非常的灵活、轻松!
一个 EOS 智能合约要想成功部署发布,需要经过编写、编译、部署、测试、调试、修复,漫长的等待过程。
FIBOS 支持本地合约模式,随时修改随时测试,结合一些 IDE 工具可以做到一键研发测试。
EOS 使用 C++ 参与编写研发,并不能做到 NPM 这样的生态环境,而 FIBOS 支持 NPM 包管理,与庞大的 NPM 生态紧密连接。
EOS 编写合约需要让 C++ 代码编译到 WASM,而 WASM 编译文件非常庞大,让发布部署运行合约成本非常高昂。
FIBOS 编写的合约可以通过打包脚本,压缩文件极大的降低部署发布成本。
EOS 合约编译成 WASM 后,对审计阅读合约代码带来了极大的困难,开发者无法评估合约的安全性。
FIBOS 的合约使用 JavaScript 编写并且全部开源,方便社区审计,迅速形成共识。
FIBOS 应该是我目前最好的选择
还是那句话,根据 FIBOS 的文档,FIBOS 在技术上优势明显。当然劣势也很显然,目前社区远小于 ETH 和 EOS,质量高、热闹的 DApp 也没有。
还是那句话,你想真正了解一件事情最好的方法是参与其中。我想要了解 FIBOS 是不是真的如此优秀,就是自己动手写一个 DApp。
那选哪个方向呢,我的想法是选一个业务代码不是很多的,但是需要区块链解决中心化 APP 无法解决的信任问题的场景去实现。这里先卖一个关子。
基本上技术架构:
上链部分:FIBOS # 需要学习,但是门槛应该不高
前 端:Vue.js # 比较擅长
后 端:SpingBoot # 最近正在学习,正好实践一下
目前暂时是有空就去做,还没有具体的计划。后续的进度和一些开发中的有趣的点应该也会在后续文章中分享。
大家也可以关注我的知乎,知乎更新会及时一点。https://www.zhihu.com/people/...
查看原文赞 1 收藏 0 评论 1
xcwong 关注了问题 · 2019-01-31
<style lang="less" scoped rel="stylesheet/less">
@import "../../common/less/mixin"; //正确
@import "src/common/less/mixin.less"; //报错
</style>
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'@': resolve('src'),
'src': resolve('src'),
'common': resolve('src/common'),
'components': resolve('src/components'),
'base': resolve('src/base'),
'api': resolve('src/api')
}
}
在vue里面@import路径问题,在webpack.base.config.js里面配置好别名,为什么在style标签里面直接用"src/"
会报错,需要在别的地方配置相关设置吗?在script标签里面引入没有问题,求解决方式!
关注 5 回答 2
xcwong 提出了问题 · 2019-01-20
一个时间戳在不同的系统上转为北京时间相差了一个小时,请问是什么原因?
时间戳:516466800
没有搞明白什么原因,请大神赐教。
关注 2 回答 1
xcwong 评论了文章 · 2018-12-06
本文仅仅是用于自我学习记录,参考了JohnLui的原创文章: https://segmentfault.com/a/11...
随着PHP标准和Composer包管理工具的面世,普通开发者撸一个框架已经不再是什么难事了。
无论是路由管理、ORM管理、还是视图渲染都有许许多多优秀的包可以使用。我们就像堆积木一样把这些包用composer一个个堆积起来。
接下来我们便是简单地实现一个MVC框架,来加深我们对框架的理解。
创建一个空的 composer.json
文件。
{
}
或者在空目录下执行:
composer init
则可以生成一个类型如下的文件:
{
"name": "charlie/my_first_framework",
"description": "My First Framework",
"type": "project",
"license": "MIT",
"authors": [
{
"name": "Charlie",
"email": "demo@qq.com"
}
],
"minimum-stability": "dev",
"require": {}
}
我们接下来安装一个管理路由的包: noahbuscher/macaw
。 功能比这个更加强大的路由包有很多,但是为了简单起见,我们选择安装这个包。
composer require noahbuscher/macaw
当前目录结构为:
➜ demo tree
.
├── composer.json
├── composer.lock
└── vendor
├── autoload.php
├── composer
│ ├── ClassLoader.php
│ ├── LICENSE
│ ├── autoload_classmap.php
│ ├── autoload_namespaces.php
│ ├── autoload_psr4.php
│ ├── autoload_real.php
│ ├── autoload_static.php
│ └── installed.json
└── noahbuscher
└── macaw
├── LICENSE.md
├── Macaw.php
├── README.md
├── composer.json
├── composer.lock
└── config
├── Web.config
└── nginx.conf
我们在根目录下新建一个public文件夹,并在该文件夹下新建 index.php
。index.php
文件类似于一个大厦的入口,我们所有的请求都运行 index.php
。
下面是 index.php
文件的代码:
// 自动加载vendor目录下的包
require '../vendor/autoload.php';
此时我们观察 index.php
,除了把vendor下面的包都 require 进来了外,其他啥都没干。那么如何响应各种各样的请求呢?
我们需要定义路由。路由就有点像快递分拣站,把写着不同地址的请求分拨给不同的控制器处理。
那么我们在根目录下创建一个 routes
文件夹,并在该文件夹下创建 web.php
文件。文件内容:
<?php
use NoahBuscher\Macaw\Macaw;
Macaw::get('hello', function () {
echo "hello world";
});
Macaw::dispatch();
然后我们启动php内置的开发服务器:
> cd public
> php -S localhost:8001
我们访问 http://localhost:8001/hello
就能看到我们预期的 "hello world".
上面我们仅仅实现了访问一个地址,返回一个字符串。下面我们来真正搭建MVC框架。MVC其实就是Model、View、Controller三个的简称。
不管怎么样,我们先新建三个文件夹再说,即 views
、models
、controllers
。
新建 controllersHomeController.php 文件,代码如下:
<?php
namespace App\Controllers;
use App\Models\Article;
class HomeController extends BaseController
{
public function home()
{
echo "<h1>This is Home Page</h1>";
}
}
另外我们在 routes/web.php
中添加一条路由:
Macaw::get('', 'App\\Controllers\\HomeController@home');
整体代码为:
<?php
use NoahBuscher\Macaw\Macaw;
Macaw::get('hello', function () {
echo "hello world";
});
Macaw::get('', 'App\\Controllers\\HomeController@home');
Macaw::dispatch();
此时已经绑定了一个路由至我们一个控制器的方法,但是我们去访问 http://localhost:8001
会出现 Uncaught Error: Class 'App\Controllers\HomeController' not found in
的错误。
为什么呢?
因为此时我们还并没有将控制器加载进来,程序并不知道控制器在哪儿。我们可以用 composer 的 classmap 方法加载进来。修改 composer.json 中添加:
"autoload": {
"classmap": [
"app/controllers",
"app/models"
]
}
我们顺便把models也加载进来。
composer dump-autoload
刷新自动加载
我们创建一个Article Model,这个 Model 对应数据库一张表。此时我们先用mysql 命令行工具新建一个 demo_database
的数据库,里面有一张表 articles
, 表的结构大致如下:
mysql> desc articles;
+---------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| title | varchar(256) | YES | | NULL | |
| content | varchar(256) | YES | | NULL | |
+---------+--------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)
我们再在表里面填入数据:
mysql> select * from articles;
+----+--------+--------------+
| id | title | content |
+----+--------+--------------+
| 1 | hhhhh | heheheheheh |
| 2 | hhhhh2 | heheheheh2eh |
+----+--------+--------------+
2 rows in set (0.00 sec)
当然了,我们现在是直接通过 MySQL 的 insert 命令直接填入数据,后续我们可以通过我们的框架新建 model 。
接下来我们要做的就是怎么在 Model 中连接数据库取到数据库里面的数据啦! 本文使用的 php 7.1,我们使用 mysqli 来连接数据库查询数据:
<?php
namespace App\Models;
class Article
{
public static function first()
{
//mysql_connect is deprecated
$mysqli = new \mysqli('localhost', 'root', 'w.x.c.123', 'demo_database');
if ($mysqli->connect_errno) {
echo "Failed to connect to Mysql: (" . $mysqli->connect_errno . ") " . $mysqli->connect_error();
}
$mysqli->set_charset('utf-8');
$result = $mysqli->query("SELECT * FROM articles limit 0,1");
$article = $result->fetch_array();
if ($article) {
echo "<p>" . $article['title'] . "</p>";
echo "<p>" . $article['content'] . "</p>";
}
$mysqli->close();
}
}
这么一来我们就可以在控制器里面使用
`
Article::first();
`
来查询 articles 表里面的第一条article数据,然后我们再通过 echo 返回给浏览器。
<?php
namespace App\Controllers;
use App\Models\Article;
class HomeController extends BaseController
{
public function home()
{
Article::first();
}
}
看上面的代码,我们在 Article Model 中连续写了两条 echo 语句来格式化输出。如果后续我们的页面十分复杂的时候,我们把所有的格式化输出的代码都写在 Model 里面感觉是个灾难。我们应该把这些格式化输出的代码分离出来,这便是我们说的 MVC 层的 View 层。
我们在 views 目录下新建 home.php:
<?php
echo "<p>" . $article['title'] . "</p>";
echo "<p>" . $article['content'] . "</p>";
我们再改写一下 Article.php,删除echo 那两行,直接
return article;
然后我们在 HomeController 中指定要使用的 view:
<?php
namespace App\Controllers;
use App\Models\Article;
class HomeController extends BaseController
{
public function home()
{
$article = Article::first();
require dirname(__FILE__) . "/../views/home.php";
}
}
我们这里的 view 层仅仅是用 PHP 拼接了 html,后续我们需要实现更加复杂的视图的时候,我们可以引入模版引擎。
我们一路从一个空文件夹开始,打造一个自己的一个框架,感觉并没有写多少代码,唯一写了好几行代码感觉比较麻烦的就是连接数据库来查询数据了。我们我们有很多 Model,要实现 增删改查的话,我们岂不是要重复 连接,查询、插入、删除、更新,然后关闭连接?我们应该把这些功能分装一下。
怎么封装?有其他人写好的包了,直接拿来用吧~ 当然如果你想自己造轮子的话,也可以自己实现一下。
我们这里使用 illuminate/database
:
composer require illuminate/database
然后我们在 public/index.php 中引入:
use Illuminate\Database\Capsule\Manager as Capsule;
require '../vendor/autoload.php';
// Eloquent ORM
$capsule = new Capsule();
$capsule->addConnection(require '../config/database.php');
$capsule->bootEloquent();
//路由配置
require '../routes/web.php';
我们在连接数据的时候,使用了 config/database.php 的数据库配置文件。
<?php
return [
'driver' => 'mysql',
'host' => 'localhost',
'database' => 'demo_database',
'username' => 'root',
'password' => 'secret',
'charset' => 'utf8',
'collation' => 'utf8_general_ci',
'prefix' => ''
];
接下来我们就可以删掉 models/Article.php 文件中我们写的大部分代码,而仅仅需要继承IlluminateDatabaseEloquentModel 来使用 Eloquent ORM 的功能:
<?php
/**
* Created by PhpStorm.
* User: W
* Date: 24/03/2018
* Time: 22:23
*/
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Article extends Model
{
public $timestamps = false;
}
更多Eloquent ORM的功能,您也可以自己查阅文档。
好了,我们一个 MVC 框架基本上就搭建完了,我们回头看一下整个框架目录结构,是不是跟 Laravel 有点像呢?
➜ myFirstFramework git:(master) ✗ tree
.
├── README.md
├── app
│ ├── controllers
│ │ ├── BaseController.php
│ │ └── HomeController.php
│ ├── models
│ │ └── Article.php
│ └── views
│ └── home.php
├── composer.json
├── composer.lock
├── config
│ └── database.php
├── public
│ └── index.php
├── routes
│ └── web.php
└── vendor ...
本文用到的示例代码已经上传到github:https://github.com/charliex2/...
查看原文xcwong 关注了专栏 · 2018-12-06
PHP 框架 TinyLara 作者。 http://TinyLara.com/ iOS & OS X 开发者。
关注 74
xcwong 回答了问题 · 2018-07-26
已经找到答案。看起来是一个 Laravel 的 bug
相似的问题:
https://laracasts.com/discuss...
Eloquent 应该写成:
$sample->update([
'ext'=>[
'hello'=>'world2'
]
]);
DB class 可以写成
$sample->update(['ext.hello'=>'world2']);
关注 1 回答 1
xcwong 提出了问题 · 2018-07-26
https://laravel-china.org/top...
Laravel 5.5
MySQL 5.7.22
$table->json('ext')->nullable()->comment('额外的信息');
protected $casts = [
'ext' => 'object'
];
>>> $sample = App\Entities\Sample::find(6);
=> App\Entities\Sample {#2947
id: 6,
surveyor_group_id: null,
share_code: "2sssshell2o",
code: "222221234526",
name: "charlie",
mobile: "xxxxxx",
telephone: "xxx-xxxx",
address: "8282891",
location_id: null,
ext: "{"hello": "word"}",
created_at: "2018-07-26 03:21:52",
updated_at: "2018-07-26 03:21:52",
}
>>> $sample->update(['ext->hello'=>'nihao']);
=> true
>>> $sample
=> App\Entities\Sample {#2947
id: 6,
surveyor_group_id: null,
share_code: "2sssshell2o",
code: "222221234526",
name: "charlie",
mobile: "xxxxx",
telephone: "xxx-xxxx",
address: "8282891",
location_id: null,
ext: "{"hello": "word"}",
created_at: "2018-07-26 03:21:52",
updated_at: "2018-07-26 03:21:52",
}
>>> $sample->update(['ext->hello'=>'nihao']);
数据库为更新没有生效?各位大神是啥问题呢?
关注 1 回答 1
xcwong 提出了问题 · 2018-05-18
records
表中有一个 column
叫 value
,里面存的是一个字符串,类似:{ number: 20}
.
现在需要对这个表进行 where 筛选,筛选出 value 里 number < 100 的。
Laravel Query 该怎么写呢?
注意:Laravel 5.2 Mysql 5.6 不支持 json query。
关注 1 回答 0
xcwong 回答了问题 · 2018-04-07
Laravel 5.5 开始支持包自动发现,因此可以在自己开发的包内使用其他 Laravel 包。
因此仅仅需要 composer require
进来即可。
关注 2 回答 2
xcwong 提出了问题 · 2018-04-03
在自己开发一个laravel包的时候,如何使用例如l5-repository(https://packagist.org/package...)之类的laravel包?
我们可以用composer require进来,但是如何在包内注册 ServiceProvider 呢?
关注 2 回答 2
推荐关注