zoukankan      html  css  js  c++  java
  • Laravel 框架中常用的 PHP 语法

    Laravel 框架中常用的 PHP 语法

     阅读约 21 分钟

    原文:wuYin/blog,转载注明来源即可。

    前言

    Laravel 框架因为其组件化的设计并恰当使用设计模式,使得框架本身简洁易扩展。区别于 ThinkPHP 那种整合式功能的框架(功能要么全用要么全不用),Laravel 使用 composer 工具进行 package 的管理,想加功能直接添加组件即可。比如你写爬虫使用页面采集组件: composer require jaeger/querylist

    本文简要介绍 Laravel 中频繁用到的 PHP 特性与新语法,具体可参考。

    组件化开发

    Laravel 进行组件化开发,得益于遵循 PSR-4 规范的 composer 工具,其利用命名空间和自动加载来组织项目文件。更多参考:composer 自动加载机制

    命名空间

    命名冲突

    在团队协作、引入第三方依赖代码时,往往可能会出现类、函数和接口重名的情况。比如:

    <?php    
    # google.php
    class User 
    {
        private $name;
    }
    <?php    
    # mine.php
    // 引入第三方依赖
    include 'google.php';
    
    class User
    {
        private $name;
    }
    
    $user = new User();    // 命名冲突

    因为同时定义了类 User 导致命名冲突:

    image-20180609193721860

    解决办法

    从 PHP 5.3 开始引入,参考 PHP 手册 能知道命名空间有 2 个作用:避免命名冲突、保持命名简短。比如使用命名空间后:

    <?php
    # google.php
    namespace Google;
    
    // 模拟第三方依赖
    class User {
        private $name = 'google';
    
        public function getName() {
            echo $this->name . PHP_EOL;
        }
    }
    <?php
    # mine.php
    namespace Mine;
    
    // 导入并命名别名
    use Google as G;
    
    // 导入文件使得 google.php 命名空间变为 mine.php 的子命名空间
    include 'google.php';
    
    /* 避免了命名冲突 */
    class User
    {
        private $name = 'mine';
    
        public function getName() {
            echo $this->name . PHP_EOL;
        }
    }
    
    /* 保持了命名简短 */
    // 如果没有命名空间,为了类名也不冲突,可能会出现这种函数名
    // $user = new Google_User();
    // Zend 风格并不提倡
    $user = new GUser();
    
    // 为了函数名也不冲突,可能会出现这种函数名
    // $user->google_get_name()
    $user->getName();
    
    $user = new User();
    $user->getName();

    运行:

    $ php demo.php
    google
    mine

    PSR 规范

    其实 namespace 与文件名无关,但按 PSR 标准要求:命名空间与文件路径一致 & 文件名与类名一致。比如 Laravel 默认生成的 laravel-demo/app/Http/Controllers/Auth/LoginController.php,其命名空间为 AppHttpControllersAuth & 类名为 LoginController

    遵循规范,上边的 mine.php 和 google.php 都应叫 User.php

    namespace 操作符与__NAMESPACE__ 魔术常量

    ...
    // $user = new User();
    $user = new namespaceUser();    // 值为当前命名空间
    $user->getName();
    
    echo __NAMESPACE__ . PHP_EOL;    // 直接获取当前命名空间字符串    // 输出 Mine

    三种命名空间的导入

    <?php
    namespace CurrentNameSpace;
    
    // 不包含前缀
    $user = new User();        # CurrentNameSpaceUser();
    
    // 指定前缀
    $user = new GoogleUser();    # CurrentNameSpaceGoogleUser();
    
    // 根前缀
    $user = new GoogleUser();    # GoogleUser();

    全局命名空间

    如果引用的类、函数没有指定命名空间,则会默认在当在 __NAMESPACE__下寻找。若要引用全局类:

    <?php
    namespace Demo;
    
    // 均不会被使用到
    function strlen() {}
    const INI_ALL = 3;
    class Exception {}
    
    $a = strlen('hi');         // 调用全局函数 strlen
    $b = CREDITS_GROUP;          // 访问全局常量 CREDITS_GROUP
    $c = new Exception('error');   // 实例化全局类 Exception

    多重导入与多个命名空间

    // use 可一次导入多个命名空间
    use Google,
        Microsoft;
    
    // 良好实践:每行一个 use
    use Google;
    use Microsoft;
    <?php
    // 一个文件可定义多个命名空间
    namespace Google {
        class User {}
    }    
        
    namespace Microsoft {
        class User {}
    }   
    
    // 良好实践:“一个文件一个类”

    导入常量、函数

    从 PHP 5.6 开始,可使用 use function 和 use const 分别导入函数和常量使用:

    # google.php
    const CEO = 'Sundar Pichai';
    function getMarketValue() {
        echo '770 billion dollars' . PHP_EOL;
    }
    # mine.php
    use function GooglegetMarketValue as thirdMarketValue;
    use const GoogleCEO as third_CEO;
    
    thirdMarketValue();
    echo third_CEO;

    运行:

    $ php mine.php
    google
    770 billion dollars
    Sundar Pichaimine
    Mine

    文件包含

    手动加载

    使用 include 或 require 引入指定的文件,(字面理解)需注意 require 出错会报编译错误中断脚本运行,而 include 出错只会报 warning 脚本继续运行。

    include 文件时,会先去 php.ini 中配置项 include_path 指定的目录找,找不到才在当前目录下找:

    image-20180609203210194

    <?php
        
    // 引入的是 /usr/share/php/System.php
    include 'System.php';

    自动加载

    void __autoload(string $class ) 能进行类的自动加载,但一般都使用 spl_autoload_register 手动进行注册:

    <?php
    
    // 自动加载子目录 classes 下 *.class.php 的类定义
    function __autoload($class) {
        include 'classes/' . $class . '.class.php';
    }
    
    // PHP 5.3 后直接使用匿名函数注册
    $throw = true;        // 注册出错时是否抛出异常
    $prepend = false;    // 是否将当前注册函数添加到队列头
    
    spl_autoload_register(function ($class) {
        include 'classes/' . $class . '.class.php';
    }, $throw, $prepend);

    在 composer 生成的自动加载文件 laravel-demo/vendor/composer/autoload_real.php 中可看到:

    class ComposerAutoloaderInit8b41a
    {
        private static $loader;
    
        public static function loadClassLoader($class)
        {
            if ('ComposerAutoloadClassLoader' === $class) {
                // 加载当前目录下文件
                require __DIR__ . '/ClassLoader.php';
            }
        }
        
         public static function getLoader()
        {
            if (null !== self::$loader) {
                return self::$loader;
            }
        
            // 注册自己的加载器
            spl_autoload_register(array('ComposerAutoloaderInit8b41a6', 'loadClassLoader'), true, true);
            self::$loader = $loader = new ComposerAutoloadClassLoader();
            spl_autoload_unregister(array('ComposerAutoloaderInit8b41a6a', 'loadClassLoader'));
    
            ...
         }
     
        ...
    }    

    这里只提一下,具体 Laravel 整体是怎么做自动加载的,后边的文章会细说。

    反射

    参考 PHP 手册,可简单的理解为在运行时获取对象的完整信息。反射有 5 个类:

    ReflectionClass     // 解析类名
    ReflectionProperty     // 获取和设置类属性的信息(属性名和值、注释、访问权限)
    ReflectionMethod     // 获取和设置类函数的信息(函数名、注释、访问权限)、执行函数等
    ReflectionParameter    // 获取函数的参数信息
    ReflectionFunction    // 获取函数信息

    比如 ReflectionClass 的使用:

    <?php
    
    class User
    {
        public $name;
        public $age;
    
        public function __construct($name = 'Laruence', $age = 35) {
            $this->name = $name;
            $this->age  = $age;
        }
    
        public function intro() {
            echo '[name]: ' . $this->name . PHP_EOL;
            echo '[age]: '  . $this->age  . PHP_EOL;
        }
    }
    
    reflect('User');
    
    // ReflectionClass 反射类使用示例
    function reflect($class) {
        try {
            $ref = new ReflectionClass($class);
            // 检查是否可实例化
            // interface、abstract class、 __construct() 为 private 的类均不可实例化
            if (!$ref->isInstantiable()) {
                echo "[can't instantiable]: ${class}
    ";
            }
    
            // 输出属性列表
            // 还能获取方法列表、静态常量等信息,具体参考手册
            foreach ($ref->getProperties() as $attr) {
                echo $attr->getName() . PHP_EOL;
            }
    
            // 直接调用类中的方法,个人认为这是反射最好用的地方
            $obj = $ref->newInstanceArgs();
            $obj->intro();
        } catch (ReflectionException $e) {
                // try catch 机制真的不优雅
                // 相比之下 Golang 的错误处理虽然繁琐,但很简洁
            echo '[reflection exception: ]' . $e->getMessage();
        }
    }

    运行:

    $ php reflect.php
    name
    age
    [name]: Laruence
    [age]: 35

    其余 4 个反射类参考手册 demo 即可。

    后期静态绑定

    参考 PHP 手册,先看一个例子:

    <?php
    
    class Base
    {
            // 后期绑定不局限于 static 方法
        public static function call() {
            echo '[called]: ' . __CLASS__ . PHP_EOL;
        }
    
        public static function test() {
            self::call();        // self   取值为 Base  直接调用本类中的函数
            static::call();        // static 取值为 Child 调用者
        }
    }
    
    class Child extends Base
    {
        public static function call() {
            echo '[called]: ' . __CLASS__ . PHP_EOL;
        }
    }
    
    
    Child::test();

    输出:

    $ php late_static_bind.php
    [called]: Base
    [called]: Child

    在对象实例化时,self:: 会实例化根据定义所在的类,static:: 会实例化调用它的类。

    trait

    基本使用

    参考 PHP 手册,PHP 虽然是单继承的,但从 5.4 后可通过 trait 水平组合“类”,来实现“类”的多重继承,其实就是把重复的函数拆分成 triat 放到不同的文件中,通过 use 关键字按需引入、组合。可类比 Golang 的 struct 填鸭式组合来实现继承。比如:

    <?php
    
    class DemoLogger
    {
        public function log($message, $level) {
            echo "[message]: $message", PHP_EOL;
            echo "[level]: $level", PHP_EOL;
        }
    }
    
    trait Loggable
    {
        protected $logger;
    
        public function setLogger($logger) {
            $this->logger = $logger;
        }
    
        public function log($message, $level) {
            $this->logger->log($message, $level);
        }
    }
    
    class Foo
    {
            // 直接引入 Loggable 的代码片段
        use Loggable;
    }
    
    $foo = new Foo;
    $foo->setLogger(new DemoLogger);
    $foo->log('trait works', 1);

    运行:

    $ php trait.php
    [message]: trait works
    [level]: 1

    更多参考:我所理解的 PHP Trait

    重要性质

    优先级

    当前类的函数会覆盖 trait 的同名函数,trait 会覆盖父类的同名函数( use trait 相当于当前类直接覆写了父类的同名函数)

    trait 函数冲突

    同时引入多个 trait 可用 , 隔开,即多重继承。

    多个 trait 有同名函数时,引入将发生命名冲突,使用 insteadof 来指明使用哪个 trait 的函数。

    重命名与访问控制

    使用 as 关键字可以重命名的 trait 中引入的函数,还可以修改其访问权限。

    其他

    trait 类似于类,可以定义属性、方法、抽象方法、静态方法和静态属性。

    下边的苹果、微软和 Linux 的小栗子来说明:

    <?php
    
    trait Apple
    {
        public function getCEO() {
            echo '[Apple CEO]: Tim Cook', PHP_EOL;
        }
    
        public function getMarketValue() {
            echo '[Apple Market Value]: 953 billion', PHP_EOL;
        }
    }
    
    
    trait MicroSoft
    {
        public function getCEO() {
            echo '[MicroSoft CEO]: Satya Nadella', PHP_EOL;
        }
    
        public function getMarketValue() {
            echo '[MicroSoft Market Value]: 780 billion', PHP_EOL;
        }
    
        abstract public function MadeGreatOS();
    
        static public function staticFunc() {
            echo '[MicroSoft Static Function]', PHP_EOL;
        }
    
        public function staticValue() {
            static $v;
            $v++;
            echo '[MicroSoft Static Value]: ' . $v, PHP_EOL;
        }
    }
    
    
    // Apple 最终登顶,成为第一家市值超万亿美元的企业
    trait Top
    {
        // 处理引入的 trait 之间的冲突
        use Apple, MicroSoft {
            Apple::getCEO insteadof MicroSoft;
            Apple::getMarketValue insteadof MicroSoft;
        }
    }
    
    
    class Linux
    {
        use Top {
                // as 关键字可以重命名函数、修改权限控制
            getCEO as private noCEO;
        }
    
        // 引入后必须实现抽象方法
        public function MadeGreatOS() {
            echo '[Linux Already Made]', PHP_EOL;
        }
    
        public function getMarketValue() {
            echo '[Linux Market Value]: Infinity', PHP_EOL;
        }
    }
    
    $linux = new Linux();
    // 和 extends 继承一样
    // 当前类中的同名函数也会覆盖 trait 中的函数
    $linux->getMarketValue();
    
    // trait 中可以定义静态方法
    $linux::staticFunc();
    
    // 在 trait Top 中已解决过冲突,输出库克
    $linux->getCEO();
    // $linux->noCEO();        // Uncaught Error: Call to private method Linux::noCEO() 
    
    // trait 中可以定义静态变量
    $linux->staticValue();
    $linux->staticValue();

    运行:

    $ php trait.php
    [Linux Market Value]: Infinity
    [MicroSoft Static Function]
    [Apple CEO]: Tim Cook
    [MicroSoft Static Value]: 1
    [MicroSoft Static Value]: 2

    总结

    本节简要提及了命名空间、文件自动加载、反射机制与 trait 等,Laravel 正是恰如其分的利用了这些新特性,才实现了组件化开发、服务加载等优雅的特性。

    阅读 990发布于 2018-06-10
     
     

    0 条评论
     
     
    推荐阅读
    【转】php命名空间

    PHP命名空间(namespace)是在PHP5.3中加入的,如果你学过C#和Java,那命名空间就不算什么新事物。不过在PHP当中还是有着相当重要的意义。PHP命名空间可以解决以下两类问题:用户编写的代码与PHP内部的类函数常量或第三方类函数常量之间的名字冲突。为很长的标识符名称(通常是为了缓解第一类问题而定义的)创建一个别名(或简短)的名称,提高源代码的可读性。默认情况下,所有常量、类和函数名

    耕毅 阅读 142

    PHP中的命名空间

    之前没有系统学习过PHP语言,直接上手TP框架了,所以认为namespace和use是TP框架的一部分,最近学习语言模块的时候遇到了这个问题,所以汇总了一下。在没定义命名空间的情况下,所有的常量、类、函数等都在全局空间下。通过关键字namespace声明。可以在同一个文件中定义不同的命名空间代码,全局的非命名空间代码与命名空间中的代码通过大括号的形式可以组合在一起子命名空间与目录和文件的关系很像,

    好久不见 阅读 273

    PHP新特性之命名空间、性状和生成器

    1).命名空间在PHP5.3中被引入,类似于文件夹的功能。例如Symfony框架中的Request和Response,位于Symfony的命名空间下。2).命名空间始终应该在<?php标签的下面一行。3).PHP文件的命名空间和操作系统的物理文件系统不同,这是一个虚拟的概念,没有必要和文件系统的目录结构完全对应。虽然如此,绝大多数PHP组件为了兼容广泛使用的PSR4自动加载标准,会把子命名空

    肖潇 阅读 12

    Modern-php 书摘(一)namespace

    命名空间在PHP文件的顶部,<?php标签后的第一行声明;命名空间声明语句以namespace开头,随后是一个空格,然后是命名空间的名称,最后以;结尾;厂商命名空间即下面声明的“Oreilly”是最重要的命名空间;必须具有全局唯一性。子命名空间Ps:同一个命名空间下的所有类、接口、函数没必要在同一个PHP文件中声明;所以,我们可以在不同的文件中编写属于同一个命名空间的多个类。PHP引入nam

    Sugar_w 阅读 20

    Laravel 5.0 的新特性

    原文:http:laravel.comdocsmasterreleases#laravel-5.0译文:http:discuss.flarum.org.cn24-laravel-5译者:flarumLaravel5.0包括超过22个新特性。Laravel5.0引入了一个新鲜的应用架构到默认的Laravel项目中,这个架构会提供更好的Laravel应用的服务。同时还加入了新的自动加载标准(PSR-4

    justjavac 阅读 6

    【Laravel】Laravel 框架关键技术解析·读书笔记(二)

    框架应用程序根目录(5.1版本)默认的Laravel框架应用程序是符合PSR规范的,所以相应的目录结构也是基本固定的,不同的目录加载了功能文件,如果添加了新的目录,需要在composer.json文件中添加PSR规范的自动加载部分并执行update命令。app目录应用程序的大部分内容都存在于app目录下,该目录同样通过composer使用自动加载标准(PSR-4)来加载其中的文件,如果想改变目录下

    xiaoyu_v5 阅读 18

    charm_PHP,一个还有诸多问题的PHP MVC框架

    原文是在我自己博客中,小伙伴也可以点阅读原文进行跳转查看,还有好听的背景音乐噢~一个简单的MVC框架,框架中实现了C和V层,M层使用composer的PHP组件——Medoo,实现了一些简单的设计模式,单一入口、自动加载。本框架遵循PSR规范,使用命名空间来规范类于类之间的互相合作;本地下载项目:gitclonehttps:github.comcharm-vch...打开cmd,在项目下输入com

    命中水ヽ 阅读 8

    PHP 规范之PSR规范

    PSR不是PHP官方标准,而是从如Zend、Symfony2等知名PHP项目中提炼出来的一系列标准,目前有越来越多的社区项目加入并遵循该标准。参考:http:psr.phphub.org本篇规范制定了代码基本元素的相关标准,以确保共享的PHP代码间具有较高程度的技术互通性。文件(如:生成文件输出以及修改.ini配置文件等),二者只能选其一;命名空间与类常量缩进行修饰符类的属性和方法必须添加访问修饰

    寒窗 阅读 46

     
  • 相关阅读:
    zabbix--完整安装攻略
    python--8大排序(原理+代码)
    python--二分法查找
    celery生产者-消费者
    python--基础知识点梳理(之数据结构)
    mysql--事务详解
    python--基础知识点梳理(三)深浅拷贝、进线协程、os和sys、垃圾回收机制、读文件的三种方式
    python--基础知识点梳理(二)面向对象
    python--基础知识点梳理(一)数据类型、迭代生成装饰器、函数
    java 集合
  • 原文地址:https://www.cnblogs.com/SofuBlue/p/12120336.html
Copyright © 2011-2022 走看看