前言

tp5想必大家都不陌生,但是大部分人都停留在应用的层面,我将开启系列随笔,深入剖析tp5源码,以供大家顺利进阶。本章将从类的自动加载讲起,自动加载是tp框架的灵魂所在,也是成熟php框架的必备功能

入口

// [ 应用入口文件 ]
namespace think; // 加载基础文件
require __DIR__ . '/../thinkphp/base.php';

base.php

<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think; // 载入Loader类
require __DIR__ . '/library/think/Loader.php'; // 注册自动加载
Loader::register(); // 注册错误和异常处理机制
Error::register(); // 实现日志接口
if (interface_exists('Psr\Log\LoggerInterface')) {
interface LoggerInterface extends \Psr\Log\LoggerInterface
{}
} else {
interface LoggerInterface
{}
} /*
* 使用 ClassName::class 可以获取一个字符串,包含了类 ClassName 的完全限定名称
* 注册类库别名的作用:建立映射,访问 \Db 即可映射到 \think\Db
* 它的存在可提升性能
*/
// 注册类库别名
Loader::addClassAlias([
/**
* 由于命名空间的机制,
* 这里的 facade\ 相对命名空间地址会拼接此处的think命名空间,组合成为一个绝对的 \think\facade\ 命名空间
*/
'App' => facade\App::class,
'Build' => facade\Build::class,
'Cache' => facade\Cache::class,
'Config' => facade\Config::class,
'Cookie' => facade\Cookie::class,
'Db' => Db::class,
'Debug' => facade\Debug::class,
'Env' => facade\Env::class,
'Facade' => Facade::class,
'Hook' => facade\Hook::class,
'Lang' => facade\Lang::class,
'Log' => facade\Log::class,
'Request' => facade\Request::class,
'Response' => facade\Response::class,
'Route' => facade\Route::class,
'Session' => facade\Session::class,
'Url' => facade\Url::class,
'Validate' => facade\Validate::class,
'View' => facade\View::class,
]);

主角Loader.php

<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +---------------------------------------------------------------------- namespace think; use think\exception\ClassNotFoundException; class Loader
{
/**
* 类名映射信息
* @var array
*/
protected static $classMap = []; /**
* 类库别名
* @var array
*/
protected static $classAlias = []; /**
* PSR-4
* @var array
*/
private static $prefixLengthsPsr4 = [];
private static $prefixDirsPsr4 = [];
private static $fallbackDirsPsr4 = []; /**
* PSR-0
* @var array
*/
private static $prefixesPsr0 = [];
private static $fallbackDirsPsr0 = []; /**
* 需要加载的文件
* @var array
*/
private static $files = []; /**
* Composer安装路径
* @var string
*/
private static $composerPath; // 获取应用根目录
public static function getRootPath()
{ /*
* SAPI(Server Application Programming Interface)服务器应用程序编程接口,即PHP与其他应用交互的接口,
* PHP脚本要执行有很多方式,通过Web服务器,或者直接在命令行下,也可以嵌入在其他程序中。
* SAPI提供了一个和外部通信的接口,常见的SAPI有:cgi、fast-cgi、cli、apache模块的DLL、isapi
* cli表示是命令行运行
*/
if ('cli' == PHP_SAPI) {
$scriptName = realpath($_SERVER['argv'][0]);
} else {
/*
* $_SERVER['SCRIPT_FILENAME'] : 当前执行程序的绝对路径及文件名(相对index.php入口文件)
* $scriptName : D:/workspace/project/phpSite/tp5.1code/tp5/public/index.php
*/
$scriptName = $_SERVER['SCRIPT_FILENAME'];
}
/*
* dirname() 函数返回路径中的目录名称部分。如果是文件,文件名出栈,如果是目录,目录出栈
* realpath() 函数返回绝对路径。
* 该函数删除所有符号连接(比如 '/./', '/../' 以及多余的 '/'),返回绝对路径名。
* $path : D:\workspace\project\phpSite\tp5.1code\tp5\public
*/
$path = realpath(dirname($scriptName));
/*
* DIRECTORY_SEPARATOR : 目录分隔符,是定义php的内置常量。兼容操作系统
* 由于当前目录在public目录下
* 判断public目录下是否有think文件,如果没有,则目录出栈转到根目录
*/
if (!is_file($path . DIRECTORY_SEPARATOR . 'think')) {
$path = dirname($path);
}
return $path . DIRECTORY_SEPARATOR;
} // 注册自动加载机制
public static function register($autoload = '')
{
/*
PHP碰到没有定义的类就执行think\\Loader::autoload() spl_autoload_register有三个参数 autoload_function
欲注册的自动装载函数。如果没有提供任何参数,则自动注册 autoload 的默认实现函数spl_autoload()。 throw
此参数设置了 autoload_function 无法成功注册时, spl_autoload_register()是否抛出异常。 prepend
如果是 true,spl_autoload_register() 会添加函数到队列之首,而不是队列尾部。
*/
// 注册系统自动加载
spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true); $rootPath = self::getRootPath();
// $rootPath :D:\workspace\project\phpSite\tp5.1code\tp5\ self::$composerPath = $rootPath . 'vendor' . DIRECTORY_SEPARATOR . 'composer' . DIRECTORY_SEPARATOR;
// self::$composerPath : D:\workspace\project\phpSite\tp5.1code\tp5\vendor\composer\ // Composer自动加载支持
if (is_dir(self::$composerPath)) {
if (is_file(self::$composerPath . 'autoload_static.php')) {
require self::$composerPath . 'autoload_static.php';
// 返回当前所有类的集合
$declaredClass = get_declared_classes();
// 取出最后一个类,即刚刚引入的autoload_static.php中的类
$composerClass = array_pop($declaredClass); foreach (['prefixLengthsPsr4', 'prefixDirsPsr4', 'fallbackDirsPsr4', 'prefixesPsr0', 'fallbackDirsPsr0', 'classMap', 'files'] as $attr) {
// property_exists() :检测对象或类是否存在该属性
if (property_exists($composerClass, $attr)) {
// 将它的 prefixLengthsPsr4 和 prefixDirsPsr4 属性挂载到本类
self::${$attr} = $composerClass::${$attr};
}
}
} else {
self::registerComposerLoader(self::$composerPath);
}
} /*
* __DIR__ : 本文件所在目录(Loader.php文件)
* 将 think 和 traits 也挂载到本类的 prefixLengthsPsr4 和 prefixDirsPsr4 属性上
*/
// 注册命名空间定义
self::addNamespace([
'think' => __DIR__,
'traits' => dirname(__DIR__) . DIRECTORY_SEPARATOR . 'traits',
]); // 类的映射键值对,须手动生成才有这个文件
// 加载类库映射文件
if (is_file($rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'classmap.php')) {
self::addClassMap(__include_file($rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'classmap.php'));
} // 自动加载extend目录
self::addAutoLoadDir($rootPath . 'extend');
// var_dump(self::$prefixLengthsPsr4);
// var_dump(self::$prefixDirsPsr4);
// var_dump(self::$fallbackDirsPsr4);
// var_dump(self::$classMap);
/*
array(3) {
["t"]=>
array(4) {
["think\worker\"]=>
int(13)
["think\composer\"]=>
int(15)
["think\"]=>
int(6)
["traits\"]=>
int(7)
}
["a"]=>
array(1) {
["app\"]=>
int(4)
}
["W"]=>
array(1) {
["Workerman\"]=>
int(10)
}
}
array(6) {
["think\worker\"]=>
array(1) {
[0]=>
string(87) "D:\workspace\project\phpSite\tp5.1code\tp5\vendor\composer/../topthink/think-worker/src"
}
["think\composer\"]=>
array(1) {
[0]=>
string(90) "D:\workspace\project\phpSite\tp5.1code\tp5\vendor\composer/../topthink/think-installer/src"
}
["app\"]=>
array(1) {
[0]=>
string(76) "D:\workspace\project\phpSite\tp5.1code\tp5\vendor\composer/../../application"
}
["Workerman\"]=>
array(2) {
[0]=>
string(81) "D:\workspace\project\phpSite\tp5.1code\tp5\vendor\composer/../workerman/workerman"
[1]=>
string(89) "D:\workspace\project\phpSite\tp5.1code\tp5\vendor\composer/../workerman/workerman-for-win"
}
["think\"]=>
array(1) {
[0]=>
string(65) "D:\workspace\project\phpSite\tp5.1code\tp5\thinkphp\library\think"
}
["traits\"]=>
array(1) {
[0]=>
string(66) "D:\workspace\project\phpSite\tp5.1code\tp5\thinkphp\library\traits"
}
}
array(1) {
[0]=>
string(49) "D:\workspace\project\phpSite\tp5.1code\tp5\extend"
}
array(0) {
} */
} // 自动加载
public static function autoload($class)
{
// 如果之前配置过别名,注册别名
if (isset(self::$classAlias[$class])) {
return class_alias(self::$classAlias[$class], $class);
} // 通过命名空间解析到文件名
if ($file = self::findFile($class)) { // Win环境严格区分大小写
if (strpos(PHP_OS, 'WIN') !== false && pathinfo($file, PATHINFO_FILENAME) != pathinfo(realpath($file), PATHINFO_FILENAME)) {
return false;
} // echo $class.'---'.$file.'<br />';
__include_file($file);
return true;
}
} /**
* 查找文件
* @access private
* @param string $class
* @return string|false
*/
private static function findFile($class)
{
if (!empty(self::$classMap[$class])) {
// 类库映射
return self::$classMap[$class];
} /*
* 利用 Psr4 的规则,执行匹配查询,如果匹配到,则返回文件名。
* Psr4 的规则一般包含composer安装的类库的命名空间对应的目录,
* 和app、以及框架底层 think、traits命名空间对应的目录
*/
// 查找 PSR-4
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . '.php'; $first = $class[0];
// var_dump($first );die;
if (isset(self::$prefixLengthsPsr4[$first])) {
foreach (self::$prefixLengthsPsr4[$first] as $prefix => $length) {
if (0 === strpos($class, $prefix)) {
foreach (self::$prefixDirsPsr4[$prefix] as $dir) {
if (is_file($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
return $file;
}
}
}
}
} /**
* 这里匹配 extend 下面的类目
*/
// 查找 PSR-4 fallback dirs
foreach (self::$fallbackDirsPsr4 as $dir) {
if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
} // 查找 PSR-0
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . '.php';
} if (isset(self::$prefixesPsr0[$first])) {
foreach (self::$prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
} // 查找 PSR-0 fallback dirs
foreach (self::$fallbackDirsPsr0 as $dir) {
if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
} return self::$classMap[$class] = false;
} // 注册classmap
public static function addClassMap($class, $map = '')
{
if (is_array($class)) {
self::$classMap = array_merge(self::$classMap, $class);
} else {
self::$classMap[$class] = $map;
}
} // 注册命名空间
public static function addNamespace($namespace, $path = '')
{
if (is_array($namespace)) {
foreach ($namespace as $prefix => $paths) {
self::addPsr4($prefix . '\\', rtrim($paths, DIRECTORY_SEPARATOR), true);
}
} else {
self::addPsr4($namespace . '\\', rtrim($path, DIRECTORY_SEPARATOR), true);
}
} // 添加Ps0空间
private static function addPsr0($prefix, $paths, $prepend = false)
{
if (!$prefix) {
if ($prepend) {
self::$fallbackDirsPsr0 = array_merge(
(array) $paths,
self::$fallbackDirsPsr0
);
} else {
self::$fallbackDirsPsr0 = array_merge(
self::$fallbackDirsPsr0,
(array) $paths
);
} return;
} $first = $prefix[0];
if (!isset(self::$prefixesPsr0[$first][$prefix])) {
self::$prefixesPsr0[$first][$prefix] = (array) $paths; return;
} if ($prepend) {
self::$prefixesPsr0[$first][$prefix] = array_merge(
(array) $paths,
self::$prefixesPsr0[$first][$prefix]
);
} else {
self::$prefixesPsr0[$first][$prefix] = array_merge(
self::$prefixesPsr0[$first][$prefix],
(array) $paths
);
}
} // 添加Psr4空间
private static function addPsr4($prefix, $paths, $prepend = false)
{
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
self::$fallbackDirsPsr4 = array_merge(
(array) $paths,
self::$fallbackDirsPsr4
);
} else {
self::$fallbackDirsPsr4 = array_merge(
self::$fallbackDirsPsr4,
(array) $paths
);
}
} elseif (!isset(self::$prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
} self::$prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
self::$prefixDirsPsr4[$prefix] = (array) $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
self::$prefixDirsPsr4[$prefix] = array_merge(
(array) $paths,
self::$prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
self::$prefixDirsPsr4[$prefix] = array_merge(
self::$prefixDirsPsr4[$prefix],
(array) $paths
);
}
} // 注册自动加载类库目录
public static function addAutoLoadDir($path)
{
self::$fallbackDirsPsr4[] = $path;
} // 注册类别名
public static function addClassAlias($alias, $class = null)
{ if (is_array($alias)) {
self::$classAlias = array_merge(self::$classAlias, $alias);
} else {
self::$classAlias[$alias] = $class;
}
} // 注册composer自动加载
public static function registerComposerLoader($composerPath)
{
if (is_file($composerPath . 'autoload_namespaces.php')) {
$map = require $composerPath . 'autoload_namespaces.php';
foreach ($map as $namespace => $path) {
self::addPsr0($namespace, $path);
}
} if (is_file($composerPath . 'autoload_psr4.php')) {
$map = require $composerPath . 'autoload_psr4.php';
foreach ($map as $namespace => $path) {
self::addPsr4($namespace, $path);
}
} if (is_file($composerPath . 'autoload_classmap.php')) {
$classMap = require $composerPath . 'autoload_classmap.php';
if ($classMap) {
self::addClassMap($classMap);
}
} if (is_file($composerPath . 'autoload_files.php')) {
self::$files = require $composerPath . 'autoload_files.php';
}
} // 加载composer autofile文件
public static function loadComposerAutoloadFiles()
{
foreach (self::$files as $fileIdentifier => $file) {
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
__require_file($file); $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
}
}
} /**
* 字符串命名风格转换
* type 0 将Java风格转换为C的风格 1 将C风格转换为Java的风格
* @access public
* @param string $name 字符串
* @param integer $type 转换类型
* @param bool $ucfirst 首字母是否大写(驼峰规则)
* @return string
*/
public static function parseName($name, $type = 0, $ucfirst = true)
{
if ($type) {
$name = preg_replace_callback('/_([a-zA-Z])/', function ($match) {
return strtoupper($match[1]);
}, $name);
return $ucfirst ? ucfirst($name) : lcfirst($name);
} return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_"));
} /**
* 创建工厂对象实例
* @access public
* @param string $name 工厂类名
* @param string $namespace 默认命名空间
* @return mixed
*/
public static function factory($name, $namespace = '', ...$args)
{
$class = false !== strpos($name, '\\') ? $name : $namespace . ucwords($name); if (class_exists($class)) {
return Container::getInstance()->invokeClass($class, $args);
} else {
throw new ClassNotFoundException('class not exists:' . $class, $class);
}
}
} /**
* 作用范围隔离
*
* @param $file
* @return mixed
*/
function __include_file($file)
{
return include $file;
} function __require_file($file)
{
return require $file;
}

autoload_static.php

<?php

// autoload_static.php @generated by Composer

namespace Composer\Autoload;

class ComposerStaticInita1e68a2034863afda74e609f9cf189d1
{
public static $prefixLengthsPsr4 = array (
't' =>
array (
'think\\worker\\' => 13,
'think\\composer\\' => 15,
),
'a' =>
array (
'app\\' => 4,
),
'W' =>
array (
'Workerman\\' => 10,
),
); public static $prefixDirsPsr4 = array (
'think\\worker\\' =>
array (
0 => __DIR__ . '/..' . '/topthink/think-worker/src',
),
'think\\composer\\' =>
array (
0 => __DIR__ . '/..' . '/topthink/think-installer/src',
),
'app\\' =>
array (
0 => __DIR__ . '/../..' . '/application',
),
'Workerman\\' =>
array (
0 => __DIR__ . '/..' . '/workerman/workerman',
1 => __DIR__ . '/..' . '/workerman/workerman-for-win',
),
); public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInita1e68a2034863afda74e609f9cf189d1::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInita1e68a2034863afda74e609f9cf189d1::$prefixDirsPsr4; }, null, ClassLoader::class);
}
}

总结:类的自动加载,其实就是先绑定了自动加载函数,然后在类的属性上面定义了一系列的映射关系,然后在自动加载函数中通过命名空间/类名参数查询映射关系,将映射的目录include引入的操作

thinkphp5源码剖析系列1-类的自动加载机制的更多相关文章

  1. WorldWind源码剖析系列:星球球体的加载与渲染

    WorldWind源码剖析系列:星球球体的加载与渲染 WorldWind中主函数Main()的分析 在文件WorldWind.cs中主函数Main()阐明了WorldWind的初始化运行机制(如图1所 ...

  2. Spring5.0源码学习系列之浅谈懒加载机制原理

    前言介绍 附录:Spring源码学习专栏 在上一章的学习中,我们对Bean的创建有了一个粗略的了解,接着本文挑一个比较重要的知识点Bean的懒加载进行学习 1.什么是懒加载? 懒加载(Lazy-ini ...

  3. CI框架源码阅读笔记9 CI的自动加载机制autoload

    本篇并不是对某一组件的详细源码分析,而只是简单的跟踪了下CI的autoload的基本流程.因此,可以看做是Loader组件的分析前篇. CI框架中,允许你配置autoload数组,这样,在你的应用程序 ...

  4. thinkphp系列:类的自动加载是如何设计的

    在使用框架开发时,可以发现框架有很多核心类,却很少看到显示的引入某个文件的代码,这是因为框架都采用了类的自动加载机制,即使用到类时,框架会自动找到该类所在文件的位置并引入该文件.为了更容易看出代码思路 ...

  5. tp5底层源码分析之------tp5.1类的自动加载机制

    tp框架作为国内主流框架,目前已经发布了6.0版本,相当于3.*版本是进行了重构,今天我们从源码的角度来研究下tp5.1自动加载的实现 作为单入口框架,从入口文件看起,入口文件在public/下,那么 ...

  6. WorldWind源码剖析系列:星球类World

    星球类World代表通用的星球类,因为可能需要绘制除地球之外的其它星球,如月球.火星等.该类的类图如下. 需要说明的是,在WorldWind中星球球体的渲染和经纬网格的渲染时分别绘制的.经纬网格的渲染 ...

  7. 【java集合框架源码剖析系列】java源码剖析之TreeSet

    本博客将从源码的角度带领大家学习TreeSet相关的知识. 一TreeSet类的定义: public class TreeSet<E> extends AbstractSet<E&g ...

  8. 【java集合框架源码剖析系列】java源码剖析之TreeMap

    注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本.本博客将从源码角度带领大家学习关于TreeMap的知识. 一TreeMap的定义: public class TreeMap&l ...

  9. 【java集合框架源码剖析系列】java源码剖析之ArrayList

    注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本. 本博客将从源码角度带领大家学习关于ArrayList的知识. 一ArrayList类的定义: public class Arr ...

随机推荐

  1. Centos 7 中 部署 asp.net core 3.0 + nginx + mongodb 的一些新手简单入门,非docker

    目录 零.准备工作 一.部署Mongodb 1.安装Mongodb 2.创建mongodb的数据目录 3.设置目录权限 4.设置mongodb启动 5.修改mongodb的配置文件 6.启动Mongo ...

  2. 为什么要使用webpack?

    在网页中会引用到哪些常见的静态资源? js (.js  .jsx  .coffee  .ts) css (.css  .less  .sass  .scss scss是sass的plus版) imag ...

  3. NLP(二十四)利用ALBERT实现命名实体识别

      本文将会介绍如何利用ALBERT来实现命名实体识别.如果有对命名实体识别不清楚的读者,请参考笔者的文章NLP入门(四)命名实体识别(NER) .   本文的项目结构如下:   其中,albert_ ...

  4. 【春招】 java static 执行顺序

    package static类型.执行顺序; public class Test { Person person = new Person("Test"); static{ Sys ...

  5. 《前端之路》 - 初试 TypeScript(一)基础数据类型

    一.先讲讲 TypeScript 什么是 typeScript ? typeScript 是 Javascript 的超集 我们用一张图来简单介绍下 ts 和 js 清清楚楚明明白白的关系- 为什么会 ...

  6. go-admin基于Gin + Vue + Element UI的前后端分离权限管理系统

    ✨ 特性 遵循 RESTful API 设计规范 基于 GIN WEB API 框架,提供了丰富的中间件支持(用户认证.跨域.访问日志.追踪ID等) 基于Casbin的 RBAC 访问控制模型 JWT ...

  7. Mac Maven安装与配置

    下载 官网地址:http://maven.apache.org/download.cgi 配置环境变量 总步骤 编辑.bash_profile文件 vim ~/.bash_profile 配置mave ...

  8. CSS的SVG学习

    SVG 意为可缩放矢量图形(Scalable Vector Graphics). HTML三种方法导入svg文件: <html xmlns:svg="http://www.w3.org ...

  9. MySQL 【教程二】

    MySQL 创建数据表 创建MySQL数据表需要以下信息: 表名 表字段名 定义每个表字段 语法 以下为创建MySQL数据表的SQL通用语法: # CREATE TABLE table_name (c ...

  10. Java多线程并发04——合理使用线程池

    在此之前,我们已经了解了关于线程的基本知识,今天将为各位带来,线程池这一技术.关注我的公众号「Java面典」了解更多 Java 相关知识点. 为什么使用线程池?线程池做的工作主要是控制运行的线程的数量 ...