深入研究 larvael 源码第二天
12


前言

上一篇文章,研究了larvael框架入口文件 index.php,这一篇深入研究 获取框架实例底层代码
至于 composer自动加载源码分析请到这观看


bootstrap/app.php文件分析

 $app = new Illuminate\Foundation\Application(
    realpath(__DIR__.'/../')
);

$app->singleton(
    Illuminate\Contracts\Http\Kernel::class,
    App\Http\Kernel::class
);

$app->singleton(
    Illuminate\Contracts\Console\Kernel::class,
    App\Console\Kernel::class
);

$app->singleton(
    Illuminate\Contracts\Debug\ExceptionHandler::class,
    App\Exceptions\Handler::class
);

return $app;
  1. 第一步 获取应用实例
  2. 第二步 绑定框架核心 http内核,控制台内核,异常处理单例


Illuminate\Foundation\Application文件分析

  1. 引入
    use Closure;   //PHP 匿名函数类
    use RuntimeException; //php 运行异常类
    use Illuminate\Support\Arr; //框架支持的arr类
    use Illuminate\Support\Str;//框架支持的str类
    use Illuminate\Http\Request;//框架http 请求类
    use Illuminate\Support\Collection;//框架支持的集合类
    use Illuminate\Container\Container;//
    use Illuminate\Filesystem\Filesystem;//框架文件系统的文件类
    use Illuminate\Log\LogServiceProvider;//框架日志组件日志服务提供者
    use Illuminate\Support\ServiceProvider;//框架支持的服务提供者
    use Illuminate\Events\EventServiceProvider;//框架事件系统的事件服务提供者
    use Illuminate\Routing\RoutingServiceProvider;//框架路由组件的路由服务提供者
    use Symfony\Component\HttpKernel\HttpKernelInterface;//Symfony框架http内核接口
    use Symfony\Component\HttpKernel\Exception\HttpException;//Symfony框架http内核异常下http异常类
    use Illuminate\Contracts\Http\Kernel as HttpKernelContract;//框架http内核契约
    use Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables;//框架基础加载环境变量类
    use Symfony\Component\HttpFoundation\Request as SymfonyRequest;//Symfony框架http请求类
    use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;//Symfony框架http内核异常类
    use Illuminate\Contracts\Foundation\Application as ApplicationContract;//框架契约基础类
  2. 定义类定义属性
    class Application extends Container implements ApplicationContract, HttpKernelInterface
    {//定义Application对象继承Container,实现ApplicationContract, HttpKernelInterface
    const VERSION = '5.7.12'; //版本号
    protected $basePath;//基本路径
    protected $hasBeenBootstrapped = false;//应用程序是否已被自举。
    protected $booted = false;//引用程序是否已启动
    protected $bootingCallbacks = [];//引导回调的数组
    protected $bootedCallbacks = [];//启动回调的数组
    protected $terminatingCallbacks = [];//终止回调的数组
    protected $serviceProviders = [];//注册的服务提供者
    protected $loadedProviders = [];//加载的服务提供者的名称
    protected $deferredServices = [];//延迟的服务提供者的名称
    protected $databasePath;//数据库路径
    protected $storagePath;//存储路径
    protected $environmentPath;//环境变量路径
    protected $environmentFile = '.env';//环境变量文件
    protected $namespace;//应用程序名称空间
  3. 构造函数,初始化处理

    public function __construct($basePath = null)
    {
        if ($basePath) {
            $this->setBasePath($basePath);
        }
        $this->registerBaseBindings();
        $this->registerBaseServiceProviders();
        $this->registerCoreContainerAliases();
    }

    1、 如果传入路径设置基础路径

    // 设置应用基础路径
    public function setBasePath($basePath)
    {
        $this->basePath = rtrim($basePath, '\/');
    
        $this->bindPathsInContainer();
    
        return $this;
    }
    
      //   绑定容器中的所有应用程序路径
        protected function bindPathsInContainer()
    {
        $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());
    }

    2.、将基本绑定注册到容器中

      //注册基本绑定
       protected function registerBaseBindings()
     {
        static::setInstance($this);//父类方法设置当前可用实例
    
        $this->instance('app', $this);
    
        $this->instance(Container::class, $this);//将现有实例注册为容器共享
    
        $this->instance(PackageManifest::class, new PackageManifest(
            new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
        ));//文件实例绑定到PackageManifest
    }

    3、注册所有的基础服务提供者

        protected function registerBaseServiceProviders()
    {
        $this->register(new EventServiceProvider($this));//1. 事件服务提供者        
        $this->register(new LogServiceProvider($this));// 2. 日志服务提供者
        $this->register(new RoutingServiceProvider($this));  //3. 路由服务提供者
    }

    4、< href="#registerCoreContainerAliases"> 在容器中注册核心类别名(加入注册别名数组,加入抽象别名数组)

  4. 注册方法剖析
    1、getProvider
    2、resolveProvider
    3、bind
    4、singleton
    5、markAsRegistered
    6、bootProvider

    public function register($provider, $force = false)
    {
        //判断该服务提供者是否已注册,已经注册一直返回
        if (($registered = $this->getProvider($provider)) && ! $force) {
            return $registered;
        }
        //如果传入一个字符串,直接去实例化
        if (is_string($provider)) {
            $provider = $this->resolveProvider($provider);
        }
        //如果该服务提供者存在register方法,直接执行
        if (method_exists($provider, 'register')) {
            $provider->register();
        }
       //如果服务提供者包含bindings属性,则进行
        if (property_exists($provider, 'bindings')) {
            foreach ($provider->bindings as $key => $value) {
                $this->bind($key, $value);
            }
        }
        //如果服务提供者包含singletons属性,则进行
        if (property_exists($provider, 'singletons')) {
            foreach ($provider->singletons as $key => $value) {
                $this->singleton($key, $value);
            }
        }
        //标记该程序为已注册,加入已注册服务提供者数组和已加载服务提供者数组
        $this->markAsRegistered($provider);
        //如果程序已启动,调用服务提供者boot方法
        if ($this->booted) {
            $this->bootProvider($provider);
        }
    
        return $provider;
    }
  5. 获取版本号
        public function version()
  6. 注册回调以在加载环境之后运行
    public function afterLoadingEnvironment(Closure $callback)
    {
        return $this->afterBootstrapping(
            LoadEnvironmentVariables::class, $callback
        );
    }
  7. 注册回调以在引导程序之后运行。
    public function afterBootstrapping($bootstrapper, Closure $callback)
    {
        $this['events']->listen('bootstrapped: '.$bootstrapper, $callback);//监听引导后事件
    }
  8. 注册回调在引导程序之前运行
    public function beforeBootstrapping($bootstrapper, Closure $callback)
    {
        $this['events']->listen('bootstrapping: '.$bootstrapper, $callback);//监听引导前事件
    }
  9. 是否程序自举

    public function hasBeenBootstrapped()
    {
        return $this->hasBeenBootstrapped;
    }
  10. 获取app目录路径
    public function path($path = '')
    {
        return $this->basePath.DIRECTORY_SEPARATOR.'app'.($path ? DIRECTORY_SEPARATOR.$path : $path);
    }
  11. 获取laravel根路径
    public function basePath($path = '')
    {
        return $this->basePath.($path ? DIRECTORY_SEPARATOR.$path : $path);
    }
  12. 获取bootstrap路径
    public function bootstrapPath($path = '')
    {
        return $this->basePath.DIRECTORY_SEPARATOR.'bootstrap'.($path ? DIRECTORY_SEPARATOR.$path : $path);
    }
  13. 获取config路径
    public function configPath($path = '')
    {
        return $this->basePath.DIRECTORY_SEPARATOR.'config'.($path ? DIRECTORY_SEPARATOR.$path : $path);
    }
  14. 获取database路径
    public function databasePath($path = '')
    {
        return ($this->databasePath ?: $this->basePath.DIRECTORY_SEPARATOR.'database').($path ? DIRECTORY_SEPARATOR.$path : $path);
    }
  15. 设置databsee路径

    public function useDatabasePath($path)
    {
        $this->databasePath = $path;
    
        $this->instance('path.database', $path);//添加到容器里
    
        return $this;
    }
  16. 获取资源resource路径
    public function resourcePath($path = '')
    {
        return $this->basePath.DIRECTORY_SEPARATOR.'resources'.($path ? DIRECTORY_SEPARATOR.$path : $path);
    }
  17. 获取public路径
    public function publicPath()
    {
        return $this->basePath.DIRECTORY_SEPARATOR.'public';
    }
  18. 获取语言包路径
    public function langPath()
    {
        return $this->resourcePath().DIRECTORY_SEPARATOR.'lang';
    }
  19. 获取strorge的路径

    public function storagePath()
    {
        return $this->storagePath ?: $this->basePath.DIRECTORY_SEPARATOR.'storage';
    }
  20. 设置strorge的路径

    public function useStoragePath($path)
    {
        $this->storagePath = $path;
    
        $this->instance('path.storage', $path);
    
        return $this;
    }
  21. 获取resource的路径
    public function resourcePath($path = '')
    {
        return $this->basePath.DIRECTORY_SEPARATOR.'resources'.($path ? DIRECTORY_SEPARATOR.$path : $path);
    }
  22. 获取env路径
    public function environmentPath()
    {
        return $this->environmentPath ?: $this->basePath;
    }
  23. 设置env路径

    
    public function useEnvironmentPath($path)
    {
        $this->environmentPath = $path;
    
        return $this;
    }
    ``
  24. 设置env文件

    public function loadEnvironmentFrom($file)
    {
        $this->environmentFile = $file;
    
        return $this;
    }
  25. 获取env文件
    public function environmentFile()
    {
        return $this->environmentFile ?: '.env';
    }
  26. 获取env文件路径
    public function environmentFilePath()
    {
        return $this->environmentPath().DIRECTORY_SEPARATOR.$this->environmentFile();
    }
  27. 获取应用env配置

    public function environment()
    {
        //判断是否有参数
        if (func_num_args() > 0) {
            $patterns = is_array(func_get_arg(0)) ? func_get_arg(0) : func_get_args();
    
            return Str::is($patterns, $this['env']);//字符串搜索
        }
    
        return $this['env'];
    }
  28. 环境是否本地开发
    public function isLocal()
    {
        return $this['env'] === 'local';
    }
  29. 检测应用当前环境 detect方法详解

    public function detectEnvironment(Closure $callback)
    {
        $args = $_SERVER['argv'] ?? null;//获取cli模式下的参数
    
        return $this['env'] = (new EnvironmentDetector)->detect($callback, $args);//
    }
  30. 确定应用是否在控制台运行

    public function runningInConsole()
    {
        if (isset($_ENV['APP_RUNNING_IN_CONSOLE'])) {
            return $_ENV['APP_RUNNING_IN_CONSOLE'] === 'true';
        }
    
        return php_sapi_name() === 'cli' || php_sapi_name() === 'phpdbg';//cli模式或者phpdbg调试模式
    }
  31. 确定应用是否运行在unit单元测试
    public function runningUnitTests()
    {
        return $this['env'] === 'testing';
    }
  32. 注册所有配置的提供程序

    public function registerConfiguredProviders()
    {
        $providers = Collection::make($this->config['app.providers'])//创建一个集合实例从app.php获取到的服务提供者数组
                        ->partition(function ($provider) {
                            return Str::startsWith($provider, 'Illuminate\\');//判断是否已$needles字符串开头
                        });//*使用给定的回调或键将集合划分为两个数组。一部分框架核心一部分自定义
    
       //将所有包的所有服务提供者类名填充到1的位置
        $providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]);获取所。
    
        //存入服务提供者缓存文件 
        (new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
                    ->load($providers->collapse()->toArray());
    }
  33. 如果已注册服务提供程序实例存在,则获取该实例

    
    public function getProvider($provider)
    {
        return array_values($this->getProviders($provider))[0] ?? null;
    }
  34. 获取所有注册的服务提供者

    public function getProviders($provider)
    {
        $name = is_string($provider) ? $provider : get_class($provider);//获取类名称
    
        return Arr::where($this->serviceProviders, function ($value) use ($name) {
            return $value instanceof $name;//判断是否是实例
        });
    }
  35. 类名解析服务提供者实例
    public function resolveProvider($provider)
    {
        return new $provider($this);
    }
  36. 标记程序为已注册

    protected function markAsRegistered($provider)
    {
        $this->serviceProviders[] = $provider;
    
        $this->loadedProviders[get_class($provider)] = true;
    }
  37. 加载所有延迟服务提供者

    public function loadDeferredProviders()
    {
    
        foreach ($this->deferredServices as $service => $provider) {
            $this->loadDeferredProvider($service);//循环加载延迟服务提供者
        }
        $this->deferredServices = [];//将延迟服务提供者置为空
    }
  38. 加载延迟服务提供者
    public function loadDeferredProvider($service)
    {
       //判断是否属于延迟服务提供者
        if (! isset($this->deferredServices[$service])) {
            return;
        }
        //判断是否已加载
        if (! isset($this->loadedProviders[$provider])) {
            //注册延迟服务提供者
            $this->registerDeferredProvider($provider, $service);
        }
    }
  39. 注册延迟服务提供者

    public function registerDeferredProvider($provider, $service = null)
    {
    
        if ($service) {
            unset($this->deferredServices[$service]);//将该服务清空出延时服务提供者数组
        }
    
        $this->register($instance = new $provider($this));//注册服务
    
        if (! $this->booted) {
            $this->booting(function () use ($instance) {
                $this->bootProvider($instance);//启动服务调用boot
            });
        }
    }
  40. 从容器中解析实例
    1、getAlias
    2、parent::make

    public function make($abstract, array $parameters = [])
    {
        $abstract = $this->getAlias($abstract);//获取别名
         //如果是延迟服务提供者并且没有实例化就加载延迟程序
        if (isset($this->deferredServices[$abstract]) && ! isset($this->instances[$abstract])) {
            $this->loadDeferredProvider($abstract);
        }
    
        return parent::make($abstract, $parameters);//使用父类方法解析实例并返回
    }
  41. 确定给定的抽象类型是否已经被绑定
    1、parent::bound
    public function bound($abstract)
    {
        return isset($this->deferredServices[$abstract]) || parent::bound($abstract);
    }
  42. 应用是否已启动
    public function isBooted()
    {
        return $this->booted;
    }
  43. 启动应用
    1、fireAppCallbacks
    public function boot()
    {
        if ($this->booted) {
            return;
        }
        //调用引导回调
        $this->fireAppCallbacks($this->bootingCallbacks);
        array_walk($this->serviceProviders, function ($p) {
            $this->bootProvider($p);//启动服务提供者
        });
        $this->booted = true;//设置程序已启动
        //调用启动后
        $this->fireAppCallbacks($this->bootedCallbacks);
    }
  44. 启动服务提供者
    protected function bootProvider(ServiceProvider $provider)
    {
        if (method_exists($provider, 'boot')) {
            return $this->call([$provider, 'boot']);
        }
    }
  45. 设置应用引导回调数组
    public function booting($callback)
    {
        $this->bootingCallbacks[] = $callback;
    }
  46. 设置应用启动回调数组

    public function booted($callback)
    {
        $this->bootedCallbacks[] = $callback;
    
        if ($this->isBooted()) {
            $this->fireAppCallbacks([$callback]);//如果已启动,调用回调
        }
    }
  47. 调用程序回调
    protected function fireAppCallbacks(array $callbacks)
    {
        foreach ($callbacks as $callback) {
            call_user_func($callback, $this);
        }
    }
  48. handle
    public function handle(SymfonyRequest $request, $type = self::MASTER_REQUEST, $catch = true)
    {
        return $this[HttpKernelContract::class]->handle(Request::createFromBase($request));
    }
  49. shouldSkipMiddleware
    1、bound
    2、make
    
    public function shouldSkipMiddleware()
    {
        return $this->bound('middleware.disable') &&
               $this->make('middleware.disable') === true;
    }```
  50. 获取缓存服务路径
    1、bootstrapPath
    public function getCachedServicesPath()
    {
        return $this->bootstrapPath().'/cache/services.php';
    }
  51. 获取packages.php的路径
    1、bootstrapPath
    public function getCachedPackagesPath()
    {
        return $this->bootstrapPath().'/cache/packages.php';
    }
  52. *确定是否缓存了应用程序配置
    1、getCachedConfigPath
    public function configurationIsCached()
    {
        return file_exists($this->getCachedConfigPath());
    }
  53. 获取config文件路径
    1、bootstrapPath
    public function getCachedConfigPath()
    {
        return $this->bootstrapPath().'/cache/config.php';
    }
  54. 确定是否缓存了路由配置
    1、getCachedRoutesPath
    public function routesAreCached()
    {
        return $this['files']->exists($this->getCachedRoutesPath());
    }
  55. 获取路由缓存路径
    1、bootstrapPath
    public function getCachedRoutesPath()
    {
        return $this->bootstrapPath().'/cache/routes.php';
    }
  56. 确定应用程序当前是否停机进行维护
    1、storagePath
    public function isDownForMaintenance()
    {
        return file_exists($this->storagePath().'/framework/down');
    }
  57. 抛出http异常

    public function abort($code, $message = '', array $headers = [])
    {
        if ($code == 404) {
            throw new NotFoundHttpException($message);
        }
    
        throw new HttpException($code, $message, null, $headers);
    }
  58. 设置程序终止的回调

    public function terminating(Closure $callback)
    {
        $this->terminatingCallbacks[] = $callback;
    
        return $this;
    }
  59. 执行终止程序回调
    public function terminate()
    {
        foreach ($this->terminatingCallbacks as $terminating) {
            $this->call($terminating);
        }
    }
  60. 获取所有已加载的服务提供者
    public function getLoadedProviders()
    {
        return $this->loadedProviders;
    }
  61. 获取所有延迟加载服务提供者
    public function getDeferredServices()
    {
        return $this->deferredServices;
    }
  62. 设置延迟服务提供者
    public function setDeferredServices(array $services)
    {
        $this->deferredServices = $services;
    }
  63. 添加延迟服务提供者
    public function addDeferredServices(array $services)
    {
        $this->deferredServices = array_merge($this->deferredServices, $services);
    }
  64. 验证是否是延迟服务提供者
    public function isDeferredService($service)
    {
        return isset($this->deferredServices[$service]);
    }
  65. 设置服务门面命名空间
    1、AliasLoader::setFacadeNamespace
    public function provideFacades($namespace)
    {
        AliasLoader::setFacadeNamespace($namespace);
    }
  66. 获取当前区域设置
    public function getLocale()
    {
        return $this['config']->get('app.locale');
    }
  67. 设置当前区域

    public function setLocale($locale)
    {
        $this['config']->set('app.locale', $locale);
    
        $this['translator']->setLocale($locale);
    
        $this['events']->dispatch(new Events\LocaleUpdated($locale));
    }
  68. 判断当前区域
    public function isLocale($locale)
    {
        return $this->getLocale() == $locale;
    }
  69. 在容器中注册核心类别名
    1、alias
    public function registerCoreContainerAliases()
    {
        foreach ([
            'app'                  => [\Illuminate\Foundation\Application::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],
            '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],
            'encrypter'            => [\Illuminate\Encryption\Encrypter::class, \Illuminate\Contracts\Encryption\Encrypter::class],
            'db'                   => [\Illuminate\Database\DatabaseManager::class],
            'db.connection'        => [\Illuminate\Database\Connection::class, \Illuminate\Database\ConnectionInterface::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],
            '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],
            '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);
            }
        }
    }
  70. 清除所有绑定和解决实例的容器
    1、parent::flush
    public function flush()
    {
        parent::flush();
        $this->buildStack = [];
        $this->loadedProviders = [];
        $this->bootedCallbacks = [];
        $this->bootingCallbacks = [];
        $this->deferredServices = [];
        $this->reboundCallbacks = [];
        $this->serviceProviders = [];
        $this->resolvingCallbacks = [];
        $this->afterResolvingCallbacks = [];
        $this->globalResolvingCallbacks = [];
    }
  71. 获取命名空间

    public function getNamespace()
    {
        if (! is_null($this->namespace)) {
            return $this->namespace;
        }
    
        $composer = json_decode(file_get_contents(base_path('composer.json')), true);
    
        foreach ((array) data_get($composer, 'autoload.psr-4') as $namespace => $path) {
            foreach ((array) $path as $pathChoice) {
                if (realpath(app_path()) == realpath(base_path().'/'.$pathChoice)) {
                    return $this->namespace = $namespace;
                }
            }
        }
    
        throw new RuntimeException('Unable to detect application namespace.');
    }
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 1
ZeLingNing

???这就完了

1周前

  • 请注意单词拼写,以及中英文排版,参考此页
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`, 更多语法请见这里 Markdown 语法
  • 支持表情,使用方法请见 Emoji 自动补全来咯,可用的 Emoji 请见 :metal: :point_right: Emoji 列表 :star: :sparkles:
  • 上传图片, 支持拖拽和剪切板黏贴上传, 格式限制 - jpg, png, gif
  • 发布框支持本地存储功能,会在内容变更时保存,「提交」按钮点击时清空
  请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!