SegmentFault Laravel最新的文章
2022-05-17T23:21:13+08:00
https://segmentfault.com/feeds/blogs
https://creativecommons.org/licenses/by-nc-nd/4.0/
DeFi 程序设计:Uniswap V2(1)
https://segmentfault.com/a/1190000041859308
2022-05-17T23:21:13+08:00
2022-05-17T23:21:13+08:00
stoneworld
https://segmentfault.com/u/stoneworld
0
<h2>DeFi 程序设计:Uniswap V2(1)</h2><h3>什么是 Uniswap ?</h3><p>简单来说,<a href="https://link.segmentfault.com/?enc=hU1mu8nj8ho18UuEQUbiGw%3D%3D.RryXpXvgNl%2FqRdJ9fA%2B%2BHS7HHQTo23%2F%2Beak7jBJe2Nc%3D" rel="nofollow">Uniswap</a> 是一个去中心化交易所(DEX),旨在成为中心化交易所的替代品。它在以太坊区块链上运行并且完全自动化:没有管理员或具有特权访问权限的用户。</p><p>更深层次的说法是,它是一种算法,允许创建交易池或者代币交易对,并为它们填充流动性,让用户使用这种流动性交换代币。这种算法称为自动做市商或自动流动性提供者。</p><p>那什么是做市商呢?</p><p><a href="https://link.segmentfault.com/?enc=tuI5hP6un331SxjievbfHQ%3D%3D.u391YpEJXgwbOOyTSGKeCokaKkHXicGLwEchKBGuW3v1e%2FOBYC%2FH5a%2FKq1ly0TOSHQdDGQYWhxbz6APW8RchzA%3D%3D" rel="nofollow">做市商</a> 是向市场提供流动性(交易资产)的实体。交易的本质其实是流动性:如果您想出售某样东西但没有人购买,则不会进行交易。一些交易对具有高流动性(例如 BTC-USDT),但有些交易对的流动性低或根本没有流动性(例如一些山寨币)。</p><p><code>DEX</code>(去中心化交易所) 必须有大量的流动性才能发挥作用,才有可能替换传统的中心化交易所。获得流动性的一个方法是 DEX 的开发者将他们自己的钱(或他们投资者的钱)投入其中,成为做市商。然而,这不是一个现实的解决方案,因为考虑到 DEX 允许任何代币之间的交换,他们需要大量的资金来为所有货币对提供足够的流动性。此外,这将使 <code>DEX</code> 中心化:作为唯一的做市商,开发人员将在他们手中拥有大量的权力,这与去中心化的理念相悖,所以肯定是行不通的。</p><p>更好的解决方案是允许任何人成为做市商,这就是 <code>Uniswap</code> 成为自动做市商的原因:任何用户都可以将他们的资金存入交易对(并从中受益)。</p><p><code>Uniswap</code> 扮演的另一个重要角色是价格预言机。价格预言机是从中心化交易所获取代币价格并将其提供给智能合约的服务——这样的价格通常难以操纵,因为中心化交易所的交易量通常非常大。然而,虽然没有那么大的交易量,Uniswap 仍然可以作为价格预言机。</p><p><code>Uniswap</code> 作为一个二级市场,吸引了套利者,他们通过 <code>Uniswap</code> 和 <code>CEX</code> 之间的价格差异获利,这使得 <code>Uniswap</code> 资金池上的价格尽可能地接近大交易所的价格。</p><h3>恒定乘积做市商</h3><p><code>Uniswap</code> 核心是恒定乘积函数:<br><img src="/img/remote/1460000041859310" alt="" title=""></p><p>其中 X 是 ETH 储备,Y 是代币储备(或反之),K 是一个常数。<code>Uniswap</code> 要求 K 保持不变,无论有多少 X 或 Y 的储备。当你用以太坊换代币时,你把你的以太存入合约,并得到一定数量的代币作为回报。<code>Uniswap</code> 确保每次交易后 K 保持不变(这并不是真的,我们将在后面看到原因),这个公式也负责定价计算,随后我们会看到具体的实现,至此 <code>Uniswap</code> 的实现原理已经讲述完成了,随后我们将实现一个 <code>Uniswap V2</code>。</p><h3>工具集</h3><p>在本教程系列中,我这里将使用 <a href="https://link.segmentfault.com/?enc=vdT2pmXtyJI3GK95iYqAIA%3D%3D.%2FHgbubdWnTN0%2BP2HBM1WLVWnv0FM8nNUje0x%2BmoM2WUV8uOFhH0TxzOsSlBG5qg2" rel="nofollow">Foundry</a> 进行合约开发和测试,<code>Foundry</code> 是用 <code>Rust</code> 编写的现代化的以太坊工具包,相比 <code>Hardhat</code> 更快,更重要的是允许我们使用 <code>Solidity</code> 编写测试代码,这对于一个后端开发更加友好和方便。</p><p>我们还将使用 <a href="https://link.segmentfault.com/?enc=naa6BKGcW9LTcntchwYzBg%3D%3D.F31kRwbK%2F86vFnxUl756bVf1OTIul5bwKU3lEHzFbTJaYJwKuphx9xb1p2QdRxed" rel="nofollow">solmate</a>,代替 <code>OpenZeppelin</code> 来实现 <code>ERC20</code>,因为后者有些臃肿和固执己见。在这个项目中不使用 <code>OpenZeppelin</code> 来实现 <code>ERC20</code> 的一个具体原因是它不允许将代币转移到零地址。反过来,Solmate 是一系列 gas 优化合约,并没有那么限制。</p><p>还值得注意的是,自 2020 年 <code>Uniswap V2</code> 推出以来,许多事情都发生了变化。例如,SafeMath 自 <code>Solidity</code> 0.8 发布以来,库已经过时,它引入了本机溢出检查。所以可以说,我们正在构建一个现代版本的 Uniswap。</p><h3>Uniswap V2 架构</h3><p>Uniswap V2 的核心架构思想是流动性池子:流动性提供者可以在合约中质押他们的流动性;抵押的流动性允许其他任何人以去中心化的方式进行交易。与 Uniswap V1 类似,交易者支付少量费用,这些费用在合约中累积,然后由所有流动性提供者共享。</p><p>Uniswap V2 的核心合约是 UniswapV2Pair。该合约的主要目的是接受用户的代币,并使用累积的代币储备来进行交换。这就是为什么它是一个汇集合约。每个UniswapV2Pair合约只能汇集一对代币,并且只允许在这两个代币之间进行交换——这就是它被称为“Pair”的原因。</p><p>Uniswap V2 合约的代码库分为两个存储库:</p><ol><li><a href="https://link.segmentfault.com/?enc=%2FSSYo2rOppTbJCWjfPAQKQ%3D%3D.v2CfVL6WZwj8gKXx36UUSta2QDaXc5pDbftEvH4S3H1Gd6fI9FTGLB3J7Fbfjhm1" rel="nofollow">核心(v2-core)</a></li><li><a href="https://link.segmentfault.com/?enc=Tfmp%2FjQI8meOCtSTnm5inw%3D%3D.Q%2Fv2l8UstxGszO%2B7OceNMiTRxYDQfHMROemwBQ9f20CYlOlny2kZnrFFElXZp6dU" rel="nofollow">外围(v2-periphery)</a></li></ol><p>核心存储库存储这些合约:</p><ol><li>UniswapV2ERC20 – 用于 LP 代币的扩展 ERC20 实现。它还实现了 EIP-2612 用来支持代币转移的链下批准。</li><li>UniswapV2Factory – 这是一个工厂合约,它创建 Pair 合约并充当它们的注册表,其用 <code>create2</code> 的方式生成配对地址 —— 后续我们将详细了解它是如何工作的。</li><li>UniswapV2Pair – 负责核心逻辑的主合约。</li></ol><p>外围存储库包含多个使 Uniswap 更易于使用的合约。其中包括 UniswapV2Router,它是 Uniswap UI 和其他在 Uniswap 之上工作的去中心化应用程序的主要入口点。</p><p>外围存储库中的另一个重要合约是 UniswapV2Library,它是实现重要计算的辅助函数的集合。我们将实现这两个合约。</p><p>好吧,让我们开始吧!</p><h3>流动性资金池</h3><p>没有流动性,就不可能有交易。因此,我们需要实现的第一个功能是流动资金池。它是如何工作的?</p><p>流动性池只是存储代币流动性的合约,并允许执行使用这种流动性的互换。因此,"流动性池 "意味着将代币发送到一个智能合约,并在那里储存一段时间。</p><p>你可能已经知道,每个合同都有自己的存储空间,ERC20代币也是如此,每个代币都有一个连接地址和余额的 <code>mapping</code> 。而我们的资金池将在 ERC20 合约中拥有自己的余额。这足以让资金池有流动性吗?事实证明,不会。</p><p>主要原因是,仅依靠 ERC20 余额将使价格操纵成为可能:想象一下,有人向一个池子发送大量的代币,进行有利可图的交换,并在最后兑现。为了避免这种情况,我们需要跟踪我们这边的资金池储备,而且我们还需要控制它们的更新时间。<br>我们将使用 reserve0 和 reserve1 变量来跟踪池子里的储备。</p><pre><code class="Solidity">contract ZuniswapV2Pair is ERC20, Math {
...
uint256 private reserve0;
uint256 private reserve1;
...
}
</code></pre><blockquote>为了简洁起见,我省略了很多的代码。请查看 <a href="https://link.segmentfault.com/?enc=%2BxA0eDt3U7g0X33HKnYMMQ%3D%3D.WuZvCcPhMz22pgtODA4wkhf5xYLQcmjEYxPsga2yHDytklhZsjBZxlxV67MuI4fI" rel="nofollow">GitHub repo</a> 的完整代码。</blockquote><p>Uniswap V2 在外围合约 UniswapV2Router 中实现了一个增加流动性的方法,但其底层的流动性其实还是存在于配对合约中:流动性管理被简单地看作是 LP-tokens 管理。当你向一个配对添加流动性时,合约就会 mint LP-tokens;当你移除流动性时,LP-tokens 就会被 burn,核心合约是较底层的合约,只执行核心操作。</p><p>如下是存入流动性的底层函数:</p><pre><code class="Solidity">function mint() public {
uint256 balance0 = IERC20(token0).balanceOf(address(this));
uint256 balance1 = IERC20(token1).balanceOf(address(this));
uint256 amount0 = balance0 - reserve0; // 尚未被计算的新存入的金额
uint256 amount1 = balance0 - reserve1; // 尚未被计算的新存入的金额
uint256 liquidity;
if (totalSupply == 0) {
liquidity = ???
_mint(address(0), MINIMUM_LIQUIDITY);
} else {
liquidity = ???
}
if (liquidity <= 0) revert InsufficientLiquidityMinted();
_mint(msg.sender, liquidity);
_update(balance0, balance1);
emit Mint(msg.sender, amount0, amount1);
}</code></pre><p>首先,我们需要计算尚未被计算的新存入的金额(保存在储备金中),注释中也有写清除,然后,我们计算必须发行的 LP 代币的数量,作为对提供流动性的奖励。然后,我们发行代币并更新储备(函数 _update 简单地将余额保存到储备变量中),整个流动性提供的方法已经完成了。对于最初的 LP 金额,Uniswap V2 最终使用了存入金额的几何平均值,现在,让我们计算一下在已经有一些流动性的情况下发行的 LP 代币。</p><p>这里的主要要求是:</p><ol><li>与存入的金额成正比。</li><li>与LP-tokens的总发行量成比例。</li></ol><p>白皮书上给到了这样一个公式:</p><p><img src="/img/remote/1460000041859311" alt="" title=""></p><p>新的 LP 代币数量,与存入的代币数量成正比,被铸造出来。但是,在V2中,有两个基础代币--我们应该在公式中使用哪一个?</p><p><img src="/img/remote/1460000041859312" alt="" title=""></p><p>我们可以选择其中之一,但有一个有趣的问题:存入金额的比例与储备金的比例越接近,差异越小。因此,如果存入金额的比例不同,LP 金额也会不同,其中一个会比另一个大。如果我们选择较大的那个,那么我们就会通过提供流动性来激励价格变化,这就导致了价格操纵。如果我们选择较小的一个,我们将惩罚存放不平衡的流动性(流动性提供者将得到较少的LP-tokens)。很明显,选择较小的数字更有利,这就是 Uniswap 正在做的事情,其实你会发现这里并没有去计算你质押 A Token,需要多少的 B Token 来平衡流动性,这个事情其实是放到了外围的路由合约中实现的,但底层的合约足够简单,任何人也可以不通过路由合约直接调用 pair 合约本身去提供流动性。</p><p>举个例子,假设 A 用户按照 100:100 提供了流动性,他当前 LP-Token 是 100,此时 B 按照 200:100 去提供了流动性,如果按照 200 去计算发现,B 的 LP-Token 是 200,但对于 A 而言是不公平的,而且会导致价格波动太大。如果按照 100 去计算,那对 B 而言其实提供 100:100 就可以获得 100 的流动性,但却多付出了 100 的 A Token,导致最后用户提取流动性的时候会损失一部分代币,算是对 B 用户提供流动性不平衡的惩罚,所以最终的代码如下:</p><pre><code>if (totalSupply == 0) {
liquidity = Math.sqrt(amount0 * amount1) - MINIMUM_LIQUIDITY;
_mint(address(0), MINIMUM_LIQUIDITY);
} else {
liquidity = Math.min(
(amount0 * totalSupply) / _reserve0,
(amount1 * totalSupply) / _reserve1
);
}</code></pre><p>在 totalSupply == 0 时,我们在提供初始流动性时减去 MINIMUM_LIQUIDITY(这是一个常数1000)。这可以防止一个流动性池的代币份额(1e-18)变得太贵,这将拒绝小型流动性提供者。简单地从初始流动性中减去1000,使得一个流动性份额的价格便宜了1000倍。这里有一篇文章分析了这个问题。<a href="https://link.segmentfault.com/?enc=r252BZzHpv0LxSHRhK7Wag%3D%3D.QKssEYTM2AmRx3jLDgaIfIizvJj0%2Bh0g0TGp6tbWGYo8bH5Nslei8OtLltx79zZs" rel="nofollow">Uniswap V2 设计迷思</a></p><h3>在 Solidity 中编写测试</h3><p>正如我上面所说的,我将使用 Foundry 来测试我们的智能合约--这将使我们能够快速建立我们的测试,并且不与 JavaScript 有任何业务。</p><p>首先我们先初始化测试合约:</p><pre><code>contract ZuniswapV2PairTest is DSTest {
ERC20Mintable token0;
ERC20Mintable token1;
ZuniswapV2Pair pair;
function setUp() public {
token0 = new ERC20Mintable("Token A", "TKNA");
token1 = new ERC20Mintable("Token B", "TKNB");
pair = new ZuniswapV2Pair(address(token0), address(token1));
token0.mint(10 ether);
token1.mint(10 ether);
}
// Any function starting with "test" is a test case.
}</code></pre><p>让我们为提供初始流动性添加一个测试:</p><pre><code>function testMintBootstrap() public {
token0.transfer(address(pair), 1 ether);
token1.transfer(address(pair), 1 ether);
pair.mint();
assertEq(pair.balanceOf(address(this)), 1 ether - 1000);
assertReserves(1 ether, 1 ether);
assertEq(pair.totalSupply(), 1 ether);
}
</code></pre><p>1 ether token0 和1 ether token1 被添加到测试池中。结果,1 ether LP 代币被发行,我们得到了1 ether -1000(减去最小流动性)。池子的储备和总供应量得到相应的改变。</p><p>当平衡的流动性被提供给一个已经有一些流动性的池子时会发生什么?让我们来看看。</p><pre><code>function testMintWhenTheresLiquidity() public {
token0.transfer(address(pair), 1 ether);
token1.transfer(address(pair), 1 ether);
pair.mint(); // + 1 LP
token0.transfer(address(pair), 2 ether);
token1.transfer(address(pair), 2 ether);
pair.mint(); // + 2 LP
assertEq(pair.balanceOf(address(this)), 3 ether - 1000);
assertEq(pair.totalSupply(), 3 ether);
assertReserves(3 ether, 3 ether);
}
</code></pre><p>这里的一切看起来都是正确的。让我们看看当提供不平衡的流动性时会发生什么:</p><pre><code>function testMintUnbalanced() public {
token0.transfer(address(pair), 1 ether);
token1.transfer(address(pair), 1 ether);
pair.mint(); // + 1 LP
assertEq(pair.balanceOf(address(this)), 1 ether - 1000);
assertReserves(1 ether, 1 ether);
token0.transfer(address(pair), 2 ether);
token1.transfer(address(pair), 1 ether);
pair.mint(); // + 1 LP
assertEq(pair.balanceOf(address(this)), 2 ether - 1000);
assertReserves(3 ether, 2 ether);
}
</code></pre><p>这就是我们所说的:即使用户提供的 token0 流动性多于 token1 流动性,他们仍然只得到 1 个 LP-token。 现在让我们转向流动性移除。</p><h3>移除流动性</h3><p>流动性的消除与供应相反。同样地,燃烧与铸造相反。从池中移除流动性意味着燃烧 LP 代币以换取相应数量的基础代币。返回给提供流动性的代币数量计算公式如下:</p><p><img src="/img/remote/1460000041859313" alt="" title=""></p><p>简单地说:返回的代币数量与持有的 LP 代币数量与 LP 代币的总供应量成正比。你的 LP 代币份额越大,你燃烧后得到的储备份额就越大。<br>功能实现如下:</p><pre><code>function burn() public {
uint256 balance0 = IERC20(token0).balanceOf(address(this));
uint256 balance1 = IERC20(token1).balanceOf(address(this));
uint256 liquidity = balanceOf[msg.sender];
uint256 amount0 = (liquidity * balance0) / totalSupply;
uint256 amount1 = (liquidity * balance1) / totalSupply;
if (amount0 <= 0 || amount1 <= 0) revert InsufficientLiquidityBurned();
_burn(msg.sender, liquidity);
_safeTransfer(token0, msg.sender, amount0);
_safeTransfer(token1, msg.sender, amount1);
balance0 = IERC20(token0).balanceOf(address(this));
balance1 = IERC20(token1).balanceOf(address(this));
_update(balance0, balance1);
emit Burn(msg.sender, amount0, amount1);
}</code></pre><p>可以看到 uniswap 是不支持移除部分流动性的,当然上述代码其实是存在部分问题,后续我们会解决这些问题。下面我们继续完善合约测试部分。</p><pre><code>function testBurn() public {
token0.transfer(address(pair), 1 ether);
token1.transfer(address(pair), 1 ether);
pair.mint();
pair.burn();
assertEq(pair.balanceOf(address(this)), 0);
assertReserves(1000, 1000);
assertEq(pair.totalSupply(), 1000);
assertEq(token0.balanceOf(address(this)), 10 ether - 1000);
assertEq(token1.balanceOf(address(this)), 10 ether - 1000);
}
</code></pre><p>我们看到,除了发送到零地址的最低流动性外,资金池回到了未初始化的状态。</p><p>现在,让我们看看当我们在提供不平衡的流动性后燃烧时会发生什么:</p><pre><code>function testBurnUnbalanced() public {
token0.transfer(address(pair), 1 ether);
token1.transfer(address(pair), 1 ether);
pair.mint();
token0.transfer(address(pair), 2 ether);
token1.transfer(address(pair), 1 ether);
pair.mint(); // + 1 LP
pair.burn();
assertEq(pair.balanceOf(address(this)), 0);
assertReserves(1500, 1000);
assertEq(pair.totalSupply(), 1000);
assertEq(token0.balanceOf(address(this)), 10 ether - 1500);
assertEq(token1.balanceOf(address(this)), 10 ether - 1000);
}</code></pre><p>我们在这里看到的是,我们已经失去了 500 wei 的 token0!这是我们在上面谈到的对价格操纵的惩罚。但这个数额小得离谱,看起来一点都不重要。这是因为我们目前的用户(测试合约)是唯一的流动性提供者。如果我们向一个由另一个用户初始化的池子提供不平衡的流动性,会怎么样?让我们来看看。</p><pre><code>function testBurnUnbalancedDifferentUsers() public {
testUser.provideLiquidity(
address(pair),
address(token0),
address(token1),
1 ether,
1 ether
);
assertEq(pair.balanceOf(address(this)), 0);
assertEq(pair.balanceOf(address(testUser)), 1 ether - 1000);
assertEq(pair.totalSupply(), 1 ether);
token0.transfer(address(pair), 2 ether);
token1.transfer(address(pair), 1 ether);
pair.mint(); // + 1 LP
assertEq(pair.balanceOf(address(this)), 1);
pair.burn();
assertEq(pair.balanceOf(address(this)), 0);
assertReserves(1.5 ether, 1 ether);
assertEq(pair.totalSupply(), 1 ether);
assertEq(token0.balanceOf(address(this)), 10 ether - 0.5 ether);
assertEq(token1.balanceOf(address(this)), 10 ether);
}</code></pre><p>我们现在损失了 0.5 ether,这是我们存入的 1/4。现在这是一个很大的数量! 那么是谁最终得到了这 0.5 ether:配对还是测试用户呢?写个测试函数试试呢?</p><h3>结论</h3><p>今天的文章到此就结束了,如果有什么问题请给我留言。</p><p>后续更新请关注<a href="https://link.segmentfault.com/?enc=%2BafBXbzmS2VXTtc6ZYtoxw%3D%3D.wBphGAddxVFh4rX16crYw0padQ8MMLSsitiim9QVfl2m%2BiBedzMxPkjLCp7VakXUh%2FMqOmBwqBOulTiEm9NTXhJREFFn2mX66G%2FXci0KHeHCIwG9Koc%2FuLVakPpeadEIUDEPuBQiUdNPuU7Idfok%2FDxB5okc%2FBGAyY%2FUBtd8ZRLlt9%2BhQpGbkv%2BtnzuVXeSXYRWbge4OuAZkQQCTmx9YD9CQCi1K%2FzSVyE%2BFnQz12%2FGg%2BKvSyMlaBvXwzfFtj%2FgUb3B0c2IbGZZ0CRVIoysAZm7R%2F75YhQ7rFhcotzp1Ivk%3D" rel="nofollow">公众号</a></p>
MySQL数据库设计规范
https://segmentfault.com/a/1190000007616456
2016-11-27T21:30:20+08:00
2016-11-27T21:30:20+08:00
stoneworld
https://segmentfault.com/u/stoneworld
7
<h3>命名规范</h3>
<ol>
<li><p>表名字段名均使用小写字母,单词间以下划线分割。</p></li>
<li><p>表名字段名长度禁止超过32个字符,最大支持为64个字符,为了统一规范,易于查询,超过的单词尽量可读缩略的形式进行书写。</p></li>
<li><p>普通索引名称以 idx_ 开头,唯一索引以 uk_ 开头。</p></li>
<li><p>外键尽量以被引用表名称加 _id 组成。</p></li>
</ol>
<h3>基础规范</h3>
<ol>
<li><p>统一使用 INNODB 存储引擎,除非某些特定原因再行商议。</p></li>
<li><p>表字符集统一使用 UTF8,UTF8 字符集存储汉字占用3个字节,存储英文字符占用一个字节,如果emoji等表情符号的存储需求,可申请使用 UTF8MB4 字符集。</p></li>
<li><p>字段统一添加注释,id 可除外,type 型需指明主要值的含义,如”1 公开课,2 线上课”。</p></li>
<li><p>使用 timestamp 存储时间。</p></li>
<li><p>表必需指定主键,尽量采用自增方式。</p></li>
<li><p>不强制使用外键约束,此过程由业务端实现,提高性能。</p></li>
<li><p>能不用 NOT IN 就不用 NOT IN,会把空和NULL给查出来。</p></li>
<li><p>尽可能少的使用 TEXT、BLOB 类型。</p></li>
</ol>
<h3>索引优化规范</h3>
<ol>
<li><p>对于复杂的查询,执行 explain,查看索引使用情况。</p></li>
<li><p>重要的 SQL 必须被索引,比如 UPDATE、DELETE 语句的WHERE条件列 ORDER BY、GROUP BY、DISTINCT的字段。</p></li>
<li><p>不在低基数列上建立索引,例如“性别”。</p></li>
<li><p>如果是索引字段,一定要定义为not null,因为 null 值会影响 cordinate 统计,影响优化器对索引的选择,不能保证有值,设置相应的默认值。</p></li>
<li><p>单表索引个数尽量限制在5个以内。</p></li>
<li><p>避免使大表的 JOIN。</p></li>
<li><p>最左前缀原则,mysql 使用联合索引时,从左向右匹配,遇到断开或者范围查询时,无法用到后续的索引列。</p></li>
<li><p>尽量减少直接使用 SELECT * 读取全部字段。</p></li>
<li><p>使用 like 模糊匹配,%不要放首位。</p></li>
</ol>
<h4>本文参考</h4>
<ul>
<li><p><a href="https://link.segmentfault.com/?enc=in15VPu43A7PK9MPgBg6bw%3D%3D.zp1BY%2FSB5U0E5W9moUz6r%2B8QE5i%2Bza57%2Bk0QNdUDJg51URclciWgFHTZVZAQVJAa" rel="nofollow">互联网MySQL开发规范</a></p></li>
<li><p><a href="https://link.segmentfault.com/?enc=nDYRJAB%2FfLcqB65lfZ1mVA%3D%3D.YKR1cDoKvnM2EVTvNG8xuAQSHJJpBPkeiC4N11pbNeTwYVg1N4MXyvcu1cCYUKMJvg0Oqh4CiVBzfxWB57AcjA%3D%3D" rel="nofollow">MySQL数据库开发规范</a></p></li>
</ul>
Laravel 队列服务
https://segmentfault.com/a/1190000007469803
2016-11-13T21:41:36+08:00
2016-11-13T21:41:36+08:00
stoneworld
https://segmentfault.com/u/stoneworld
1
<p><img src="/img/bVFvpd?w=980&h=274" alt="Laravel" title="Laravel"></p>
<p><a href="https://link.segmentfault.com/?enc=df0xz4vRDn9ZfLcEc8uAcg%3D%3D.c9IPlLGPW6yaZbqEF43UpxzonyIwcyq1eljJvU5aSx5ms5U19dn6vA2T%2FMshJbWy" rel="nofollow">看完文档</a> 后总想知道是怎么样一个开始,又是怎样的一个结束!<a href="https://link.segmentfault.com/?enc=2IaVqt6rIhrlTynyBL4zrw%3D%3D.Kh%2FZB4%2FihEXpsmM7GQKIIQO%2BX4G93EvaeRtLpvlX1vU%3D" rel="nofollow">图片来源</a></p>
<h3><code>QueueServiceProvider</code></h3>
<p><code>Laravel</code> 各种服务的注册大多都是通过各种 <code>ServiceProvider</code> 进行绑定的,队列服务也不例外,打开 <code>namespace IlluminateQueueQueueServiceProvider</code> 文件定位到 <code>register</code> 方法,</p>
<pre><code class="php">public function register()
{
// 注册队列管理器 一旦实例化,为队列连接器注册各种解析器,这些连接器负责创建接受队列配置和实例化各种不同队列处理的类。
// 按照配置文件注册一个默认连接方式 在此使用 redis
$this->registerManager();
// 注册队列各种命令 队列连接 重启等。
$this->registerWorker();
// 注册队列监听命令
$this->registerListener();
// 5.1后弃用
$this->registerSubscriber();
// 注册队列失败处理
$this->registerFailedJobServices();
// Register the Illuminate queued closure job. 什么用,后面再看。
$this->registerQueueClosure();
}</code></pre>
<h3>任务创建与分配</h3>
<pre><code>php artisan make:job SendReminderEmail
</code></pre>
<p>按照文档的方式生成了一个队列任务类,该类继承了 <code>namespaceAppJobsJob</code>, 实现了接口 <code>SelfHandling</code> 和 <code>ShouldQueue</code> , 或许你会问这两个接口啥规范都没规定啥用啊(先略过), 重点在两个 <code>trait</code> 内,对队列任务实现了各种操作,删除,重试,延迟等。<br>在分配任务的时候我们使用了辅助函数 <code>dispatch</code> ,其实是 <code>IlluminateBus</code> 下 <code>Dispatcher</code> 类的 <code>dispatch</code>方法</p>
<pre><code class="php">public function dispatch($command, Closure $afterResolving = null)
{
if ($this->queueResolver && $this->commandShouldBeQueued($command)) {
// 队列执行
return $this->dispatchToQueue($command);
} else {
// 立即执行
return $this->dispatchNow($command, $afterResolving);
}
}
protected function commandShouldBeQueued($command)
{
if ($command instanceof ShouldQueue) { // 就这用。。
return true;
}
return (new ReflectionClass($this->getHandlerClass($command)))->implementsInterface(
'Illuminate\Contracts\Queue\ShouldQueue'
);
}</code></pre>
<p>在此,我们先看下 <code>namespace IlluminateBusBusServiceProvider</code> 下的</p>
<pre><code class="php">public function register()
{
$this->app->singleton('Illuminate\Bus\Dispatcher', function ($app) {
return new Dispatcher($app, function () use ($app) {
// 'queue.connection' => 'Illuminate\Contracts\Queue\Queue', 再回看 QueueServiceProvider 的 registerManager 方法,就很清晰了。
return $app['Illuminate\Contracts\Queue\Queue']; // 默认队列连接
});
});
}</code></pre>
<p>下面看 <code>dispatchToQueue</code></p>
<pre><code class="php">public function dispatchToQueue($command)
{
$queue = call_user_func($this->queueResolver); // 在此为设置的默认值 将实例化 RedisQueue
// 异常则抛出!
if (! $queue instanceof Queue) {
throw new RuntimeException('Queue resolver did not return a Queue implementation.');
}
if (method_exists($command, 'queue')) {
// 可以自定义
return $command->queue($queue, $command);
} else {
// 在此使用的是进入队列方式 最终结果类似 $queue->push(); 看 RedisQueue 下的 push 方法。
return $this->pushCommandToQueue($queue, $command);
}
}</code></pre>
<p>上面任务进入队列的整个流程就明白了。那任务出队列呢?在文档中我们可以看到,我们通过执行 <code>php artisan queue:work</code> 这条语句进行队列的监听,那在此就看下 <code>namespace IlluminateQueueConsoleWorkCommand::fire()</code>,夜很深了,下面自己看吧!</p>
::class 关键字
https://segmentfault.com/a/1190000004560484
2016-03-08T10:31:11+08:00
2016-03-08T10:31:11+08:00
stoneworld
https://segmentfault.com/u/stoneworld
2
<p>要求:PHP >= 5.5</p>
<pre><code class="php"><?php
class User {
}
echo User::class; // return User</code></pre>
<pre><code class="php"><?php
namespace App\Models;
class User {
}
echo User::class; // return App\Models\User</code></pre>
<pre><code class="php">
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Phone extends Model
{
/**
* Get the user that owns the phone.
*/
public function user()
{
return $this->belongsTo('App\Models\User');
//return $this->belongsTo(User::class);
}
}</code></pre>
Laravel 5.1 事件、事件监听的简单应用
https://segmentfault.com/a/1190000004051306
2015-11-26T22:37:18+08:00
2015-11-26T22:37:18+08:00
stoneworld
https://segmentfault.com/u/stoneworld
15
<p><img src="/img/bVq95A" alt="bVqBce" title="bVqBce"></p>
<p> 有时候当我们单纯的看 <code>Laravel</code> 手册的时候会有一些疑惑,比如说系统服务下的授权和事件,这些功能服务的应用场景是什么,其实如果没有经历过一定的开发经验有这些疑惑是很正常的事情,但是当我们在工作中多加思考会发现有时候这些服务其实我们一直都见过。下面就<a href="https://link.segmentfault.com/?enc=QyLzKRd1LiaH1DPU9LF65Q%3D%3D.1F6Jsaxwu35H8LiN4xcan1rg5pdfI9eZW1HfsT%2F3M%2FtsDID7Qktb5eoTsqNAtO3suj%2B45XZqV07V3YF%2FEx%2Bh0g%3D%3D" rel="nofollow">事件、事件监听</a>举一个很简单的例子你就会发现。</p>
<p> 这个例子是关于文章的浏览数的实现,当用户查看文章的时候文章的浏览数会增加1,用户查看文章就是一个事件,有了事件,就需要一个事件监听器,对监听的事件发生后执行相应的操作(文章浏览数加1),其实这种监听机制在 <code>Laravel</code> 中是通过观察者模式实现的.</p>
<h3>注册事件以及监听器</h3>
<p>首先我们需要在 <code>app/Providers/</code>目录下的<code>EventServiceProvider.php</code>中注册事件监听器映射关系,如下:</p>
<pre><code class="php">protected $listen = [
'App\Events\BlogView' => [
'App\Listeners\BlogViewListener',
],
];</code></pre>
<p>然后项目根目录下执行如下命令</p>
<pre><code class="php">php artisan event:generate</code></pre>
<p>该命令完成后,会分别自动在 <code>app/Events</code>和<code>app/Listensers</code>目录下生成 <code>BlogView.php</code>和<code>BlogViewListener.php</code>文件。</p>
<h3>定义事件</h3>
<pre><code class="php"><?php
namespace App\Events;
use App\Events\Event;
use App\Post;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
class BlogView extends Event
{
use SerializesModels;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(Post $post)
{
$this->post = $post;
}
/**
* Get the channels the event should be broadcast on.
*
* @return array
*/
public function broadcastOn()
{
return [];
}
}
</code></pre>
<p>其实看到这些你会发现该事件类只是注入了一个 <code>Post</code>实例罢了,并没有包含多余的逻辑。</p>
<h3>定义监听器</h3>
<p>事件监听器在<code>handle</code>方法中接收事件实例,event:generate命令将会自动在handle方法中导入合适的事件类和类型提示事件。在<code>handle</code>方法内,你可以执行任何需要的逻辑以响应事件,我们的代码实现如下:</p>
<pre><code class="php"><?php
namespace App\Listeners;
use App\Events\BlogView;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Session\Store;
class BlogViewListener
{
protected $session;
/**
* Create the event listener.
*
* @return void
*/
public function __construct(Store $session)
{
$this->session = $session;
}
/**
* Handle the event.
*
* @param BlogView $event
* @return void
*/
public function handle(BlogView $event)
{
$post = $event->post;
//先进行判断是否已经查看过
if (!$this->hasViewedBlog($post)) {
//保存到数据库
$post->view_cache = $post->view_cache + 1;
$post->save();
//看过之后将保存到 Session
$this->storeViewedBlog($post);
}
}
protected function hasViewedBlog($post)
{
return array_key_exists($post->id, $this->getViewedBlogs());
}
protected function getViewedBlogs()
{
return $this->session->get('viewed_Blogs', []);
}
protected function storeViewedBlog($post)
{
$key = 'viewed_Blogs.'.$post->id;
$this->session->put($key, time());
}
}
</code></pre>
<p>注释中也已经说明了一些逻辑。</p>
<h3>触发事件</h3>
<p>事件和事件监听完成后,我们要做的就是实现整个监听,即触发用户打开文章事件在此我们使用和 <code>Event</code>提供的 <code>fire</code>方法,如下:</p>
<pre><code class="php"><?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Post;
use Illuminate\Support\Facades\Event;
use App\Http\Requests;
use App\Events\BlogView;
use App\Http\Controllers\Controller;
class BlogController extends Controller
{
public function showPost($slug)
{
$post = Post::whereSlug($slug)->firstOrFail();
Event::fire(new BlogView($post));
return view('home.blog.content')->withPost($post);
}
}
</code></pre>
<p>现在打开页面发现数据库中的 <code>view_cache</code> 已经正常加1了,这样整个就完成了。</p>
在Laravel5.* 中使用 AdminLTE
https://segmentfault.com/a/1190000003917210
2015-10-28T09:06:03+08:00
2015-10-28T09:06:03+08:00
stoneworld
https://segmentfault.com/u/stoneworld
9
<p><img src="/img/bVqBce" alt="图片描述" title="图片描述"></p>
<h3>在Laravel5.* 中使用 AdminLTE</h3>
<p>AdminLTE是一个很棒的单纯的由 HTML 和 CSS 构建的后台模板,在这片文章中,我将讲述如何将 AdminLTE 和 Laravel 优雅的整合在一起,而且我们可以通过 Bower 来及时的更新和管理 AdminLTE。</p>
<h3>我们使用的工具</h3>
<ol>
<li><p>Laravel</p></li>
<li><p>AdminLTE 2.3.2</p></li>
<li><p>Bower</p></li>
<li><p>Composer</p></li>
</ol>
<h3>下载一个全新的 Laravel</h3>
<p>如果不太清楚可以去官方网站查看文档<a href="https://link.segmentfault.com/?enc=LqKMuurDPE6MHeYNQrTzUQ%3D%3D.GoFbxaMPYMAUx1tWFakxMuKHeldu0KuNHqlxPAsJ05E%3D" rel="nofollow">link</a><br>在此我们直接使用命令行即可</p>
<pre><code> composer create-project laravel/laravel myapp --prefer-dist
</code></pre>
<p>通过这个命令我们创建了一个全新的名字为 myapp 的Laravel项目,如果你成功的话你可以看到下面的图片<br><img src="/img/bVqBck" alt="图片描述" title="图片描述"></p>
<h3>通过 Bower 下载 AdminLTE</h3>
<p>进入到 myapp/public 文件夹</p>
<pre><code>
cd myapp/public
</code></pre>
<p>在这个文件夹下执行下面的命令</p>
<pre><code> bower install admin-lte
</code></pre>
<p>一旦完成,你会发现多了一个 bower_componets 的文件夹,而且在这个文件夹中你会看到 AdminLTE</p>
<h3>将 AdminLTE 的starter.html 转化为 Blade 模板</h3>
<p>Laravel 在此使用了一个很好的模板引擎 Blade,为了更充分的利用Blade,我们需要将一些常规的通用的 HTML 的 起始页面应用到 Blade 模板中,首先创建一个 view 在 <code>resources/views</code>文件夹中,命名为<code>admin_template.blade.php</code>,而后我们为这个页面创建一个对应的路由。如下面我所创建的</p>
<pre><code>
Route::get('admin', function () {
return view('admin_template');
});
</code></pre>
<p>然后,将<code>bower_components/admin-lte/starter.html</code>中的内容复制到我们视图模板中,并且将其中的相关链接指向我们的 AdminLTE 的对应目录下,如下是我初步的设置:</p>
<pre><code><script src="{{ asset("/bower_components/AdminLTE/plugins/jQuery/jQuery-2.1.4.min.js")}}"></script>
<!-- Bootstrap 3.3.5 -->
<script src="{{ asset("/bower_components/AdminLTE/bootstrap/js/bootstrap.min.js")}}"></script>
<!-- AdminLTE App -->
<script src="{{ asset("/bower_components/AdminLTE/dist/js/app.min.js")}}"></script>
</code></pre>
<p>类似这样,将css 和 js 的相关的链接指向相应的目录下,而后我们通过 <code>localhost:8000/admin</code> 查看页面的变化,此时页面变成了如下图:<br><img src="/img/bVqBco" alt="图片描述" title="图片描述"></p>
<p>现在我们拥有了所有的使用 AdminLTE 的所有的资源,下面对我们的主要视图增加最后的收尾工作,我将分开这个模板为三个文件,<code>sidebar.blade.php</code>, <code>header.blade.php</code>, 和 <code>footer.blade.php</code><br>这三个文件的内容分别是<code>admin_template.blade.php</code>header 部分和 aside 部分和footer 部分,将这三部分截取出来依次放到三个文件中。</p>
<h3>最后的润色工作</h3>
<p>现在我们已经将我们的模板个性化的分离开了,下面我们需要设置我们的最初的<code>admin_template.blade.php</code><br>模板以便于内容动态加载,如下所示:</p>
<pre><code><!DOCTYPE html>
<html>
head>
<meta charset="UTF-8">
<title>{{ $page_title or "AdminLTE Dashboard" }}</title>
<meta content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no' name='viewport'>
<!-- Bootstrap 3.3.2 -->
<link href="{{ asset("/bower_components/AdminLTE/bootstrap/css/bootstrap.min.css") }}" rel="stylesheet" type="text/css" />
<!-- Font Awesome Icons -->
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" rel="stylesheet" type="text/css" />
<!-- Ionicons -->
<link href="http://code.ionicframework.com/ionicons/2.0.0/css/ionicons.min.css" rel="stylesheet" type="text/css" />
<!-- Theme style -->
<link href="{{ asset("/bower_components/AdminLTE/dist/css/AdminLTE.min.css")}}" rel="stylesheet" type="text/css" />
<!-- AdminLTE Skins. We have chosen the skin-blue for this starter
page. However, you can choose any other skin. Make sure you
apply the skin class to the body tag so the changes take effect.
-->
<link href="{{ asset("/bower_components/AdminLTE/dist/css/skins/skin-blue.min.css")}}" rel="stylesheet" type="text/css" />
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
<![endif]-->
</head>
<body class="hold-transition skin-blue sidebar-mini">
<div class="wrapper">
<!-- Header -->
@include('header')
<!-- Sidebar -->
@include('sidebar')
<!-- Content Wrapper. Contains page content -->
<div class="content-wrapper">
<!-- Content Header (Page header) -->
<section class="content-header">
<h1>
{{ $page_title or "Page Title" }}
<small>{{ $page_description or null }}</small>
</h1>
<!-- You can dynamically generate breadcrumbs here -->
<ol class="breadcrumb">
<li><a href="#"><i class="fa fa-dashboard"></i> Level</a></li>
<li class="active">Here</li>
</ol>
</section>
<!-- Main content -->
<section class="content">
<!-- Your Page Content Here -->
@yield('content')
</section><!-- /.content -->
</div><!-- /.content-wrapper -->
<!-- Footer -->
@include('footer')
<aside class="control-sidebar control-sidebar-dark">
<!-- Create the tabs -->
<ul class="nav nav-tabs nav-justified control-sidebar-tabs">
<li class="active"><a href="#control-sidebar-home-tab" data-toggle="tab"><i class="fa fa-home"></i></a></li>
<li><a href="#control-sidebar-settings-tab" data-toggle="tab"><i class="fa fa-gears"></i></a></li>
</ul>
<!-- Tab panes -->
<div class="tab-content">
<!-- Home tab content -->
<div class="tab-pane active" id="control-sidebar-home-tab">
<h3 class="control-sidebar-heading">Recent Activity</h3>
<ul class="control-sidebar-menu">
<li>
<a href="javascript::;">
<i class="menu-icon fa fa-birthday-cake bg-red"></i>
<div class="menu-info">
<h4 class="control-sidebar-subheading">Langdon's Birthday</h4>
<p>Will be 23 on April 24th</p>
</div>
</a>
</li>
</ul>
<!-- /.control-sidebar-menu -->
<h3 class="control-sidebar-heading">Tasks Progress</h3>
<ul class="control-sidebar-menu">
<li>
<a href="javascript::;">
<h4 class="control-sidebar-subheading">
Custom Template Design
<span class="label label-danger pull-right">70%</span>
</h4>
<div class="progress progress-xxs">
<div class="progress-bar progress-bar-danger" style="width: 70%"></div>
</div>
</a>
</li>
</ul>
<!-- /.control-sidebar-menu -->
</div>
<!-- /.tab-pane -->
<!-- Stats tab content -->
<div class="tab-pane" id="control-sidebar-stats-tab">Stats Tab Content</div>
<!-- /.tab-pane -->
<!-- Settings tab content -->
<div class="tab-pane" id="control-sidebar-settings-tab">
<form method="post">
<h3 class="control-sidebar-heading">General Settings</h3>
<div class="form-group">
<label class="control-sidebar-subheading">
Report panel usage
<input type="checkbox" class="pull-right" checked>
</label>
<p>
Some information about this general settings option
</p>
</div>
<!-- /.form-group -->
</form>
</div>
<!-- /.tab-pane -->
</div>
</aside>
<!-- /.control-sidebar -->
<!-- Add the sidebar's background. This div must be placed
immediately after the control sidebar -->
<div class="control-sidebar-bg"></div>
</div><!-- ./wrapper -->
<!-- REQUIRED JS SCRIPTS -->
<!-- jQuery 2.1.3 -->
<script src="{{ asset ("/bower_components/AdminLTE/plugins/jQuery/jQuery-2.1.3.min.js") }}"></script>
<!-- Bootstrap 3.3.2 JS -->
<script src="{{ asset ("/bower_components/AdminLTE/bootstrap/js/bootstrap.min.js") }}" type="text/javascript"></script>
<!-- AdminLTE App -->
<script src="{{ asset ("/bower_components/AdminLTE/dist/js/app.min.js") }}" type="text/javascript"></script>
<!-- Optionally, you can add Slimscroll and FastClick plugins.
Both of these plugins are recommended to enhance the
user experience -->
</body>
</html>
</code></pre>
<p>在上面代码中,我们添加了<code>contetn</code>,这里包含着我们的主要的内容,增加了页面标题针对不同的页面,将其重命名为<code>dashboard.blade.php</code>现在这个模板已经可以使用了。</p>
<h4>测试页面</h4>
<p>为了验证我们之前所做的工作,我将创建一个简单的页面</p>
<p>1.创建 <code>test.blade.php</code></p>
<pre><code>
@extends('dashboard')
@section('content')
<div class='row'>
<div class='col-md-6'>
<!-- Box -->
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">Randomly Generated Tasks</h3>
<div class="box-tools pull-right">
<button class="btn btn-box-tool" data-widget="collapse" data-toggle="tooltip" title="Collapse"><i class="fa fa-minus"></i></button>
<button class="btn btn-box-tool" data-widget="remove" data-toggle="tooltip" title="Remove"><i class="fa fa-times"></i></button>
</div>
</div>
<div class="box-body">
@foreach($tasks as $task)
<h5>
{{ $task['name'] }}
<small class="label label-{{$task['color']}} pull-right">{{$task['progress']}}%</small>
</h5>
<div class="progress progress-xxs">
<div class="progress-bar progress-bar-{{$task['color']}}" style="width: {{$task['progress']}}%"></div>
</div>
@endforeach
</div><!-- /.box-body -->
<div class="box-footer">
<form action='#'>
<input type='text' placeholder='New task' class='form-control input-sm' />
</form>
</div><!-- /.box-footer-->
</div><!-- /.box -->
</div><!-- /.col -->
<div class='col-md-6'>
<!-- Box -->
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">Second Box</h3>
<div class="box-tools pull-right">
<button class="btn btn-box-tool" data-widget="collapse" data-toggle="tooltip" title="Collapse"><i class="fa fa-minus"></i></button>
<button class="btn btn-box-tool" data-widget="remove" data-toggle="tooltip" title="Remove"><i class="fa fa-times"></i></button>
</div>
</div>
<div class="box-body">
A separate section to add any kind of widget. Feel free
to explore all of AdminLTE widgets by visiting the demo page
on <a href="https://almsaeedstudio.com">Almsaeed Studio</a>.
</div><!-- /.box-body -->
</div><!-- /.box -->
</div><!-- /.col -->
</div><!-- /.row -->
@endsection
</code></pre>
<p>2.创建<code>TestController.php</code></p>
<pre><code> php artisan make:controller TestController --plain</code></pre>
<p>下面是这个控制器的代码部分:</p>
<pre><code>
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
class TestController extends Controller
{
public function index() {
$data['tasks'] = [
[
'name' => 'Design New Dashboard',
'progress' => '87',
'color' => 'danger'
],
[
'name' => 'Create Home Page',
'progress' => '76',
'color' => 'warning'
],
[
'name' => 'Some Other Task',
'progress' => '32',
'color' => 'success'
],
[
'name' => 'Start Building Website',
'progress' => '56',
'color' => 'info'
],
[
'name' => 'Develop an Awesome Algorithm',
'progress' => '10',
'color' => 'success'
]
];
return view('test')->with($data);
}
}
</code></pre>
<p>3.创建对应的路由</p>
<pre><code> Route::get('test', 'TestController@index');
</code></pre>
<p>4.打开对应的页面,如果你没有出错的 应该如下图所示<br><img src="/img/bVqBcv" alt="图片描述" title="图片描述"></p>
<p>这样整个过程就完成了,当然有什么问题可以下面留言。</p>
为什么你应该使用 Repository
https://segmentfault.com/a/1190000003488038
2015-08-24T21:17:28+08:00
2015-08-24T21:17:28+08:00
stoneworld
https://segmentfault.com/u/stoneworld
23
<blockquote><p>原文来自<a href="https://link.segmentfault.com/?enc=h95woJokxIAtf2zIX9mOTQ%3D%3D.I5cw8HgxLJxeuPAou3fLqBzRSUeuUWPpszYXi1Y8KELgDSRfwgpCBmMB0NqsVEXd" rel="nofollow"></a><a href="https://link.segmentfault.com/?enc=sotvAD0SryW5qNcCqPfimA%3D%3D.Ixmq7Oa3KBGTq2%2BhYsvLeQgBDP5VXFE1RkGAxMTa7shGyiJXshxugZJgcuGBQPGX" rel="nofollow">http://vegibit.com/laravel-repository-pattern/</a></p></blockquote>
<h3>Repository 模式</h3>
<p>为了保持代码的整洁性和可读性,使用<code>Repository Pattern</code> 是非常有用的。事实上,我们也不必仅仅为了使用这个特别的设计模式去使用<code>Laravel</code>,然而在下面的场景下,我们将使用<code>OOP</code>的框架<code>Laravel</code> 去展示如何使用<code>repositories</code> 使我们的<code>Controller</code>层不再那么啰嗦、更加解耦和易读。下面让我们更深入的研究一下。</p>
<h3>不使用 <code>repositories</code>
</h3>
<p>其实使用<code>Repositories</code>并不是必要的,在你的应用中你完全可以不使用这个设计模式的前提下完成绝大多数的事情,然而随着时间的推移你可能把自己陷入一个死角,比如不选择使用<code>Repositories</code>会使你的应用测试很不容易,具体的实现将会变的很复杂,下面我们看一个例子。<br><code>HousesController.php</code></p>
<pre><code><?php
class HousesController extends BaseController {
public function index()
{
$houses = House::all();
return View::make('houses.index',compact('houses'));
}
public function create()
{
return View::make('houses.create');
}
public function show($id)
{
$house = House::find($id);
return View::make('houses.show',compact('house'));
}
}
</code></pre>
<p>这是一个很典型的一段代码使用<code>Eloquent</code>和数据库交互,这段代码工作的很正常,但是<code>controller</code>层对于<code>Eloquent</code>而言将是紧耦合的。在此我们可以注入一个<code>repository</code>创建一个解耦类型的代码版本,这个解耦的版本代码可以使后续程序的具体实现更加简单。</p>
<h3>使用 <code>repositories</code>
</h3>
<p>其实完成整个<code>repository</code>模式需要相当多的步骤,但是一旦你完成几次就会自然而然变成了一种习惯了,下面我们将详细介绍每一步。</p>
<h4>1.创建 <code>Repository</code> 文件夹</h4>
<p>首先我们需要在<code>app</code>文件夹创建自己<code>Repository </code>文件夹<code>repositories</code>,然后文件夹的每一个文件都要设置相应的命名空间。</p>
<h4>2: 创建相应的 <code>Interface</code>类</h4>
<p>第二步创建对应的接口,其决定着我们的<code>repository</code>类必须要实现的相关方法,如下例所示,在此再次强调的是命名空间一定要记得加上。<br><code>HouseRepositoryInterface.php</code></p>
<pre><code><?php namespace App\Repositories;
interface HouseRepositoryInterface {
public function selectAll();
public function find($id);
}
</code></pre>
<h4>3:创建对应的 <code>Repository</code>类</h4>
<p>现在我们可以创建我们<code>repository</code>类 来给我们干活了,在这个类文件中我们可以把我们的绝大多数的数据库查询都放进去,不论多么复杂。如下面的例子<br><code>DbHouseRepository.php</code></p>
<pre><code>
<?php namespace App\Repositories;
use House;
class DbHouseRepository implements HouseRepositoryInterface {
public function selectAll()
{
return House::all();
}
public function find($id)
{
return House::find($id);
}
}</code></pre>
<h4>4:创建后端服务提供</h4>
<p>首先你需要理解所谓服务提供,请参考手册<a href="https://link.segmentfault.com/?enc=3tydXl0d2FvjtTPJqmrRow%3D%3D.5JFWARRjKeIGPAFCl2d1qTtQhNPympeT5LOxDRMIOFXZ1tidFWwh2r0C8toB1R5Jw4hieOs9d7KyCSitswQlvg%3D%3D" rel="nofollow">服务提供者</a><br><code>BackendServiceProvider.php</code></p>
<pre><code>
<?php namespace App\Repositories;
use IlluminateSupportSeriveProvider;
class BackSerivePrivider extends ServiceProvider {
public function register()
{
$this->app->bind('App\Repositories\HouseRepositoryInterface', 'App\Repositories\DbHouseRepository');
}
}</code></pre>
<p>当然你也可以新建一个文件夹主要放我们的<code>provider</code>相关文件。<br>上面一段代码主要说的是,当你在<code>controller</code>层使用类型提示<code>HouseRepositoryInterface</code>,我们知道你将会使用<code>DbHouseRepository</code>.</p>
<h5>5:更新你的<code>Providers Array</code>
</h5>
<p>其实在上面的代码中,我们已经实现了一个依赖注入,但如果我们要使用在此我们是需要手动去写的,为了更为方面,我们需要增加这个<code>providers </code>到app/config/app.php 中的 <code>providers</code>数组里面,只需要在最后加上<code>App\Repositories\BackendServiceProvider::class,</code></p>
<h4>6:最后使用依赖注入更新你的<code>controller</code>
</h4>
<p>当我们完成上面的那些内容之后,我们在<code>Controller</code>只需要简单的调用方法代替之前的复杂的数据库调用,如下面内容:<br><code>HousesController.php</code></p>
<pre><code><?php
use App\repositories\HouseRepositoryInterface;
class HousesController extends BaseController {
public function __construct(HouseRepositoryInterface $house)
{
$this->house = $house;
}
public function index()
{
$houses = $this->house->selectAll();
return View::make('houses.index', compact('houses'));
}
public function create()
{
return View::make('houses.create');
}
public function show($id)
{
$house = $this->house->find($id);
return View::make('houses.show', compact('house'));
}
}</code></pre>
<p>这样整个的流程就完成了。翻译的不太好,请大家见谅,做了一些改动。</p>