迁移文档

This commit is contained in:
想打瞌睡 2024-07-08 18:15:43 +09:00
parent 43b9a48461
commit 3966e64d3e
49 changed files with 11662 additions and 89 deletions

4
.gitignore vendored
View File

@ -1,2 +1,4 @@
node_modules
.vitepress/
.vitepress/cache
.vitepress/dist
dist/

46
.vitepress/config.mts Normal file
View File

@ -0,0 +1,46 @@
import { defineConfig } from 'vitepress'
import sidebar from './sidebar'
import nav from './nav'
// https://vitepress.dev/reference/site-config
export default defineConfig({
title: "源码阅读",
description: "记录源码阅读记录",
srcDir: 'src',
lastUpdated: true,
outDir: '/usr/src/app/dist',
markdown: {
container: {
tipLabel: '提示',
warningLabel: '警告',
dangerLabel: '危险',
infoLabel: '信息',
detailsLabel: '详细信息'
},
image: {
// 默认禁用图片懒加载
lazyLoading: true
},
},
themeConfig: {
outlineTitle: '页面导航',
// https://vitepress.dev/reference/default-theme-config
nav: nav,
sidebar: sidebar,
outline: {
level: [2, 6]
},
socialLinks: [
{ icon: 'github', link: 'https://github.com/vuejs/vitepress' }
],
search: {
provider: 'local'
},
lastUpdatedText: "最近更新时间",
docFooter: { prev: '上一篇', next: '下一篇' },
footer: {
message: 'Released under the MIT License.',
copyright: 'Copyright © 2024'
}
}
})

21
.vitepress/nav.ts Normal file
View File

@ -0,0 +1,21 @@
var record = '/record/'
export default [
// 首页
{ text: 'Home', link: '/' },
// hyperf
{
text: 'Hyperf',link: '/hyperf'
},
// laravel
{ text: 'Laravel', link: '/laravel' },
// 杂记
{
text: '杂记',
items: [
{'text': 'env文件读取',link: record+ 'env文件读取'},
{'text': '主题语法',link: record+ '主题语法'},
{'text': 'iStoreOS路由系统',link: record+ 'iStoreOS路由系统'},
{'text': 'NGINX-PROXY',link: record+ 'NGINX-PROXY'}
]
}
]

52
.vitepress/sidebar.ts Normal file
View File

@ -0,0 +1,52 @@
var record = '/record/'
var hyperf = '/hyperf/'
var laravel = '/laravel/'
export default [
{
// Hyperf
text: 'Hyperf',
collapsed: true,
items: [
{ text: '阅读版本', link: hyperf + '阅读版本' },
{ text: '入口文件', link: hyperf + '入口文件' },
{ text: 'init', link: hyperf + 'init' },
{ text: 'container', link: hyperf + 'container' },
{ text: '中间件', link: hyperf + '中间件' },
{ text: '启动服务', link: hyperf + '启动服务' },
{ text: '响应', link: hyperf + '响应' },
{ text: '请求', link: hyperf + '请求' },
{ text: '路由寻址', link: hyperf + '路由寻址' },
]
},
{
// Laravel
text: 'Laravel',
collapsed: true,
items: [
{ text: 'index入口文件', link: laravel + 'index入口文件' },
{ text: 'laravel配置', link: laravel + 'laravel配置' },
{ text: '处理请求', link: laravel + '处理请求' },
{ text: '实例化容器', link: laravel + '实例化容器' },
{ text: '实现自动加载', link: laravel + '实现自动加载' },
{ text: '容器类', link: laravel + '容器类' },
{ text: '服务提供者', link: laravel + '服务提供者' },
{ text: '注册基础绑定', link: laravel + '注册基础绑定' },
{ text: '注册服务提供者', link: laravel + '注册服务提供者' },
{ text: '渲染页面', link: laravel + '渲染页面' },
{ text: '终止', link: laravel + '终止' },
{ text: '解析HTTP内核', link: laravel + '解析HTTP内核' },
{ text: '解析路由', link: laravel + '解析路由' },
{ text: '设置基础目录', link: laravel + '设置基础目录' },
]
},
{
text: '杂记',
collapsed: true,
items: [
{ 'text': 'env文件读取', link: record + 'env文件读取' },
{ 'text': '主题语法', link: record + '主题语法' },
{ 'text': 'iStoreOS路由系统', link: record + 'iStoreOS路由系统' },
{ 'text': 'NGINX-PROXY', link: record + 'NGINX-PROXY' }
]
}
]

17
.vitepress/theme/index.ts Normal file
View File

@ -0,0 +1,17 @@
// https://vitepress.dev/guide/custom-theme
import { h } from 'vue'
import type { Theme } from 'vitepress'
import DefaultTheme from 'vitepress/theme'
import './style.css'
export default {
extends: DefaultTheme,
Layout: () => {
return h(DefaultTheme.Layout, null, {
// https://vitepress.dev/guide/extending-default-theme#layout-slots
})
},
enhanceApp({ app, router, siteData }) {
// ...
}
} satisfies Theme

139
.vitepress/theme/style.css Normal file
View File

@ -0,0 +1,139 @@
/**
* Customize default theme styling by overriding CSS variables:
* https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css
*/
/**
* Colors
*
* Each colors have exact same color scale system with 3 levels of solid
* colors with different brightness, and 1 soft color.
*
* - `XXX-1`: The most solid color used mainly for colored text. It must
* satisfy the contrast ratio against when used on top of `XXX-soft`.
*
* - `XXX-2`: The color used mainly for hover state of the button.
*
* - `XXX-3`: The color for solid background, such as bg color of the button.
* It must satisfy the contrast ratio with pure white (#ffffff) text on
* top of it.
*
* - `XXX-soft`: The color used for subtle background such as custom container
* or badges. It must satisfy the contrast ratio when putting `XXX-1` colors
* on top of it.
*
* The soft color must be semi transparent alpha channel. This is crucial
* because it allows adding multiple "soft" colors on top of each other
* to create a accent, such as when having inline code block inside
* custom containers.
*
* - `default`: The color used purely for subtle indication without any
* special meanings attched to it such as bg color for menu hover state.
*
* - `brand`: Used for primary brand colors, such as link text, button with
* brand theme, etc.
*
* - `tip`: Used to indicate useful information. The default theme uses the
* brand color for this by default.
*
* - `warning`: Used to indicate warning to the users. Used in custom
* container, badges, etc.
*
* - `danger`: Used to show error, or dangerous message to the users. Used
* in custom container, badges, etc.
* -------------------------------------------------------------------------- */
:root {
--vp-c-default-1: var(--vp-c-gray-1);
--vp-c-default-2: var(--vp-c-gray-2);
--vp-c-default-3: var(--vp-c-gray-3);
--vp-c-default-soft: var(--vp-c-gray-soft);
--vp-c-brand-1: var(--vp-c-indigo-1);
--vp-c-brand-2: var(--vp-c-indigo-2);
--vp-c-brand-3: var(--vp-c-indigo-3);
--vp-c-brand-soft: var(--vp-c-indigo-soft);
--vp-c-tip-1: var(--vp-c-brand-1);
--vp-c-tip-2: var(--vp-c-brand-2);
--vp-c-tip-3: var(--vp-c-brand-3);
--vp-c-tip-soft: var(--vp-c-brand-soft);
--vp-c-warning-1: var(--vp-c-yellow-1);
--vp-c-warning-2: var(--vp-c-yellow-2);
--vp-c-warning-3: var(--vp-c-yellow-3);
--vp-c-warning-soft: var(--vp-c-yellow-soft);
--vp-c-danger-1: var(--vp-c-red-1);
--vp-c-danger-2: var(--vp-c-red-2);
--vp-c-danger-3: var(--vp-c-red-3);
--vp-c-danger-soft: var(--vp-c-red-soft);
}
/**
* Component: Button
* -------------------------------------------------------------------------- */
:root {
--vp-button-brand-border: transparent;
--vp-button-brand-text: var(--vp-c-white);
--vp-button-brand-bg: var(--vp-c-brand-3);
--vp-button-brand-hover-border: transparent;
--vp-button-brand-hover-text: var(--vp-c-white);
--vp-button-brand-hover-bg: var(--vp-c-brand-2);
--vp-button-brand-active-border: transparent;
--vp-button-brand-active-text: var(--vp-c-white);
--vp-button-brand-active-bg: var(--vp-c-brand-1);
}
/**
* Component: Home
* -------------------------------------------------------------------------- */
:root {
--vp-home-hero-name-color: transparent;
--vp-home-hero-name-background: -webkit-linear-gradient(
120deg,
#bd34fe 30%,
#41d1ff
);
--vp-home-hero-image-background-image: linear-gradient(
-45deg,
#bd34fe 50%,
#47caff 50%
);
--vp-home-hero-image-filter: blur(44px);
}
@media (min-width: 640px) {
:root {
--vp-home-hero-image-filter: blur(56px);
}
}
@media (min-width: 960px) {
:root {
--vp-home-hero-image-filter: blur(68px);
}
}
/**
* Component: Custom Block
* -------------------------------------------------------------------------- */
:root {
--vp-custom-block-tip-border: transparent;
--vp-custom-block-tip-text: var(--vp-c-text-1);
--vp-custom-block-tip-bg: var(--vp-c-brand-soft);
--vp-custom-block-tip-code-bg: var(--vp-c-brand-soft);
}
/**
* Component: Algolia
* -------------------------------------------------------------------------- */
.DocSearch {
--docsearch-primary-color: var(--vp-c-brand-1) !important;
}

4
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,4 @@
{
"search.useGlobalIgnoreFiles": true,
"search.useParentIgnoreFiles": true
}

View File

@ -1,49 +0,0 @@
---
outline: deep
---
# Runtime API Examples
This page demonstrates usage of some of the runtime APIs provided by VitePress.
The main `useData()` API can be used to access site, theme, and page data for the current page. It works in both `.md` and `.vue` files:
```md
<script setup>
import { useData } from 'vitepress'
const { theme, page, frontmatter } = useData()
</script>
## Results
### Theme Data
<pre>{{ theme }}</pre>
### Page Data
<pre>{{ page }}</pre>
### Page Frontmatter
<pre>{{ frontmatter }}</pre>
```
<script setup>
import { useData } from 'vitepress'
const { site, theme, page, frontmatter } = useData()
</script>
## Results
### Theme Data
<pre>{{ theme }}</pre>
### Page Data
<pre>{{ page }}</pre>
### Page Frontmatter
<pre>{{ frontmatter }}</pre>
## More
Check out the documentation for the [full list of runtime APIs](https://vitepress.dev/reference/runtime-api#usedata).

View File

@ -11,4 +11,4 @@ services:
TZ: Asia/Shanghai
working_dir: /usr/src/app
network_mode: host
entrypoint: ['./entrypoint.sh','docs:dev']
entrypoint: ['./entrypoint.sh','src:dev']

View File

@ -1,10 +0,0 @@
export default {
// 站点级选项
title: 'VitePress',
description: 'Just playing around.',
themeConfig: {
// 主题级选项
}
}

View File

@ -1,25 +0,0 @@
---
# https://vitepress.dev/reference/default-theme-home-page
layout: home
hero:
name: "My Awesome Project"
text: "A VitePress Site"
tagline: My great project tagline
actions:
- theme: brand
text: Markdown Examples
link: /markdown-examples
- theme: alt
text: API Examples
link: /api-examples
features:
- title: Feature A
details: Lorem ipsum dolor sit amet, consectetur adipiscing elit
- title: Feature B
details: Lorem ipsum dolor sit amet, consectetur adipiscing elit
- title: Feature C
details: Lorem ipsum dolor sit amet, consectetur adipiscing elit
---

View File

@ -3,8 +3,8 @@
"vitepress": "^1.3.0"
},
"scripts": {
"docs:dev": "vitepress dev",
"docs:build": "vitepress build",
"docs:preview": "vitepress preview"
"src:dev": "vitepress dev",
"src:build": "vitepress build",
"src:preview": "vitepress preview"
}
}

191
src/hyperf/container.md Normal file
View File

@ -0,0 +1,191 @@
---
layout: wiki
wiki: hyperf #项目名
date: 2024-03-22 16:56
categories: [Hyperf]
title: 初始化容器类
---
本节分析容器类,
```php
$container = require BASE_PATH . '/config/container.php';
```
这是容器类初始化的代码,
```php
use Hyperf\Context\ApplicationContext;
use Hyperf\Di\Container;
use Hyperf\Di\Definition\DefinitionSourceFactory;
$container = new Container((new DefinitionSourceFactory())());
return ApplicationContext::setContainer($container);
```
> 首先,可以看出,先是`new`了 一个`DefinitionSourceFactory`类,然后使用方法的调用方式调用类,会触发该类的`__invoke`方法,最后返回的内容作为`Container`类的构造函数的参数,然后返回容器类。
### DefinitionSourceFactory类
先看该类做了那些工作,
```php
declare(strict_types=1);
namespace Hyperf\Di\Definition;
use Hyperf\Config\ProviderConfig;
use Hyperf\Di\Exception\Exception;
class DefinitionSourceFactory
{
public function __invoke(): DefinitionSource
{
// 常量不存在,报错
if (! defined('BASE_PATH')) {
throw new Exception('BASE_PATH is not defined.');
}
// 该方法上面一节分析过,获取所有的服务提供者返回的配置信息数组
$configFromProviders = [];
if (class_exists(ProviderConfig::class)) {
$configFromProviders = ProviderConfig::load();
}
// 同理这里会获取配置文件中的依赖配置信息如果存在相同的key则覆盖掉
$serverDependencies = $configFromProviders['dependencies'] ?? [];
$dependenciesPath = BASE_PATH . '/config/autoload/dependencies.php';
if (file_exists($dependenciesPath)) {
$definitions = include $dependenciesPath;
$serverDependencies = array_replace($serverDependencies, $definitions ?? []);
}
// 返回DefinitionSource对象
return new DefinitionSource($serverDependencies);
}
}
```
只有一个方法,也是会被调用的`__invoke`方法。这里返回实例化的`DefinitionSource`对象。
### DefinitionSource类
> `DefinitionSource类`是管理容器中的绑定关系,即将抽象接口或类绑定到其对应的实现类或实例,来看源码。
```php
namespace Hyperf\Di\Definition;
use Hyperf\Di\ReflectionManager;
use ReflectionFunctionAbstract;
use ReflectionNamedType;
use function class_exists;
use function interface_exists;
use function is_callable;
use function is_string;
use function method_exists;
class DefinitionSource implements DefinitionSourceInterface
{
protected array $source;
public function __construct(array $source)
{
// source 是服务提供者配置文件中的依赖配置信息
$this->source = $this->normalizeSource($source);
}
/**
* Normalize the user definition source to a standard definition source.
*/
protected function normalizeSource(array $source): array
{
$definitions = [];
foreach ($source as $identifier => $definition) {
$normalizedDefinition = $this->normalizeDefinition($identifier, $definition);
if (! is_null($normalizedDefinition)) {
$definitions[$identifier] = $normalizedDefinition;
}
}
return $definitions;
}
/**
* 绑定的内容可以是数组、类名、闭包
* @param array|callable|string $definition
*/
protected function normalizeDefinition(string $identifier, $definition): ?DefinitionInterface
{
if ($definition instanceof PriorityDefinition) {
$definition = $definition->getDefinition();
}
if (is_string($definition) && class_exists($definition)) {
if (method_exists($definition, '__invoke')) {
return new FactoryDefinition($identifier, $definition, []);
}
return $this->autowire($identifier, new ObjectDefinition($identifier, $definition));
}
if (is_callable($definition)) {
return new FactoryDefinition($identifier, $definition, []);
}
return null;
}
/**
* 根据类名自动完成构造函数的依赖注入
*/
protected function autowire(string $name, ObjectDefinition $definition = null): ?ObjectDefinition
{
$className = $definition ? $definition->getClassName() : $name;
if (! class_exists($className) && ! interface_exists($className)) {
return $definition;
}
$definition = $definition ?: new ObjectDefinition($name);
/**
* Constructor.
*/
$class = ReflectionManager::reflectClass($className);
$constructor = $class->getConstructor();
if ($constructor && $constructor->isPublic()) {
$constructorInjection = new MethodInjection('__construct', $this->getParametersDefinition($constructor));
$definition->completeConstructorInjection($constructorInjection);
}
return $definition;
}
}
```
> `normalizeSource`方法会将绑定的内容进行标准化,标准化后的内容是`DefinitionInterface`接口的实现类。(意思就是将绑定的`key`和`value`转换成`DefinitionInterface`接口的实现类)
最终,将绑定和对应的实现类保存到`$this->source`属性中。
实际上,这里`(new DefinitionSourceFactory())()`执行过后返回的是一个`DefinitionSource`对象,该对象中保存了所有的绑定关系。
然后继续往下看,
```php
$container = new Container((new DefinitionSourceFactory())());
```
> 这里返回`DefinitionSource`对象后,再调用`new Container`方法,传入`DefinitionSource`对象,返回`Container`对象。
查看`Container`类的构造函数源码,
```php
public function __construct(protected Definition\DefinitionSourceInterface $definitionSource)
{
$this->definitionResolver = new ResolverDispatcher($this);
// Auto-register the container.
$this->resolvedEntries = [
self::class => $this,
PsrContainerInterface::class => $this,
HyperfContainerInterface::class => $this,
];
}
```
总结:
1. 将传入的`$definitionSource`对象放到`$this->definitionSource`属性中。
2. 创建`ResolverDispatcher`解析器,调度不同类型的解析器来处理容器中的定义,它会根据定义的类型(如类、工厂函数等)来选择适当的解析器进行解析
3. 注册容器本身的实例
{%quot el:h3 总结%}
1. 首先创建一个`DefinitionSourceFactory`工厂实例,并且触发该工厂实例的`__invoke`方法
2. `__invoke`方法中,首先加载配置文件包括类映射和依赖配置
3. 跟`config`目录下`dependencies.php`文件中的依赖配置进行合并
4. 创建`DefinitionSource`对象,传入依赖配置信息,负责管理容器中的绑定关系和依赖注入的定义
5. 实例化容器对象,将`DefinitionSource`对象传入
6. 构造函数中实例化`ResolverDispatcher`调度器作为容器类的属性,并注册容器本身的实例

1
src/hyperf/index.md Normal file
View File

@ -0,0 +1 @@
hhhh

301
src/hyperf/init.md Normal file
View File

@ -0,0 +1,301 @@
---
title: init方法
---
这节分析这一行代码的作用
```php
Hyperf\Di\ClassLoader::init();
```
`init`方法有三个可选参数,默认是不传参数进来
```php
public static function init(?string $proxyFileDirPath = null, ?string $configDir = null, ?ScanHandlerInterface $handler = null): void
{
// 代理文件目录
if (! $proxyFileDirPath) {
$proxyFileDirPath = BASE_PATH . '/runtime/container/proxy/';
}
// 配置文件目录
if (! $configDir) {
$configDir = BASE_PATH . '/config/';
}
// 扫描器,用来扫描类文件并生成类映射
// 利用 PHP 的 pcntl 扩展提供的功能来进行并发扫描,以加快扫描速度
if (! $handler) {
$handler = new PcntlScanHandler();
}
// 获取composer的自动加载器实例
$composerLoader = Composer::getLoader();
// 这就没啥说的, 如果.env文件存在就加载.env文件
// 使用vlucas/phpdotenv库
if (file_exists(BASE_PATH . '/.env')) {
static::loadDotenv();
}
// Scan by ScanConfig to generate the reflection class map
$config = ScanConfig::instance($configDir);
$composerLoader->addClassMap($config->getClassMap());
$scanner = new Scanner($config, $handler);
$composerLoader->addClassMap(
$scanner->scan($composerLoader->getClassMap(), $proxyFileDirPath)
);
// Initialize Lazy Loader. This will prepend LazyLoader to the top of autoload queue.
LazyLoader::bootstrap($configDir);
}
protected static function loadDotenv(): void
{
$repository = RepositoryBuilder::createWithNoAdapters()
->addAdapter(Adapter\PutenvAdapter::class)
->immutable()
->make();
Dotenv::create($repository, [BASE_PATH])->load();
}
```
### instance方法
看这行,`instance`方法创建一个`ScanConfig`实例,用于加载并解析目录下的配置文件,并生成类映射等扫描相关的配置信息。不理解可以往下看,
```php
$config = ScanConfig::instance($configDir);
```
进入该方法,
```php
public static function instance(string $configDir): self
{
// 如果该实例存在,则直接返回该实例
if (self::$instance) {
return self::$instance;
}
$configDir = rtrim($configDir, '/');
[$config, $serverDependencies, $cacheable] = static::initConfigByFile($configDir);
// 这里返回该实例
return self::$instance = new self(
$cacheable,
$configDir,
$config['paths'] ?? [],
$serverDependencies ?? [],
$config['ignore_annotations'] ?? [],
$config['global_imports'] ?? [],
$config['collectors'] ?? [],
$config['class_map'] ?? []
);
}
```
#### initConfigByFile方法
配置的获取在该方法中,
```php
private static function initConfigByFile(string $configDir): array
{
$config = [];
$configFromProviders = [];
$cacheable = false;
// 该class是hyperf config包中带的
if (class_exists(ProviderConfig::class)) {
$configFromProviders = ProviderConfig::load();
}
//...
}
```
继续往下看,`ProviderConfig::load()`进入该方法,
```php
public static function load(): array
{
// 如果未赋值,则进行赋值
if (! static::$providerConfigs) {
// 该行是从composer.json中获取配置信息并返回数组
// 这里会获得所有的服务提供者信息
$providers = Composer::getMergedExtra('hyperf')['config'] ?? [];
static::$providerConfigs = static::loadProviders($providers);
}
return static::$providerConfigs;
}
```
这里用到了[ConfigProvider 机制](https://hyperf.wiki/3.1/#/zh-cn/component-guide/configprovider),参考官网讲解。
`Composer::getMergedExtra`获取`composer.json`文件中的配置信息,内容如下
![](https://oss.xiaokeaii.top/2024/composer::getMergedExtra.png)
打印获取的内容如下,是所有服务提供者的数组
![](https://oss.xiaokeaii.top/2024/provider.png)
接下来查看`loadProviders`方法
```php
protected static function loadProviders(array $providers): array
{
$providerConfigs = [];
// 循环所有服务提供者
foreach ($providers as $provider) {
// 如果是字符串且存在该类且该类存在__invoke方法
if (is_string($provider) && class_exists($provider) && method_exists($provider, '__invoke')) {
// 当通过方法调用类时会触发__invoke方法
// 所以(new $provider())()相当于执行服务提供者的__invoke方法
$providerConfigs[] = (new $provider())();
}
}
return static::merge(...$providerConfigs);
}
```
我们以`hyperf/config`包为例,打开目录下`ConfigProvider`类,打开`__invoke`方法,会发现,这里返回的是一个配置数组,代码如下,
```php
class ConfigProvider
{
public function __invoke(): array
{
return [
'dependencies' => [
ConfigInterface::class => ConfigFactory::class,
],
'aspects' => [
ValueAspect::class,
],
'listeners' => [
RegisterPropertyHandlerListener::class,
],
];
}
}
```
最终生成的`$providerConfigs`数组格式如下,每个服务提供者都会生成一个数组。
```php
array(22) {
[0]=>
array(5) {
["dependencies"]=>
array(1) {
["Psr\SimpleCache\CacheInterface"]=>
string(18) "Hyperf\Cache\Cache"
}
["listeners"]=>
array(1) {
[0]=>
string(36) "Hyperf\Cache\Listener\DeleteListener"
}
["annotations"]=>
array(1) {
["scan"]=>
array(1) {
["collectors"]=>
array(1) {
[0]=>
string(35) "Hyperf\Cache\CacheListenerCollector"
}
}
}
["aspects"]=>
array(5) {
[0]=>
string(35) "Hyperf\Cache\Aspect\CacheableAspect"
[1]=>
string(36) "Hyperf\Cache\Aspect\CacheAheadAspect"
[2]=>
string(36) "Hyperf\Cache\Aspect\CacheEvictAspect"
[3]=>
string(34) "Hyperf\Cache\Aspect\CachePutAspect"
[4]=>
string(35) "Hyperf\Cache\Aspect\FailCacheAspect"
}
["publish"]=>
array(1) {
[0]=>
array(4) {
["id"]=>
string(6) "config"
["description"]=>
string(21) "The config for cache."
["source"]=>
string(74) "/data/project/hyperf-skeleton/vendor/hyperf/cache/src/../publish/cache.php"
["destination"]=>
string(55) "/data/project/hyperf-skeleton/config/autoload/cache.php"
}
}
}
//...
}
```
最后再看`return static::merge(...$providerConfigs);`这一行,查看`static::merge`方法,该方法是将所有服务提供者的配置数组进行合并,最终返回一个数组。
```php
protected static function merge(...$arrays): array
{
if (empty($arrays)) {
return [];
}
// 递归地合并一个或多个数组,
// 如果输入的数组中有相同的字符串键名,则这些值会被合并到一个数组中去
$result = array_merge_recursive(...$arrays);
// 如果存在dependencies key则对依赖进行合并
if (isset($result['dependencies'])) {
$result['dependencies'] = [];
foreach ($arrays as $item) {
foreach ($item['dependencies'] ?? [] as $key => $value) {
$depend = $result['dependencies'][$key] ?? null;
if (! $depend instanceof PriorityDefinition) {
$result['dependencies'][$key] = $value;
continue;
}
if ($value instanceof PriorityDefinition) {
$depend->merge($value);
}
}
}
}
// 返回合并后的配置数组
return $result;
}
```
以上,`ProviderConfig::load()`方法走完。
往下看以下是将config目录下配置与上面获取的配置进行合并存在重复则配置文件优先
```php
$serverDependencies = $configFromProviders['dependencies'] ?? [];
if (file_exists($configDir . '/autoload/dependencies.php')) {
$definitions = include $configDir . '/autoload/dependencies.php';
$serverDependencies = array_replace($serverDependencies, $definitions ?? []);
}
$config = static::allocateConfigValue($configFromProviders['annotations'] ?? [], $config);
// 这里是注解相关的配置,回来在分析
if (file_exists($configDir . '/autoload/annotations.php')) {
$annotations = include $configDir . '/autoload/annotations.php';
$config = static::allocateConfigValue($annotations, $config);
}
// Load the config/config.php and merge the config
if (file_exists($configDir . '/config.php')) {
$configContent = include $configDir . '/config.php';
$appEnv = $configContent['app_env'] ?? 'dev';
$cacheable = value($configContent['scan_cacheable'] ?? $appEnv === 'prod');
if (isset($configContent['annotations'])) {
$config = static::allocateConfigValue($configContent['annotations'], $config);
}
}
return [$config, $serverDependencies, $cacheable];
```
`instance`方法的三个返回值都已经清楚,接下来返回自身实例
```php
return self::$instance = new self(
$cacheable,
$configDir,
$config['paths'] ?? [],
$serverDependencies ?? [],
$config['ignore_annotations'] ?? [],
$config['global_imports'] ?? [],
$config['collectors'] ?? [],
$config['class_map'] ?? []
);
```
现在,`config`文件配置已经得到,
```php
$config = ScanConfig::instance($configDir);
```
分析下一行,
```php
// 将提供的类映射添加到 Composer 的自动加载器中
$composerLoader->addClassMap($config->getClassMap());
```
由上面配置文件可知,默认情况下,`class_map`为空。

25
src/hyperf/中间件.md Normal file
View File

@ -0,0 +1,25 @@
---
layout: wiki
wiki: hyperf #项目名
date: 2024-03-28 16:56
categories: [Hyperf]
title: 中间件
---
#### getAttribute方法
上面将匹配的路由信息放到`attribute`属性中,现在通过`getAttribute`方法获取。
```php
$dispatched = $psr7Request->getAttribute(Dispatched::class);
// 获取中间件
$middlewares = $this->middlewares;
$registeredMiddlewares = [];
// 如果匹配到路由,则获取路由定义的中间件
if ($dispatched->isFound()) {
$registeredMiddlewares = MiddlewareManager::get($this->serverName, $dispatched->handler->route, $psr7Request->getMethod());
$middlewares = array_merge($middlewares, $registeredMiddlewares);
}
// 对路由进行排序
if ($this->option?->isMustSortMiddlewares() || $registeredMiddlewares) {
$middlewares = MiddlewareManager::sortMiddlewares($middlewares);
}
```

View File

@ -0,0 +1,37 @@
---
title: 入口文件
---
## 入口文件
通过项目启动命令可知,入口文件为`bin/hyperf.php`
```php
ini_set('display_errors', 'on');
ini_set('display_startup_errors', 'on');
// 设置内存限制
ini_set('memory_limit', '1G');
error_reporting(E_ALL);
// 如果BASE_PATH未定义,则定义常量BASE_PATH
! defined('BASE_PATH') && define('BASE_PATH', dirname(__DIR__, 1));
// 引入自动加载
require BASE_PATH . '/vendor/autoload.php';
// 同上,如果未定义,则定义该常量SWOOLE_HOOK_FLAGS
! defined('SWOOLE_HOOK_FLAGS') && define('SWOOLE_HOOK_FLAGS', Hyperf\Engine\DefaultOption::hookFlags());
// 自调用的匿名函数,它创建了自己的作用域并保持全局命名空间的清洁。
(function () {
Hyperf\Di\ClassLoader::init();
/** @var Psr\Container\ContainerInterface $container */
$container = require BASE_PATH . '/config/container.php';
/**
* @var \Symfony\Component\Console\Application $application
*/
$application = $container->get(Hyperf\Contract\ApplicationInterface::class);
$application->run();
})();
```
入口文件内容只有这些,继续往下看,`init()`方法

1025
src/hyperf/启动服务.md Normal file

File diff suppressed because it is too large Load Diff

255
src/hyperf/响应.md Normal file
View File

@ -0,0 +1,255 @@
---
layout: wiki
wiki: hyperf #项目名
date: 2024-03-28 16:56
categories: [Hyperf]
title: 响应
---
```php
// $this->dispatcher 是实例化时传入的HttpDispatcher对象
$psr7Response = $this->dispatcher->dispatch($psr7Request, $middlewares, $this->coreMiddleware);
```
`$this->coreMiddleware`传入的是一个`CoreMiddleware`对象,
`$middlewares`传入的是配置的中间件数组。
> 文件位置: /vendor/hyperf/dispatcher/src/HttpDispatcher.php
```php
public function dispatch(...$params): ResponseInterface
{
/**
* @param RequestInterface $request
* @param array $middlewares
* @param MiddlewareInterface $coreHandler
*/
[$request, $middlewares, $coreHandler] = $params;
// 实例化
$requestHandler = new HttpRequestHandler($middlewares, $coreHandler, $this->container);
return $requestHandler->handle($request);
}
```
`dispatch`方法实例化`HttpRequestHandler`对象,并调用`handle`方法。
### HttpRequestHandler类
```php
$requestHandler = new HttpRequestHandler($middlewares, $coreHandler, $this->container);
```
该类没有构造方法,在父类中定义了`__construct`方法,接收三个参数。
```php
public function __construct(protected array $middlewares, protected $coreHandler, protected ContainerInterface $container)
{ // 将中间件数组的value取出
$this->middlewares = array_values($this->middlewares);
}
```
实例化后调用`handle`方法。
```php
public function handle(ServerRequestInterface $request): ResponseInterface
{
return $this->handleRequest($request);
}
```
`handleRequest`方法,这里执行所有的中间件,全部执行完成之后,会执行`CoreMiddleware`对象中的`process`方法。
```php
protected function handleRequest($request)
{
// 如果中间件不存在则执行CoreMiddleware对象中的process方法
if (! isset($this->middlewares[$this->offset])) {
$handler = $this->coreHandler;
} else {
// 中间件存在,取出并实例化中间件
$handler = $this->middlewares[$this->offset];
is_string($handler) && $handler = $this->container->get($handler);
}
// 如果中间件不存在或者没有process方法则抛出异常
if (! $handler || ! method_exists($handler, 'process')) {
throw new InvalidArgumentException('Invalid middleware, it has to provide a process() method.');
}
// 调用中间件的process方法
return $handler->process($request, $this->next());
}
protected function next(): self
{
++$this->offset;
return $this;
}
```
#### CoreMiddleware的process方法
> 文件位置: /vendor/hyperf/http-server/src/CoreMiddleware.php
```php
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
// 将请求对象设置到上下文中
$request = RequestContext::set($request);
/** @var Dispatched $dispatched */
$dispatched = $request->getAttribute(Dispatched::class);
if (! $dispatched instanceof Dispatched) {
throw new ServerException(sprintf('The dispatched object is not a %s object.', Dispatched::class));
}
// 检查是否匹配到路由
$response = match ($dispatched->status) {
Dispatcher::NOT_FOUND => $this->handleNotFound($request), // 会抛出NotFoundHttpException异常
Dispatcher::METHOD_NOT_ALLOWED => $this->handleMethodNotAllowed($dispatched->params, $request), // 会抛出MethodNotAllowedHttpException异常
Dispatcher::FOUND => $this->handleFound($dispatched, $request),
default => null,
};
if (! $response instanceof ResponsePlusInterface) {
$response = $this->transferToResponse($response, $request);
}
// 添加服务器信息
return $response->addHeader('Server', 'Hyperf');
}
```
当匹配到路由时,执行`handleFound`方法。
`dispatched`对象结构如下,
{%image https://oss.xiaokeaii.top/2024/dispatched.png %}
```php
protected function handleFound(Dispatched $dispatched, ServerRequestInterface $request): mixed
{
// 闭包情况
if ($dispatched->handler->callback instanceof Closure) {
$parameters = $this->parseClosureParameters($dispatched->handler->callback, $dispatched->params);
$callback = $dispatched->handler->callback;
$response = $callback(...$parameters);
} else {
// 控制器情况
[$controller, $action] = $this->prepareHandler($dispatched->handler->callback);
$controllerInstance = $this->container->get($controller);
// 控制器方法不存在抛异常
if (! method_exists($controllerInstance, $action)) {
throw new ServerErrorHttpException('Method of class does not exist.');
}
// 解析控制器方法参数
$parameters = $this->parseMethodParameters($controller, $action, $dispatched->params);
// 调用控制器方法
$response = $controllerInstance->{$action}(...$parameters);
}
// 返回响应
return $response;
}
```
##### 控制器类型分析
`prepareHandler`方法解析出控制器类和控制器方法。
```php
protected function prepareHandler(string|array $handler): array
{
if (is_string($handler)) {
if (str_contains($handler, '@')) {
return explode('@', $handler);
}
if (str_contains($handler, '::')) {
return explode('::', $handler);
}
return [$handler, '__invoke'];
}
if (is_array($handler) && isset($handler[0], $handler[1])) {
return $handler;
}
throw new RuntimeException('Handler not exist.');
}
```
解析出控制器实例。
```php
$controllerInstance = $this->container->get($controller);
```
##### 闭包类型分析
解析闭包参数,调用闭包,返回响应。
```php
$parameters = $this->parseClosureParameters($dispatched->handler->callback, $dispatched->params);
$callback = $dispatched->handler->callback;
$response = $callback(...$parameters);
```
`handleFound`方法走完,获取到响应对象。
##### 转换响应对象
如果响应对象不是`ResponsePlusInterface`接口的实现类,需要进行转换。
```php
if (! $response instanceof ResponsePlusInterface) {
$response = $this->transferToResponse($response, $request);
}
protected function transferToResponse($response, ServerRequestInterface $request): ResponsePlusInterface
{
if (is_string($response)) {
return $this->response()->addHeader('content-type', 'text/plain')->setBody(new SwooleStream($response));
}
if ($response instanceof ResponseInterface) {
return new ResponsePlusProxy($response);
}
if (is_array($response) || $response instanceof Arrayable) {
return $this->response()
->addHeader('content-type', 'application/json')
->setBody(new SwooleStream(Json::encode($response)));
}
if ($response instanceof Jsonable) {
return $this->response()
->addHeader('content-type', 'application/json')
->setBody(new SwooleStream((string) $response));
}
if ($this->response()->hasHeader('content-type')) {
return $this->response()->setBody(new SwooleStream((string) $response));
}
return $this->response()->addHeader('content-type', 'text/plain')->setBody(new SwooleStream((string) $response));
}
```
1. 如果是字符串,则转换为文本类型的响应对象,并设置相应的头部信息和响应体
2. 如果是`ResponseInterface`的实现类,则返回`ResponsePlusProxy`对象 {%u (?)%}
3. 如果是数组或者`Arrayable`的实现类,则转换为`JSON`类型的响应对象,并设置相应的头部信息和响应体
4. 如果是实现了`Jsonable`的接口,则转换为`JSON`类型的响应对象,并设置相应的头部信息和响应体
5. 如果响应已经设置了`content-type`头部信息,则将其转换为对应的响应对象
6. 如果以上条件都不满足,则将其视为文本类型的响应对象
返回响应对象。
### 发送响应到客户端
```php
if (isset($psr7Request) && $psr7Request->getMethod() === 'HEAD') {
$this->responseEmitter->emit($psr7Response, $response, false);
} else {
$this->responseEmitter->emit($psr7Response, $response);
}
```
#### emit方法
发送响应到客户端。
```php
public function emit(ResponseInterface $response, mixed $connection, bool $withContent = true): void
{
try {
// 检查连接对象的 Upgrade 头部信息是否为 websocket如果是则直接返回不发送响应内容
if (strtolower($connection->header['Upgrade'] ?? '') === 'websocket') {
return;
}
// 将响应对象转换为 Swoole 响应对象
$this->buildSwooleResponse($connection, $response);
// 获取响应对象的主体内容
$content = $response->getBody();
// 如果主体内容是文件,则使用 sendfile 方法发送文件内容
if ($content instanceof FileInterface) {
$connection->sendfile($content->getFilename());
return;
}
// 是否发送响应内容
if ($withContent) {
$connection->end((string) $content);
} else {
$connection->end();
}
} catch (Throwable $exception) {
$this->logger?->critical((string) $exception);
}
}
```

115
src/hyperf/请求.md Normal file
View File

@ -0,0 +1,115 @@
---
layout: wiki
wiki: hyperf #项目名
date: 2024-03-22 16:56
categories: [Hyperf]
title: 请求
---
由前一节可知,事件的注册在`Hyerf\Server\Server`类的`registerSwooleEvents`方法中完成。
{%image https://oss.xiaokeaii.top/2024/event.png %}
注册的回调事件的执行顺序如下:
| 所属类 | 事件 | 描述 | 触发时机 | 触发顺序 |
| --- | --- | --- | --- | --- |
| StartCallback | onStart | 当 swoole_server 实例启动后触发 | 仅当 master 进程启动成功后触发 | 1 |
| ManagerStartCallback | onManagerStart | 当 swoole_server 实例启动后触发 | 仅当 manager 进程启动成功后触发 | 2 |
| WorkerStartCallback | onWorkerStart | 当 worker 进程启动后触发 | 仅当 worker 进程启动成功后触发 | 3 |
| WorkerStopCallback | onWorkerStop | 当 worker 进程关闭后触发 | 仅当 worker 进程关闭后触发 | 4 |
| WorkerExitCallback | onWorkerExit | 当 worker 进程退出后触发 | 仅当 worker 进程退出后触发 | 5 |
| Server | onRequest | 当接收到请求时触发 | 仅当 worker 进程接收到请求后触发 | 6 |
| PipeMessageCallback | onPipeMessage | 当接收到消息时触发 | 仅当 worker 进程接收到消息后触发 | 7 |
> 要理解上面事件,请参考`Swoole`的运行流程。
### Swoole流程
首先了解`Swoole`的运行流程,(图源网络)
{%image https://oss.xiaokeaii.top/2024/swoole流程.jpg%}
具体内容参考[swoole官网](https://wiki.swoole.com/)
当请求过来时,会触发`onRequest`事件,然后执行`onRequest`回调函数。
### OnRequest方法
```php
public function onRequest($request, $response): void
{
try {
CoordinatorManager::until(Constants::WORKER_START)->yield();
[$psr7Request, $psr7Response] = $this->initRequestAndResponse($request, $response);
$psr7Request = $this->coreMiddleware->dispatch($psr7Request);
$this->option?->isEnableRequestLifecycle() && $this->event?->dispatch(new RequestReceived(
request: $psr7Request,
response: $psr7Response,
server: $this->serverName
));
/** @var Dispatched $dispatched */
$dispatched = $psr7Request->getAttribute(Dispatched::class);
$middlewares = $this->middlewares;
$registeredMiddlewares = [];
if ($dispatched->isFound()) {
$registeredMiddlewares = MiddlewareManager::get($this->serverName, $dispatched->handler->route, $psr7Request->getMethod());
$middlewares = array_merge($middlewares, $registeredMiddlewares);
}
if ($this->option?->isMustSortMiddlewares() || $registeredMiddlewares) {
$middlewares = MiddlewareManager::sortMiddlewares($middlewares);
}
$psr7Response = $this->dispatcher->dispatch($psr7Request, $middlewares, $this->coreMiddleware);
} catch (Throwable $throwable) {
// Delegate the exception to exception handler.
$psr7Response = $this->container->get(SafeCaller::class)->call(function () use ($throwable) {
return $this->exceptionHandlerDispatcher->dispatch($throwable, $this->exceptionHandlers);
}, static function () {
return (new Psr7Response())->withStatus(400);
});
} finally {
// isEnableRequestLifecycle默认不开启
if (isset($psr7Request) && $this->option?->isEnableRequestLifecycle()) {
defer(fn () => $this->event?->dispatch(new RequestTerminated(
request: $psr7Request,
response: $psr7Response ?? null,
exception: $throwable ?? null,
server: $this->serverName
)));
$this->event?->dispatch(new RequestHandled(
request: $psr7Request,
response: $psr7Response ?? null,
exception: $throwable ?? null,
server: $this->serverName
));
}
// Send the Response to client.
if (! isset($psr7Response) || ! $psr7Response instanceof ResponseInterface) {
return;
}
// 发送响应到客户端
if (isset($psr7Request) && $psr7Request->getMethod() === 'HEAD') {
$this->responseEmitter->emit($psr7Response, $response, false);
} else {
$this->responseEmitter->emit($psr7Response, $response);
}
}
}
```
#### CoordinatorManager类
`CoordinatorManager::until`用于创建一个协程等待器,指示协程等待某个事件的完成,
这里是等待`worker`进程启动完成。
```php
CoordinatorManager::until(Constants::WORKER_START)->yield();
```
确保在处理请求之前,所有的`Worker`进程都已经启动完成,以保证后续的操作能够顺利执行。
#### initRequestAndResponse方法
将请求和响应对象转换为符合 `PSR-7`标准的对象。
```php
[$psr7Request, $psr7Response] = $this->initRequestAndResponse($request, $response);
```

View File

@ -0,0 +1,30 @@
---
layout: wiki
wiki: hyperf #项目名
date: 2024-03-28 16:56
categories: [Hyperf]
title: 路由寻址
---
对请求进行匹配,放到请求的`attribute`属性中,`key`为`Dispatched::class`。
```php
$psr7Request = $this->coreMiddleware->dispatch($psr7Request);
```
> 文件位置:/vendor/hyperf/http-server/src/CoreMiddleware.php
```php
public function dispatch(ServerRequestInterface $request): ServerRequestInterface
{
// 获取路由信息
$routes = $this->dispatcher->dispatch($request->getMethod(), $request->getUri()->getPath());
$dispatched = new Dispatched($routes, $this->serverName);
return RequestContext::set($request)->setAttribute(Dispatched::class, $dispatched);
}
```
该行默认不触发事件,如果需要触发事件,要设置`enable_request_lifecycle`为`true`。
```php
$this->option?->isEnableRequestLifecycle() && $this->event?->dispatch(new RequestReceived(
request: $psr7Request,
response: $psr7Response,
server: $this->serverName
));
```

View File

@ -0,0 +1,34 @@
---
title: 版本记录
---
> 记录阅读代码版本
### docker镜像
::: tip
基于`hyperf/hyperf:8.1-alpine-v3.18-swoole`镜像环境
:::
### php版本
::: tip
`php`版本要求`>=8.1.0`
:::
### hyperf版本
::: tip
`hyperf`版本`~3.1.0`
:::
### 启动命令
在命令行中输入以下命令启动项目
```bash
docker run --name hyperf \
-v $PWD/skeleton:/data/project \
-w /data/project \
-p 9501:9501 -it \
--privileged -u root \
--entrypoint /bin/sh \
hyperf/hyperf:8.1-alpine-v3.18-swoole
# 其中 $PWD/skeleton可替换为自己的目录
```

26
src/index.md Normal file
View File

@ -0,0 +1,26 @@
---
# https://vitepress.dev/reference/default-theme-home-page
layout: home
hero:
name: "源码阅读"
text: "记录源码阅读记录"
# tagline: 一个PHP开发者
image:
src: /logo.gif
alt: logo
actions:
- theme: alt #brand
text: Hyperf
link: /hyperf
- theme: alt #alt
text: Laravel
link: /laravel
# features:
# - title: Feature A
# details: Lorem ipsum dolor sit amet, consectetur adipiscing elit
---
::: tip
文档完善中...
:::

View File

@ -0,0 +1,530 @@
---
layout: wiki
wiki: laravel #项目名
date: 2024-02-05 16:56
categories: [Laravel]
title: Eloquent构造器_1
---
{%quot el:h2 数据库 %}
`Laravel`的`ORM`是`Eloquent``Eloquent`是基于`DB`的查询构建器。
> `DB`, `DB`是与PHP底层的`PDO`直接进行交互的,通过查询构建器提供了一个方便的接口来创建及运行数据库查询语句。
> `Eloquent Model`, `Eloquent`是建立在`DB`的查询构建器基础之上,对数据库进行了抽象的`ORM`功能十分丰富让我们可以避免写复杂的SQL语句并用优雅的方式解决了数据表之间的关联关系。
> 上面说的这两个部分都包括在了`Illuminate/Database`包里面除了作为Laravel的数据库层`Illuminate/Database`还是一个PHP数据库工具集 在任何项目里你都可以通过`composer install illuminate/databse`安装并使用它。
## 服务注册
`Database`作为服务注册到服务容器中,服务提供者是`Illuminate\Database\DatabaseServiceProvider`
```php
namespace Illuminate\Database;
use Faker\Factory as FakerFactory;
use Faker\Generator as FakerGenerator;
use Illuminate\Contracts\Queue\EntityResolver;
use Illuminate\Database\Connectors\ConnectionFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\QueueEntityResolver;
use Illuminate\Support\ServiceProvider;
class DatabaseServiceProvider extends ServiceProvider
{
/**
* The array of resolved Faker instances.
*
* @var array
*/
protected static $fakers = [];
/**
* 启动服务
*
* @return void
*/
public function boot()
{
// 设置数据库连接解析器
Model::setConnectionResolver($this->app['db']);
// 注册模型监听,监听数据库事件
Model::setEventDispatcher($this->app['events']);
}
/**
* 在这里注册服务
*
* @return void
*/
public function register()
{
// 确保在注册数据库服务时清理掉这些已经启动的模型,以确保注册过程的干净
Model::clearBootedModels();
// 第二步注册ConnectionServices
$this->registerConnectionServices();
// 创建数据库模型实例,方便地生成模拟数据,用于测试或种子数据的填充。
$this->registerEloquentFactory();
// 注册队列实体解析器
$this->registerQueueableEntityResolver();
}
/**
* Register the primary database bindings.
*
* @return void
*/
protected function registerConnectionServices()
{
// 创建数据库连接实例
$this->app->singleton('db.factory', function ($app) {
return new ConnectionFactory($app);
});
// DatabaseManger 作为Database面向外部的接口DB这个Facade就是DatabaseManager的静态代理。
// 应用中所有与Database有关的操作都是通过与这个接口交互来完成的。
$this->app->singleton('db', function ($app) {
return new DatabaseManager($app, $app['db.factory']);
});
// 数据库连接实例是与底层PDO接口进行交互的底层类可用于数据库的查询、更新、创建等操作。
$this->app->bind('db.connection', function ($app) {
return $app['db']->connection();
});
// 提供数据库事务管理相关的功能
$this->app->singleton('db.transactions', function ($app) {
return new DatabaseTransactionsManager;
});
}
/**
* Register the Eloquent factory instance in the container.
*
* @return void
*/
protected function registerEloquentFactory()
{
$this->app->singleton(FakerGenerator::class, function ($app, $parameters) {
$locale = $parameters['locale'] ?? $app['config']->get('app.faker_locale', 'en_US');
if (! isset(static::$fakers[$locale])) {
static::$fakers[$locale] = FakerFactory::create($locale);
}
static::$fakers[$locale]->unique(true);
return static::$fakers[$locale];
});
}
/**
* Register the queueable entity resolver implementation.
*
* @return void
*/
protected function registerQueueableEntityResolver()
{
$this->app->singleton(EntityResolver::class, function () {
return new QueueEntityResolver;
});
}
}
```
`DatabaseManager`作为接口与外部交互,在应用需要时通过`ConnectionFactory`创建了数据库连接实例,最后执行数据库的增删改查是由数据库连接实例来完成。
`DB Facade`代理的是`DatabaseManager`,所以使用`DB`实际操作的是`DatabaseManager`,而数据库管理类实例化时,通过数据库链接对象工厂`(ConnectionFactory)`获取数据库连接对象`(Connection)`,然后进行具体的`CRUD`操作。
## DatabaseManager
数据库管理对象的构造函数
```php
// 绑定时传递进来的参数
$this->app->singleton('db', function ($app) {
// $app['db.factory'] 是传递进来的Connection工厂类
return new DatabaseManager($app, $app['db.factory']);
});
// ----------------------------------------------------------
public function __construct($app, ConnectionFactory $factory)
{
// 容器实例
$this->app = $app;
// 数据库连接工厂
$this->factory = $factory;
// 用于重连数据库连接
// 接收一个数据库连接实例作为参数
// 调用reconnect方法重新连接该数据库连接
// 保持数据库连接的稳定性和可靠性
$this->reconnector = function ($connection) {
$this->reconnect($connection->getNameWithReadWriteType());
};
}
```
当使用`DB`访问数据库时,`DB`通过`DatabaseManager`获取数据库连接实例,`DatabaseManager`没有对应的方法,会触发魔术方法`__callStatic`
```php
public function __call($method, $parameters)
{
return $this->connection()->$method(...$parameters);
}
//....
// $name指定获取的数据库连接名称
public function connection($name = null)
{
// 解析数据库连接名称
[$database, $type] = $this->parseConnectionName($name);
$name = $name ?: $database;
// 检查是否已经创建了指定名称的连接
if (! isset($this->connections[$name])) {
// 根据配置文件中的配置信息创建连接
// 创建连接后会调用configure方法对连接进行配置这可能包括设置连接的查询返回类型等
$this->connections[$name] = $this->configure(
// 创建连接
$this->makeConnection($database), $type
);
}
return $this->connections[$name];
}
```
解析数据库连接名称
```php
protected function parseConnectionName($name)
{
$name = $name ?: $this->getDefaultConnection();
// 返回指定的连接类型如果没有则默认为null
return Str::endsWith($name, ['::read', '::write'])
? explode('::', $name, 2) : [$name, null];
}
// -------------------------------------
// 获取配置文件下面的默认数据库连接名称
public function getDefaultConnection()
{
return $this->app['config']['database.default'];
}
```
### makeConnection创建配置信息
```php
protected function makeConnection($name)
{
// 获取配置文件中,配置的所有连接信息
$config = $this->configuration($name);
// 首先去检查在应用启动时是否通过连接名注册了extension(闭包), 如果有则通过extension获得连接实例
// 比如在AppServiceProvider里通过DatabaseManager::extend('mysql', function () {...})
if (isset($this->extensions[$name])) {
return call_user_func($this->extensions[$name], $config, $name);
}
// 检查是否为连接配置指定的driver注册了extension, 如果有则通过extension获得连接实例
if (isset($this->extensions[$driver = $config['driver']])) {
return call_user_func($this->extensions[$driver], $config, $name);
}
// 创建连接实例
return $this->factory->make($config, $name);
}
protected function configuration($name)
{
// 获取连接名称
$name = $name ?: $this->getDefaultConnection();
// 获取配置文件中,配置的所有连接信息
$connections = $this->app['config']['database.connections'];
// 如果连接名称不存在,则抛出异常
if (is_null($config = Arr::get($connections, $name))) {
throw new InvalidArgumentException("Database connection [{$name}] not configured.");
}
// 解析配置信息
return (new ConfigurationUrlParser)
->parseConfiguration($config);
}
```
创建连接实例
#### ConnectionFactory创建数据库连接实例
```php
public function make(array $config, $name = null)
{
// 解析配置信息
$config = $this->parseConfig($config, $name);
// 如果配置了read键则创建一个读写分离的连接
if (isset($config['read'])) {
return $this->createReadWriteConnection($config);
}
// 创建单连接
return $this->createSingleConnection($config);
}
protected function createSingleConnection(array $config)
{
// 创建PDO连接
$pdo = $this->createPdoResolver($config);
// 创建连接实例
return $this->createConnection(
$config['driver'], $pdo, $config['database'], $config['prefix'], $config
);
}
// 创建连接实例
protected function createConnection($driver, $connection, $database, $prefix = '', array $config = [])
{
// 尝试获取给定驱动类型的连接解析器。如果解析器存在,则调用解析器来创建连接,并返回连接实例。
if ($resolver = Connection::getResolver($driver)) {
return $resolver($connection, $database, $prefix, $config);
}
// 根据给定的驱动类型,创建连接实例
switch ($driver) {
case 'mysql':
return new MySqlConnection($connection, $database, $prefix, $config);
case 'pgsql':
return new PostgresConnection($connection, $database, $prefix, $config);
case 'sqlite':
return new SQLiteConnection($connection, $database, $prefix, $config);
case 'sqlsrv':
return new SqlServerConnection($connection, $database, $prefix, $config);
}
throw new InvalidArgumentException("Unsupported driver [{$driver}].");
}
protected function createPdoResolver(array $config)
{
// 创建不同类型的 PDO 连接解析器
return array_key_exists('host', $config)
? $this->createPdoResolverWithHosts($config)
: $this->createPdoResolverWithoutHosts($config);
}
protected function createPdoResolverWithHosts(array $config)
{
return function () use ($config) {
foreach (Arr::shuffle($hosts = $this->parseHosts($config)) as $key => $host) {
$config['host'] = $host;
try {
return $this->createConnector($config)->connect($config);
} catch (PDOException $e) {
continue;
}
}
throw $e;
};
}
// 创建 PDO 连接解析器
public function createConnector(array $config)
{
if (! isset($config['driver'])) {
throw new InvalidArgumentException('A driver must be specified.');
}
// 如果容器中存在给定驱动类型的连接器,则返回连接器实例
if ($this->container->bound($key = "db.connector.{$config['driver']}")) {
return $this->container->make($key);
}
switch ($config['driver']) {
case 'mysql':
return new MySqlConnector;
case 'pgsql':
return new PostgresConnector;
case 'sqlite':
return new SQLiteConnector;
case 'sqlsrv':
return new SqlServerConnector;
}
throw new InvalidArgumentException("Unsupported driver [{$config['driver']}].");
}
```
### 连接器
在`illuminate/database`中连接器Connector是专门负责与PDO交互连接数据库的以`mysql`驱动为例,其连接器为`Illuminate\Database\Connectors\MySqlConnector`
```php
namespace Illuminate\Database\Connectors;
use PDO;
class MySqlConnector extends Connector implements ConnectorInterface
{
/**
* Establish a database connection.
*
* @param array $config
* @return \PDO
*/
public function connect(array $config)
{
// 生成PDO连接数据库时用的DSN连接字符串
$dsn = $this->getDsn($config);
// 获取要传给PDO的选项参数
$options = $this->getOptions($config);
// 创建PDO连接对象
$connection = $this->createConnection($dsn, $config, $options);
// 如果设置了数据库名称,则使用该数据库
if (! empty($config['database'])) {
$connection->exec("use `{$config['database']}`;");
}
// 设置事务隔离级别
$this->configureIsolationLevel($connection, $config);
// 设置编码
$this->configureEncoding($connection, $config);
// 设置时区
$this->configureTimezone($connection, $config);
// 设置模式
$this->setModes($connection, $config);
// 返回连接对象
return $connection;
}
}
public function createConnection($dsn, array $config, array $options)
{
[$username, $password] = [
$config['username'] ?? null, $config['password'] ?? null,
];
try {
return $this->createPdoConnection(
$dsn, $username, $password, $options
);
} catch (Exception $e) {
return $this->tryAgainIfCausedByLostConnection(
$e, $dsn, $username, $password, $options
);
}
}
protected function createPdoConnection($dsn, $username, $password, $options)
{
if (class_exists(PDOConnection::class) && ! $this->isPersistentConnection($options)) {
return new PDOConnection($dsn, $username, $password, $options);
}
return new PDO($dsn, $username, $password, $options);
}
```
- `$dsn`参数是数据库连接字符串,包含了连接到数据库所需的信息,如主机、端口、数据库名等。
- `$config` 参数是一个包含了数据库连接配置信息的数组。
- `$options` 参数是一个包含了数据库连接选项的数组,如连接超时时间、字符集等。
- 方法首先尝试调用 `createPdoConnection`方法来创建`PDO`连接实例,传递了连接字符串 `$dsn`、用户名和密码 `$username`、`$password`,以及连接选项 `$options`
- 如果在创建 `PDO`连接时抛出了异常,方法会尝试调用 `tryAgainIfCausedByLostConnection` 方法来处理异常情况,可能是由于连接丢失导致的异常。在这个方法中,会尝试重新连接数据库,以应对连接丢失的情况。
总的来说,`createConnection` 方法用于根据给定的连接信息和配置信息来创建数据库连接。在创建连接时,会尝试处理连接可能出现的异常情况,以确保连接的稳定性和可靠性。
### createPdoResolver方法返回pdo连接实例
看`createConnection`方法,
```php
protected function createSingleConnection(array $config)
{
$pdo = $this->createPdoResolver($config);
return $this->createConnection(
$config['driver'], $pdo, $config['database'], $config['prefix'], $config
);
}
protected function createConnection($driver, $connection, $database, $prefix = '', array $config = [])
{
if ($resolver = Connection::getResolver($driver)) {
return $resolver($connection, $database, $prefix, $config);
}
switch ($driver) {
case 'mysql':
return new MySqlConnection($connection, $database, $prefix, $config);
case 'pgsql':
return new PostgresConnection($connection, $database, $prefix, $config);
case 'sqlite':
return new SQLiteConnection($connection, $database, $prefix, $config);
case 'sqlsrv':
return new SqlServerConnection($connection, $database, $prefix, $config);
}
throw new InvalidArgumentException("Unsupported driver [{$driver}].");
}
```
以`MySqlConnection`为例分析,所有的数据库`Connection`类都继承了`Illuminate\Database\Connection`类,其构造方法为
```php
public function __construct($pdo, $database = '', $tablePrefix = '', array $config = [])
{
$this->pdo = $pdo;
// First we will setup the default properties. We keep track of the DB
// name we are connected to since it is needed when some reflective
// type commands are run such as checking whether a table exists.
$this->database = $database;
$this->tablePrefix = $tablePrefix;
$this->config = $config;
// We need to initialize a query grammar and the query post processors
// which are both very important parts of the database abstractions
// so we initialize these to their default values while starting.
$this->useDefaultQueryGrammar();
$this->useDefaultPostProcessor();
}
public function useDefaultQueryGrammar()
{
$this->queryGrammar = $this->getDefaultQueryGrammar();
}
public function useDefaultPostProcessor()
{
$this->postProcessor = $this->getDefaultPostProcessor();
}
```
然后依次返回,最终发现`DatabaseManager`中,`make`方法返回的是`MySqlConnection`实例。
## 回到configure方法
`$this->makeConnection($database)`实际是数据库中配置的对应的驱动连接,如`MySqlConnection`实例。
```php
$this->connections[$name] = $this->configure(
$this->makeConnection($database), $type
);
protected function configure(Connection $connection, $type)
{
// 设置连接的读写类型。
$connection = $this->setPdoForType($connection, $type)->setReadWriteType($type);
// 设置连接的事件分发器
if ($this->app->bound('events')) {
$connection->setEventDispatcher($this->app['events']);
}
// 设置连接的事务管理器。
if ($this->app->bound('db.transactions')) {
$connection->setTransactionManager($this->app['db.transactions']);
}
// 设置连接的重新连接器。
$connection->setReconnector($this->reconnector);
// 注册配置的 `Doctrine` 数据库类型。
$this->registerConfiguredDoctrineTypes($connection);
return $connection;
}
```
- `$connection` 参数是要配置的数据库连接实例,它是`Illuminate\Database\Connection`类的一个实例。
- `$type` 参数是连接类型,通常是读取连接或写入连接,默认为读取连接。
- 根据应用程序是否已绑定了事件管理器和事务管理器,分别设置连接的事件分发器和事务管理器。
- `$connection->setReconnector($this->reconnector)`:这行代码设置了连接的重新连接器。重新连接器是一个可回调的闭包,用于重新连接失效的数据库连接。
- `$this->reconnector`是一个闭包,用于重新连接数据库连接,其参数是数据库连接实例,在`DatabaseManager`类的构造函数中赋值。
- 最后调用了`registerConfiguredDoctrineTypes`方法,用于注册配置过的 `Doctrine` 数据库类型。
## 总结
1. 数据库的服务提供者是`Illuminate\Database\DatabaseServiceProvider`类,其`register`方法中绑定了`Illuminate\Database\DatabaseManager`类、数据库连接工厂`ConnectionFactory`类以及数据库事物管理器`DatabaseTransactionsManager`
2. `DatabaseManager`类是数据库连接管理器,代理类是`DB`,实例化该`DatabaseManager`类时,传入容器实例以及数据库连接工厂`ConnectionFactory`类实例
3. 当使用`DB`代理类时,会调用`DatabaseManager`类的魔术方法`__callStatic`方法,该方法中通过`$this->connection()`获取数据库的连接实例对象,其中该类通过`makeConnection`方法创建对应的数据库连接实例对象
调用`DB`的方法,实际调用`DatabaseManager`类的`connection`方法,返回数据库连接实例对象,该实例对象通过`configure`方法进行配置,配置后返回配置后的数据库连接实例对象。当配置的数据库为`MySql`时,实际返回的是`MySqlConnection`实例对象,
该实例对象继承了`Illuminate\Database\MySqlConnection`类,该类中定义了`MySql`数据库连接的常用方法。最终调用`MySqlConnection`实例中定义的数据库常用方法。

View File

@ -0,0 +1,409 @@
---
layout: wiki
wiki: laravel #项目名
date: 2024-02-08 16:56
categories: [Laravel]
title: Eloquent构造器_2
---
{%quot el:h2 查询构造器 %}
当使用`Eloquent`时,我们通常会使用`QueryBuilder`构造器来构建查询,调用语句如下。
```php
Route::get('/', function (){
\Illuminate\Support\Facades\DB::table('users')->get();
});
```
当我们调用`table`方法时,实际使用的是`Connection`对应实现的类的方法,以`MysqlConnection`为例,在该类中未找到`table`方法,该方法在父类中。
```php
use Illuminate\Database\Query\Builder as QueryBuilder;
public function table($table, $as = null)
{
return $this->query()->from($table, $as);
}
public function query()
{
return new QueryBuilder(
$this, $this->getQueryGrammar(), $this->getPostProcessor()
);
}
public function getQueryGrammar()
{
return $this->queryGrammar;
}
public function getPostProcessor()
{
return $this->postProcessor;
}
public function setPostProcessor(Processor $processor)
{
$this->postProcessor = $processor;
return $this;
}
public function setQueryGrammar(Query\Grammars\Grammar $grammar)
{
$this->queryGrammar = $grammar;
return $this;
}
```
上面可以看出,`query`方法返回的是`Builder`实例,`getQueryGrammar`方法返回的是`Query\Grammars\Grammar`实例,`getPostProcessor`方法返回的是`Processor`实例,`setPostProcessor`方法设置`Processor`实例,`setQueryGrammar`方法设置`Query\Grammars\Grammar`实例。
查看`Builder`类,
```php
public function __construct(ConnectionInterface $connection,
Grammar $grammar = null,
Processor $processor = null)
{
$this->connection = $connection;
$this->grammar = $grammar ?: $connection->getQueryGrammar();
$this->processor = $processor ?: $connection->getPostProcessor();
}
```
之后,由`Builder`类调用`from`方法,
```php
public function from($table, $as = null)
{
// 如果 $table 是一个查询构建器实例,则调用 fromSub 方法
if ($this->isQueryable($table)) {
return $this->fromSub($table, $as);
}
// 设置表名,如果传入别名则设置别名
$this->from = $as ? "{$table} as {$as}" : $table;
return $this;
}
// ......
protected function isQueryable($value)
{
return $value instanceof self ||
$value instanceof EloquentBuilder ||
$value instanceof Relation ||
$value instanceof Closure;
}
```
前期的准备工作已经完成,下面开始分析`where`方法。
### Builder类
分析一下`where`方法,当调用`DB::table('users')->where('id', 1)`时,查看`Builder`类的`where`方法,
```php
// Illuminate\Database\Query\Builder;
/**
* Add a basic where clause to the query.
* Parameters:
* @var array|Closure|string $column 可以为数组、字符串、闭包
* @var mixed $operator
* @var mixed $value
* @var string $boolean
* @return Builder
*/
public function where($column, $operator = null, $value = null, $boolean = 'and')
{
// 处理数组,最终通过递归的方式,将数组转化为单个字段
if (is_array($column)) {
return $this->addArrayOfWheres($column, $boolean);
}
/**
* protected function addArrayOfWheres($column, $boolean, $method = 'where')
* {
* return $this->whereNested(function ($query) use ($column, $method, $boolean) {
* foreach ($column as $key => $value) {
* 如果是数组,则递归调用 where 方法
* if (is_numeric($key) && is_array($value)) {
* $query->{$method}(...array_values($value));
* } else {
* $query->$method($key, '=', $value, $boolean);
* }
* }
* }, $boolean);
* }
*/
// 处理操作符
[$value, $operator] = $this->prepareValueAndOperator(
$value, $operator, func_num_args() === 2
);
/**
* public function prepareValueAndOperator($value, $operator, $useDefault = false)
* {
* 如果传入参数等于2则追加一个等于符号
* if ($useDefault) {
* return [$operator, '='];
* 对$operator的值进行校验
* } elseif ($this->invalidOperatorAndValue($operator, $value)) {
* throw new InvalidArgumentException('Illegal operator and value combination.');
* }
* return [$value, $operator];
* }
*/
// 处理闭包
if ($column instanceof Closure && is_null($operator)) {
return $this->whereNested($column, $boolean);
}
// 处理可查询对象
if ($this->isQueryable($column) && ! is_null($operator)) {
[$sub, $bindings] = $this->createSub($column);
return $this->addBinding($bindings, 'where')
->where(new Expression('('.$sub.')'), $operator, $value, $boolean);
}
// 处理运算符,如果$operator无效
if ($this->invalidOperator($operator)) {
[$value, $operator] = [$operator, '='];
}
// 处理value值是闭包的情况
if ($value instanceof Closure) {
return $this->whereSub($column, $operator, $value, $boolean);
}
// 如果value为空,则追加is null的条件
if (is_null($value)) {
return $this->whereNull($column, $boolean, $operator !== '=');
}
$type = 'Basic';
// 处理JSON引用
if (Str::contains($column, '->') && is_bool($value)) {
$value = new Expression($value ? 'true' : 'false');
if (is_string($column)) {
$type = 'JsonBoolean';
}
}
if ($this->isBitwiseOperator($operator)) {
$type = 'Bitwise';
}
// 将所有条件放在wheres数组中
$this->wheres[] = compact(
'type', 'column', 'operator', 'value', 'boolean'
);
// 最后将查询条件添加到该查询构造器的bingings数组中
if (! $value instanceof Expression) {
$this->addBinding($this->flattenValue($value), 'where');
}
return $this;
}
```
### 实操
#### $column为字符串时最简单的一种情况
`web.php`中添加一个路由闭包
```php
Route::get('/', function (){
\Illuminate\Support\Facades\DB::table('users')->where('id', '1')->get();
});
```
使用`xdebug`,观察调用过程。打开`Illuminate\Database\Query\Builder.php`文件,找到`where`方法。
{%image https://oss.xiaokeaii.top/2024/where_array.png%}
将这一条语句存放到`$this->wheres`数组中,
```php
$this->wheres[] = compact(
'type', 'column', 'operator', 'value', 'boolean'
);
// 如果value不是Expression实例
// 传过来的value为1
if (! $value instanceof Expression) {
$this->addBinding($this->flattenValue($value), 'where');
}
/**
* protected function flattenValue($value)
* {
* // head(Arr::flatten($value))获取多维数组 $value 展平后的一维数组的第一个元素
* return is_array($value) ? head(Arr::flatten($value)) : $value;
* }
*/
// 直接进入addBinding方法
public function addBinding($value, $type = 'where')
{
if (! array_key_exists($type, $this->bindings)) {
throw new InvalidArgumentException("Invalid binding type: {$type}.");
}
// 如果value是数组
if (is_array($value)) {
$this->bindings[$type] = array_values(array_map(
[$this, 'castBinding'],
array_merge($this->bindings[$type], $value),
));
} else {
// 走这条将value值存放到bindings数组中
$this->bindings[$type][] = $this->castBinding($value);
}
return $this;
}
// 处理绑定,这里$value是1直接返回
public function castBinding($value)
{
if (function_exists('enum_exists') && $value instanceof BackedEnum) {
return $value->value;
}
return $value;
}
```
上面流程走完,`where`方法执行结束,`where`方法后调用`dd()`方法。
```php
Route::get('/', function (){
\Illuminate\Support\Facades\DB::table('users')->where(['id' => '1'])->dd();
});
```
##### 结果
刷新首页,`dd()`方法打印当前构建的 SQL 查询语句和绑定的参数,(`dump()`方法同样)
{%image https://oss.xiaokeaii.top/2024/简单查询sql.png 简单查询sql%}
#### $column为数组时
```php
Route::get('/', function (){
\Illuminate\Support\Facades\DB::table('users')->where(['id' => '1'])->dd();
});
```
会进入到`addArrayOfWheres`方法中,可以看到,返回调用`whereNested`方法之后的内容,
```php
protected function addArrayOfWheres($column, $boolean, $method = 'where')
{
// 首先调用whereNested方法第一个参数为闭包
// 第二个参数为where方法中最后一个参数默认为and
return $this->whereNested(function ($query) use ($column, $method, $boolean) {
// 数组为['id' => '1']
foreach ($column as $key => $value) {
if (is_numeric($key) && is_array($value)) {
$query->{$method}(...array_values($value));
} else {
// 走else相当于递归调用where方法
$query->$method($key, '=', $value, $boolean);
}
}
}, $boolean);
}
public function whereNested(Closure $callback, $boolean = 'and')
{
// 第一个参数为闭包,
// call_user_func函数把第一个参数当作回调函数其余为参数
// 反过来执行上面闭包中的内容
// $this->forNestedWhere() 该方法创建一个新的查询构造器
// 在laravel中用于嵌套查询
call_user_func($callback, $query = $this->forNestedWhere());
return $this->addNestedWhereQuery($query, $boolean);
}
// public function forNestedWhere()
// {
// return $this->newQuery()->from($this->from);
// }
// public function newQuery()
// {
// return new static($this->connection, $this->grammar, $this->processor);
// }
```
{%note 强调 `$this->forNestedWhere()` 该方法创建一个新的查询构造器`builder`类实例%}
将断点打到这行`$query->$method($key, '=', $value, $boolean);`,查看`debug`内容
{%image https://oss.xiaokeaii.top/2024/column为数组.png%}
点击下一步,再次进入`where`方法,此时参数为
{%image https://oss.xiaokeaii.top/2024/再次进入where方法.png%}
此时,和{%mark color:red $column为字符串时 %} 情况相同,`$this->wheres`数组中,添加该条件,然后进入`addBinding`方法,将`$value`存放到`$this->bindings`数组中。
##### 结果
查看`dd()`方法的输出, 可以发现跟`$column`为字符串时结果一样。
{%image https://oss.xiaokeaii.top/2024/sql输出.png %}
#### 当$column为闭包且操作符为null时
在`where`方法中对应的是以下代码,
```php
if ($column instanceof Closure && is_null($operator)) {
return $this->whereNested($column, $boolean);
}
```
更改路由,
```php
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Route;
Route::get('/', function (){
DB::table('users')->where(function (Builder $query) {
return $query->where('id',1);
})->dd();
});
```
`debug`如下,
{%image https://oss.xiaokeaii.top/2024/闭包.png %}
```php
public function whereNested(Closure $callback, $boolean = 'and')
{
call_user_func($callback, $query = $this->forNestedWhere());
return $this->addNestedWhereQuery($query, $boolean);
}
```
该方法已经分析过,会执行闭包,参数为新的查询构造器。重点看第二行
```php
return $this->addNestedWhereQuery($query, $boolean);
```
该方法将新创建的查询构造器和`$boolean`参数传入到首次实例化的查询构造器中。
```php
public function addNestedWhereQuery($query, $boolean = 'and')
{
// 如果查询构造器中有where条件
if (count($query->wheres)) {
// 类型为嵌套查询
$type = 'Nested';
// 会将参数绑定到第一个查询构造器中
$this->wheres[] = compact('type', 'query', 'boolean');
// $query->getRawBindings()['where']该方法会返回新查询构造器中保存的where条件
// addBinding同理将新查询构造器中的where条件添加到当前查询构造器的bingding变量中
$this->addBinding($query->getRawBindings()['where'], 'where');
}
return $this;
}
```
看`debug`信息可知,闭包中的条件在新的查询构造器`$query`变量中,需要从新查询构造器中取出,添加到当前查询构造器中。
{%image https://oss.xiaokeaii.top/2024/新查询构造器.png%}
而添加的方法就是`addNestedWhereQuery`方法。
##### 结果
可以发现转成`sql`语句后,结果和`$column`为字符串时一样。但是通过闭包形式可以创建更复杂的查询,代价就是效率会变低。
{%image https://oss.xiaokeaii.top/2024/sql输出.png %}
#### 当$column为闭包且操作符不为null时
该方法在`where`方法中对应的是以下代码,这里可以构建更加复杂的子查询。
```php
if ($this->isQueryable($column) && ! is_null($operator)) {
[$sub, $bindings] = $this->createSub($column);
return $this->addBinding($bindings, 'where')
->where(new Expression('('.$sub.')'), $operator, $value, $boolean);
}
protected function isQueryable($value)
{
return $value instanceof self ||
$value instanceof EloquentBuilder ||
$value instanceof Relation ||
$value instanceof Closure;
}
```
#### 如果值为闭包时
该方法在`where`方法中对应的是以下代码,
```php
if ($value instanceof Closure) {
return $this->whereSub($column, $operator, $value, $boolean);
}
```
有兴趣可自行研究。

1921
src/laravel/auth.md Normal file

File diff suppressed because it is too large Load Diff

103
src/laravel/facades.md Normal file
View File

@ -0,0 +1,103 @@
---
layout: wiki
wiki: laravel #项目名
date: 2023-12-22 16:56
title: Facade 门面模式
---
{%quot el:h2 Facade门面模式%}
参考:
Laravel中文社区8.5文档
{%link https://learnku.com/docs/laravel/8.5/facades/10367 Laravel中文社区8.5文档%}
### 环境
```php
# php artisan --version
bootLaravel Framework 8.83.27
# php -v
PHP 7.4.27 (cli) (built: Dec 16 2021 22:42:18) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
with Zend OPcache v7.4.27, Copyright (c), by Zend Technologies
with Xdebug v2.9.2, Copyright (c) 2002-2020, by Derick Rethans
```
### 简介
> `Facades` 为应用程序的 服务容器 中可用的类提供了 『静态代理』。
> `Laravel` 的所有 `Facades` 都在 `Illuminate\Support\facades`命名空间中定义
#### 什么是静态代理
参考官方手册:
{%link https://www.php.net/manual/zh/language.oop5.late-static-bindings.php 后期静态绑定%}
> php静态代理是一种在运行时动态绑定方法的技术。
> “后期绑定”的意思是说,`static::` 不再被解析为定义当前方法所在的类,而是在实际运行时计算的。也可以称之为“静态绑定”,因为它可以用于(但不限于)静态方法的调用。
### 定义路由
导入`Cache Facade`对象
```php
Route::get('/', function () {
return \Illuminate\Support\Facades\Cache::get('/');
});
```
### 查看源码
> `php`中,调用类中不存在的方法,会调用类的`__callStatic`魔术方法。
查看`\Illuminate\Support\Facades\Cache`类,没有找到`get`方法,代码会自动调用`__callStatic`魔术方法,查看该魔术方法,
```php
public static function __callStatic($method, $args)
{
// 查看方法
$instance = static::getFacadeRoot();
// 如果没有设置实例,则抛出异常
if (! $instance) {
throw new RuntimeException('A facade root has not been set.');
}
// 这里调用实例的$method方法传入对应的参数
return $instance->$method(...$args);
}
public static function getFacadeRoot()
{
// static::绑定的实际运行的类即是Cache类调用getFacadeAccessor该方法返回的是别名字符串
// cache中并没有resolveFacadeInstance方法所以还是调用了Facade类的resolveFacadeInstance方法
return static::resolveFacadeInstance(static::getFacadeAccessor());
}
```
验证一下,`Cache`类下新增`resolveFacadeInstance`方法
```php
public static function resolveFacadeInstance($name)
{
dd('resolveFacadeInstance');
}
```
访问首页,跟我们猜测一致
{%image https://oss.xiaokeaii.top/2023/facade.png resolveFacadeInstance()方法%}
#### 查看`Facade`类的`resolveFacadeInstance`方法
```php
// $name值为Cache方法中返回的字符串
protected static function resolveFacadeInstance($name)
{
// 如果$name是对象直接返回该对象
if (is_object($name)) {
return $name;
}
// 如果$name在静态属性$resolvedInstance中存在直接返回该对象
if (isset(static::$resolvedInstance[$name])) {
return static::$resolvedInstance[$name];
}
// 如果容器对象存在,从容器中解析出别名为$name的对象并将该对象放入静态属性$resolvedInstance中下次从属性中直接返回该对象然后返回解析出的对象
if (static::$app) {
return static::$resolvedInstance[$name] = static::$app[$name];
}
}
```
`resolveFacadeInstance`该方法中,如果容器对象中也不存在`$name`对应的类,则会抛出异常。
### 总结
`Facades`为应用程序的 服务容器 中可用的类提供了 『静态代理』。
`Laravel` 的所有 `Facades` 都在 `Illuminate\Support\facades`命名空间中定义。
`Facades` 使得我们可以更方便地使用框架提供的各种服务,而不需要在每个类中都进行手动绑定。
`Facades` 也使得我们可以更方便地在测试中使用框架提供的服务。

View File

@ -0,0 +1,47 @@
---
layout: wiki
wiki: laravel #项目名
date: 2023-12-15 16:58
categories: [Laravel]
title: 入口文件
---
```php
use Illuminate\Contracts\Http\Kernel;
use Illuminate\Http\Request;
// 项目保留常量,未使用
define('LARAVEL_START', microtime(true));
// 用于判断是否为维护状态,当维护时会返回503服务不可用错误
if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {
require $maintenance;
}
// 实现项目第三方库文件的自动加载
require __DIR__.'/../vendor/autoload.php';
/**
* 引入框架app启动文件实例化Illuminate\Foundation\Application对象
* 2.1 实例化容器类
* 2.1.1 设置基础目录路径
* 2.1.2 注册基础绑定
* 2.1.3 注册服务提供者
* 2.1.4 注册核心别名类
* 向容器中"装填"处理HTTP请求的核心类HttpKernel
* 向容器中"装填"处理命令行的核心类ConsoleKernel
* 向容器中"装填"处理异常的核心类
*/
$app = require_once __DIR__.'/../bootstrap/app.php';
// 从app中得到容器中装填好的处理HTTP请求的类赋值给变量kernel
$kernel = $app->make(Kernel::class);
// 运行HttpKernel类的handle方法处理接收到的HTTP请求得到需要返回的对象
$response = $kernel->handle(
$request = Request::capture()
)->send();
$kernel->terminate($request, $response);
```

View File

@ -0,0 +1,51 @@
---
layout: wiki
wiki: laravel #项目名
date: 2023-12-15 15:58
categories: [Laravel]
title: 环境配置
description: 项目环境配置简要说明
---
## 环境
{%radio checked:true php 7.4%}
{%radio checked:true laravel 8.x%}
```php
php artisan --version
# Laravel Framework 8.83.27
php -v
# PHP 7.4.27 (cli) (built: Dec 16 2021 22:42:18) ( NTS )
```
## 创建项目
拉取项目源码
```bash
composer create-project laravel/laravel="^8" demo
```
### 配置nginx
```yml
server {
listen 80;
server_name demo.cn; # 自行更改
root /www/source_analysis/public/; # 根目录
index index.php index.html index.htm;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ [^/]\.php(/|$) {
fastcgi_pass php:9000;
include fastcgi-php.conf;
include fastcgi_params;
}
}
```
### 修改host文件
修改hosts文件将域名映射到本机ip
```bash
127.0.0.1 demo.cn
```

542
src/laravel/处理请求.md Normal file
View File

@ -0,0 +1,542 @@
---
layout: wiki
wiki: laravel #项目名
date: 2023-12-17 16:56
categories: [Laravel]
title: 处理请求
---
{%quot el:h2 处理请求%}
在`index.php`文件中,处理请求的实现
```php
$response = $kernel->handle(
$request = Request::capture()
)->send();
```
调用`Kernel`类的`handle`方法
### handle()方法
```php
public function handle($request)
{
try {
// 开启_method参数
$request->enableHttpMethodParameterOverride();
// 发送请求体,返回响应体
$response = $this->sendRequestThroughRouter($request);
} catch (Throwable $e) {
$this->reportException($e);
$response = $this->renderException($request, $e);
}
$this->app['events']->dispatch(
new RequestHandled($request, $response)
);
return $response;
}
```
### 查看enableHttpMethodParameterOverride()方法
`enableHttpMethodParameterOverride()`是`\Illuminate\Http\Request`类的父类`\Symfony\Component\HttpFoundation\Request`的方法:
```php
public static function enableHttpMethodParameterOverride()
{
self::$httpMethodParameterOverride = true;
}
```
当设置`$httpMethodParameterOverride`参数值为`true`时,使用表单进行`POST`提交`http`请求时,可以通过设置表单`_method`参数值来向服务器发送`PUT`或者`DELETE`请求。即`Laravel`可以将`POST`请求转换为`PUT`或者`DELETE`请求,前提是必须设置`$httpMethodParameterOverride`参数值为`true`
### sendRequestThroughRouter()方法
```php
protected function sendRequestThroughRouter($request)
{
// 调用instance方法绑定键值对
$this->app->instance('request', $request);
// 清除绑定在容器成员变量$resolvedInstance中的request键值对
Facade::clearResolvedInstance('request');
// 运行引导程序
$this->bootstrap();
// 借助app容器使用管道类处理HTTP请求并将结果返回。
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}
```
#### bootstrap()方法
查看`bootstrap`方法
```php
public function bootstrap()
{
// 判断容器是否已经初始化,首次进入为未初始化
if (! $this->app->hasBeenBootstrapped()) {
/**
* bootstrappers()方法,返回的是$bootstrappers数组
* protected $bootstrappers = [
* \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
* \Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
* \Illuminate\Foundation\Bootstrap\HandleExceptions::class,
* \Illuminate\Foundation\Bootstrap\RegisterFacades::class,
* \Illuminate\Foundation\Bootstrap\RegisterProviders::class,
* \Illuminate\Foundation\Bootstrap\BootProviders::class,
* 1. 环境检测 DetectEnvironment
* 2. 配置加载 LoadConfiguration
* 3. 日志配置 ConfigureLogging
* 4. 异常处理 HandleException
* 5. 注册Facades RegisterFacades
* 6. 注册Providers RegisterProviders
* 7. 启动Providers BootProviders
* ];
*/
$this->app->bootstrapWith($this->bootstrappers());
}
}
```
##### bootstrapWith()方法
查看`bootstrapWith`方法
```php
public function bootstrapWith(array $bootstrappers)
{
// 设置成员变量$hasBeenBootstrapped为true表示已经初始化
$this->hasBeenBootstrapped = true;
// 循环每个value
foreach ($bootstrappers as $bootstrapper) {
/**
* events是在注册服务提供者是注册的
* $this->register(new EventServiceProvider($this));
* $this['events']写法调用是因为Container类实现了ArrayAccess接口方法offsetGet()
* public function offsetGet($key)
* {
* return $this->make($key);
* }
* 解析出来的是Illuminate\Events\Dispatcher类
* dispatch方法是框架预留的事件分发方法可以将事件分发到多个监听器中并返回最后一个监听器的返回值
*/
$this['events']->dispatch('bootstrapping: '.$bootstrapper, [$this]);
$this->make($bootstrapper)->bootstrap($this);
$this['events']->dispatch('bootstrapped: '.$bootstrapper, [$this]);
}
}
```
##### dispatch()方法
传入的`$event`为`bootstrapping:Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables`字符串,
`$payload`为数组`[0 => Application类实例]`
```php
public function dispatch($event, $payload = [], $halt = false)
{
//
[$event, $payload] = $this->parseEventAndPayload(
$event, $payload
);
if ($this->shouldBroadcast($payload)) {
$this->broadcastEvent($payload[0]);
}
$responses = [];
foreach ($this->getListeners($event) as $listener) {
$response = $listener($event, $payload);
// If a response is returned from the listener and event halting is enabled
// we will just return this response, and not call the rest of the event
// listeners. Otherwise we will add the response on the response list.
if ($halt && ! is_null($response)) {
return $response;
}
// If a boolean false is returned from a listener, we will stop propagating
// the event to any further listeners down in the chain, else we keep on
// looping through the listeners and firing every one in our sequence.
if ($response === false) {
break;
}
$responses[] = $response;
}
return $halt ? null : $responses;
}
```
##### parseEventAndPayload()方法
`$event`和`$payload`值同上
```php
protected function parseEventAndPayload($event, $payload)
{
// 判断$event是否为对象
if (is_object($event)) {
[$payload, $event] = [[$event], get_class($event)];
}
return [$event, Arr::wrap($payload)];
}
```
#### 启动阶段的服务加载
```php
// \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
// \Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
// \Illuminate\Foundation\Bootstrap\HandleExceptions::class,
// \Illuminate\Foundation\Bootstrap\RegisterFacades::class,
// \Illuminate\Foundation\Bootstrap\RegisterProviders::class,
// \Illuminate\Foundation\Bootstrap\BootProviders::class,
// 对应代码解析出对应类实例并调用bootstrap方法
$this->make($bootstrapper)->bootstrap($this);
```
##### LoadEnvironmentVariables::class类
`LoadEnvironmentVariables`类的`bootstrap`方法,可以理解为加载配置文件并解析为全局常量。
```php
public function bootstrap(Application $app)
{
// 如果缓存了配置文件,直接返回
if ($app->configurationIsCached()) {
return;
}
$this->checkForSpecificEnvironmentFile($app);
try {
// 使用Dotenv类加载配置文件
$this->createDotenv($app)->safeLoad();
} catch (InvalidFileException $e) {
$this->writeErrorAndDie($e);
}
}
```
##### LoadConfiguration::class类
该类是加载`config`目录下的所有配置文件
```php
public function bootstrap(Application $app)
{
$items = [];
// 同理如果缓存了配置文件则require该文件
if (file_exists($cached = $app->getCachedConfigPath())) {
$items = require $cached;
$loadedFromCache = true;
}
// 绑定一个配置文件的实例,初始化时为空
$app->instance('config', $config = new Repository($items));
// 如果未从缓存中加载则从config目录加载配置文件
if (! isset($loadedFromCache)) {
$this->loadConfigurationFiles($app, $config);
}
// Finally, we will set the application's environment based on the configuration
// values that were loaded. We will pass a callback which will be used to get
// the environment in a web context where an "--env" switch is not present.
$app->detectEnvironment(function () use ($config) {
return $config->get('app.env', 'production');
});
date_default_timezone_set($config->get('app.timezone', 'UTC'));
mb_internal_encoding('UTF-8');
}
```
##### HandleExceptions::class类
该类处理异常
```php
public function bootstrap(Application $app)
{
self::$reservedMemory = str_repeat('x', 32768);
$this->app = $app;
error_reporting(-1);
set_error_handler([$this, 'handleError']);
set_exception_handler([$this, 'handleException']);
register_shutdown_function([$this, 'handleShutdown']);
if (! $app->environment('testing')) {
ini_set('display_errors', 'Off');
}
}
```
##### RegisterFacades::class类
这部分代码是实现laravel的Facades功能
```php
public function bootstrap(Application $app)
{
// 清除绑定
Facade::clearResolvedInstances();
Facade::setFacadeApplication($app);
AliasLoader::getInstance(array_merge(
// 从config实例中获取aliases配置
$app->make('config')->get('app.aliases', []),
// 从PackageManifest实例中获取aliases配置
$app->make(PackageManifest::class)->aliases()
))->register();
}
```
##### RegisterProviders::class类
该类是注册服务提供者
```php
public function bootstrap(Application $app)
{
$app->registerConfiguredProviders();
}
// registerConfiguredProviders
public function registerConfiguredProviders()
{
// 取出配置文件中的providers配置
$providers = Collection::make($this->make('config')->get('app.providers'))
->partition(function ($provider) {
return strpos($provider, 'Illuminate\\') === 0;
});
$providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]);
(new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
->load($providers->collapse()->toArray());
}
```
##### BootProviders::class类
该类是启动服务提供者
```php
public function bootstrap(Application $app)
{
$app->boot();
}
public function boot()
{
// 如果已经启动过,则直接返回
if ($this->isBooted()) {
return;
}
// 启动前调用保存在bootingCallbacks中回调
$this->fireAppCallbacks($this->bootingCallbacks);
// 调用服务提供者的bootProvider方法
array_walk($this->serviceProviders, function ($p) {
$this->bootProvider($p);
});
// 标志已经启动
$this->booted = true;
// 启动后调用保存在bootedCallbacks中回调
$this->fireAppCallbacks($this->bootedCallbacks);
}
```
至此,引导程序执行完成。
```php
$this->bootstrap();
```
#### return
```php
// new Pipeline($this->app)时将容器类绑定到$container变量
return (new Pipeline($this->app))
// 将请求对象绑定到passable变量
->send($request)
/**
* shouldSkipMiddleware()首先判断中间件是否跳过,跳过则返回空数组,否则返回中间件数组
* $this->middleware的值为类App\Http\Kernel中定义$middleware
* protected $middleware = [
* // \App\Http\Middleware\TrustHosts::class,
* \App\Http\Middleware\TrustProxies::class,
* \Fruitcake\Cors\HandleCors::class,
* \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
* \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
* \App\Http\Middleware\TrimStrings::class,
* \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
* ];
* 该方法将中间件数组给$pipes变量
*/
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
```
##### dispatchToRouter()方法
`dispatchToRouter()`方法返回的时一个闭包,该闭包将请求对象`$request`绑定到容器上,然后调用`Router`类的`dispatch()`方法
```php
protected function dispatchToRouter()
{
return function ($request) {
$this->app->instance('request', $request);
return $this->router->dispatch($request);
};
}
```
##### then()方法 {%hashtag 重要%}
这里是`laravel`路由中间件的`洋葱`模型,请求经过第一个、第二、第三..等中间件,到达控制器,执行控制器的方法。在到达控制器之前,如果有未授权、未登录、未验证的异常,则会抛出异常。
```php
public function then(Closure $destination)
{
// 先看array_reduce函数
$pipeline = array_reduce(
array_reverse($this->pipes()), $this->carry(), $this->prepareDestination($destination)
);
return $pipeline($this->passable);
}
```
###### array_reduce()函数
看一下官方解释,
{%image https://oss.xiaokeaii.top/2023/array_reduce.png array_reduce()函数%}
第一个参数为数组,第二个参数是一个匿名函数,第三个参数是一个初始值。
举个例子,
```php
$arr = [1,2,3,4,5];
$sum = array_reduce($arr, function($carry, $item){
return $carry + $item;
}, 0);
echo $sum; // 15
```
这里,执行流程为:
1. 首次执行时,第三个参数`0`会赋值给`$carray``$item`第一次的值为数组的第一个元素`1`, 返回值为`1`,之后将返回的值赋值给`$carry`,进行下一次循环,循环次数为数组的长度。
2. 第二次执行时,`$carry`的值为`1``$item`的值为数组的第二个元素`2`,返回值为`3`。
3. 第三次执行时,`$carry`的值为`3``$item`的值为数组的第三个元素`3`,返回值为`6`。
4. 第四次执行时,`$carry`的值为`6``$item`的值为数组的第四个元素`4`,返回值为`10`。
5. 第五次执行时,`$carry`的值为`10``$item`的值为数组的第五个元素`5`,返回值为`15`,并将返回值给`$sum`
看源码实现,
###### 参数分析
```php
$this->pipes() // 获取中间件数组
# ------------------------------------------
$this->carry() // 要执行的回调函数
protected function carry()
{
return function ($stack, $pipe) {
// $passable是$request请求对象
return function ($passable) use ($stack, $pipe) {
try {
if (is_callable($pipe)) {
// 如果是匿名函数,则直接执行该函数
return $pipe($passable, $stack);
} elseif (! is_object($pipe)) {
// 将中间件和参数分开
[$name, $parameters] = $this->parsePipeString($pipe);
// 解析中间件名称,并将其实例化
$pipe = $this->getContainer()->make($name);
// 将请求对象、迭代的中间件、参数合并
$parameters = array_merge([$passable, $stack], $parameters);
} else {
// 如果传入的$pipe是实例对象则将请求对象、迭代的中间件合并
$parameters = [$passable, $stack];
}
// 执行中间件的handle方法
$carry = method_exists($pipe, $this->method)
? $pipe->{$this->method}(...$parameters)
: $pipe(...$parameters);
return $this->handleCarry($carry);
} catch (Throwable $e) {
// 如果有异常,捕获并抛出
return $this->handleException($passable, $e);
}
};
};
}
# ------------------------------------------
$this->prepareDestination($destination) // 初始值
# ------------------------------------------
```
要理解`carry`的执行流程,再举一个例子,由于在内部会解析出该中间件执行中间件的方法,所以,我们用匿名函数放在数组中进行迭代
[参考](https://learnku.com/articles/68357)
```php
$f1 = function (\Closure $callback) {
echo 'f1 start'.PHP_EOL;
$callback();
echo 'f1 end'.PHP_EOL;
};
$f2 = function (\Closure $callback) {
echo 'f2 start'.PHP_EOL;
$callback();
echo 'f2 end'.PHP_EOL;
};
$f3 = function (\Closure $callback) {
echo 'f3 start'.PHP_EOL;
$callback();
echo 'f3 end'.PHP_EOL;
};
$f4 = function () {
echo 'f4 执行结束'.PHP_EOL;
};
$action = array_reduce(array_reverse([$f1, $f2, $f3]), function ($carry, $item) {
return function () use ($carry, $item) {
return $item($carry);
};
}, $f4);
// 调用
$action();
```
执行流程,
> 1. 第一次迭代,`$carry`为`$f4``$item`为`$f3`,返回的`$carry`为闭包
```php
function () use ($carry, $item) {
return $f3($f4);
};
```
> 2. 第二次迭代,`$item`为`$f2`,返回的`$carry`为闭包
```php
function () use ($carry, $item) {
return $f2($f3($f4));
};
```
> 3. 第三次迭代,`$item`为`$f1`,返回的`$carry`为闭包
```php
function () use ($carry, $item) {
return $f1($f2($f3($f4)));
};
```
调用`$action()`闭包,相当于执行`$f1($f2($f3($f4)))`方法,参数为`$f2($f3($f4))`
1. 先输出`f1 start`,然后调用传入的闭包`$f2($f3($f4))`,参数为`$f3($f4)`
2. 再执行`$f2($f3($f4))`方法,输出`f2 start`,调用传入的闭包`$f3($f4)`,参数为`$f4`
3. 再执行`$f3($f4)`方法,输出`f3 start`,调用传入的闭包`$f4`,参数为空
4. 再执行`$f4`方法,输出`f4 执行结束`
5. 第三步的回调函数执行结束,输出`f3 end`,
6. 第二步步的回调函数执行结束,输出`f2 end`,
7. 第一步的回调函数执行结束,输出`f1 end`
{%note 由此不难理解,中间件的执行方式,第一个中间件执行完,传递给下一个中间件,依次类推。最终最后一个中间件调用`$this->prepareDestination($destination)`这个方法内的闭包%}
###### prepareDestination()查看该方法
```php
protected function prepareDestination(Closure $destination)
{
return function ($passable) use ($destination) {
try {
return $destination($passable);
} catch (Throwable $e) {
return $this->handleException($passable, $e);
}
};
}
```
查看传进来的`destination`闭包,闭包为`$this->dispatchToRouter()`,进入该方法,
```php
protected function dispatchToRouter()
{
return function ($request) {
$this->app->instance('request', $request);
return $this->router->dispatch($request);
};
}
```
可以发现,最终执行了`$this->router->dispatch($request)`,也就是执行了路由的分发。

View File

@ -0,0 +1,86 @@
---
layout: wiki
wiki: laravel #项目名
date: 2023-12-15 18:56
categories: [Laravel]
title: 实例化容器类
---
引入容器类,
```php
$app = require_once __DIR__.'/../bootstrap/app.php';
```
查看引入代码,
```php
// 创建容器,传入环境设置的目录或者当前目录
$app = new Illuminate\Foundation\Application(
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);
/*
|--------------------------------------------------------------------------
| Bind Important Interfaces
|--------------------------------------------------------------------------
|
| Next, we need to bind some important interfaces into the container so
| we will be able to resolve them when needed. The kernels serve the
| incoming requests to this application from both the web and CLI.
|
*/
/**
* 给容器绑定接口和实现类Kernel
* 该类处理HTTP请求
*/
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);
/**
* 给容器绑定接口和实现类Kernel
* 该类处理命令行请求
*/
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);
/**
* 给容器绑定接口和实现类Handler
* 该类处理异常
*/
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);
/*
|--------------------------------------------------------------------------
| Return The Application
|--------------------------------------------------------------------------
|
| This script returns the application instance. The instance is given to
| the calling script so we can separate the building of the instances
| from the actual running of the application and sending responses.
|
*/
return $app;
```
## Application类源码
`new`时调用构造函数,
```php
public function __construct($basePath = null)
{
// 设置基础目录路径
if ($basePath) {
$this->setBasePath($basePath);
}
// 注册基础绑定
$this->registerBaseBindings();
// 注册服务提供者
$this->registerBaseServiceProviders();
// 注册核心别名类
$this->registerCoreContainerAliases();
}
```

View File

@ -0,0 +1,7 @@
---
layout: wiki
wiki: laravel #项目名
date: 2023-12-15 17:39
categories: [Laravel]
title: 自动加载实现原理
---

2898
src/laravel/容器类.md Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,453 @@
---
layout: wiki
wiki: laravel #项目名
date: 2023-12-20 16:56
categories: [Laravel]
title: 服务提供者
---
参考网址:
[ 服务提供者](https://learnku.com/docs/laravel/8.5/providers/10366)
{%quot el:h2 服务提供者%}
### 简介
> 服务提供者是所有 Laravel 应用程序的引导中心。你的应用程序,以及通过服务器引导的 Laravel 核心服务都是通过服务提供器引导。
### 源码调用流程
首先,请求到达`index.php`文件,加载`bootstrap/app.php`文件,实例化容器类。
```php
// 从容器中解析出Kernel类在文件bootstrap/app.php中已绑定到容器类中
$kernel = $app->make(Kernel::class);
// Request::capture()获取请求对象
$response = $kernel->handle(
$request = Request::capture()
)->send();
```
### 查看handle()方法
这里调用的`handle()`方法,是`Illuminate\Foundation\Http\Kernel`类的`handle()`方法,查看该类
```php
// handle
public function handle($request)
{
try {
$request->enableHttpMethodParameterOverride();
// 重点看这行
$response = $this->sendRequestThroughRouter($request);
} catch (Throwable $e) {
$this->reportException($e);
$response = $this->renderException($request, $e);
}
$this->app['events']->dispatch(
new RequestHandled($request, $response)
);
return $response;
}
// sendRequestThroughRouter方法详细
protected function sendRequestThroughRouter($request)
{
$this->app->instance('request', $request);
Facade::clearResolvedInstance('request');
// 重点在这行
$this->bootstrap();
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}
// bootstrap方法详细
public function bootstrap()
{
// 初始化时$this->app->hasBeenBootstrapped()为false
if (! $this->app->hasBeenBootstrapped()) {
/**
* 看这行
* 参数$this->bootstrappers()为数组。
* protected function bootstrappers()
* {
* return $this->bootstrappers;
* }
*
* $this->bootstrappers为类Kernel的属性值如下
* protected $bootstrappers = [
* \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
* \Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
* \Illuminate\Foundation\Bootstrap\HandleExceptions::class,
* \Illuminate\Foundation\Bootstrap\RegisterFacades::class,
* \Illuminate\Foundation\Bootstrap\RegisterProviders::class,
* \Illuminate\Foundation\Bootstrap\BootProviders::class,
* ];
* $this->app实际是Illuminate\Foundation\Application类的实例调用该类的bootstrapWith()方法
*/
$this->app->bootstrapWith($this->bootstrappers());
}
}
```
### 查看bootstrapWith()方法
```php
public function bootstrapWith(array $bootstrappers)
{
$this->hasBeenBootstrapped = true;
/**
* $bootstrappers = [
* \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
* \Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
* \Illuminate\Foundation\Bootstrap\HandleExceptions::class,
* \Illuminate\Foundation\Bootstrap\RegisterFacades::class,
* \Illuminate\Foundation\Bootstrap\RegisterProviders::class,
* \Illuminate\Foundation\Bootstrap\BootProviders::class,
* ];
*/
foreach ($bootstrappers as $bootstrapper) {
$this['events']->dispatch('bootstrapping: '.$bootstrapper, [$this]);
// 从服务容器中解析出对应实例并调用bootstrap方法
$this->make($bootstrapper)->bootstrap($this);
$this['events']->dispatch('bootstrapped: '.$bootstrapper, [$this]);
}
}
```
{%note 该方法循环数组`$bootstrappers`的每一个元素,并调用其`bootstrap()`方法。重点看`\Illuminate\Foundation\Bootstrap\RegisterProviders::class`类的`bootstrap()`方法%}
#### RegisterProviders::class类
查看该类的`bootstrap()`方法
{%note color:warning __这是服务提供者加载的核心方法(重要!)__%}
```php
public function bootstrap(Application $app)
{
// 这里实际调用的是Application类的registerConfiguredProviders()方法
$app->registerConfiguredProviders();
}
// 该方法从配置文件config/app.php中读取providers配置调用集合的partittion方法根据条件分组
public function registerConfiguredProviders()
{
// 从容器中解析出别名为config的实例实际是illuminate/config/Repository类
$providers = Collection::make($this->make('config')->get('app.providers'))
->partition(function ($provider) {
return strpos($provider, 'Illuminate\\') === 0;
});
$providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]);
/**
*
* 查看bootstrap/cache目录下是否存在services.php文件如果存在则require该文件
* $this->getCachedServicesPath()返回缓存文件位置
* public function getCachedServicesPath()
* {
* return $this->normalizeCachePath('APP_SERVICES_CACHE', 'cache/services.php');
* }
*
* protected function normalizeCachePath($key, $default)
* {
* 如果.env文件未设置APP_SERVICES_CACHE的值则调用bootstrapPath方法
* if (is_null($env = Env::get($key))) {
* return $this->bootstrapPath($default);
* }
*
* return Str::startsWith($env, $this->absoluteCachePathPrefixes)
* ? $env
* : $this->basePath($env);
* }
* public function bootstrapPath($path = '')
* { $this->basePath为当前项目根目录 DIRECTORY_SEPARATOR为 / $path的值为cache/services.php
* return $this->basePath.DIRECTORY_SEPARATOR.'bootstrap'.($path ? DIRECTORY_SEPARATOR.$path : $path);
* }
*/
(new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
->load($providers->collapse()->toArray());
}
```
##### ProviderRepository类
`new`时,调用构造函数,将容器对象和文件系统对象传入,并将缓存服务提供者的路径传入。
```php
// 该类的构造函数
public function __construct(ApplicationContract $app, Filesystem $files, $manifestPath)
{
$this->app = $app;
$this->files = $files;
$this->manifestPath = $manifestPath;
}
```
之后调用`load`方法,
```php
public function load(array $providers)
{
/**
* 该方法判断缓存文件是否存在存在则require文件
public function loadManifest()
{
if ($this->files->exists($this->manifestPath)) {
$manifest = $this->files->getRequire($this->manifestPath);
if ($manifest) {
return array_merge(['when' => []], $manifest);
}
}
}
*/
$manifest = $this->loadManifest();
/**
* shouldRecompile()方法如果缓存文件不存在或者缓存文件跟传入的服务提供者数组不一致则返回true否则返回false
* return is_null($manifest) || $manifest['providers'] != $providers
*/
if ($this->shouldRecompile($manifest, $providers)) {
/**
* 如果不一致,或者缓存文件不存在,则解析文件,方法内解析看下面
*/
$manifest = $this->compileManifest($providers);
}
// Next, we will register events to load the providers for each of the events
// that it has requested. This allows the service provider to defer itself
// while still getting automatically loaded when a certain event occurs.
foreach ($manifest['when'] as $provider => $events) {
$this->registerLoadEvents($provider, $events);
}
// 循环providers数组调用app->register()方法
foreach ($manifest['eager'] as $provider) {
// 调用Application类的register()方法
$this->app->register($provider);
}
// 添加到延迟服务提供者
$this->app->addDeferredServices($manifest['deferred']);
}
```
###### compileManifest()方法
```php
/**
* compileManifest方法
*/
protected function compileManifest($providers)
{
// 刷新$manifest的数组结构
// $manifest = ['providers' => $providers, 'eager' => [], 'deferred' => []];
$manifest = $this->freshManifest($providers);
// 然后循环所有服务提供者
foreach ($providers as $provider) {
// 从容器中解析出对应提供者实例
// return new $provider($this->app);
$instance = $this->createProvider($provider);
/**
* 然后调用对应服务提供者的isDeferred()方法该方法是所有服务提供者父类ServiceProvider的方法
* 这里是延迟服务加载实现的方式通过实现DeferrableProvider接口然后判断如果实现了该接口则判断为是延迟服务提供者
* public function isDeferred()
* {
* return $this instanceof DeferrableProvider;
* }
*/
if ($instance->isDeferred()) {
// 调用服务提供者的provides方法将定义的键绑定到该服务提供者
foreach ($instance->provides() as $service) {
$manifest['deferred'][$service] = $provider;
}
$manifest['when'][$provider] = $instance->when();
}
else {
// else就是作为普通服务提供者在框架初始化时加载
$manifest['eager'][] = $provider;
}
}
// 写入到config/cache/services.php文件中
return $this->writeManifest($manifest);
}
```
```php
public function register($provider, $force = false)
{
// 如果已经注册过,则直接返回
if (($registered = $this->getProvider($provider)) && ! $force) {
return $registered;
}
// 如果传入的是字符串则调用resolveProvider()方法将字符串转换为Provider实例
if (is_string($provider)) {
$provider = $this->resolveProvider($provider);
}
// 这里会调用Provider类的register()方法
$provider->register();
// 如果Provider类有bindings属性则调用bind()方法
if (property_exists($provider, 'bindings')) {
foreach ($provider->bindings as $key => $value) {
$this->bind($key, $value);
}
}
// 如果Provider类有singletons属性则调用singleton()方法
if (property_exists($provider, 'singletons')) {
foreach ($provider->singletons as $key => $value) {
$this->singleton($key, $value);
}
}
// 标记为已注册
$this->markAsRegistered($provider);
// If the application has already booted, we will call this boot method on
// the provider class so it has an opportunity to do its boot logic and
// will be ready for any usage by this developer's application logic.
if ($this->isBooted()) {
$this->bootProvider($provider);
}
return $provider;
}
```
### 总结
上面我们看到,`registerConfiguredProviders()`方法会从配置文件`config/app.php`中读取`providers`配置,并调用`load()`方法,将`providers`分为`eager`和`deferred`两组,然后分别调用`register()`方法注册`eager`服务提供者,并将`deferred`服务提供者添加到延迟服务提供者列表中。
### 创建服务提供者
所有服务提供者都会继承`Illuminate\Support\ServiceProvider`类,并且需要包含一个`register()`方法和一个`boot()`方法,该方法将会在服务容器中注册服务。在`register()`方法中,只需要将服务绑定到服务容器上即可。
使用`Artisan`命令即可创建服务提供者,也可以使用框架提供的`AppServiceProvider`类,来绑定一些服务到服务容器中。
```bash
php artisan make:provider ExampleServiceProvider
```
### 使用
修改`ExampleServiceProvider`类
> 不要尝试在 register 方法中注册任何监听器,路由,或者其他任何功能。否则,你可能会意外地使用到尚未加载的服务提供者提供的服务。
```php
namespace App\Providers;
use App\Services\ExampleService;
use Illuminate\Support\ServiceProvider;
class ExampleServiceProvider extends ServiceProvider
{
public function register()
{
// 将服务绑定到服务容器上
$this->app->bind('example', function () {
return new ExampleService();
});
}
public function boot()
{
// 这里可以写一些服务启动的逻辑
}
}
```
{%note 分析源码可知,`ExampleServiceProvider`继承了`Illuminate\Support\ServiceProvider`类,当`new`类时,调用了`ServiceProvider`的构造函数,将服务容器绑定到`$this->app`中,并将`example`服务绑定到`ExampleService`实例上。%}
#### 新建服务类
新建位于`app/Services`目录下的`ExampleService.php`类,写一个测试方法
```php
namespace App\Http\Service;
class ExampleService
{
public function test()
{
echo 'test';
}
}
```
#### 注册服务提供者
然后在`config/app.php`中添加服务提供者的配置
```php
'providers' => [
//省略...
App\Providers\ExampleServiceProvider::class,
],
```
#### 修改路由
修改路由`web.php`配置,
```php
Route::get('/', function () {
var_dump(app('example'));
app('example')->test();
});
```
访问首页,可以看到输出`test`以及`ExampleService`对象实例。
{%image https://oss.xiaokeaii.top/2023/example类输出.png 输出内容%}
#### $bindings 和 $singletons属性
```php
// 如果Provider类有bindings属性则调用bind()方法
if (property_exists($provider, 'bindings')) {
foreach ($provider->bindings as $key => $value) {
$this->bind($key, $value);
}
}
// 如果Provider类有singletons属性则调用singleton()方法
if (property_exists($provider, 'singletons')) {
foreach ($provider->singletons as $key => $value) {
$this->singleton($key, $value);
}
}
```
通过上面的源码分析可知,当服务提供者有`bindings`属性时,会调用`bind()`方法,将服务提供者的`bindings`属性中的服务绑定到容器中。
同样,当服务提供者有`singletons`属性时,会调用`singleton()`方法,将服务提供者的`singletons`属性中的服务绑定到容器中,并且在每次容器实例化时,都会返回同一个实例。
##### 设置变量$bindings
```php
public $bindings = [
'example' => \App\Http\Service\ExampleService::class
];
```
注释掉`register`方法中的绑定,访问首页依旧可以正常解析
##### 设置变量$singletons
```php
public $singletons = [
'example' => \App\Http\Service\ExampleService::class
];
```
注释掉`register`方法中的绑定,访问首页依旧可以正常解析
{%note 如果有很多简单的绑定,可以使用这两个属性来简化操作%}
#### 延迟加载提供者
如果只注册,可以选择延迟加载该服务。延迟加载可以提高应用性能。
> 要延迟加载提供者,需要实现 `\Illuminate\Contracts\Support\DeferrableProvider` 接口并置一个 `provides` 方法。这个 `provides` 方法返回该提供者注册的服务容器绑定
```php
namespace App\Providers;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\ServiceProvider;
// 如果使用延迟加载该服务需要实现DeferrableProvider接口
class ExampleServiceProvider extends ServiceProvider implements DeferrableProvider
{
public $bindings = [
'ex' => \App\Http\Service\ExampleService::class,
'ex2' => \App\Http\Service\ExampleService::class
];
/**
* Register services.
*
* @return void
*/
public function register()
{
// $this->app->bind('example', function () {
// return new ExampleService();
// });
}
public function provides()
{
return [
'ex','ex2'
];
}
}
// 路由配置
Route::get('/', function () {
var_dump(app('ex2'));
app('ex2')->test();
});
```
这时访问首页也可以解析出`ExampleService`实例,并调用其中的`test`方法。

View File

@ -0,0 +1,187 @@
---
layout: wiki
wiki: laravel #项目名
date: 2023-12-16 17:39
categories: [Laravel]
title: 注册基础绑定
---
{%quot el:h2 注册基础绑定%}
对应代码
```php
$this->registerBaseBindings();
```
方法实现
```php
protected function registerBaseBindings()
{
// 将容器实例绑定到静态变量$instance上
// $instance静态变量是Container对象的属性
static::setInstance($this);
// 调用instance方法将容器实例挂载到instances数组的app key上
$this->instance('app', $this);
// 同上挂载到instances数组上key为Container::class
$this->instance(Container::class, $this);
//
$this->singleton(Mix::class);
$this->singleton(PackageManifest::class, function () {
return new PackageManifest(
new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
);
});
}
```
### setInstance()方法
```php
public static function setInstance(ContainerContract $container = null)
{
return static::$instance = $container;
}
```
查看`singleton()`方法
### singleton()方法
```php
public function singleton($abstract, $concrete = null)
{
$this->bind($abstract, $concrete, true);
}
```
继续查看`bind()`方法
#### bind()方法
> bind方法是Laravel实现绑定机制的核心方法之一
> __bind把接口和实现类绑定当make解析接口的时候创建其实现类的实例对象__
__singleton把接口和其实现类绑定当第一次make解析的时候创建实例后面都返回该实例不再创建__
instance把接口和其实现类的实例绑定直接绑定实例对象
```php
// 三个参数
// 1.首先明确第一个参数$abstract简单说就是id可以当作是存入容器中的名字它可以使一个字符串一个类甚至是一个接口。
// 2.第二个参数$concrete简单说就是真实的值可以当作是一个真正存入容器的实体。他可以是一个实现类实例或者一个闭包。
// 3.第三个参数控制shared的值
public function bind($abstract, $concrete = null, $shared = false)
{
// 绑定前先清空instances和aliases中存在同名字的服务
$this->dropStaleInstances($abstract);
// 如果没有指定具体的实现类,那么就使用$abstract当作实现类
if (is_null($concrete)) {
$concrete = $abstract;
}
// 如果$concrete不是一个闭包那么就判断是否是一个字符串如果不是那么就抛出异常
if (! $concrete instanceof Closure) {
if (! is_string($concrete)) {
throw new TypeError(self::class.'::bind(): Argument #2 ($concrete) must be of type Closure|string|null');
}
/**
* 以上条件都满足
* 通过返回创建对象闭包的方式,无需先创建对应的类实例,只需要保存生成对象的闭包
* 好处
* 1. 不用事先创建好对象,在需要用到该类的地方才创建。这有助于应用在"准备启动"的阶段节省内存。
* 2. 能更方便地控制对象的创建和获取比如resovle方法中实现的各种方式的对象解析
* 1、singleton单例(首次创建,之后缓存)2、扩展3、回调事件触发
* */
$concrete = $this->getClosure($abstract, $concrete);
}
/**
* 将返回的闭包和$shared绑定到成员变量$bindings中
* key 为$abstract,
* value 为一个数组,包含两个元素,一个是$concrete一个是$shared
*/
$this->bindings[$abstract] = compact('concrete', 'shared');
// If the abstract type was already resolved in this container we'll fire the
// rebound listener so that any objects which have already gotten resolved
// can have their copy of the object updated via the listener callbacks.
if ($this->resolved($abstract)) {
$this->rebound($abstract);
}
}
```
##### dropStaleInstances()方法
清空数组`$instances`和`$aliases`中存在的key
```php
protected function dropStaleInstances($abstract)
{
unset($this->instances[$abstract], $this->aliases[$abstract]);
}
```
##### getClosure()方法 (重点)
返回一个闭包,根据传入的参数,调用`build`和`resolve`方法,在`app`方法中,在设置基础目录时,也调用了这个方法
```php
protected function getClosure($abstract, $concrete)
{
return function ($container, $parameters = []) use ($abstract, $concrete) {
if ($abstract == $concrete) {
return $container->build($concrete);
}
return $container->resolve(
$concrete, $parameters, $raiseEvents = false
);
};
}
```
###### build()方法
返回实例对象
```php
public function build($concrete)
{
// 如果$concrete是一个闭包那么就直接调用该闭包并将容器实例和参数传入该闭包
if ($concrete instanceof Closure) {
return $concrete($this, $this->getLastParameterOverride());
}
try {
$reflector = new ReflectionClass($concrete);
} catch (ReflectionException $e) {
throw new BindingResolutionException("Target class [$concrete] does not exist.", 0, $e);
}
// $reflector能否实例化不能实例化就抛出异常
if (! $reflector->isInstantiable()) {
return $this->notInstantiable($concrete);
}
$this->buildStack[] = $concrete;
// 获取对象的构造函数信息如果没有定义构造函数则返回null
$constructor = $reflector->getConstructor();
// 如果构造函数方法不存在,则直接实例化对象,并返回
if (is_null($constructor)) {
array_pop($this->buildStack);
return new $concrete;
}
// 获取构造函数的参数信息
$dependencies = $constructor->getParameters();
// Once we have all the constructor's parameters we can create each of the
// dependency instances and then use the reflection instances to make a
// new instance of this class, injecting the created dependencies in.
try {
$instances = $this->resolveDependencies($dependencies);
} catch (BindingResolutionException $e) {
array_pop($this->buildStack);
throw $e;
}
array_pop($this->buildStack);
// 使用反射实例化对象
return $reflector->newInstanceArgs($instances);
}
```
#### 总结
1. 首先,将`容器`绑定到静态成员变量 `$instance`中,
2. 将`容器`绑定到成员变量`$instances`上的`app`键,
3. 同理,将`容器`绑定到成员变量`$instances`上的`Container::class`键
4. 注册共享变量绑定到成员变量`$bindings`上,格式为
- key 为`abstract`value 为一个数组,包含两个元素,一个是`$concrete`,一个是`$shared`

View File

@ -0,0 +1,89 @@
---
layout: wiki
wiki: laravel #项目名
date: 2023-12-17 18:56
categories: [Laravel]
title: 注册服务提供者
---
{%quot el:h2 注册服务提供者%}
```php
// 对应容器构造函数中的代码
$this->registerBaseServiceProviders();
```
### registerBaseServiceProviders()方法
查看`registerBaseServiceProviders`方法
```php
protected function registerBaseServiceProviders()
{
$this->register(new EventServiceProvider($this));
$this->register(new LogServiceProvider($this));
$this->register(new RoutingServiceProvider($this));
}
```
#### register()方法
查看`register`方法
```php
public function register($provider, $force = false)
{
if (($registered = $this->getProvider($provider)) && ! $force) {
return $registered;
}
// If the given "provider" is a string, we will resolve it, passing in the
// application instance automatically for the developer. This is simply
// a more convenient way of specifying your service provider classes.
if (is_string($provider)) {
$provider = $this->resolveProvider($provider);
}
// 调用provider的register方法
$provider->register();
// If there are bindings / singletons set as properties on the provider we
// will spin through them and register them with the application, which
// serves as a convenience layer while registering a lot of bindings.
if (property_exists($provider, 'bindings')) {
foreach ($provider->bindings as $key => $value) {
$this->bind($key, $value);
}
}
if (property_exists($provider, 'singletons')) {
foreach ($provider->singletons as $key => $value) {
$this->singleton($key, $value);
}
}
$this->markAsRegistered($provider);
// If the application has already booted, we will call this boot method on
// the provider class so it has an opportunity to do its boot logic and
// will be ready for any usage by this developer's application logic.
if ($this->isBooted()) {
$this->bootProvider($provider);
}
return $provider;
}
```
##### 查看provider的register()方法
以`EventServiceProvider`为例,挂载了`events`服务,值为一个闭包
```php
public function register()
{
$this->app->singleton('events', function ($app) {
return (new Dispatcher($app))->setQueueResolver(function () use ($app) {
return $app->make(QueueFactoryContract::class);
});
});
}
```
`$this->app`是在`new EventServiceProvider`时,调用了`ServiceProvider`的构造方法
```php
// 将容器挂载到$this->app属性上
public function __construct($app)
{
$this->app = $app;
}
```

View File

@ -0,0 +1,77 @@
---
layout: wiki
wiki: laravel #项目名
date: 2023-12-17 18:56
categories: [Laravel]
title: 注册核心别名类
---
{%quot el:h2 注册核心别名类%}
对应容器构造函数中
```php
$this->registerCoreContainerAliases();
```
### registerCoreContainerAliases()方法
注册核心类别名在容器中
```php
public function registerCoreContainerAliases()
{
foreach ([
'app' => [self::class, \Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class, \Psr\Container\ContainerInterface::class],
'auth' => [\Illuminate\Auth\AuthManager::class, \Illuminate\Contracts\Auth\Factory::class],
'auth.driver' => [\Illuminate\Contracts\Auth\Guard::class],
'blade.compiler' => [\Illuminate\View\Compilers\BladeCompiler::class],
'cache' => [\Illuminate\Cache\CacheManager::class, \Illuminate\Contracts\Cache\Factory::class],
'cache.store' => [\Illuminate\Cache\Repository::class, \Illuminate\Contracts\Cache\Repository::class, \Psr\SimpleCache\CacheInterface::class],
'cache.psr6' => [\Symfony\Component\Cache\Adapter\Psr16Adapter::class, \Symfony\Component\Cache\Adapter\AdapterInterface::class, \Psr\Cache\CacheItemPoolInterface::class],
'config' => [\Illuminate\Config\Repository::class, \Illuminate\Contracts\Config\Repository::class],
'cookie' => [\Illuminate\Cookie\CookieJar::class, \Illuminate\Contracts\Cookie\Factory::class, \Illuminate\Contracts\Cookie\QueueingFactory::class],
'db' => [\Illuminate\Database\DatabaseManager::class, \Illuminate\Database\ConnectionResolverInterface::class],
'db.connection' => [\Illuminate\Database\Connection::class, \Illuminate\Database\ConnectionInterface::class],
'encrypter' => [\Illuminate\Encryption\Encrypter::class, \Illuminate\Contracts\Encryption\Encrypter::class, \Illuminate\Contracts\Encryption\StringEncrypter::class],
'events' => [\Illuminate\Events\Dispatcher::class, \Illuminate\Contracts\Events\Dispatcher::class],
'files' => [\Illuminate\Filesystem\Filesystem::class],
'filesystem' => [\Illuminate\Filesystem\FilesystemManager::class, \Illuminate\Contracts\Filesystem\Factory::class],
'filesystem.disk' => [\Illuminate\Contracts\Filesystem\Filesystem::class],
'filesystem.cloud' => [\Illuminate\Contracts\Filesystem\Cloud::class],
'hash' => [\Illuminate\Hashing\HashManager::class],
'hash.driver' => [\Illuminate\Contracts\Hashing\Hasher::class],
'translator' => [\Illuminate\Translation\Translator::class, \Illuminate\Contracts\Translation\Translator::class],
'log' => [\Illuminate\Log\LogManager::class, \Psr\Log\LoggerInterface::class],
'mail.manager' => [\Illuminate\Mail\MailManager::class, \Illuminate\Contracts\Mail\Factory::class],
'mailer' => [\Illuminate\Mail\Mailer::class, \Illuminate\Contracts\Mail\Mailer::class, \Illuminate\Contracts\Mail\MailQueue::class],
'auth.password' => [\Illuminate\Auth\Passwords\PasswordBrokerManager::class, \Illuminate\Contracts\Auth\PasswordBrokerFactory::class],
'auth.password.broker' => [\Illuminate\Auth\Passwords\PasswordBroker::class, \Illuminate\Contracts\Auth\PasswordBroker::class],
'queue' => [\Illuminate\Queue\QueueManager::class, \Illuminate\Contracts\Queue\Factory::class, \Illuminate\Contracts\Queue\Monitor::class],
'queue.connection' => [\Illuminate\Contracts\Queue\Queue::class],
'queue.failer' => [\Illuminate\Queue\Failed\FailedJobProviderInterface::class],
'redirect' => [\Illuminate\Routing\Redirector::class],
'redis' => [\Illuminate\Redis\RedisManager::class, \Illuminate\Contracts\Redis\Factory::class],
'redis.connection' => [\Illuminate\Redis\Connections\Connection::class, \Illuminate\Contracts\Redis\Connection::class],
'request' => [\Illuminate\Http\Request::class, \Symfony\Component\HttpFoundation\Request::class],
'router' => [\Illuminate\Routing\Router::class, \Illuminate\Contracts\Routing\Registrar::class, \Illuminate\Contracts\Routing\BindingRegistrar::class],
'session' => [\Illuminate\Session\SessionManager::class],
'session.store' => [\Illuminate\Session\Store::class, \Illuminate\Contracts\Session\Session::class],
'url' => [\Illuminate\Routing\UrlGenerator::class, \Illuminate\Contracts\Routing\UrlGenerator::class],
'validator' => [\Illuminate\Validation\Factory::class, \Illuminate\Contracts\Validation\Factory::class],
'view' => [\Illuminate\View\Factory::class, \Illuminate\Contracts\View\Factory::class],
] as $key => $aliases) {
foreach ($aliases as $alias) {
$this->alias($key, $alias);
}
}
}
```
#### alias()方法
```php
public function alias($abstract, $alias)
{
// 别名不能跟类名相同
if ($alias === $abstract) {
throw new LogicException("[{$abstract}] is aliased to itself.");
}
// 设置别名和类名的映射
$this->aliases[$alias] = $abstract;
$this->abstractAliases[$abstract][] = $alias;
}
```

View File

@ -0,0 +1,85 @@
---
layout: wiki
wiki: laravel #项目名
date: 2023-12-20 18:56
categories: [Laravel]
title: 渲染页面
---
{%quot el:h2 渲染页面%}
```php
$response = $kernel->handle(
$request = Request::capture()
)->send();
```
这里只需要分析这一行代码,它将请求对象`Request::capture()`捕获,并将其传递给内核对象`$kernel->handle()`,然后将响应对象返回。
在`Laravel`中,`Request::capture()`方法会捕获当前的请求对象
### send()方法
`$response`是类`Symfony\Component\HttpFoundation\Response`实例,在该类中可以找到`send()`方法。
```php
public function send()
{
// 发送http响应头
$this->sendHeaders();
// 发送http响应内容
$this->sendContent();
if (\function_exists('fastcgi_finish_request')) {
fastcgi_finish_request();
} elseif (\function_exists('litespeed_finish_request')) {
litespeed_finish_request();
} elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
static::closeOutputBuffers(0, true);
flush();
}
return $this;
}
```
#### sendHeaders()方法
当类`Symfony\Component\HttpFoundation\Response`实例化时,构造函数详细
```php
public function __construct(?string $content = '', int $status = 200, array $headers = [])
{
$this->headers = new ResponseHeaderBag($headers);
$this->setContent($content);
$this->setStatusCode($status);
$this->setProtocolVersion('1.0');
}
public function sendHeaders()
{
// headers have already been sent by the developer
if (headers_sent()) {
return $this;
}
// 这里设置返回的响应头
foreach ($this->headers->allPreserveCaseWithoutCookies() as $name => $values) {
$replace = 0 === strcasecmp($name, 'Content-Type');
foreach ($values as $value) {
header($name.': '.$value, $replace, $this->statusCode);
}
}
// cookies
foreach ($this->headers->getCookies() as $cookie) {
header('Set-Cookie: '.$cookie, false, $this->statusCode);
}
// status
header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode);
return $this;
}
```
#### sendContent()方法
```php
public function sendContent()
{
echo $this->content;
return $this;
}
```

View File

@ -0,0 +1,35 @@
---
layout: wiki
wiki: laravel #项目名
date: 2023-12-21 16:56
categories: [Laravel]
title: 进度
---
{% okr 目标 %}
完成 {%hashtag color:red Laravel%} 请求流程的源码分析,理解各组件的实现原理
<!-- okr kr1 percent:0.58 -->
请求流程分析
- {% checkbox checked:true 配置环境 %}
- {% checkbox checked:true 入口文件 %}
- {% checkbox checked:false 实现自动加载 %}
- {% checkbox checked:true 实例化容器 %}
- {% checkbox checked:true 设置基础目录 %}
- {% checkbox checked:true 注册基础绑定 %}
- {% checkbox checked:true 注册基础服务提供者 %}
- {% checkbox checked:true 注册核心别名类 %}
- {% checkbox checked:false 解析HTTP内核 %}
- {% checkbox checked:false 处理请求 %}
- {% checkbox checked:true symbol:plus color:green 解析路由%}
- {% checkbox checked:false 渲染页面 %}
- {% checkbox checked:false 终止 %}
<!-- okr kr2 percent:0.5 -->
核心服务组件原理分析
- {%checkbox checked:false 服务容器%}
- {%checkbox checked:true 服务提供者%}
- {%checkbox checked:true Facade门面模式%}
- {%checkbox checked:true symbol:plus color:green Eloquent查询构造器_1%}
- {%checkbox checked:true symbol:plus color:green Eloquent查询构造器_2%}
{% endokr %}

60
src/laravel/终止.md Normal file
View File

@ -0,0 +1,60 @@
---
layout: wiki
wiki: laravel #项目名
date: 2023-12-20 18:56
categories: [Laravel]
title: 终止程序
---
{%quot el:h2 终止程序%}
```php
$kernel->terminate($request, $response);
```
该方法是`Kernel`类的方法
### terminate()方法
```php
public function terminate($request, $response)
{
$this->terminateMiddleware($request, $response);
// $this->app是指向Application类实例的所以查看Application类的terminate方法
$this->app->terminate();
}
/**
* 调用所有中间件的terminate方法
*/
protected function terminateMiddleware($request, $response)
{
$middlewares = $this->app->shouldSkipMiddleware() ? [] : array_merge(
$this->gatherRouteMiddleware($request),
$this->middleware
);
foreach ($middlewares as $middleware) {
if (! is_string($middleware)) {
continue;
}
[$name] = $this->parseMiddleware($middleware);
$instance = $this->app->make($name);
if (method_exists($instance, 'terminate')) {
$instance->terminate($request, $response);
}
}
}
public function terminate()
{
$index = 0;
while ($index < count($this->terminatingCallbacks)) {
$this->call($this->terminatingCallbacks[$index]);
$index++;
}
}
```
整个流程就是调用所有中间件的terminate方法然后调用Application类的terminate方法

View File

@ -0,0 +1,564 @@
---
layout: wiki
wiki: laravel #项目名
date: 2023-12-16 16:56
categories: [Laravel]
title: 解析HTTP内核
---
{%quot el:h2 解析HTTP内核%}
在此之前,已经从容器中解析出内核实例
```php
$kernel = $app->make(Kernel::class);
```
查看`make()`方法
```php
public function make($abstract, array $parameters = [])
{
return $this->resolve($abstract, $parameters);
}
```
在此之前,已经分析过该方法的具体实现
### Kernel类
查看该类
```php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* 定义的全局中间件
* @var array<int, class-string|string>
*/
protected $middleware = [
// \App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\TrustProxies::class,
\Fruitcake\Cors\HandleCors::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
];
/**
* The application's route middleware groups.
*
* @var array<string, array<int, class-string|string>>
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
/**
* The application's route middleware.
*
* These middleware may be assigned to groups or used individually.
*
* @var array<string, class-string|string>
*/
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];
}
```
该类仅定义了一些成员变量,查看继承的父类`HttpKernel`
### HttpKernel类
```php
namespace Illuminate\Foundation\Http;
use Illuminate\Contracts\Debug\ExceptionHandler;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\Http\Kernel as KernelContract;
use Illuminate\Foundation\Http\Events\RequestHandled;
use Illuminate\Routing\Pipeline;
use Illuminate\Routing\Router;
use Illuminate\Support\Facades\Facade;
use InvalidArgumentException;
use Throwable;
class Kernel implements KernelContract
{
/**
* The application implementation.
*
* @var \Illuminate\Contracts\Foundation\Application
*/
protected $app;
/**
* The router instance.
*
* @var \Illuminate\Routing\Router
*/
protected $router;
/**
* 加载基础服务
*
* @var string[]
*/
protected $bootstrappers = [
\Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
\Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
\Illuminate\Foundation\Bootstrap\HandleExceptions::class,
\Illuminate\Foundation\Bootstrap\RegisterFacades::class,
\Illuminate\Foundation\Bootstrap\RegisterProviders::class,
\Illuminate\Foundation\Bootstrap\BootProviders::class,
];
/**
* The application's middleware stack.
*
* @var array
*/
protected $middleware = [];
/**
* The application's route middleware groups.
*
* @var array
*/
protected $middlewareGroups = [];
/**
* The application's route middleware.
*
* @var array
*/
protected $routeMiddleware = [];
/**
* The priority-sorted list of middleware.
*
* Forces non-global middleware to always be in the given order.
*
* @var string[]
*/
protected $middlewarePriority = [
\Illuminate\Cookie\Middleware\EncryptCookies::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class,
\Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Auth\Middleware\Authorize::class,
];
/**
* Create a new HTTP kernel instance.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @param \Illuminate\Routing\Router $router
* @return void
*/
public function __construct(Application $app, Router $router)
{
$this->app = $app;
$this->router = $router;
$this->syncMiddlewareToRouter();
}
/**
* Handle an incoming HTTP request.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function handle($request)
{
try {
$request->enableHttpMethodParameterOverride();
$response = $this->sendRequestThroughRouter($request);
} catch (Throwable $e) {
$this->reportException($e);
$response = $this->renderException($request, $e);
}
$this->app['events']->dispatch(
new RequestHandled($request, $response)
);
return $response;
}
/**
* Send the given request through the middleware / router.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
protected function sendRequestThroughRouter($request)
{
$this->app->instance('request', $request);
Facade::clearResolvedInstance('request');
$this->bootstrap();
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}
/**
* Bootstrap the application for HTTP requests.
*
* @return void
*/
public function bootstrap()
{
if (! $this->app->hasBeenBootstrapped()) {
$this->app->bootstrapWith($this->bootstrappers());
}
}
/**
* Get the route dispatcher callback.
*
* @return \Closure
*/
protected function dispatchToRouter()
{
return function ($request) {
$this->app->instance('request', $request);
return $this->router->dispatch($request);
};
}
/**
* Call the terminate method on any terminable middleware.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Http\Response $response
* @return void
*/
public function terminate($request, $response)
{
$this->terminateMiddleware($request, $response);
$this->app->terminate();
}
/**
* Call the terminate method on any terminable middleware.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Http\Response $response
* @return void
*/
protected function terminateMiddleware($request, $response)
{
$middlewares = $this->app->shouldSkipMiddleware() ? [] : array_merge(
$this->gatherRouteMiddleware($request),
$this->middleware
);
foreach ($middlewares as $middleware) {
if (! is_string($middleware)) {
continue;
}
[$name] = $this->parseMiddleware($middleware);
$instance = $this->app->make($name);
if (method_exists($instance, 'terminate')) {
$instance->terminate($request, $response);
}
}
}
/**
* Gather the route middleware for the given request.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
protected function gatherRouteMiddleware($request)
{
if ($route = $request->route()) {
return $this->router->gatherRouteMiddleware($route);
}
return [];
}
/**
* Parse a middleware string to get the name and parameters.
*
* @param string $middleware
* @return array
*/
protected function parseMiddleware($middleware)
{
[$name, $parameters] = array_pad(explode(':', $middleware, 2), 2, []);
if (is_string($parameters)) {
$parameters = explode(',', $parameters);
}
return [$name, $parameters];
}
/**
* Determine if the kernel has a given middleware.
*
* @param string $middleware
* @return bool
*/
public function hasMiddleware($middleware)
{
return in_array($middleware, $this->middleware);
}
/**
* Add a new middleware to the beginning of the stack if it does not already exist.
*
* @param string $middleware
* @return $this
*/
public function prependMiddleware($middleware)
{
if (array_search($middleware, $this->middleware) === false) {
array_unshift($this->middleware, $middleware);
}
return $this;
}
/**
* Add a new middleware to end of the stack if it does not already exist.
*
* @param string $middleware
* @return $this
*/
public function pushMiddleware($middleware)
{
if (array_search($middleware, $this->middleware) === false) {
$this->middleware[] = $middleware;
}
return $this;
}
/**
* Prepend the given middleware to the given middleware group.
*
* @param string $group
* @param string $middleware
* @return $this
*
* @throws \InvalidArgumentException
*/
public function prependMiddlewareToGroup($group, $middleware)
{
if (! isset($this->middlewareGroups[$group])) {
throw new InvalidArgumentException("The [{$group}] middleware group has not been defined.");
}
if (array_search($middleware, $this->middlewareGroups[$group]) === false) {
array_unshift($this->middlewareGroups[$group], $middleware);
}
$this->syncMiddlewareToRouter();
return $this;
}
/**
* Append the given middleware to the given middleware group.
*
* @param string $group
* @param string $middleware
* @return $this
*
* @throws \InvalidArgumentException
*/
public function appendMiddlewareToGroup($group, $middleware)
{
if (! isset($this->middlewareGroups[$group])) {
throw new InvalidArgumentException("The [{$group}] middleware group has not been defined.");
}
if (array_search($middleware, $this->middlewareGroups[$group]) === false) {
$this->middlewareGroups[$group][] = $middleware;
}
$this->syncMiddlewareToRouter();
return $this;
}
/**
* Prepend the given middleware to the middleware priority list.
*
* @param string $middleware
* @return $this
*/
public function prependToMiddlewarePriority($middleware)
{
if (! in_array($middleware, $this->middlewarePriority)) {
array_unshift($this->middlewarePriority, $middleware);
}
$this->syncMiddlewareToRouter();
return $this;
}
/**
* Append the given middleware to the middleware priority list.
*
* @param string $middleware
* @return $this
*/
public function appendToMiddlewarePriority($middleware)
{
if (! in_array($middleware, $this->middlewarePriority)) {
$this->middlewarePriority[] = $middleware;
}
$this->syncMiddlewareToRouter();
return $this;
}
/**
* Sync the current state of the middleware to the router.
*
* @return void
*/
protected function syncMiddlewareToRouter()
{
$this->router->middlewarePriority = $this->middlewarePriority;
foreach ($this->middlewareGroups as $key => $middleware) {
$this->router->middlewareGroup($key, $middleware);
}
foreach ($this->routeMiddleware as $key => $middleware) {
$this->router->aliasMiddleware($key, $middleware);
}
}
/**
* Get the priority-sorted list of middleware.
*
* @return array
*/
public function getMiddlewarePriority()
{
return $this->middlewarePriority;
}
/**
* Get the bootstrap classes for the application.
*
* @return array
*/
protected function bootstrappers()
{
return $this->bootstrappers;
}
/**
* Report the exception to the exception handler.
*
* @param \Throwable $e
* @return void
*/
protected function reportException(Throwable $e)
{
$this->app[ExceptionHandler::class]->report($e);
}
/**
* Render the exception to a response.
*
* @param \Illuminate\Http\Request $request
* @param \Throwable $e
* @return \Symfony\Component\HttpFoundation\Response
*/
protected function renderException($request, Throwable $e)
{
return $this->app[ExceptionHandler::class]->render($request, $e);
}
/**
* Get the application's route middleware groups.
*
* @return array
*/
public function getMiddlewareGroups()
{
return $this->middlewareGroups;
}
/**
* Get the application's route middleware.
*
* @return array
*/
public function getRouteMiddleware()
{
return $this->routeMiddleware;
}
/**
* Get the Laravel application instance.
*
* @return \Illuminate\Contracts\Foundation\Application
*/
public function getApplication()
{
return $this->app;
}
/**
* Set the Laravel application instance.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @return $this
*/
public function setApplication(Application $app)
{
$this->app = $app;
return $this;
}
}
```
### 总结
1. `Kernel`类就是负责解析`HTTP`请求的核心类

212
src/laravel/解析路由.md Normal file
View File

@ -0,0 +1,212 @@
---
layout: wiki
wiki: laravel #项目名
date: 2023-12-28 16:56
categories: [Laravel]
title: 解析路由
---
{%quot el:h2 解析路由%}
上面说到,请求经过中间件之后,最终调用`dispatchToRouter()`方法,接下来分析`dispatchToRouter()`方法的实现。
```php
protected function dispatchToRouter()
{
return function ($request) {
// 将request绑定到容器中
$this->app->instance('request', $request);
// 路由匹配
return $this->router->dispatch($request);
};
}
```
### 查看dispatch()方法
源码,
```php
public function dispatch(Request $request)
{
// 将请求给Router类的currentRequest变量
$this->currentRequest = $request;
// 继续看下面的dispatchToRoute()方法
return $this->dispatchToRoute($request);
}
public function dispatchToRoute(Request $request)
{
return $this->runRoute($request, $this->findRoute($request));
}
```
先看`findRoute()`方法,
```php
protected function findRoute($request)
{
// 查找匹配的路由如果路由不存在则抛出NotFoundHttpException异常
$this->current = $route = $this->routes->match($request);
// 绑定路由到容器中
$route->setContainer($this->container);
// 将路由绑定到容器中
$this->container->instance(Route::class, $route);
return $route;
}
# -----------------------------------------
// $this->routes 为RouteCollection实例
public function __construct(Dispatcher $events, Container $container = null)
{
$this->events = $events;
$this->routes = new RouteCollection;
$this->container = $container ?: new Container;
}
# -------------------------------------------
// match方法
public function match(Request $request)
{
// 获取对应请求方法的所有路由
$routes = $this->get($request->getMethod());
// First, we will see if we can find a matching route for this current request
// method. If we can, great, we can just return it so that it can be called
// by the consumer. Otherwise we will check for routes with another verb.
$route = $this->matchAgainstRoutes($routes, $request);
return $this->handleMatchedRoute($request, $route);
}
```
#### 查看runRoute()方法
```php
protected function runRoute(Request $request, Route $route)
{
$request->setRouteResolver(function () use ($route) {
return $route;
});
// 触发RouteMatched事件
$this->events->dispatch(new RouteMatched($route, $request));
// 返回响应内容
return $this->prepareResponse($request,
$this->runRouteWithinStack($route, $request)
);
}
```
##### runRouteWithinStack()方法
返回响应内容
```php
protected function runRouteWithinStack(Route $route, Request $request)
{
$shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
$this->container->make('middleware.disable') === true;
// 这里的中间件可以是全局中间件,也可以是路由中间件
$middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);
// 这里又执行了一次在处理请求时的Pipeline类只是最后传的匿名函数不同
// 最终返回响应内容给toResponse()方法
return (new Pipeline($this->container))
->send($request)
->through($middleware)
->then(function ($request) use ($route) {
return $this->prepareResponse(
$request, $route->run()
);
});
}
#--------------------------------------------------------------------
// run方法
public function run()
{
$this->container = $this->container ?: new Container;
try {
/**
* 判断是否是控制器方法
* 是则运行控制器的方法
*/
if ($this->isControllerAction()) {
/**
* protected function runController()
* {
* return $this->controllerDispatcher()->dispatch(
* $this, $this->getController(), $this->getControllerMethod()
* );
* }
*/
return $this->runController();
}
// 如果不是,则运行回调函数
return $this->runCallable();
} catch (HttpResponseException $e) {
return $e->getResponse();
}
}
```
##### prepareResponse()方法
```php
# -----------------------------------------------------------
# prepareResponse()方法,第一个是请求对象,第二个是$this->runRouteWithinStack($route, $request)方法返回的响应内容
public function prepareResponse($request, $response)
{
return static::toResponse($request, $response);
}
// toResponse()方法
// 判断响应内容是那种类型,然后实例化对应的响应对象
public static function toResponse($request, $response)
{
if ($response instanceof Responsable) {
$response = $response->toResponse($request);
}
if ($response instanceof PsrResponseInterface) {
$response = (new HttpFoundationFactory)->createResponse($response);
} elseif ($response instanceof Model && $response->wasRecentlyCreated) {
$response = new JsonResponse($response, 201);
} elseif ($response instanceof Stringable) {
$response = new Response($response->__toString(), 200, ['Content-Type' => 'text/html']);
} elseif (! $response instanceof SymfonyResponse &&
($response instanceof Arrayable ||
$response instanceof Jsonable ||
$response instanceof ArrayObject ||
$response instanceof JsonSerializable ||
$response instanceof \stdClass ||
is_array($response))) {
$response = new JsonResponse($response);
} elseif (! $response instanceof SymfonyResponse) {
$response = new Response($response, 200, ['Content-Type' => 'text/html']);
}
if ($response->getStatusCode() === Response::HTTP_NOT_MODIFIED) {
$response->setNotModified();
}
return $response->prepare($request);
}
```
最终`dispatchToRouter()`方法返回一个Response对象然后执行`carry()`方法
```php
protected function carry()
{
return function ($stack, $pipe) {
return function ($passable) use ($stack, $pipe) {
try {
// 省略...
$carry = method_exists($pipe, $this->method)
? $pipe->{$this->method}(...$parameters)
: $pipe(...$parameters);
// 最终return handleCarry这个方法
// carry是返回的Response对象
return $this->handleCarry($carry);
} catch (Throwable $e) {
return $this->handleException($passable, $e);
}
};
};
}
```
### 查看handleCarry()方法
上面内容跑完之后,最终返回的就是一个`Response`对象,然后响应对象调用`toResponse`方法,将`Request`对象从容器中取出传参
```php
protected function handleCarry($carry)
{
return $carry instanceof Responsable
? $carry->toResponse($this->getContainer()->make(Request::class))
: $carry;
}
```

View File

@ -0,0 +1,253 @@
---
layout: wiki
wiki: laravel #项目名
date: 2023-12-16 16:56
categories: [Laravel]
title: 设置基础目录路径
---
{%quot el:h2 设置基础目录路径%}
给容器变量`$instance`设置基础路径
```php
public function setBasePath($basePath)
{
$this->basePath = rtrim($basePath, '\/');
$this->bindPathsInContainer();
return $this;
}
```
### 查看bindPathsInContainer()方法
调用`bindPathsInContainer()`方法
```php
protected function bindPathsInContainer()
{
// 将基础路径绑定到容器变量instance中
$this->instance('path', $this->path());
$this->instance('path.base', $this->basePath());
$this->instance('path.lang', $this->langPath());
$this->instance('path.config', $this->configPath());
$this->instance('path.public', $this->publicPath());
$this->instance('path.storage', $this->storagePath());
$this->instance('path.database', $this->databasePath());
$this->instance('path.resources', $this->resourcePath());
$this->instance('path.bootstrap', $this->bootstrapPath());
}
```
#### 查看instance()方法
```php
public function instance($abstract, $instance)
{
// 如果已经绑定了该键,则先移除该键的抽象别名
$this->removeAbstractAlias($abstract);
$isBound = $this->bound($abstract);
unset($this->aliases[$abstract]);
// 将实例绑定到容器变量中
$this->instances[$abstract] = $instance;
// 如果已经绑定了该键则触发rebound事件
if ($isBound) {
$this->rebound($abstract);
}
return $instance;
}
```
##### 查看removeAbstractAlias() 方法
检测`abstractAliases`成员变量中是否包含`$searched`这个键,如果包含则删除该键的别名
```php
protected function removeAbstractAlias($searched)
{
// 如果aliases中包含该键则删除该键的别名
if (! isset($this->aliases[$searched])) {
return;
}
// Remove the alias from the list of the aliases.
foreach ($this->abstractAliases as $abstract => $aliases) {
foreach ($aliases as $index => $alias) {
if ($alias == $searched) {
unset($this->abstractAliases[$abstract][$index]);
}
}
}
}
```
##### 查看bound()方法
如果已经绑定了该键则返回true
```php
public function bound($abstract)
{
return isset($this->bindings[$abstract]) ||
isset($this->instances[$abstract]) ||
$this->isAlias($abstract);
}
```
{%note 第一次进来都是未绑定状态%}
{%quot el:h2 app()方法实现 %}
在代码中,有几个辅助函数可以快速获取容器中的服务,例如
```php
if (! function_exists('config_path')) {
/**
* Get the configuration path.
*
* @param string $path
* @return string
*/
function config_path($path = '')
{
return app()->configPath($path);
}
}
```
看一下`app()`辅助方法的实现
```php
function app($abstract = null, array $parameters = [])
{
// 为空时返回自身实例
if (is_null($abstract)) {
return Container::getInstance();
}
// 如果是字符串,则返回容器中的实例
return Container::getInstance()->make($abstract, $parameters);
}
```
### 了解Container类
{%note color:red 对于容器类,还需要很多更详细的分析和了解,另外起一篇,这里就不详细分析了%}
#### Container::getInstance()方法
此处是一个单例模式,如果实例不存在,则创建一个`Container`实例,并返回
```php
public static function getInstance()
{
if (is_null(static::$instance)) {
// 延迟静态绑定
static::$instance = new static;
}
return static::$instance;
}
```
`new static` 使用`static`关键字返回时会返回调用者自身实例
##### 延迟静态绑定
官网参考地址:
{%link https://www.php.net/manual/zh/language.oop5.late-static-bindings.php%}
延迟静态绑定是一种特殊的绑定,它会在第一次访问时才绑定实例,并且只绑定一个实例,后续访问将返回相同的实例。
{%link https://learnku.com/laravel/t/3844/understanding-php-delayed-static-binding-late-static-bindings#3e8425 社区参考%}
#### Container::make()方法
继续看传值时调用`make()`方法,`getInstance()`返回自身实例,在`Container`类中,找到`make()`方法
```php
public function make($abstract, array $parameters = [])
{
// 从container中获取实例
return $this->resolve($abstract, $parameters);
}
```
##### resolve()方法
`resolve()`方法是`Laravel`框架容器方法`make`的核心实现
```php
protected function resolve($abstract, $parameters = [], $raiseEvents = true)
{
// 获取别名对应的键,如果没有返回自身
$abstract = $this->getAlias($abstract);
// 使用make方法解析对象时恒调用这个方法
// 触发执行绑定在解析对象上的回调事件
if ($raiseEvents) {
$this->fireBeforeResolvingCallbacks($abstract, $parameters);
}
// 获取具体的类名
$concrete = $this->getContextualConcrete($abstract);
// 判断是否需要使用上下文绑定
$needsContextualBuild = ! empty($parameters) || ! is_null($concrete);
// 如果已经绑定,从容器中取出实例并返回
if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
return $this->instances[$abstract];
}
// 将传递进来的参数保存在数组with中
$this->with[] = $parameters;
//
if (is_null($concrete)) {
$concrete = $this->getConcrete($abstract);
}
// 如果$concrete === $abstract 或者 $concrete 是一个匿名函数则返回ture
if ($this->isBuildable($concrete, $abstract)) {
// 如果$concrete是一个匿名函数则返回匿名函数的执行结果如果是类则利用反射实例化一个$abstract对象
$object = $this->build($concrete);
} else {
// 如果isBuildable返回false调用make进入递归make 再去 getConcrete 函数,去上下文绑定数组和 binding 数组
// 查询这个时候这个・类路径下・(就是 abstruct有没有对应的闭包或类路径。
// 但不管怎么样。最后下来要么闭包,要么相等,他都会进入 build 函数创建对象。
$object = $this->make($concrete);
}
// If we defined any extenders for this type, we'll need to spin through them
// and apply them to the object being built. This allows for the extension
// of services, such as changing configuration or decorating the object.
foreach ($this->getExtenders($abstract) as $extender) {
$object = $extender($object, $this);
}
// If the requested type is registered as a singleton we'll want to cache off
// the instances in "memory" so we can return it later without creating an
// entirely new instance of an object on each subsequent request for it.
// 接下来看是否是单例分享的如果是的话就存入instance
if ($this->isShared($abstract) && ! $needsContextualBuild) {
$this->instances[$abstract] = $object;
}
// 触发回调函数
if ($raiseEvents) {
$this->fireResolvingCallbacks($abstract, $object);
}
// 标记为已解析放在resolved中
$this->resolved[$abstract] = true;
// 将with数组中最后一个元素弹出
array_pop($this->with);
// 返回对象
return $object;
}
```
###### getAlias()方法
获取别名对应的键
```php
public function getAlias($abstract)
{
/**
* $this->aliases的键值对格式
* Illuminate\Contracts\Filesystem\Factory => filesystem
* "Illuminate\Auth\AuthManager"=> "auth"
*/
// 如果在aliases中存在则返回别名对应的值
return isset($this->aliases[$abstract])
? $this->getAlias($this->aliases[$abstract])
: $abstract;
}
```
###### raiseEvents()方法 {%hashtag 待完善可跳过%}
当使用`make()`方法时,`$raiseEvents`参数值为`true`,一定会执行`fireBeforeResolvingCallbacks`方法,并将`$abstract $parameters`两个参数值传过去
```php
protected function fireBeforeResolvingCallbacks($abstract, $parameters = [])
{
$this->fireBeforeCallbackArray($abstract, $parameters, $this->globalBeforeResolvingCallbacks);
foreach ($this->beforeResolvingCallbacks as $type => $callbacks) {
if ($type === $abstract || is_subclass_of($abstract, $type)) {
$this->fireBeforeCallbackArray($abstract, $parameters, $callbacks);
}
}
}
```

BIN
src/public/logo.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 405 KiB

330
src/record/NGINX-PROXY.md Normal file
View File

@ -0,0 +1,330 @@
---
layout: post
title: NGINX反向代理服务器
date: 2023-11-16 19:49:06
tags: [nginx,反向代理]
categories: [nginx]
description: 总结一下NINGX用于反向代理服务器的配置。
keywords: nginx,反向代理
---
科普时间,反向代理服务器是什么?
> 反向代理服务器是指以代理服务器来接受internet上的连接请求然后将请求转发给内部网络上的服务器并将从服务器上得到的结果返回给internet上请求连接的客户端此时代理服务器对外就表现为一个反向。
{%note color:blue 大白话就是我想要找张三问事情,但是我联系不到张三,我通过另一个人以托话的形式让别人帮我问张三,并把结果告诉我。 %}
{%link https://nginx.org/en/ nginx官网%}
## (一)开始前的准备
### 1.1 安装`nginx`
方便起见,使用`docker`服务安装 `nginx`
```bash
docker pull nginx #默认使用最新的
```
受限于网络原因,可能拉取比较慢,稍等片刻
{%image https://oss.xiaokeaii.top/2023/docker_pull_nginx.png 拉取成功%}
### 1.2 配置映射目录
克隆`nginx`配置到本地
```bash
git clone https://gitea.xiaokeaii.top/yinxiaolong/nginx_proxy.git
```
{%link https://gitea.xiaokeaii.top/yinxiaolong/nginx_proxy.git%}
### 1.3 运行
#### 1.3.1 进入到`nginx`目录
```bash
cd nginx_proxy/nginx
```
#### 1.3.2 启动容器
启动容器命令如下:
```bash
docker run -p 8080:80 --name nginx_proxy \
-v `pwd`/conf.d:/etc/nginx/conf.d \
-v `pwd`/html:/usr/share/nginx/html \
-v `pwd`/logs:/var/log/nginx/ \
nginx
```
> `-p 8080:80`表示将主机的8080端口映射到容器的80端口。
> `--name nginx_proxy`表示将容器命名为`nginx_proxy`,方便后续使用。
> `-v` 是将主机目录映射到容器目录,其中`pwd`表示当前目录。
> `nginx` 表示镜像名称。
{% image https://oss.xiaokeaii.top/2023/start_nginx.png 运行成功%}
{%note color:red 不要关闭命令窗口,因为容器还在运行中,如果关闭命令窗口,容器将会停止运行。%}
#### 1.3.3 查看容器运行状态
命令如下:
```bash
docker ps
```
{%image https://oss.xiaokeaii.top/2023/look_nginx.png 查看容器运行状态%}
以上安装完成后,我们就可以通过浏览器访问`http://localhost`+端口来访问`nginx`服务器了。
{%image https://oss.xiaokeaii.top/2023/welecome_nginx.png 访问nginx服务器%}
{%note 热身结束,下面进入正题%}
## (二)配置反向代理服务器
### 2.1 官网地址
关于反向代理服务器的配置地址如下:
{%link https://nginx.org/en/docs/stream/ngx_stream_proxy_module.html#example%}
### 2.2 简单配置
#### 2.2.1 修改配置文件
修改`conf`文件下的`default.conf`配置
```yaml
server {
listen 80; # 端口请勿更改
server_name localhost;
location / {
proxy_pass https://www.xiaokeaii.top;
}
}
```
以上配置表示将`http://localhost`代理到 `https://www.xiaokeaii.top`,访问`http://localhost`时,将会被代理到`https://www.xiaokeaii.top`。
#### 2.2.2 重启容器
使用以下命令重启容器:(方案一)
```bash
docker restart nginx_proxy
```
或者关闭之前打开的命令行窗口,再次启动容器。使用{%kbd ctrl%} + {%kbd c%}关闭命令行窗口。 (方案二)
{%image https://oss.xiaokeaii.top/2023/stop_nginx.png 停止容器%}
重启容器:
```bash
docker start nginx_proxy # nginx_proxy为容器名称
```
#### 2.2.3 访问
访问`http://localhost`+端口,即可看到效果。
### 2.3 常用参数(参数太多,之后再补充详细使用)
#### 2.3.1 proxy_pass
指定反向代理后端服务器地址,语法:
```yaml
Syntax: proxy_pass address; # 代理服务器地址
Default: —
Context: server
```
指定要代理的服务器地址可以是域名也可以是IP地址还可以使用`unix`协议。 这个没啥说的,常用。
#### 2.3.2 proxy_connect_timeout
指定连接后端服务器超时时间,语法:
```yaml
Syntax: proxy_connect_timeout time;
Default: 60s
Context: server, location # Context意思是作用域server表示服务器location表示路径
```
指定连接后端服务器超时时间单位为秒。默认时间为60秒。默认就行。
#### 2.3.3 proxy_read_timeout
指定读取后端服务器响应超时时间,语法:
```yaml
Syntax: proxy_read_timeout time;
Default: 60s
Context: server, location
```
指定读取后端服务器响应超时时间单位为秒。默认时间为60秒。默认就行。
#### 2.3.4 proxy_send_timeout
指定向后端服务器发送请求超时时间,语法:
```yaml
Syntax: proxy_send_timeout time;
Default: 60s
Context: server, location
```
指定向后端服务器发送请求超时时间单位为秒。默认时间为60秒。默认就行。
#### 2.3.5 proxy_buffer_size
指定缓冲区大小,语法:
```yaml
Syntax: proxy_buffer_size size;
Default: 16k
Context: server, location
```
指定缓冲区大小单位为字节。默认大小为16k。
#### 2.3.6 proxy_buffers
指定缓冲区数量,语法:
```yaml
Syntax: proxy_buffers number size;
Default: 4 32k
Context: server, location
```
指定缓冲区数量和缓冲区大小。默认数量为4大小为32k。
#### 2.3.7 proxy_busy_buffers_size
指定缓冲区空闲大小,语法:
```yaml
Syntax: proxy_busy_buffers_size size;
Default: 16k
Context: server, location
```
指定缓冲区空闲大小单位为字节。默认大小为16k。
#### 2.3.8 proxy_max_temp_file_size
指定临时文件大小,语法:
```yaml
Syntax: proxy_max_temp_file_size size;
Default: 1m
Context: server, location
```
指定临时文件大小单位为字节。默认大小为1m。
#### 2.3.9 proxy_pass_request_body
是否将请求体传递给后端服务器,语法:
```yaml
Syntax: proxy_pass_request_body on|off;
Default: off
Context: server, location
```
指定是否将请求体传递给后端服务器。默认不传递。
#### 2.3.10 proxy_set_header
设置请求头,语法:
```yaml
Syntax: proxy_set_header field value;
Default: —
Context: server, location
```
设置请求头,可以设置多个请求头。常用。
主要有以下几种设置请求头:
- `Host`:指定请求头`Host`的值,即请求的域名。
- `X-Real-IP`:指定请求头`X-Real-IP`的值即请求的IP地址。
- `X-Forwarded-For`:指定请求头`X-Forwarded-For`的值即请求的IP地址。
#### 2.3.11 proxy_hide_header
隐藏请求头,语法:
```yaml
Syntax: proxy_hide_header field;
Default: —
Context: server, location
```
隐藏请求头,可以隐藏多个请求头。
#### 2.3.12 proxy_pass_request_headers
是否将请求头传递给后端服务器,语法:
```yaml
Syntax: proxy_pass_request_headers on|off;
Default: off
Context: server, location
```
指定是否将请求头传递给后端服务器。默认不传递。
#### 2.3.13 proxy_buffering
是否开启缓冲区,语法:
```yaml
Syntax: proxy_buffering on|off;
Default: on
Context: server, location
```
指定是否开启缓冲区。默认开启。
#### 2.3.14 proxy_ignore_client_abort
是否忽略客户端中断连接,语法:
```yaml
Syntax: proxy_ignore_client_abort on|off;
Default: off
Context: server, location
```
指定是否忽略客户端中断连接。默认不忽略。
#### 2.3.15 proxy_intercept_errors
是否拦截错误,语法:
```yaml
Syntax: proxy_intercept_errors on|off;
Default: off
Context: server, location
```
指定是否拦截错误。默认不拦截。
#### 2.3.16 proxy_next_upstream
指定下一次请求的处理方式,语法:
```yaml
Syntax: proxy_next_upstream error timeout http_500 http_502 http_503 http_504;
Default: —
Context: server, location
```
指定下一次请求的处理方式。
#### 2.3.17 proxy_redirect
重定向,语法:
```yaml
Syntax: proxy_redirect [exact] [code] url;
Default: —
Context: server, location
```
重定向。
#### 2.3.18 proxy_redirect_errors
重定向错误,语法:
```yaml
Syntax: proxy_redirect_errors [code] url;
Default: —
Context: server, location
```
重定向错误。
#### 2.3.19 proxy_set_body
设置响应体,语法:
```yaml
Syntax: proxy_set_body value;
Default: —
Context: server, location
```
设置响应体。
#### 2.3.20 proxy_ssl_certificate
指定SSL证书语法
```yaml
Syntax: proxy_ssl_certificate file;
Default: —
Context: server, location
```
指定SSL证书。
#### 2.3.21 proxy_ssl_certificate_key
指定SSL证书密钥语法
```yaml
Syntax: proxy_ssl_certificate_key file;
Default: —
Context: server, location
```
指定SSL证书密钥。
#### 2.3.22 proxy_ssl_session_reuse
是否复用SSL会话语法
```yaml
Syntax: proxy_ssl_session_reuse on|off;
Default: off
Context: server, location
```
指定是否复用SSL会话。默认不复用。
#### 2.3.23 proxy_ssl_protocols
指定SSL协议语法
```yaml
Syntax: proxy_ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
Default: TLSv1 TLSv1.1 TLSv1.2
Context: server, location
```
指定SSL协议。默认支持TLSv1、TLSv1.1和TLSv1.2。
#### 2.3.24 proxy_ssl_ciphers
指定SSL加密算法语法
```yaml
Syntax: proxy_ssl_ciphers "cipher list";
Default: —
Context: server, location
```
指定SSL加密算法。
#### 2.3.25 proxy_ssl_verify
是否验证SSL证书语法
```yaml
Syntax: proxy_ssl_verify on|off;
Default: on
Context: server, location
```
指定是否验证SSL证书。默认验证。
#### 2.3.26 proxy_ssl_verify_depth
指定SSL证书验证的最大深度语法
```yaml
Syntax: proxy_ssl_verify_depth number;
Default: 1
Context: server, location
```
指定SSL证书验证的最大深度。默认深度为1。

View File

@ -0,0 +1,195 @@
---
layout: post
title: env文件读取
date: 2024-03-18 10:55:47
tags:
- env文件
- vlucas/phpdotenv库
cover: https://oss.xiaokeaii.top/2024/dotenv.png
description: vlucas/phpdotenv库源码分析
---
{%note color:red 库版本 vlucas/phpdotenv : ^5.6 %}
`phpdotenv`是一个流行的 PHP 库,用于加载应用程序的环境变量。用来读取本地环境变量,以及在运行时动态设置环境变量。
源码地址如下,
{%link https://github.com/vlucas/phpdotenv%}
### Dotenv类
`Dotenv`类是`vlucas/phpdotenv`库的核心文件,实现了`.env `文件的加载和解析逻辑,以及环境变量的设置功能。
查看该类源码,
```php
declare(strict_types=1);
namespace Dotenv;
use Dotenv\Exception\InvalidPathException;
use Dotenv\Loader\Loader;
use Dotenv\Loader\LoaderInterface;
use Dotenv\Parser\Parser;
use Dotenv\Parser\ParserInterface;
use Dotenv\Repository\Adapter\ArrayAdapter;
use Dotenv\Repository\Adapter\PutenvAdapter;
use Dotenv\Repository\RepositoryBuilder;
use Dotenv\Repository\RepositoryInterface;
use Dotenv\Store\StoreBuilder;
use Dotenv\Store\StoreInterface;
use Dotenv\Store\StringStore;
class Dotenv
{
/**
* The store instance.
*
* @var \Dotenv\Store\StoreInterface
*/
private $store;
/**
* The parser instance.
*
* @var \Dotenv\Parser\ParserInterface
*/
private $parser;
/**
* The loader instance.
*
* @var \Dotenv\Loader\LoaderInterface
*/
private $loader;
/**
* The repository instance.
*
* @var \Dotenv\Repository\RepositoryInterface
*/
private $repository;
/**
* 构造函数
*/
public function __construct(
StoreInterface $store,
ParserInterface $parser,
LoaderInterface $loader,
RepositoryInterface $repository
) {
$this->store = $store;
$this->parser = $parser;
$this->loader = $loader;
$this->repository = $repository;
}
//...
}
```
该类实例化时需要传入四个参数,分别是`StoreInterface`、`ParserInterface`、`LoaderInterface`、`RepositoryInterface`接口的实现类。
官方示例代码如下,
```php
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();
```
### createImmutable方法
打开`Dotenv`类的`createImmutable`方法,查看其具体实现逻辑,
```php
/**
*
* @var string|string[] $paths
* @var null|string|string[] $names
* @var bool $shortCircuit
* @var null|string $fileEncoding 找到第一个存在的配置文件后停止加载其他配置文件
* @return Dotenv
*/
public static function createImmutable($paths, $names = null, bool $shortCircuit = true, string $fileEncoding = null)
{
// createWithDefaultAdapters()返回的是一个RepositoryBuilder实例然后调用make方法
// 返回一个RepositoryInterface接口的实现类AdapterRepository
$repository = RepositoryBuilder::createWithDefaultAdapters()->immutable()->make();
// 调用create方法返回一个Dotenv实例
return self::create($repository, $paths, $names, $shortCircuit, $fileEncoding);
}
```
#### RepositoryBuilder类
查看静态方法`createWithDefaultAdapters`,
```php
public static function createWithDefaultAdapters()
{
// iterator_to_array 拷贝迭代器中的元素到数组中
// $adapters该数组变量就会保存DEFAULT_ADAPTERS常量中实例化的类
$adapters = \iterator_to_array(self::defaultAdapters());
// 返回一个RepositoryBuilder实例
// 这里会触发RepositoryBuilder类的构造函数
return new self($adapters, $adapters);
}
private static function defaultAdapters()
{
foreach (self::DEFAULT_ADAPTERS as $adapter) {
$instance = $adapter::create();
if ($instance->isDefined()) {
// 这里会返回DEFAULT_ADAPTERS常量中实例化的类
// 这里会保存对应的alue在下面进行迭代时取出
yield $instance->get();
}
}
}
// DEFAULT_ADAPTERS
private const DEFAULT_ADAPTERS = [
ServerConstAdapter::class,
EnvConstAdapter::class,
];
```
首先理解`yield`关键字,它提供了一种简单的方法来遍历数据,具体可参考[PHP yield关键字](https://learnku.com/php/t/28336)。
##### immutable方法
该方法只有一行,重新实例化`RepositoryBuilder`类,并将第三个参数`$$immutable`值改为`true`,即创建一个不可变的`RepositoryBuilder`实例。(键名不可重复,重复抛错)
```php
public function immutable()
{
return new self($this->readers, $this->writers, true, $this->allowList);
}
```
##### make方法
```php
// 看最后的make方法
$repository = RepositoryBuilder::createWithDefaultAdapters()->immutable()->make();
//
public function make()
{
// 创建读取器和写入器
$reader = new MultiReader($this->readers);
$writer = new MultiWriter($this->writers);
// 如果设置字段为true则创建一个不可变的写入器
// 当键相同时,则抛出异常
// 键不能重复
if ($this->immutable) {
$writer = new ImmutableWriter($writer, $reader);
}
// 限制写入的数据只能是允许列表中的键
if ($this->allowList !== null) {
$writer = new GuardedWriter($writer, $this->allowList);
}
// 创建AdapterRepository实例用于读取和写入数据
return new AdapterRepository($reader, $writer);
}
```
现在,我们知道了`$repository`是一个`AdapterRepository`实例,接下来看`create`方法。
#### create方法
查看`create`方法的实现逻辑,
```php
public static function create(RepositoryInterface $repository, $paths, $names = null, bool $shortCircuit = true, string $fileEncoding = null)
{
$builder = $names === null ? StoreBuilder::createWithDefaultName() : StoreBuilder::createWithNoNames();
foreach ((array) $paths as $path) {
$builder = $builder->addPath($path);
}
foreach ((array) $names as $name) {
$builder = $builder->addName($name);
}
if ($shortCircuit) {
$builder = $builder->shortCircuit();
}
return new self($builder->fileEncoding($fileEncoding)->make(), new Parser(), new Loader(), $repository);
}
```

View File

@ -0,0 +1,73 @@
---
layout: post
title: iStoreOS路由系统安装
date: 2023-11-30 20:00:51
tags: [软路由, iStoreOS]
categories: [软路由,iStoreOS]
description: iStoreOS路由系统的安装教程
---
{%note `iStoreOS`路由系统是一款基于`OpenWrt`的路由系统,支持`x86`、`ARM`等硬件平台。相对于`OpenWrt`来说,`iStoreOS`更容易上手,而且功能也丰富,更加适合不怎么爱折腾的人使用。可以理解为硬路由的扩展,可以拓展路由器的功能。%}
> iStoreOS 目标是提供一个人人会用的路由兼轻 NAS 系统,不管是作为路由还是 NAS你都有相似的操作体验。系统本身开源免费,代码在GitHub上开源可以自行编译。 --来自官方的介绍
{%quot el:h2 iStoreOS安装%}
### 官网地址
官网地址如下
{%link https://www.istores.cn/%}
源代码地址
{%link https://github.com/istoreos/istoreos%}
{%note 如果对此感兴趣,可自行去官网研究,也可以跟着我的步骤,安装`iStoreOS`路由系统%}
## 选择硬件
首先,安装路由系统之前,需要选择硬件设备。
{%note 硬件选择有以下几种:%}
{%image https://oss.xiaokeaii.top/2023/支持的硬件.png 支持的硬件 bg:white%}
我这里使用的是友善的`R2S`设备咸鱼淘的全套下来240块钱成色较新包括设备本身、`TF卡`、网线、电源、读卡器。
硬件选择好了之后进入下一步。
## 寻找固件
通过官网,找到对应的`R2S`固件
{%image https://oss.xiaokeaii.top/2023/下载固件.png 下载固件%}
点击后缀为`img.gz`的固件,下载到本地。(下载下来的文件不需要解压)
{%image https://oss.xiaokeaii.top/2023/固件链接.png %}
## 刷入固件
将`TF`卡插在读卡器上,读卡器插在电脑上
{%note color:blue 请到官网下载,以防第三方带有广告%}
### `Windows`系统
使用`Win32 Disk Imager`软件,烧录固件到`TF`卡中
{%link https://win32diskimager.org/ Win32 Disk Imager软件官网%}
### `MAC`系统
使用`balenaEtcher`软件,烧录固件到`TF`卡中
{%link https://www.balena.io/etcher/ balenaEtcher官网%}
{%note 我使用的是`MAC`系统,这里仅演示本系统%}
打开`balenaEtcher`软件,选择`istoreos-22.03.5-2023082509-r2s-squashfs.img.gz`文件,选择目标磁盘,点击烧录,等待烧录完成。(文件名日期不一定相同,使用自己下载的文件即可)
{%image https://oss.xiaokeaii.top/2023/系统烧录.png 烧录系统%}
## 启动软路由
将`TF`卡插入软路由,接通电源,将网线一头接在电脑的`LAN`口,另一头接在软路由的`LAN`口。等待红色指示灯闪烁。首次启动大概1-5分钟
### 使用方法
- 默认IP http://192.168.100.1
- 默认密码password
- 如果只有一个网口,默认的网口是 LAN如果大于一个网口默认 eth0 是 WAN 口,其它都是 LAN。
#### 查看路由器IP
连接家里`Wi-Fi`查看路由器的IP地址。
- 小米路由器默认IP地址192.158.31.1
- 华为路由器默认IP地址192.168.3.1
如果不知道自家路由器`IP`地址,可通过百度搜索各大品牌的默认`IP`地址。
#### 更改默认IP
1. 使用电脑打开浏览器,输入`192.168.100.1`,出现`iStoreOS`的登录界面,说明启动成功。
{%image https://oss.xiaokeaii.top/2023/istoreos登录.png iStoreOS登录%}
2. 使用默认的密码登录,进入系统
{%image https://oss.xiaokeaii.top/2023/istoreos首页.png istoreos首页%}
3. 点击左边菜单的网络向导,进入网络设置页面
{% note 这里有三种配置,根据自身需要进行选择,这里仅演示配置为`旁路由模式`的方式,其他两种请自行寻找 %}
4. 点击`配置为旁路由`按钮,点击手动配置
{%image https://oss.xiaokeaii.top/2023/旁路由配置.png 旁路由配置%}
{% note `IP`地址填与路由器最后一个点后面不同的数字可以填一个100以内的。子网掩码就填`255.255.255.0`,网关填写路由器的`IP`地址DNS填路由器的`IP`地址,点击保存即可。关闭`DHCP`服务,关闭`IPV6`客户端%}
5. 点击保存即可
6. 拔出接入电脑的网线,将网线插到路由器的`LAN`口上,连接路由器`Wi-Fi`,访问上面给软路由配置的`IP`地址,如果看到登录界面即为安装成功,如果未显示登录界面,请检查以上步骤

1
src/record/index.md Normal file
View File

@ -0,0 +1 @@
杂记

128
src/record/主题语法.md Normal file
View File

@ -0,0 +1,128 @@
---
layout: post
title: 主题语法
date: 2023-10-20 10:44:07
tags:
description: stallar的语法使用了markdown的语法在此基础上进行了扩展支持一些常用的功能方便使用。
---
{%hashtag 官方地址 https://xaoxuu.com/wiki/stellar/tag-plugins/express%}
## emoji 表情
语法 示例 {%emoji qq 微笑%}
支持的表情来源在主题配置文件下:
![](https://oss.xiaokeaii.top/2023/emoji来源.png)
```
{% emoji [source] name [height:1.75em] %}
```
## mark 行内文本标记
多彩标记 示例:{% mark 默认 %}
```
模板: {% mark 类型 %}
```
## tag 标签
类似mark标签支持链接示例 {%hashtag GitHub https://github.com %}
模板:
```
{% tag 内容 [链接地址]%}
```
## 主题的image 图片标签
可以放置下载按钮,语法格式
```markdown
{% image src [description] [download:bool/string] [width:px] [padding:px] [bg:hex] %}
```
参数说明
```yaml
src: 图片地址
description: 图片描述
download: href # 下载地址,设置此值后鼠标放在图片上会显示下载地址,如果下载地址为图片地址,可以设置为 true
width: 200px # 图片宽度
padding: 16px # 图片四周填充宽度
bg: '#ffffff' # 图片区域背景颜色16进制
```
## quot 引用
居中的引用,示例
{%quot 主题配置%}
语法
```md
{%quot title%}
```
## link 链接卡片
外链卡片标签
```
{% link href [title] [icon:src] [desc:true/false] %}
```
{%link https://github.com GitHub%}
参数
```yaml
href: 链接
title: 可选,手动设置标题(为空时会自动抓取页面标题)
icon: 可选,手动设置图标(为空时会自动抓取页面图标)
desc: 可选是否显示摘要描述为true时将会显示页面描述
```
## radio 单选
```
{% radio 没有勾选的单选框 %}
{% radio checked:true 已勾选的单选框 %}
```
参数
```yaml
checked: true/false
color: red/orange/yellow/green/cyan/blue/purple
```
## checkbox 复选
### 语法
```
{% checkbox 普通的没有勾选的复选框 %}
{% checkbox checked:true 普通的已勾选的复选框 %}
{% checkbox symbol:plus color:green checked:true 显示为加号的绿色的已勾选的复选框 %}
{% checkbox symbol:minus color:yellow checked:true 显示为减号的黄色的已勾选的复选框 %}
{% checkbox symbol:times color:red checked:true 显示为乘号的红色的已勾选的复选框 %}
```
### 参数
```yaml
checked: true/false
color: red/orange/yellow/green/cyan/blue/purple
symbol: plus/minus/times
```
## 文本修饰标签集
### 示例
- 这是 {% psw 密码 %} 标签
- 这是 {% u 下划线 %} 标签
- 这是 {% emp 着重号 %} 标签
- 这是 {% wavy 波浪线 %} 标签
- 这是 {% del 删除线 %} 标签
- 这是 {% sup 上角标 color:red %} 标签
- 这是 {% sub 下角标 %} 标签
- 这是 {% kbd 键盘样式 %} 标签,试一试:{% kbd ⌘ %} + {% kbd D %}
### 语法
```
- 这是 {% psw 密码 %} 标签
- 这是 {% u 下划线 %} 标签
- 这是 {% emp 着重号 %} 标签
- 这是 {% wavy 波浪线 %} 标签
- 这是 {% del 删除线 %} 标签
- 这是 {% sup 上角标 color:red %} 标签
- 这是 {% sub 下角标 %} 标签
- 这是 {% kbd 键盘样式 %} 标签,试一试:{% kbd ⌘ %} + {% kbd D %}
```