精华内容
下载资源
问答
  • Circuit Breaker(断路器)模式关于断路器模式是在微服务架构/远程调用环境下经常被使用到一个模式。...图片引用自参考资料1。其中从client和supplier可以分别理解成调用者和远程方法。在没有Circuit

    Circuit Breaker(断路器)模式

    关于断路器模式是在微服务架构/远程调用环境下经常被使用到的一个模式。它的作用一言以蔽之就是提高系统的可用性,在出现的问题通过服务降级的手段来保证系统的整体可用,而不至于因为部分问题导致整个系统不可用。

    用下面这张图可以很好的说明它能够解决的问题:


    图片引用自参考资料1。

    其中从client和supplier可以分别理解成调用者和远程方法。在没有Circuit Breaker这个组件之前,两者是直接发生交互的,因此当远程方法不可用时,调用者这边可能会阻塞或者失败。由于在微服务架构/远程调用环境下,方法调用之间往往都有依赖性,因此当本次方法调动失败后有可能会影响到后续的业务,从而层层失败(Cascading Failures)导致整个系统的不可用。

    通过引入断路器模式(即图中的Circuit Breaker组件),让它负责Client和远程资源的调用和协调。当调用正常的时候,并不会感觉到断路器的存在,然而当调用发生异常,比如连续性的Timeouts,这个时候断路器会被触发(也就是图中的trip),被触发后的断路器处于打开(Open)的状态,此时由Client发起的调用请求会被断路器拒绝,完成服务的降级。

    降级后的返回值因应用场景而异,如果能够有默认值的情况可以返回给调用方默认值。比如在一个购物网站中,会根据用户的浏览记录动态地推荐相关产品,如果这个动态推荐的服务暂时不可用,那么可以考虑推荐一些默认的畅销产品,这些结果一般会存放在缓存中,因此也不需要消耗什么计算资源。如果没有的话可以提示调用者流量过大,请稍后重试。就像每逢双11零点的时候经常会被各大购物网站拒绝访问一样。

    希望通过以上的解释,能够大概说明Circuit Breaker模式的意图。更多信息可以查看参考资料的1和2。同时,在业界也有一些厂商针对这个模式有一些开源工具,比如Netflix的Hystrix项目,这个项目也被Spring整合到其Spring Cloud微服务技术栈中。

    和Retry的区别

    在上一篇文章中我们讨论了如何使用Retry机制来处理调用中可能出现的失败,Retry和Circuit Breaker尤其共通之处:

    • 都涉及到对于目标方法的多次调用
    • 都有阈值的概念(重试次数vs断路前的失败次数)

    但是Retry机制尤其自身的问题,比如:

    • 当服务不可用时容易堆积大量调用
    • 服务再次可用的时候容易被大量的堆积请求再次弄崩
    • 策略上不够灵活

    以上问题的症结在于重试机制没有办法去区分服务是暂时不可用(随机性的网络异常)还是真的不可用(服务挂了),也许通过区分异常类型可以判断,但是多个调用线程的重试是彼此独立的,并没有一个统一的管控方(比如Circuit Breaker)进行协调。这就导致在服务确实不可用的时候,调用还是会发起请求,哪怕重试的次数因为异常类型的缘故不那么多。

    而使用断路器时,它能够根据情况服务状况调整请求数量,比如在服务不可用的时候能够大量地减少请求数量。并且断路器本身会根据业务性质实现一些恢复策略,比如断路器开启30秒后进行重试,如果调用成功则关闭断路器等等。

    下面,我们就来看看如何通过AOP实现Circuit Breaker模式。

    Aspect实现

    目标业务方法(可以考虑成远程调用)

    @Service
    @Scope("prototype")
    public class CircuitBreakerService {
    
      private int counter = 0;
    
      @CircuitBreaker
      public int service() {
        if (counter++ < 1) {
          throw new RuntimeException("服务不可用");
        }
    
        return 1;
      }
    
    }

    以上的service方法便是目标业务方法了,里面一般会包含远程调用。这里为了模拟远程调用出现的问题,在初次调用的时候会抛出RuntimeException,第二次调用的时候返回正常结果。

    标记注解

    @CircuitBreaker注解用来标注业务方法作为Pointcut的定位方式,目前注解只是一个Marker Annotation:

    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface CircuitBreaker {
    
    }

    Aspect

    @Component
    @Scope("prototype")
    @Aspect("perthis(com.rxjiang.aop.custom.cb.CircuitBreakerAspect.circuitBreakerTargets())")
    public class CircuitBreakerAspect {
    
      @Pointcut("execution(@com.rxjiang.aop.custom.cb.CircuitBreaker * *(..))")
      public void circuitBreakerTargets() {}
    
      private AtomicInteger counter = new AtomicInteger(0);
      private Throwable throwable;
    
      @Around("com.rxjiang.aop.custom.cb.CircuitBreakerAspect.circuitBreakerTargets()")
      public Object advice(ProceedingJoinPoint pjp) throws Throwable {
        try {
          if (counter.get() == 0 || counter.addAndGet(1) == 10) {
            Object result = pjp.proceed();
            counter.set(0);
            return result;
          }
        } catch (Throwable throwable) {
          this.throwable = throwable;
          counter.set(1);
        }
    
        throw this.throwable;
      }
    
    }

    上述代码由以下几个部门组成:

    • CircuitBreakerAspect的内部状态以及声明方式
    • Circuit Breaker的逻辑(advice方法)

    下面我们分别来看看这两个部分的具体细节。

    Prototype类型的Aspect

    @Component
    @Scope("prototype")
    @Aspect("perthis(com.rxjiang.aop.custom.cb.CircuitBreakerAspect.circuitBreakerTargets())")
    public class CircuitBreakerAspect {
      private AtomicInteger counter = new AtomicInteger(0);
      private Throwable throwable;
    
      // ...
    }

    由于该Aspect内部存在两个成员变量,即它是有状态的。因此在被多个Service使用的时候,需要使用不同的Aspect实例。因此也就有了上面的@Scope以及@Aspect中的perthis语法声明。关于perthis的作用,简而言之就是会为每个调用目标方法的Service对象都创建一个Aspect。更多信息请查看参考资料3。

    Circuit Breaker的逻辑

    大概逻辑是:

    当初次调用或者调用次数积累到一定程度(这里设定的是10次),会尝试调用目标方法。调用目标方法的过程中如果发生了异常将异常记录为成员变量然后将计数器设置为1;如果没有发生异常则将计数器清零并且返回结果。那么当下次调用目标方法的时候,有两种情况:

    1. 之前发生过异常,此时的计数器值应该大于0,并且在没有累积一定次数之前会直接抛出异常;如果积累达到10次,那么再次尝试方法调用。
    2. 之前没有发生过异常,此时的计数器应该为0,那么会正常调用目标方法。

    反映到代码中就是下面这样:

    @Around("com.rxjiang.aop.custom.cb.CircuitBreakerAspect.circuitBreakerTargets()")
    public Object advice(ProceedingJoinPoint pjp) throws Throwable {
      try {
        if (counter.get() == 0 || counter.addAndGet(1) == 10) {
          Object result = pjp.proceed();
          counter.set(0);
          return result;
        }
      } catch (Throwable throwable) {
        this.throwable = throwable;
        counter.set(1);
      }
    
      throw this.throwable;
    }

    测试方法

    为了测试Aspect是否有多个实例,创建了两个服务(CircuitBreakerService本身也是prototype类型的):

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = {CustomAopConfiguration.class})
    public class CircuitBreakerTest {
    
      @Autowired
      private CircuitBreakerService service1;
    
      @Autowired
      private CircuitBreakerService service2;
    
      @Test
      public void testCircuitBreakerService1() {
        coreCircuitBreakerService(service1);
      }
    
      @Test
      public void testCircuitBreakerService2() {
        coreCircuitBreakerService(service2);
      }
    
      public void coreCircuitBreakerService(CircuitBreakerService service) {
        for (int i = 0; i < 9; i++) {
          try {
            service.service();
            fail("不应该到这里");
          } catch (RuntimeException e) {
    
          }
        }
    
        assertEquals(1, service.service());
      }
    
    }

    相关扩展

    仔细分析上面Circuit Breaker的逻辑部分,可以提炼出下面的通用结构:

    @Around("com.rxjiang.aop.custom.cb.CircuitBreakerAspect.circuitBreakerTargets()")
    public Object advice(ProceedingJoinPoint pjp) throws Throwable {
      try {
        if (cb.isClosed()) {
          Object result = pjp.proceed();
          cb.reset();
          return result;
        }
      } catch (Throwable throwable) {
        cb.catchedException(throwable)
      }
    
      return cb.process(pjp);
    }

    那么我们可以有一个具体的CircuitBreaker对象(上述代码中的cb对象)用来处理和断路器相关的逻辑,因此可以设计这样一个接口:

    public interface ICircuitBreaker {
    
      boolean isClosed();
    
      void reset();
    
      void catchedException(Throwable throwable);
    
      Object process(ProceedingJoinPoint pjp) throws Throwable;
    
    }

    同时为了让断路器在打开的时候能够调用默认实现,可以向注解中添加一个属性:

    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface CircuitBreaker {
    
      String fallbackMethod() default "";
    
    }

    然后在断路器实现中,可以通过反射的方式去检查指定的fallback方法是否存在,如果存在并且方法接受的参数类型以及返回值类型都一致的话,就会尝试去调用默认方法,而不是直接抛出异常。

    铺垫了这么多,下面是一个基于计数器的断路器实现:

    public class CounterCircuitBreaker implements ICircuitBreaker {
    
      private int threshold;
      private AtomicInteger counter = new AtomicInteger(0);
      private Throwable throwable;
    
      public CounterCircuitBreaker(int threshold) {
        this.threshold = threshold;
      }
    
      @Override
      public boolean isClosed() {
        return counter.get() == 0 || counter.addAndGet(1) == threshold;
      }
    
      @Override
      public void reset() {
        counter.set(0);
      }
    
      @Override
      public void catchedException(Throwable throwable) {
        this.throwable = throwable;
        this.counter.set(1);
      }
    
      @Override
      public Object process(ProceedingJoinPoint pjp) throws Throwable {
        // 获取被调用的对象以及CircuitBreaker注解对象
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        String methodName = signature.getMethod().getName();
        Class<?>[] parameterTypes = signature.getMethod().getParameterTypes();
        CircuitBreaker cbAnno = pjp.getTarget().getClass().getMethod(methodName, parameterTypes)
            .getAnnotation(CircuitBreaker.class);
    
        String fallbackMethodName = cbAnno.fallbackMethod();
        if (StringUtils.isEmpty(fallbackMethodName)) {
          if (throwable != null) {
            throw throwable;
          }
        } else {
          if (fallbackExistsAndSignatureCorrect(pjp, fallbackMethodName)) {
            Method fallbackMethod = pjp.getTarget().getClass().getMethod(fallbackMethodName);
            return fallbackMethod.invoke(pjp.getTarget(), pjp.getArgs());
          } else {
            throw new IllegalArgumentException("指定的fallback方法不存在或者参数签名/返回值与目标方法不同");
          }
        }
    
        throw new IllegalArgumentException("被调对象或者方法为空");
      }
    
      private boolean fallbackExistsAndSignatureCorrect(ProceedingJoinPoint pjp,
          String fallbackMethodName) throws Throwable {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method fallbackMethod;
        try {
          fallbackMethod =
              pjp.getTarget().getClass().getMethod(fallbackMethodName, signature.getParameterTypes());
        } catch (NoSuchMethodException e) {
          return false;
        }
    
        if (fallbackMethod == null) {
          return false;
        }
    
        // 校验方法参数以及返回值是否一致
        String fbReturnType = fallbackMethod.getReturnType().getCanonicalName();
        String targetReturnType = signature.getReturnType().getCanonicalName();
        if (StringUtils.isEmpty(fbReturnType) || StringUtils.isEmpty(targetReturnType)
            || !fbReturnType.equalsIgnoreCase(targetReturnType)) {
          return false;
        }
    
        Class<?>[] fbParamTypes = fallbackMethod.getParameterTypes();
        Class<?>[] targetParamTypes = signature.getParameterTypes();
        if (fbParamTypes.length != targetParamTypes.length) {
          return false;
        }
        for (int i = 0; i < fbParamTypes.length; i++) {
          if (!fbParamTypes[i].getCanonicalName().equals(targetParamTypes[i].getCanonicalName())) {
            return false;
          }
        }
    
        return true;
      }
    
    }

    主体结构还是非常清晰的,细节部分主要是和反射相关的一些处理工作。

    相应的,在Service中定义一个fallback方法以及一个使用它的目标业务方法:

    @CircuitBreaker(fallbackMethod = "fallbackService")
    public int serviceWithFallback() {
      if (counter++ < 1) {
        throw new RuntimeException("服务不可用");
      }
    
      return 1;
    }
    
    public int fallbackService() {
      return 2;
    }

    相关测试:

    @Test
    public void testCircuitBreakerServiceWithFallback() {
      assertEquals(2, service1.serviceWithFallback());
    }

    参考资料

    1. Circuit Breaker by Martin Fowler
    2. MSDN Circuit Breaker
    3. Spring AOP Doc - Instantiation Models
    4. Hystrix
    展开全文
  • python跨目录引用模块

    2020-08-01 11:08:43
    最近学习python,导入模块时候遇到了一些问题,通过查找资料解决。做一些必要笔记,一来是对自己学习知识巩固,二来对有同样问题人有参考作用 文章目录一 跨目录调用模块二 补充说明三 总结 一 跨目录...

    最近学习python,导入模块的时候遇到了一些问题,通过查找资料解决。做一些必要的笔记,一来是对自己学习的知识的巩固,二来对有同样问题的人有参考作用



    一 跨目录调用模块

    1、调用同级目录下的模块

    目录结构:

    – src
    |– mod1.py
    |– test1.py

    若在程序 test1.py 中导入模块 mod1 , 则直接使用:

    import mod1 引入模块, 然后通过 mod1.function_name() 调用方法;

    或者通过 from mod1 import function_name 引入方法,通过 function_name() 调用方法。

    2、调用子目录下的模块

    目录结构如下:

    – src
    |– mod1.py
    |– lib
    | |– mod2.py
    |– test1.py

    这时,如果想在程序 test1.py 中导入模块 mod2.py ,可以在lib件夹中建立空文件 __init__.py 文件。

    新的目录结构如下:

    – src
    |– mod1.py
    |– lib
    | |–init.py
    | |– mod2.py
    |– test1.py

    然后使用:

    from lib import mod2 引入模块,然后通过 mod2.function_name() 调用方法;

    3、调用上级目录下的模块

    目录结构如下:

    – src
    |– mod1.py
    |– lib
    | |– mod2.py
    |– sub
    | |– test2.py

    这里想要实现 test2.py 调用 mod1.pymod2.py ,做法是我们先跳到src目录下面,然后在lib上当下建一个空文件 __init__.py ,就可以像第二步调用子目录下的模块一样,通过 import lib.mod2 进行调用了。具体代码如下:

    import sys
    sys.path.append('..')
    from lib import mod2
    

    然后就可以通过 mod2.function_name() 的方式调用方法。

    append() 方法进行追加,.. 表示回到上一级目录中去。

    再来看我们这个栗子,test2.py 回到上一级就是 src 这个目录下,这样就和lib是同一级目录了,lib 下面已经创建了__init__.py 文件,已经是个模块可以用来引入,这个时候 from lib import mod2 就没有问题可以顺利调用啦。

    4、通过绝对路径导入模块

    只要在被调用文件所属的文件夹下创建好 __init__.py 文件,然后引入 sys 模块,在用 append() 进行追加的时候直接写上需要调用文件的绝对路径。

    比如,对于调用上级目录下的模块:

    import sys
    sys.path.append('C:\\test\\src')
    import mod1
    from lib import mod2
    

    这里想要实现 test2.py 调用 mod1.pymod2.py ,做法是我们先跳到 src 目录下面,直接可以调用 mod1,然后在 lib 上当下建一个空文件 __init__.py ,就可以像第二步调用子目录下的模块一样,通过 from lib import mod2 进行调用了。

    注意: sys.path添加目录时注意是在windows还是在Linux下,windows下需要 ‘\\’ 否则会出错。

    二 补充说明

    1、_init_.py

    一个包是一个带有特殊文件 __init__.py 的目录。 __init__.py 文件定义了包的属性和方法。其实它可以什么也不定义;可以只是一个空文件,但是必须存在。如果 __init__.py 不存在,这个目录就仅仅是一个目录,而不是一个包,它就不能被导入或者包含其它的模块和嵌套包。

    2、.pyc文件

    执行起来 .pyc.py 是一样的, .py 是直接以源码的形式进行呈现,而 .pyc 只是字节码文件。


    三 总结

    如有错误恳请指正,如有侵权请联系我删除
    参考文章: Python模块(跨目录)调用总结
                   Python在不同目录下导入模块的方法

    展开全文
  • Java语言程序设计课程期末复习资料 一客观部分单项选择多项选择不定项选择判断 二简答 1基本类型变量与引用型变量有何区别(p31) 2什么静态变量什么是静态方法一般通过什么方式访问静态变量和静态方法(p119,p123) 3...
  • C#微软培训资料

    2014-01-22 14:10:17
    C#语言在.NET 框架中的作用及其特性 1.1 Microsoft.NET 一场新的革命 1.1.1 什么是.NET 2000 年 6 月 22 日 不论对 Microsoft 还是对整个 IT 业界都将成为值得纪念的一天 这一天 微软公司正式推出...
  • 说明*所引用的论点、资料和数据均有出处可查,以便读者核查。 4、向读者推荐一批经过精选文献。参考文献能为读者深入探讨某些问题提供有关文献线索,帮助其查阅原始文献,进一步研读作者引用的内容,以求证自己...
  • 整理了下关于php基础知识,参考了些资料,如下: ...$GLOBALS — 引用全局作用域中可用全部变量 说明 一个包含了全部变量全局组合数组。变量名字就是数组键。 范例 <?php function test() { $fo...
        

    整理了下关于php的基础知识,参考了些资料,如下:

    超全局变量

    超全局变量 — 超全局变量是在全部作用域中始终可用的内置变量:

    $GLOBALS

    $GLOBALS — 引用全局作用域中可用的全部变量

    • 说明

    一个包含了全部变量的全局组合数组。变量的名字就是数组的键。

    • 范例
    <?php
    function test() {
        $foo = "local variable";
    
        echo '$foo in global scope: ' . $GLOBALS["foo"] . "\n";
        echo '$foo in current scope: ' . $foo . "\n";
    }
    
    $foo = "Example content";
    test();
    ?>
    

    以上例程的输出类似于:

    $foo in global scope: Example content
    $foo in current scope: local variable
    

    $_SERVER

    $_SERVER -- $HTTP_SERVER_VARS [已删除] — 服务器和执行环境信息

    • 说明

    $_SERVER 是一个包含了诸如头信息(header)、路径(path)、以及脚本位置(script locations)等等信息的数组。这个数组中的项目由 Web 服务器创建。不能保证每个服务器都提供全部项目;服务器可能会忽略一些,或者提供一些没有在这里列举出来的项目。这也就意味着大量的此类变量都会在» CGI 1.1 规范中说明,所以应该仔细研究一下。

    Note: PHP 5.4.0 之前,$HTTP_SERVER_VARS 包含着相同的信息,但它不是一个超全局变量。 (注意
    $HTTP_SERVER_VARS 与 $_SERVER 是不同的变量,PHP处理它们的方式不同)
    • 目录

    在 $_SERVER 中,你也许能够,也许不能够找到下面的这些元素。注意,如果以命令行方式运行 PHP,下面列出的元素几乎没有有效的(或是没有任何实际意义的)。

    'PHP_SELF'
    当前执行脚本的文件名,与 document root 有关。例如,在地址为 http://example.com/foo/bar.php 的脚本中使用 $_SERVER['PHP_SELF'] 将得到 /foo/bar.php。__FILE__ 常量包含当前(例如包含)文件的完整路径和文件名。 从 PHP 4.3.0 版本开始,如果 PHP 以命令行模式运行,这个变量将包含脚本名。之前的版本该变量不可用。
    'argv'
    传递给该脚本的参数的数组。当脚本以命令行方式运行时,argv 变量传递给程序 C 语言样式的命令行参数。当通过 GET 方式调用时,该变量包含query string。
    'argc'
    包含命令行模式下传递给该脚本的参数的数目(如果运行在命令行模式下)。
    'GATEWAY_INTERFACE'
    服务器使用的 CGI 规范的版本;例如,“CGI/1.1”。
    'SERVER_ADDR'
    当前运行脚本所在的服务器的 IP 地址。
    'SERVER_NAME'
    当前运行脚本所在的服务器的主机名。如果脚本运行于虚拟主机中,该名称是由那个虚拟主机所设置的值决定。
    Note: 在 Apache 2 里,必须设置 UseCanonicalName = On 和 ServerName。 否则该值会由客户端提供,就有可能被伪造。 上下文有安全性要求的环境里,不应该依赖此值。
    'SERVER_SOFTWARE'
    服务器标识字符串,在响应请求时的头信息中给出。
    'SERVER_PROTOCOL'
    请求页面时通信协议的名称和版本。例如,“HTTP/1.0”。
    'REQUEST_METHOD'
    访问页面使用的请求方法;例如,“GET”, “HEAD”,“POST”,“PUT”。
    Note:如果请求方法为 HEAD,PHP 脚本将在发送 Header 头信息之后终止(这意味着在产生任何输出后,不再有输出缓冲)。
    'REQUEST_TIME'
    请求开始时的时间戳。从 PHP 5.1.0 起可用。
    'REQUEST_TIME_FLOAT'
    请求开始时的时间戳,微秒级别的精准度。 自 PHP 5.4.0 开始生效。
    'QUERY_STRING'
    query string(查询字符串),如果有的话,通过它进行页面访问。
    'DOCUMENT_ROOT'
    当前运行脚本所在的文档根目录。在服务器配置文件中定义。
    'HTTP_ACCEPT'
    当前请求头中 Accept: 项的内容,如果存在的话。
    'HTTP_ACCEPT_CHARSET'
    当前请求头中 Accept-Charset: 项的内容,如果存在的话。例如:“iso-8859-1,*,utf-8”。
    'HTTP_ACCEPT_ENCODING'
    当前请求头中 Accept-Encoding: 项的内容,如果存在的话。例如:“gzip”。
    'HTTP_ACCEPT_LANGUAGE'
    当前请求头中 Accept-Language: 项的内容,如果存在的话。例如:“en”。
    'HTTP_CONNECTION'
    当前请求头中 Connection: 项的内容,如果存在的话。例如:“Keep-Alive”。
    'HTTP_HOST'
    当前请求头中 Host: 项的内容,如果存在的话。
    'HTTP_REFERER'
    引导用户代理到当前页的前一页的地址(如果存在)。由 user agent 设置决定。并不是所有的用户代理都会设置该项,有的还提供了修改 HTTP_REFERER 的功能。简言之,该值并不可信。
    'HTTP_USER_AGENT'
    当前请求头中 User-Agent: 项的内容,如果存在的话。该字符串表明了访问该页面的用户代理的信息。一个典型的例子是:Mozilla/4.5 [en] (X11; U; Linux 2.2.9 i586)。除此之外,你可以通过 get_browser() 来使用该值,从而定制页面输出以便适应用户代理的性能。
    'HTTPS'
    如果脚本是通过 HTTPS 协议被访问,则被设为一个非空的值。
    Note: 注意当使用 IIS 上的 ISAPI 方式时,如果不是通过 HTTPS 协议被访问,这个值将为 off。
    'REMOTE_ADDR'
    浏览当前页面的用户的 IP 地址。
    'REMOTE_HOST'
    浏览当前页面的用户的主机名。DNS 反向解析不依赖于用户的 REMOTE_ADDR。
    Note: 你的服务器必须被配置以便产生这个变量。例如在 Apache 中,你需要在 httpd.conf 中设置 HostnameLookups On 来产生它。参见 gethostbyaddr()。
    'REMOTE_PORT'
    用户机器上连接到 Web 服务器所使用的端口号。
    'REMOTE_USER'
    经验证的用户
    'REDIRECT_REMOTE_USER'
    验证的用户,如果请求已在内部重定向。
    'SCRIPT_FILENAME'
    当前执行脚本的绝对路径。
    Note:如果在命令行界面(Command Line Interface, CLI)使用相对路径执行脚本,例如 file.php 或 ../file.php,那么 $_SERVER['SCRIPT_FILENAME'] 将包含用户指定的相对路径。
    'SERVER_ADMIN'
    该值指明了 Apache 服务器配置文件中的 SERVER_ADMIN 参数。如果脚本运行在一个虚拟主机上,则该值是那个虚拟主机的值。
    'SERVER_PORT'
    Web 服务器使用的端口。默认值为 “80”。如果使用 SSL 安全连接,则这个值为用户设置的 HTTP 端口。
    Note: 在 Apache 2 里,为了获取真实物理端口,必须设置 UseCanonicalName = On 以及 UseCanonicalPhysicalPort = On。 否则此值可能被伪造,不一定会返回真实端口值。 上下文有安全性要求的环境里,不应该依赖此值。
    'SERVER_SIGNATURE'
    包含了服务器版本和虚拟主机名的字符串。
    'PATH_TRANSLATED'
    当前脚本所在文件系统(非文档根目录)的基本路径。这是在服务器进行虚拟到真实路径的映像后的结果。
    Note: 自 PHP 4.3.2 起,PATH_TRANSLATED 在 Apache 2 SAPI 模式下不再和 Apache 1 一样隐含赋值,而是若 Apache 不生成此值,PHP 便自己生成并将其值放入 SCRIPT_FILENAME 服务器常量中。这个修改遵守了 CGI 规范,PATH_TRANSLATED 仅在 PATH_INFO 被定义的条件下才存在。 Apache 2 用户可以在 httpd.conf 中设置 AcceptPathInfo = On 来定义 PATH_INFO。
    'SCRIPT_NAME'
    包含当前脚本的路径。这在页面需要指向自己时非常有用。__FILE__ 常量包含当前脚本(例如包含文件)的完整路径和文件名。
    'REQUEST_URI'
    URI 用来指定要访问的页面。例如 “/index.html”。
    'PHP_AUTH_DIGEST'
    当作为 Apache 模块运行时,进行 HTTP Digest 认证的过程中,此变量被设置成客户端发送的“Authorization” HTTP 头内容(以便作进一步的认证操作)。
    'PHP_AUTH_USER'
    当 PHP 运行在 Apache 或 IIS(PHP 5 是 ISAPI)模块方式下,并且正在使用 HTTP 认证功能,这个变量便是用户输入的用户名。
    'PHP_AUTH_PW'
    当 PHP 运行在 Apache 或 IIS(PHP 5 是 ISAPI)模块方式下,并且正在使用 HTTP 认证功能,这个变量便是用户输入的密码。
    'AUTH_TYPE'
    当 PHP 运行在 Apache 模块方式下,并且正在使用 HTTP 认证功能,这个变量便是认证的类型。
    'PATH_INFO'
    包含由客户端提供的、跟在真实脚本名称之后并且在查询语句(query string)之前的路径信息,如果存在的话。例如,如果当前脚本是通过 URL http://www.example.com/php/pa... 被访问,那么 $_SERVER['PATH_INFO'] 将包含 /some/stuff。
    'ORIG_PATH_INFO'
    在被 PHP 处理之前,“PATH_INFO” 的原始版本。

    • 范例
    <?php
    echo $_SERVER['SERVER_NAME'];
    ?>
    

    以上例程的输出类似于:

    www.example.com
    

    $_GET

    $_GET — HTTP GET 变量

    • 说明

    通过 URL 参数传递给当前脚本的变量的数组。

    • 范例
    <?php
    echo 'Hello ' . htmlspecialchars($_GET["name"]) . '!';
    ?>
    

    假设用户访问的是 http://example.com/?name=Hannes

    以上例程的输出类似于:

    Hello Hannes!
    

    $_POST

    $_POST — HTTP POST 变量

    • 说明

    当 HTTP POST 请求的 Content-Type 是 application/x-www-form-urlencoded 或 multipart/form-data 时,会将变量以关联数组形式传入当前脚本。

    $HTTP_POST_VARS 包含相同的信息,但它不是一个超全局变量。 (注意 $HTTP_POST_VARS 和 $_POST 是不同的变量,PHP 处理它们的方式不同)

    • 范例
    <?php
    echo 'Hello ' . htmlspecialchars($_POST["name"]) . '!';
    ?>
    

    假设用户通过 HTTP POST 方式传递了参数 name=Hannes

    以上例程的输出类似于:

    Hello Hannes!
    

    $_FILES

    $_FILES — HTTP 文件上传变量

    • 说明

    通过 HTTP POST 方式上传到当前脚本的项目的数组。 此数组的概况在 POST 方法上传 章节中有描述。

    $HTTP_POST_FILES 包含相同的信息,但它不是一个超全局变量。 (注意 $HTTP_POST_FILES 和 $_FILES 是不同的变量,PHP 处理它们的方式不同)

    $_REQUEST

    $_REQUEST — HTTP Request 变量

    • 说明

    默认情况下包含了 $_GET,$_POST 和 $_COOKIE 的数组。

    $_SESSION

    $_SESSION — Session 变量

    • 说明

    当前脚本可用 SESSION 变量的数组。更多关于如何使用的信息,参见 Session 函数 文档。

    $HTTP_SESSION_VARS 包含相同的信息,但它不是一个超全局变量。 (注意 $HTTP_SESSION_VARS 和 $_SESSION 是不同的变量,PHP 处理它们的方式不同)

    $_ENV

    $_ENV -- $HTTP_ENV_VARS [已弃用] — 环境变量

    • 说明

    通过环境方式传递给当前脚本的变量的数组。

    这些变量被从 PHP 解析器的运行环境导入到 PHP 的全局命名空间。很多是由支持 PHP 运行的 Shell 提供的,并且不同的系统很可能运行着不同种类的 Shell,所以不可能有一份确定的列表。请查看你的 Shell 文档来获取定义的环境变量列表。

    其他环境变量包含了 CGI 变量,而不管 PHP 是以服务器模块还是 CGI 处理器的方式运行。

    $HTTP_ENV_VARS 包含相同的信息,但它不是一个超全局变量。 (注意 $HTTP_ENV_VARS 和 $_ENV 是不同的变量,PHP 处理它们的方式不同)

    • 范例
    <?php
    echo 'My username is ' .$_ENV["USER"] . '!';
    ?>
    

    假设 "bjori" 运行此段脚本

    以上例程的输出类似于:

    My username is bjori!
    

    $_COOKIE

    $_COOKIE -- $HTTP_COOKIE_VARS [已弃用] — HTTP Cookies

    • 说明

    通过 HTTP Cookies 方式传递给当前脚本的变量的数组。

    $HTTP_COOKIE_VARS 包含相同的信息,但它不是一个超全局变量。 (注意 $HTTP_COOKIE_VARS 和 $_COOKIE 是不同的变量,PHP 处理它们的方式不同)

    • 范例
    <?php
    echo 'Hello ' . htmlspecialchars($_COOKIE["name"]) . '!';
    ?>
    

    假设之前发送了 "name" Cookie

    以上例程的输出类似于:

    Hello Hannes!
    

    魔术常量

    PHP 向它运行的任何脚本提供了大量的预定义常量。不过很多常量都是由不同的扩展库定义的,只有在加载了这些扩展库时才会出现,或者动态加载后,或者在编译时已经包括进去了。

    有八个魔术常量它们的值随着它们在代码中的位置改变而改变。例如 __LINE__ 的值就依赖于它在脚本中所处的行来决定。这些特殊的常量不区分大小写,如下:

    几个 PHP 的“魔术常量”


    名称 说明
    __LINE__ 文件中的当前行号。
    __FILE__ 文件的完整路径和文件名。如果用在被包含文件中,则返回被包含的文件名。自 PHP 4.0.2 起,__FILE__总是包含一个绝对路径(如果是符号连接,则是解析后的绝对路径),而在此之前的版本有时会包含一个相对路径。
    __DIR__ 文件所在的目录。如果用在被包括文件中,则返回被包括的文件所在的目录。它等价于 dirname(__FILE__)。除非是根目录,否则目录中名不包括末尾的斜杠。(PHP 5.3.0中新增)
    __FUNCTION__ 函数名称(PHP 4.3.0 新加)。自 PHP 5 起本常量返回该函数被定义时的名字(区分大小写)。在 PHP 4 中该值总是小写字母的。
    __CLASS__ 类的名称(PHP 4.3.0 新加)。自 PHP 5 起本常量返回该类被定义时的名字(区分大小写)。在 PHP 4 中该值总是小写字母的。类名包括其被声明的作用区域(例如 FooBar)。注意自 PHP 5.4 起 __CLASS__ 对 trait 也起作用。当用在 trait 方法中时,__CLASS__ 是调用 trait 方法的类的名字。
    __TRAIT__ Trait 的名字(PHP 5.4.0 新加)。自 PHP 5.4 起此常量返回 trait 被定义时的名字(区分大小写)。Trait 名包括其被声明的作用区域(例如 FooBar)。
    __METHOD__ 类的方法名(PHP 5.0.0 新加)。返回该方法被定义时的名字(区分大小写)。
    __NAMESPACE__ 当前命名空间的名称(区分大小写)。此常量是在编译时定义的(PHP 5.3.0 新增)。
    参见 get_class(),get_object_vars(),file_exists() 和 function_exists()。

    魔术方法

    以下内容转自脚本之家PHP之十六个魔术方法详细介绍,如有侵权,联系删除。
    PHP中把以两个下划线__开头的方法称为魔术方法(Magic methods),这些方法在PHP中充当了举足轻重的作用。 魔术方法包括:

    方法名 说明
    __construct() 类的构造函数
    __destruct() 类的析构函数
    __call() 在对象中调用一个不可访问方法时调用
    __callStatic() 用静态方式中调用一个不可访问方法时调用
    __get() 获得一个类的成员变量时调用
    __set() 设置一个类的成员变量时调用
    __isset() 当对不可访问属性调用isset()或empty()时调用
    __unset() 当对不可访问属性调用unset()时被调用。
    __sleep() 执行serialize()时,先会调用这个函数
    __wakeup() 执行unserialize()时,先会调用这个函数
    __toString() 类被当成字符串时的回应方法
    __invoke() 调用函数的方式调用一个对象时的回应方法
    __set_state() 调用var_export()导出类时,此静态方法会被调用。
    __clone() 当对象复制完成时调用
    __autoload() 尝试加载未定义的类
    __debugInfo() 打印所需调试信息

    下面让我们以实例的形式向大家讲解下这几个魔术方法时如何使用的。

    一、 __construct(),类的构造函数

    php中构造方法是对象创建完成后第一个被对象自动调用的方法。在每个类中都有一个构造方法,如果没有显示地声明它,那么类中都会默认存在一个没有参数且内容为空的构造方法。

    1、 构造方法的作用

    通常构造方法被用来执行一些有用的初始化任务,如对成员属性在创建对象时赋予初始值。

    2、 构造方法的在类中的声明格式

    function __constrct([参数列表]){
    
      方法体 //通常用来对成员属性进行初始化赋值
    }
    

    3、 在类中声明构造方法需要注意的事项

    1、在同一个类中只能声明一个构造方法,原因是,PHP不支持构造函数重载

    2、构造方法名称是以两个下画线开始的__construct()
    下面是它的例子:

    <?php
      class Person
      {                                   
          public $name;    
          public $age;    
          public $sex;    
                                     
        /**
         * 显示声明一个构造方法且带参数
         */                                            
        public function __construct($name="", $sex="男", $age=22)
        {   
          $this->name = $name;
          $this->sex = $sex;
          $this->age = $age;
        }
        
        /**
         * say 方法
         */
        public function say()
        { 
          echo "我叫:" . $this->name . ",性别:" . $this->sex . ",年龄:" . $this->age;
        }  
                                                  
      }
    
    创建对象$Person1且不带任参数
    
    $Person1 = new Person();
    echo $Person1->say(); //输出:我叫:,性别:男,年龄:27
    创建对象$Person2且带参数“小明”
    
    $Person2 = new Person("小明");
    echo $Person2->say(); //输出:我叫:张三,性别:男,年龄:27
    创建对象$Person3且带三个参数
    
    $Person3 = new Person("李四","男",25);
    echo $Person3->say(); //输出:我叫:李四,性别:男,年龄:25
    

    二、__destruct(),类的析构函数

    通过上面的讲解,现在我们已经知道了什么叫构造方法。那么与构造方法对应的就是析构方法。

    析构方法允许在销毁一个类之前执行的一些操作或完成一些功能,比如说关闭文件、释放结果集等。

    析构方法是PHP5才引进的新内容。

    析造方法的声明格式与构造方法 __construct() 比较类似,也是以两个下划线开始的方法 __destruct() ,这种析构方法名称也是固定的。

    1、 析构方法的声明格式

    function __destruct()
    {
     //方法体
    }
    

    注意:析构函数不能带有任何参数。

    2、 析构方法的作用

    一般来说,析构方法在PHP中并不是很常用,它属类中可选择的一部分,通常用来完成一些在对象销毁前的清理任务
    举例演示,如下:

    <?php
    class Person{   
                                
      public $name;     
      public $age;     
      public $sex;     
                                      
      public function __construct($name="", $sex="男", $age=22)
      {  
        $this->name = $name;
        $this->sex = $sex;
        $this->age = $age;
      }
      
      /**
       * say 说话方法
       */
      public function say()
      { 
        echo "我叫:".$this->name.",性别:".$this->sex.",年龄:".$this->age;
      }  
      
      /**
       * 声明一个析构方法
       */
      public function __destruct()
      {
          echo "我觉得我还可以再抢救一下,我的名字叫".$this->name;
      }
    }
    
    $Person = new Person("小明");
    unset($Person); //销毁上面创建的对象$Person
    

    上面的程序运行时输出:

    我觉得我还可以再抢救一下,我的名字叫小明
    

    三、 __call(),在对象中调用一个不可访问方法时调用。

    该方法有两个参数,第一个参数 $function_name 会自动接收不存在的方法名,第二个 $arguments 则以数组的方式接收不存在方法的多个参数。

    1、 __call() 方法的格式:

    function __call(string $function_name, array $arguments)
    {
      // 方法体
    }
    

    2、 __call() 方法的作用:

    为了避免当调用的方法不存在时产生错误,而意外的导致程序中止,可以使用 __call() 方法来避免。

    该方法在调用的方法不存在时会自动调用,程序仍会继续执行下去。
    请参考如下代码:

    <?php
    class Person
    {               
      function say()
      { 
                   
          echo "Hello, world!<br>"; 
      }   
        
      /**
       * 声明此方法用来处理调用对象中不存在的方法
       */
      function __call($funName, $arguments)
      { 
         echo "你所调用的函数:" . $funName . "(参数:" ; // 输出调用不存在的方法名
         print_r($arguments); // 输出调用不存在的方法时的参数列表
         echo ")不存在!<br>\n"; // 结束换行           
      }                     
    }
    $Person = new Person();      
    $Person->run("teacher"); // 调用对象中不存在的方法,则自动调用了对象中的__call()方法
    $Person->eat("小明", "苹果");       
    $Person->say(); 
    
    

    运行结果:

    你所调用的函数:run(参数:Array ( [0] => teacher ) )不存在!
    
    你所调用的函数:eat(参数:Array ( [0] => 小明 [1] => 苹果 ) )不存在!
    
    Hello, world!
    

    四、 __callStatic(),用静态方式中调用一个不可访问方法时调用

    此方法与上面所说的 __call() 功能除了 __callStatic() 是为静态方法准备的之外,其它都是一样的。

    请看下面代码:

    <?php
    class Person
    {
      function say()
      {
    
        echo "Hello, world!<br>";
      }
    
      /**
       * 声明此方法用来处理调用对象中不存在的方法
       */
      public static function __callStatic($funName, $arguments)
      {
        echo "你所调用的静态方法:" . $funName . "(参数:" ; // 输出调用不存在的方法名
        print_r($arguments); // 输出调用不存在的方法时的参数列表
        echo ")不存在!<br>\n"; // 结束换行
      }
    }
    $Person = new Person();
    $Person::run("teacher"); // 调用对象中不存在的方法,则自动调用了对象中的__call()方法
    $Person::eat("小明", "苹果");
    $Person->say();
    

    运行结果如下:

    你所调用的静态方法:run(参数:Array ( [0] => teacher ) )不存在!
    你所调用的静态方法:eat(参数:Array ( [0] => 小明 [1] => 苹果 ) )不存在!
    Hello, world!
    

    五、 __get(),获得一个类的成员变量时调用

    在 php 面向对象编程中,类的成员属性被设定为 private 后,如果我们试图在外面调用它则会出现“不能访问某个私有属性”的错误。那么为了解决这个问题,我们可以使用魔术方法 __get()。

    魔术方法__get()的作用
    在程序运行过程中,通过它可以在对象的外部获取私有成员属性的值。
    我们通过下面的 __get() 的实例来更进一步的连接它吧:

    <?php
    class Person
    {
      private $name;
      private $age;
    
      function __construct($name="", $age=1)
      {
        $this->name = $name;
        $this->age = $age;
      }
    
      /**
       * 在类中添加__get()方法,在直接获取属性值时自动调用一次,以属性名作为参数传入并处理
       * @param $propertyName
       *
       * @return int
       */
      public function __get($propertyName)
      {  
        if ($propertyName == "age") {
          if ($this->age > 30) {
            return $this->age - 10;
          } else {
            return $this->$propertyName;
          }
        } else {
          return $this->$propertyName;
        }
      }
    }
    $Person = new Person("小明", 60);  // 通过Person类实例化的对象,并通过构造方法为属性赋初值
    echo "姓名:" . $Person->name . "<br>";  // 直接访问私有属性name,自动调用了__get()方法可以间接获取
    echo "年龄:" . $Person->age . "<br>";  // 自动调用了__get()方法,根据对象本身的情况会返回不同的值
    

    运行结果:

    姓名:小明
    年龄:50
    

    六、 __set(),设置一个类的成员变量时调用

    __set() 的作用:
    __set( $property, $value )` 方法用来设置私有属性, 给一个未定义的属性赋值时,此方法会被触发,传递的参数是被设置的属性名和值。

    请看下面的演示代码:

    <?php
    class Person
    {
      private $name;
      private $age;
    
      public function __construct($name="", $age=25)
      {
        $this->name = $name;
        $this->age = $age;
      }
    
      /**
       * 声明魔术方法需要两个参数,真接为私有属性赋值时自动调用,并可以屏蔽一些非法赋值
       * @param $property
       * @param $value
       */
      public function __set($property, $value) {
        if ($property=="age")
        {
          if ($value > 150 || $value < 0) {
            return;
          }
        }
        $this->$property = $value;
      }
    
      /**
       * 在类中声明说话的方法,将所有的私有属性说出
       */
      public function say(){
        echo "我叫".$this->name.",今年".$this->age."岁了";
      }
    }
    
    $Person=new Person("小明", 25); //注意,初始值将被下面所改变
    //自动调用了__set()函数,将属性名name传给第一个参数,将属性值”李四”传给第二个参数
    $Person->name = "小红";   //赋值成功。如果没有__set(),则出错。
    //自动调用了__set()函数,将属性名age传给第一个参数,将属性值26传给第二个参数
    $Person->age = 16; //赋值成功
    $Person->age = 160; //160是一个非法值,赋值失效
    $Person->say(); //输出:我叫小红,今年16岁了
    

    运行结果:

    我叫小红,今年16岁了
    

    七、 __isset(),当对不可访问属性调用isset()或empty()时调用

    在看这个方法之前我们看一下isset()函数的应用,isset()是测定变量是否设定用的函数,传入一个变量作为参数,如果传入的变量存在则传回true,否则传回false。

    那么如果在一个对象外面使用isset()这个函数去测定对象里面的成员是否被设定可不可以用它呢?

    分两种情况,如果对象里面成员是公有的,我们就可以使用这个函数来测定成员属性,如果是私有的成员属性,这个函数就不起作用了,原因就是因为私有的被封装了,在外部不可见。那么我们就不可以在对象的外部使用isset()函数来测定私有成员属性是否被设定了呢?当然是可以的,但不是一成不变。你只要在类里面加上一个__isset()方法就可以了,当在类外部使用isset()函数来测定对象里面的私有成员是否被设定时,就会自动调用类里面的__isset()方法了帮我们完成这样的操作。

    __isset()的作用:当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用
    请看下面代码演示:

    <?php
    class Person
    {
      public $sex;
      private $name;
      private $age;
    
      public function __construct($name="", $age=25, $sex='男')
      {
        $this->name = $name;
        $this->age = $age;
        $this->sex = $sex;
      }
    
      /**
       * @param $content
       *
       * @return bool
       */
      public function __isset($content) {
        echo "当在类外部使用isset()函数测定私有成员{$content}时,自动调用<br>";
        echo isset($this->$content);
      }
    }
    
    $person = new Person("小明", 25); // 初始赋值
    echo isset($person->sex),"<br>";
    echo isset($person->name),"<br>";
    echo isset($person->age),"<br>";
    

    运行结果如下:

    1 // public 可以 isset()
    当在类外部使用isset()函数测定私有成员name时,自动调用 // __isset() 内 第一个echo
    1 // __isset() 内第二个echo
    当在类外部使用isset()函数测定私有成员age时,自动调用 // __isset() 内 第一个echo
    1 // __isset() 内第二个echo
    

    八、 __unset(),当对不可访问属性调用unset()时被调用。

    看这个方法之前呢,我们也先来看一下 unset() 函数,unset()这个函数的作用是删除指定的变量且传回true,参数为要删除的变量。

    那么如果在一个对象外部去删除对象内部的成员属性用unset()函数可以吗?

    这里自然也是分两种情况:

    1、 如果一个对象里面的成员属性是公有的,就可以使用这个函数在对象外面删除对象的公有属性。

    2、 如果对象的成员属性是私有的,我使用这个函数就没有权限去删除。

    虽然有以上两种情况,但我想说的是同样如果你在一个对象里面加上__unset()这个方法,就可以在对象的外部去删除对象的私有成员属性了。在对象里面加上了__unset()这个方法之后,在对象外部使用“unset()”函数删除对象内部的私有成员属性时,对象会自动调用__unset()函数来帮我们删除对象内部的私有成员属性。

    请看如下代码:

    <?php
    class Person
    {
      public $sex;
      private $name;
      private $age;
    
      public function __construct($name="", $age=25, $sex='男')
      {
        $this->name = $name;
        $this->age = $age;
        $this->sex = $sex;
      }
    
      /**
       * @param $content
       *
       * @return bool
       */
      public function __unset($content) {
        echo "当在类外部使用unset()函数来删除私有成员时自动调用的<br>";
        echo isset($this->$content);
      }
    }
    
    $person = new Person("小明", 25); // 初始赋值
    unset($person->sex);
    unset($person->name);
    unset($person->age);
    

    运行结果:

    当在类外部使用unset()函数来删除私有成员时自动调用的
    1当在类外部使用unset()函数来删除私有成员时自动调用的
    

    九、 __sleep(),执行serialize()时,先会调用这个函数

    serialize() 函数会检查类中是否存在一个魔术方法 __sleep()。如果存在,则该方法会优先被调用,然后才执行序列化操作。

    此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。

    如果该方法未返回任何内容,则 NULL 被序列化,并产生一个 E_NOTICE 级别的错误。

    注意:

    __sleep() 不能返回父类的私有成员的名字。这样做会产生一个 E_NOTICE 级别的错误。可以用 Serializable 接口来替代。
    作用:

    __sleep() 方法常用于提交未提交的数据,或类似的清理操作。同时,如果有一些很大的对象,但不需要全部保存,这个功能就很好用。
    具体请参考如下代码:

    <?php
    class Person
    {
      public $sex;
      public $name;
      public $age;
    
      public function __construct($name="", $age=25, $sex='男')
      {
        $this->name = $name;
        $this->age = $age;
        $this->sex = $sex;
      }
    
      /**
       * @return array
       */
      public function __sleep() {
        echo "当在类外部使用serialize()时会调用这里的__sleep()方法<br>";
        $this->name = base64_encode($this->name);
        return array('name', 'age'); // 这里必须返回一个数值,里边的元素表示返回的属性名称
      }
    }
    
    $person = new Person('小明'); // 初始赋值
    echo serialize($person);
    echo '<br/>';
    

    代码运行结果:

    当在类外部使用serialize()时会调用这里的__sleep()方法
    O:6:"Person":2:{s:4:"name";s:8:"5bCP5piO";s:3:"age";i:25;}
    

    十、 __wakeup(),执行unserialize()时,先会调用这个函数

    如果说 __sleep() 是白的,那么 __wakeup() 就是黑的了。

    那么为什么呢?

    因为:

    与之相反,unserialize() 会检查是否存在一个 __wakeup() 方法。如果存在,则会先调用 __wakeup 方法,预先准备对象需要的资源。
    作用:
    __wakeup() 经常用在反序列化操作中,例如重新建立数据库连接,或执行其它初始化操作。
    还是看代码:

    <?php
    class Person
    {
      public $sex;
      public $name;
      public $age;
    
      public function __construct($name="", $age=25, $sex='男')
      {
        $this->name = $name;
        $this->age = $age;
        $this->sex = $sex;
      }
    
      /**
       * @return array
       */
      public function __sleep() {
        echo "当在类外部使用serialize()时会调用这里的__sleep()方法<br>";
        $this->name = base64_encode($this->name);
        return array('name', 'age'); // 这里必须返回一个数值,里边的元素表示返回的属性名称
      }
    
      /**
       * __wakeup
       */
      public function __wakeup() {
        echo "当在类外部使用unserialize()时会调用这里的__wakeup()方法<br>";
        $this->name = 2;
        $this->sex = '男';
        // 这里不需要返回数组
      }
    }
    
    $person = new Person('小明'); // 初始赋值
    var_dump(serialize($person));
    var_dump(unserialize(serialize($person)));
    

    运行结果:

    当在类外部使用serialize()时会调用这里的__sleep()方法
    string(58) "O:6:"Person":2:{s:4:"name";s:8:"5bCP5piO";s:3:"age";i:25;}" 当在类外部使用serialize()时会调用这里的__sleep()方法
    当在类外部使用unserialize()时会调用这里的__wakeup()方法
    object(Person)#2 (3) { ["sex"]=> string(3) "男" ["name"]=> int(2) ["age"]=> int(25) }
    

    十一、 __toString(),类被当成字符串时的回应方法

    作用:

    __toString() 方法用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。
    注意:

    此方法必须返回一个字符串,否则将发出一条 E_RECOVERABLE_ERROR 级别的致命错误。
    警告:

    不能在 __toString() 方法中抛出异常。这么做会导致致命错误。
    代码:

    <?php
    class Person
    {
      public $sex;
      public $name;
      public $age;
    
      public function __construct($name="", $age=25, $sex='男')
      {
        $this->name = $name;
        $this->age = $age;
        $this->sex = $sex;
      }
    
      public function __toString()
      {
        return 'go go go';
      }
    }
    
    $person = new Person('小明'); // 初始赋值
    echo $person;
    

    结果:

    go go go
    

    那么如果类中没有 __toString() 这个魔术方法运行会发生什么呢?让我们来测试下:

    代码:

    <?php
    class Person
    {
      public $sex;
      public $name;
      public $age;
    
      public function __construct($name="", $age=25, $sex='男')
      {
        $this->name = $name;
        $this->age = $age;
        $this->sex = $sex;
      }
      
    }
    
    $person = new Person('小明'); // 初始赋值
    echo $person;
    

    结果:

    Catchable fatal error: Object of class Person could not be converted to string in D:\phpStudy\WWW\test\index.php on line 18
    很明显,页面报了一个致命错误,这是语法所不允许的。
    

    十二、 __invoke(),调用函数的方式调用一个对象时的回应方法

    作用:

    尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。
    注意:

    本特性只在 PHP 5.3.0 及以上版本有效。
    直接上代码:

    <?php
    class Person
    {
      public $sex;
      public $name;
      public $age;
    
      public function __construct($name="", $age=25, $sex='男')
      {
        $this->name = $name;
        $this->age = $age;
        $this->sex = $sex;
      }
    
      public function __invoke() {
        echo '这可是一个对象哦';
      }
    
    }
    
    $person = new Person('小明'); // 初始赋值
    $person();
    

    查看运行结果:

    这可是一个对象哦
    

    当然,如果你执意要将对象当函数方法使用,那么会得到下面结果:

    Fatal error: Function name must be a string in D:\phpStudy\WWW\test\index.php on line 18
    

    十三、 __set_state(),调用var_export()导出类时,此静态方法会被调用。

    作用:

    自 PHP 5.1.0 起,当调用 var_export() 导出类时,此静态方法会被自动调用。
    参数:

    本方法的唯一参数是一个数组,其中包含按 array('property' => value, ...) 格式排列的类属性。
    下面我们先来看看在没有加 __set_state() 情况按下,代码及运行结果如何:

    上代码:

    <?php
    class Person
    {
      public $sex;
      public $name;
      public $age;
    
      public function __construct($name="", $age=25, $sex='男')
      {
        $this->name = $name;
        $this->age = $age;
        $this->sex = $sex;
      }
    
    }
    
    $person = new Person('小明'); // 初始赋值
    var_export($person);
    

    看结果:

    Person::__set_state(array( 'sex' => '男', 'name' => '小明', 'age' => 25, ))
    

    很明显,将对象中的属性都打印出来了

    加了 __set_state() 之后:

    继续上代码:

    <?php
    class Person
    {
      public $sex;
      public $name;
      public $age;
    
      public function __construct($name="", $age=25, $sex='男')
      {
        $this->name = $name;
        $this->age = $age;
        $this->sex = $sex;
      }
    
      public static function __set_state($an_array)
      {
        $a = new Person();
        $a->name = $an_array['name'];
        return $a;
      }
    
    }
    
    $person = new Person('小明'); // 初始赋值
    $person->name = '小红';
    var_export($person);
    

    继续看结果:

    Person::__set_state(array( 'sex' => '男', 'name' => '小红', 'age' => 25, ))
    

    十四、 __clone(),当对象复制完成时调用

    在多数情况下,我们并不需要完全复制一个对象来获得其中属性。但有一个情况下确实需要:如果你有一个 GTK 窗口对象,该对象持有窗口相关的资源。你可能会想复制一个新的窗口,保持所有属性与原来的窗口相同,但必须是一个新的对象(因为如果不是新的对象,那么一个窗口中的改变就会影响到另一个窗口)。还有一种情况:如果对象 A 中保存着对象 B 的引用,当你复制对象 A 时,你想其中使用的对象不再是对象 B 而是 B 的一个副本,那么你必须得到对象 A 的一个副本。

    作用:

    对象复制可以通过 clone 关键字来完成(如果可能,这将调用对象的 __clone() 方法)。对象中的 __clone() 方法不能被直接调用。

    语法:

    $copy_of_object = clone $object;
    

    注意:

    当对象被复制后,PHP 5 会对对象的所有属性执行一个浅复制(shallow copy)。所有的引用属性 仍然会是一个指向原来的变量的引用。

    当复制完成时,如果定义了 __clone() 方法,则新创建的对象(复制生成的对象)中的 __clone() 方法会被调用,可用于修改属性的值(如果有必要的话)。
    看代码:

    <?php
    class Person
    {
      public $sex;
      public $name;
      public $age;
    
      public function __construct($name="", $age=25, $sex='男')
      {
        $this->name = $name;
        $this->age = $age;
        $this->sex = $sex;
      }
    
      public function __clone()
      {
        echo __METHOD__."你正在克隆对象<br>";
      }
    
    }
    
    $person = new Person('小明'); // 初始赋值
    $person2 = clone $person;
    
    var_dump('persion1:');
    var_dump($person);
    echo '<br>';
    var_dump('persion2:');
    var_dump($person2);
    

    看结果:

    Person::__clone你正在克隆对象
    string(9) "persion1:" object(Person)#1 (3) { ["sex"]=> string(3) "男" ["name"]=> string(6) "小明" ["age"]=> int(25) } 
    string(9) "persion2:" object(Person)#2 (3) { ["sex"]=> string(3) "男" ["name"]=> string(6) "小明" ["age"]=> int(25) }
    

    克隆成功。

    十五、__autoload(),尝试加载未定义的类

    作用:

    你可以通过定义这个函数来启用类的自动加载
    在魔术函数 __autoload() 方法出现以前,如果你要在一个程序文件中实例化100个对象,那么你必须用include或者require包含进来100个类文件,或者你把这100个类定义在同一个类文件中 —— 相信这个文件一定会非常大,然后你就痛苦了。

    但是有了 __autoload() 方法,以后就不必为此大伤脑筋了,这个类会在你实例化对象之前自动加载制定的文件。

    还是通过例子来看看吧:

    先看看以往的方式:

    /** 
     * 文件non_autoload.php 
     */ 
      
    require_once('project/class/A.php'); 
    require_once('project/class/B.php'); 
    require_once('project/class/C.php'); 
      
    if (条件A) { 
      $a = new A(); 
      $b = new B(); 
      $c = new C(); 
      // … 业务逻辑 
    } else if (条件B) { 
      $a = newA(); 
      $b = new B(); 
      // … 业务逻辑 
    }
    

    看到了吗?不用100个,只是3个看起来就有点烦了。而且这样就会有一个问题:如果脚本执行“条件B”这个分支时,C.php这个文件其实没有必要包含。因为,任何一个被包含的文件,无论是否使用,均会被php引擎编译。如果不使用,却被编译,这样可以被视作一种资源浪费。更进一步,如果C.php包含了D.php,D.php包含了E.php。并且大部分情况都执行“条件B”分支,那么就会浪费一部分资源去编译C.php,D.php,E.php三个“无用”的文件。

    那么如果使用 __autoload() 方式呢?

    /** 
     * 文件autoload_demo.php 
     */ 
    function __autoload($className) { 
      $filePath = “project/class/{$className}.php”; 
      if (is_readable($filePath)) { 
        require($filePath); 
      } 
    } 
      
    if (条件A) { 
      $a = new A(); 
      $b = new B(); 
      $c = new C(); 
      // … 业务逻辑 
    } else if (条件B) { 
      $a = newA(); 
      $b = new B(); 
      // … 业务逻辑 
    }
    

    ok,不论效率怎么用,最起码界面看起来舒服多了,没有太多冗余的代。

    再来看看这里的效率如何,我们分析下:

    当php引擎第一次使用类A,但是找不到时,会自动调用 __autoload 方法,并将类名“A”作为参数传入。所以,我们在 __autoload() 中需要的做的就是根据类名,找到相应的文件,并包含进来,如果我们的方法也找不到,那么php引擎就会报错了。

    注意:

    这里可以只用require,因为一旦包含进来后,php引擎再遇到类A时,将不会调用__autoload,而是直接使用内存中的类A,不会导致多次包含。
    扩展:

    其实php发展到今天,已经有将 spl_autoload_register — 注册给定的函数作为 __autoload 的实现了,但是这个不在啊本文讲解之内,有兴趣可以自行看手册。

    十六、__debugInfo(),打印所需调试信息

    注意:

    该方法在PHP 5.6.0及其以上版本才可以用,如果你发现使用无效或者报错,请查看啊你的版本。
    看代码:

    <?php
    class C {
      private $prop;
    
      public function __construct($val) {
        $this->prop = $val;
      }
    
      /**
       * @return array
       */
      public function __debugInfo() {
        return [
          'propSquared' => $this->prop ** 2,
        ];
      }
    }
    
    var_dump(new C(42));
    

    结果:

    object(C)#1 (1) { ["propSquared"]=> int(1764) }
    

    再次注意:

    这里的 ** 是乘方的意思,也是在PHP5.6.0及其以上才可以使用,详情请查看PHP手册

    展开全文
  • 为了解决含硫化物矿山中AMD蚀化对岩石力学性质影响问题,采用概率论和连续损伤理论相结合的方法,研究了AMD蚀化下岩石损伤统计本构模型,并引用试验资料对模型进行了验证。结果表明:模型理论曲线与试验曲线具有较高...
  • jQuery 1.3.2(2009年2月):这次小版本升级进一步提升了库的性能,例如改进了:visible/:hidden选择符、.height()/.width()方法的底层处理机制。另外,也支持查询的元素按文档顺序返回。 jQuery 1.4(2010年1月14号...
  • 在init部分的作用是告诉DWR一些类实例和关于这些类怎样运行的信息.实际上并不会使用.这有点向java中的import语句,多数类在使用之前需要引入,但引入了类并不意味着这些在使用,每个creator和converter需要有个id属性来...
  • 大学文献检索资料 DOC

    2009-11-28 10:35:24
    第二节 信息检索目的和作用 1.通过科技文献检索能够打开人类知识宝库钥匙。 2.通过科技文献检索能使科技工作者及时把握科技发展动态和趋势。 3.通过科技文献检索能有助于开拓知识面,改善知识结构。 4....
  • C++复习资料之系列

    2008-12-30 21:35:45
    复习资料 1.1选择题 1.在一个C++程序中,main函数位置( c )。 (a) 必须在程序开头 (b) 必须在程序后面 ( c ) 可以在程序任何地方 (d) 必须在其它函数中间 2.用C++语言编制源程序要变为目标程序必须...
  • 《你必须知道495个C语言问题》

    热门讨论 2010-03-20 16:41:18
    许多知识点阐述都是其他资料中所没有,弥足珍贵。 涵盖C99标准 目录 ~第1章 声明和初始化 1 基本类型 1 1.1 我该如何决定使用哪种整数类型? 1  1.2 为什么不精确定义标准类型大小? 2 1.3 因为...
  • 而你要将该变量的作用范围限制在该函数之内,使用static语句。 $g_var = 1 ; // 全局范围 function test() { global $g_var; // 这样就可以声明全局变量了 } 更先进一些的是变量的变量表示。请参考PHP手册。这在...
  • excel使用

    2012-11-25 17:06:01
    合并不同单元格的内容,还有一种方法是利用CONCATENATE函数,此函数的作用是将若干文字串合并到一个字串中,具体操作为“=CONCATENATE(B1,C1)”。比如,假设在某一河流生态调查工作表中,B2包含“物种”、B3包含...
  • 许多知识点阐述都是其他资料中所没有,弥足珍贵。  涵盖C99标准  “本书是Summit以及C FAQ在线列表许多参与者多年心血结晶,是C语言界最为珍贵财富之一。我向所有C语言程序员推荐本书。”.  ——...
  • 具备相当编程经验人,也可以从本书了解到使用c++更有效的方法。 译者序 前言 第1章 C++基础 1 1.1 C++简介 1 1.1.1 C++语言起源 1 1.1.2 C++与面向对象程序设计 1 1.1.3 C++特点 2 1.1.4 C++术语 2...
  • dreamweaver各种组件

    2008-06-26 16:55:56
    Background that fit 这个Object的作用是插入一个图象作为网页的背景,而当显示此网页 的浏览窗口的大小发生变化时,背景图片会自动调整大小以适合当前窗口 的大小。 Shockwave Flash 3-4 这是一个专为Flash4设计的...
  • 许多知识点阐述都是其他资料中所没有,弥足珍贵。  涵盖C99标准  “本书是Summit以及C FAQ在线列表许多参与者多年心血结晶,是C语言界最为珍贵财富之一。我向所有C语言程序员推荐本书。”.  ——...
  • 软件工程教程

    热门讨论 2012-07-06 23:10:29
    类图是面向对象方法的支柱 如果没用到类图?? 找电杆撞下,看是否用面向对象方法 用 类图 的危险! 类图用滥了,建狗屋画了10页类图 类图没分清粗细层次: 概念类图 规约类图 实现类图 鸟类图 鸟类图 鸟类图 ...
  • java面试题

    2018-01-01 15:35:15
    Java 软件工程师面试资料大整合 1 Java 面霸 1 1. int 和 Integer 有什么区别? 8 2. String 和StringBuffer区别 8 3. 运行时异常与一般异常有何异同? 8 4. 说出ArrayList,Vector,LinkedList存储性能和特性 8 5...
  • 2.1 接口的作用 11 2.1.1 可复用应用程序架构 12 2.1.2 COM接口的其他优点 13 2.2 COM接口的实现 13 2.2.1 编码约定 14 2.2.2 一个完整的例子 15 2.2.3 非接口通信 18 2.2.4 实现细节 18 2.3 接口理论:第...
  • 7.5.1方法的参数 7.5.2static关键字 7.6self关键字 7.7在方法中分配和返回对象 7.8练习 8继承 8.1一切从根类开始 8.2通过继承来扩展:添加新方法 8.2.1point类和对象创建 8.2.2@class指令 8.2.3具有对象的...
  • 16.3.1 避免使用手动方法的原因 648 16.3.2 手动方法的性能影响 648 16.3.3 何时使用手动方法 652 16.4 实现列级加密 652 16.4.1 如何使用列级加密 653 16.4.2 列级加密的数据存储 653 16.4.3 测量列级加密的...
  •  作者ivor horton采用了容易理解讲授方法,并提供了详尽示例,帮助读者迅速地成为一名优秀c++编程人员。《visual c++ 2010入门经典(第5版)》针对visual c++ 2010版本进行了全面更新,介绍了最新开发环境和...
  • 第1章 JavaScript在万维网及其他方面的作用3 1.1 Web上的竞争3 1.2 其他Web技术4 1.2.1 超文本标记语言(HTML和XHTML)4 1.2.2 CSS(层叠样式表)4 1.2.3 服务器编程4 1.2.4 辅助程序和插件程序5 1.3 JavaScript:语言的...
  • 软件工程知识点

    2012-12-02 21:34:25
    螺旋模型是一种引入了风险分析与规避机制的过程模型,是瀑布模型、快速原型方法和风险分析方法的有机结合。其基本方法是,在各个阶段创建原型进行项目试验,以降低各个阶段可能遇到的项目风险。 6.喷泉模型 喷泉...
  • Linux 操作系统基础教程 清华大学信息学院计算机系 ...从网上下载,但是我不推荐易用这种方法得到 Linux,因为仅仅核心就有几十个 Mbit 数据量,而一个完整发行版本大概都是 1Gbit 左右数据量...
  • 特别说明:该版本是目前网络上最全版本:修正了268-367页缺页问题。 该资料是《Visual C++ 2008入门经典》源代码及课后练习答案 对应书籍资料见: Visual C++ 2008入门经典 基本信息 原书名: Ivor Horton...
  • Java程序员面试宝典pdf

    热门讨论 2013-02-21 13:06:13
    1.2.2 面试资料的准备 5 1.2.3 简历的写法及应注意的问题 5 1.2.4 求职信的写法及应注意的问题 8 1.2.5 面试的准备 9 1.3 面试的方式 10 1.3.1 笔试 10 1.3.2 电话面试 11 1.3.3 面试 12 1.4 小结 13 第2篇 Java基础...
  • 的作用域是这样开始搜索的,先搜索message自己的变量对象中是否存在wow,如果有就访问并且立马停止搜索,如果没有则继续往上访问它,有wow,则访问并且立马停止搜索,...

空空如也

空空如也

1 2 3
收藏数 59
精华内容 23
关键字:

引用资料的说明方法的作用