分类目录归档:PHP

PHP时间验证范例

$dt = "2016-02-30 01:04:44";
$e=date_parse_from_format("Y-m-d H:i:s",$dt);        
print_r($e);

// 2月份肯定没有30号,发出警告
Array
(
    [year] => 2016
    [month] => 2
    [day] => 30
    [hour] => 1
    [minute] => 4
    [second] => 44
    [fraction] =>
    [warning_count] => 1
    [warnings] => Array
        (
            [19] => The parsed date was invalid
        )

    [error_count] => 0
    [errors] => Array
        (
        )

    [is_localtime] =>
)

$dt = "2016-02-22 01:04:60";
$e=date_parse_from_format("Y-m-d H:i:s",$dt);        
print_r($e);
// 时间没有60秒
Array
(
    [year] => 2016
    [month] => 2
    [day] => 22
    [hour] => 1
    [minute] => 4
    [second] => 60
    [fraction] =>
    [warning_count] => 1
    [warnings] => Array
        (
            [19] => The parsed time was invalid
        )

    [error_count] => 0
    [errors] => Array
        (
        )

    [is_localtime] =>
)

$dt = "2016-2-2 01:04:20";
$e=date_parse_from_format("Y-m-d H:i:s",$dt);        
print_r($e);
// 合法,不过好像不是很满意,月份和日期望是两位数
Array
(
    [year] => 2016
    [month] => 2
    [day] => 2
    [hour] => 1
    [minute] => 4
    [second] => 20
    [fraction] =>
    [warning_count] => 0
    [warnings] => Array
        (
        )

    [error_count] => 0
    [errors] => Array
        (
        )

    [is_localtime] =>
)

从输出可以看到,要保证给定时间合法,输出的数组warning_count和error_count都应该等于0,从输出来看,它只是根据给定的格式来解析时间,所以2和02都是合法的月份。那么,在验证通过后,可以对时间来一次格式化:

$dt = "2016-2-2 01:04:20";
$e=date_parse_from_format("Y-m-d H:i:s",$dt);  
if(($e['warning_count'] === 0) && ($e['error_count'] === 0)) {
    $dt = date("Y-m-d H:i:s", strtotime($dt));
    echo $dt;
}

看起来,这样做法是可以的。另外,strtotime()在转换一个字符串为时间戳时,如果不成功就返回false,看如下例子:

        // 输出2016-03-02 01:04:20 
        $dt = "2016-2-31 01:04:20";
        $fdt = strtotime($dt);
        if(false !== $fdt) {
            echo date("Y-m-d H:i:s", $fdt)."\n";
        } else {
            echo $dt." xxx\n";
        }
               
        // 无法转换
        $dt = "2016-2-2 01:04:61";
        $fdt = strtotime($dt);
        if(false !== $fdt) {
            echo date("Y-m-d H:i:s", $fdt)."\n";
        } else {
            echo $dt." xxx\n";
        }

可以看到,2月31号,它认为是合法的,实际会给你变成3月2号,但是61秒就无法向上进一变成05分02秒,很明显,这个不是我们想要的,不符合预期。

PHP DOM文档操作

PHP中的DOM扩展(默认启用,可用–disable-dom关闭)依赖libxml(默认启用),这个扩展提供了操作DOM文档的全部方法,不过这个这些操作方法相对还是原始了一点,这个类似于在JavaScript中操作使用原生的函数操作DOM,相比之下,我们可能更加喜欢使用JQuery来代替这个原始的操作,同样,在PHP中我们也喜欢有类似Jquery这样的工具,可以非常方便的操作DOM文档。

目前在PHP的第三方包中,phpQuery几乎实现了JQuery大部分通用的方法,不过这个包已经很久未更新了。另外一个我觉得不错的是Symfony的组件包DomCrawler,虽然它没有提供JQuery那么多的方法,也没有一一对应它的方法,但是使用它来替代phpQuery,完全是可以的。

官方文档参考:https://symfony.com/doc/current/components/dom_crawler.html,从这个包的composer.json中可以知道,它依赖CssSelector,这个也是Symfony的组件包之一,DomCrawler用它来把CSS选择器转换成xpath。

DomCrawler实际是PHP DOM扩展的包装器而已。在DOM中, DOMNode是基本类,其它的DOMElement和DOMDocument都是继承DOMNode的,可以理解为它们是特殊一点的DOMNode。以下是对DomCrawler的一些使用例子(来自官方文档)。

        $html = <<<'HTML'
<!DOCTYPE html>
<html>
    <body>
        <p class="message">Hello World!</p>
        <p>Hello Crawler!</p>
    </body>
</html>
HTML;
        $crawler = new Crawler($html);
        
        foreach ($crawler as $domElement) {
            var_dump($domElement->nodeName);
        }
        //$crawler = $crawler->filterXPath('descendant-or-self::body/p');
        
        $crawler = $crawler->filter('body > p');
        $crawler->each(function($c, $i){
            echo $c->html();
        });
        
        // 原生DOMElement
        foreach($crawler as $cr) {
            $cro = new Crawler($cr);
            
            echo $cro->html();
        }

这里的两种遍历的方法,第一个是$crawler对象的each方法,看一下这个方法源代码:

    public function each(\Closure $closure)
    {
        $data = array();
        foreach ($this as $i => $node) {
            $data[] = $closure($this->createSubCrawler($node), $i);
        }

        return $data;
    }

把自身对象保存的$node进行遍历,放入createSubCrawler()方法,这个方法接收的参数类型是\DOMElement|\DOMElement[]|\DOMNodeList|null,这个说明$crawler对象里面保存的$node就是原生的DOMNode,在each的时候又使用DomCrawler来包装这个DOMNode让其是一个DomCrawler对象,这样就可以直接操作DomCrawler的方法,我们几乎可以把$crawler对象看做是JQuery中的美元符($)。

不过这里的createSubCrawler()方法是私有的,不能在外部使用,那么如果我有一个DOMElement(DOMNode),应该如何让它变成一个Crawler对象呢,很简单,就是以上的第二个例子,直接new一个Crawler,把DOMNode传入构造函数,这个构造函数实际内部会调用add()方法,这个方法负责实例化Crawler,查看add()方法可以知道,它除了接受DOMNode和DOMNodeList外(包括DOMElement等)还接受一个数组和字符串,数组自然应该是add()方法可以接受的参数类型,如果还是数组就会递归。

明白这个之后,操作就很简单了。直接来一些例子就好了:

$crawler->filter('body > p')->eq(0);
$crawler->filter('body > p')->first();
$crawler->filter('body > p')->last();
$crawler->filter('body > p')->siblings();
$crawler->filter('body > p')->nextAll();
$crawler->filter('body > p')->previousAll();
$crawler->filter('body')->children();
$crawler->filter('body > p')->parents();

$crawler = new Crawler('<html><body /></html>');

$crawler->addHtmlContent('<html><body /></html>');
$crawler->addXmlContent('<root><node /></root>');

$crawler->addContent('<html><body /></html>');
$crawler->addContent('<root><node /></root>', 'text/xml');

$crawler->add('<html><body /></html>');
$crawler->add('<root><node /></root>');

/////////////////////////////////////////
// 节点值
$tag = $crawler->filterXPath('//body/*')->nodeName();
$message = $crawler->filterXPath('//body/p')->text();
$class = $crawler->filterXPath('//body/p')->attr('class');
$attributes = $crawler
    ->filterXpath('//body/p')
    ->extract(array('_text', 'class'))

/////////////////////////////////////////
$document = new \DOMDocument();
$document->loadXml('<root><node /><node /></root>');
$nodeList = $document->getElementsByTagName('node');
$node = $document->getElementsByTagName('node')->item(0);

$crawler->addDocument($document);
$crawler->addNodeList($nodeList);
$crawler->addNodes(array($node));
$crawler->addNode($node);
$crawler->add($document);

/////////////////////////////////////
$html = '';

foreach ($crawler as $domElement) {
    $html .= $domElement->ownerDocument->saveHTML($domElement);
}

$html = $crawler->html();

这个包还提供提供了针对链接和表单的特定操作,用来快速操作它们。

在filter中可以使用的选择器语法,几乎和JQuery是一样的,这个包中实际是把CSS选择器转换成xpath来执行,它是由CssSelector来驱动的。

向Composer发布代码

Composer可以很方便管理应用的包与包依赖,自定义的包也可以发布到到packagist.org,然后通过Composer拉回来,已达到快速部署。当然,如果自定义的包非开源的,就不要这么干了。

首先,在github.com上创建账户,并把项目推送到github.com,这样会得到一个链接。
然后到packagist.org,点击使用Github账户登录,接着点击submit,这时候会要求输入github.com项目地址,接下来就是检查,这个检查会通知你当前可能重复的其它包,然后输入你包的名称(会从composer.json中自动检出),然后确认,这样包就发布出去了。

由于代码托管在github上,当向github推送代码时,packagist.org默认并不会同步更新,所以为了让packagist.org可以同步,需要在github中设置,参考:https://packagist.org/about#how-to-update-packages

To do so you can:

Go to your GitHub repository
Click the “Settings” button
Click “Integrations & services”
Add a “Packagist” service, and configure it with your API token, plus your Packagist username
Check the “Active” box and submit the form
You can then hit the “Test Service” button to trigger it and check if Packagist removes the warning about the package not being auto-updated.

简单来说这是一个事件通知服务,在github中设置一个钩子,当由事件触发时,通过packagist提供的API通知packagist,packagist就会去和github做同步操作。

接下来就是运行:

composer require ifeeline/wkhtmltox:dev-master

如果要拉开发分支,在包后加冒号接dev-master即可。

以下是一个例子:
ifeeline_whhtmltox

PHP Laravel Excel

PHP Laravel Excel官方网址:http://www.maatwebsite.nl/laravel-excel/docs;

GitHub: https://github.com/Maatwebsite/Laravel-Excel;
Packagist: https://packagist.org/packages/maatwebsite/excel

这个程序包依赖PHPOffice/PHPExcel,实际就是在这个工具包上做了一些易于用户操作的封装。需要说明的是,目录依赖的PHPOffice/PHPExcel版本是1.8.*(也是当前的稳定版),在1.9.*和2.0.*中开始引入PHP的命名空间,意味着PHP版本至少要5.3以上,这两个分支还在开发中,看起来这个包的作者非常的保守。

安装就按照Laravel套路来就好:

#往composer.json中添加"maatwebsite/excel": "~2.1.0",然后update

#添加ServiceProvider
vi config/app.php
'Maatwebsite\Excel\ExcelServiceProvider',

#添加Facade(可选)
'Excel' => 'Maatwebsite\Excel\Facades\Excel',

#配置(会添加excel.php配置文件)
php artisan vendor:publish --provider="Maatwebsite\Excel\ExcelServiceProvider"

#获取excel实例
$excel = App::make('excel');

Maatwebsite/excel本身有一个默认的配置文件,如果应用级别有配置文件,那么应用配置将覆盖默认配置配置,具体实现是在ExcelServiceProvider中:

    public function boot()
    {
        $this->publishes([
            __DIR__ . '/../../config/excel.php' => config_path('excel.php'),
        ]);

        $this->mergeConfigFrom(
            __DIR__ . '/../../config/excel.php', 'excel'
        );

        //Set the autosizing settings
        $this->setAutoSizingSettings();
    }

表格操作,主要涉及输入和输出。

Excel::load('file.xls', function($reader) {

    // Getting all results
    $results = $reader->get();

    // ->all() is a wrapper for ->get() and will work the same
    $results = $reader->all();

});

使用get和all方法获取结果,默认,如果表格只有一个sheet,那么直接返回行集合,如果有多个sheet,那么返回sheet的集合,每个sheet又是行的集合。为了统一操作,可以设置配置文件中的force_sheets_collection设置为true,这样都会返回sheet的集合。

表格sheet的第一行是头部,默认会被转换成slug(别名),可以设置import.heading为false表示不使用文件头,可用值true|false|slugged|slugged_with_count|ascii|numeric|hashed|trans|original,设置为original表示使用字面值作为key,这个比较常见。

Sheet/行/单元格都是集合,使用了get()之后,就可以使用集合的方法。

#
$reader->get()->groupBy('firstname');

#依赖force_sheets_collection,可能返回第一个sheet或第一个行
$reader->first();
// Get workbook title
$workbookTitle = $reader->getTitle();

foreach($reader as $sheet)
{
    // get sheet title
    $sheetTitle = $sheet->getTitle();
}

// You can either use ->take()
$reader->take(10);

// Or ->limit()
$reader->limit(10);

// Skip 10 results
$reader->skip(10);

// Skip 10 results with limit, but return all other rows
$reader->limit(false, 10);

// Skip and take
$reader->skip(10)->take(10);

// Limit with skip and take
$reader->($skip, $take);

$reader->toArray();

$reader->toObject();

// Dump the results
$reader->dump();

// Dump results and die
$reader->dd();

#也可以使用foreach
// Loop through all sheets
$reader->each(function($sheet) {

    // Loop through all rows
    $sheet->each(function($row) {

    });

});

选择Sheet和列

#仅加载sheet1
Excel::selectSheets('sheet1')->load("xx.xls", function($reader) {});
Excel::selectSheets('sheet1', 'sheet2')->load();

#通过下标选择比较靠谱
// First sheet
Excel::selectSheetsByIndex(0)->load();

// First and second sheet
Excel::selectSheetsByIndex(0, 1)->load();

#选择列,很熟悉的用法?
#All get methods (like all(), first(), dump(), toArray(), ...) accept an array of columns.
// Select
$reader->select(array('firstname', 'lastname'))->get();

// Or
$reader->get(array('firstname', 'lastname'));

日期:
By default the dates will be parsed as a Carbon object.

分批导入:

Excel::filter('chunk')->load('file.csv')->chunk(250, function($results)
{
        foreach($results as $row)
        {
            // do stuff
        }
});

每次读入250行,处理完毕在导入250行??

批量导入:

Excel::batch('app/storage/uploads', function($rows, $file) {

    // Explain the reader how it should interpret each row,
    // for every file inside the batch
    $rows->each(function($row) {

        // Example: dump the firstname
        dd($row->firstname);

    });

});

$files = array(
    'file1.xls',
    'file2.xls'
);

Excel::batch($files, function($rows, $file) {

});

Excel::batch('app/storage/uploads', function($sheets, $file) {

    $sheets->each(function($sheet) {

    });

});

导出也有很多定制化的操作,参考:http://www.maatwebsite.nl/laravel-excel/docs/export

例子:

// 关联数组,输出表头
$excel_array = [
    [
        "表头1" => "xxxx",
        "表头2" => "yyyy"
    ],
    [
        "表头1" => "xxxx2",
        "表头2" => "yyyy3"
    ]
];
// 直接数据输入
$excel_array2 = [
    [
        "表头1", "表头2"
    ],
    [
        "xxxx", "yyyy"
    ],
    [
        "xxxx2", "yyyy3"
    ]
];

        \Excel::create("test1", function ($excel) use($excel_array) {
            $excel->sheet('sheet1', function ($sheet) use($excel_array) {
                $sheet->fromArray($excel_array);
            });
        })->store("xls","d:/");
        
        \Excel::create("test2", function ($excel) use($excel_array2) {
            $excel->sheet('sheet1', function ($sheet) use($excel_array2) {
                $sheet->fromArray($excel_array2, null, 'A1', false, false);
            });
        })->save("xls");

默认,如果不指定导入的路径,会保存到storage_path(‘exports’),即app/storage/exports。可以修改配置文件export.store.path的值。

导出的sheet,默认第一行总是头部,这个可以修改配置文件的export.generate_heading_by_indices为false取消这个默认值。也可以指定fromArray的第5参数为false达到同样目的。

store()的第一参数是导入文件的类型,第二参数是路径(不需要包含文件名),第三参数控制是否返回保存文件的信息(比如保存的路径,扩展名等)。

PHP GuzzleHttp使用文档

内容来自:http://docs.guzzlephp.org/en/latest/,这个Http库实现上可以用完美来形容,之前使用过Zend\Http库,感觉已经很不错了,不过看起来,GuzzleHttp更胜一筹。

Guzzlehttp/guzzle当前最新分支6.x,5.x是维护阶段,4.x之前已经终结。

6.x需要PHP5.5以上,依赖guzzlehttp下的psr7和promises包。它提供两种驱动支持,一是PHP的stream扩展,需要在php.ini中启用allow_url_fopen,二是cURL扩展。如果cURL没有安装,那么就使用PHP的stream扩展,也可以指定自己的驱动。

Composer安装:

 {
   "require": {
      "guzzlehttp/guzzle": "~6.0"
   }
}

开始开始:

use GuzzleHttp\Client;
// 客户端
$client = new Client([
    'base_uri' => 'https://foo.com/api/',
    'timeout'  => 2.0,
]);
// 发起请求
$response = $client->request('GET', 'test');
$response = $client->request('GET', '/root');

看明白这里的请求URL是关键,第一个发起的URL是https://foo.com/api/test,第二发起的URL是https://foo.com/root。(RFC 3986规范)

客户端Client构造函数接受的参数是base_uri,handler和任何请求参数(可以传递到request对象的参数)。这里的参数除了handler,都是可以覆盖的。handler参数的解释:
“(callable) Function that transfers HTTP requests over thewire. The function is called with a Psr7\Http\Message\RequestInterface and array of transfer options, and must return a GuzzleHttp\Promise\PromiseInterface that is fulfilled with a Psr7\Http\Message\ResponseInterface on success. “handler” is a constructor only option that cannot be overridden in per/request options. If no handler is provided, a default handler will be created that enables all of the request options below by attaching all of the default middleware to the handler.”

要理解这段话并不容易。大体上是说这个handler被Psr7\Http\Message\RequestInterface对象调用返回一个被Psr7\Http\Message\ResponseInterface填充的GuzzleHttp\Promise\PromiseInterface。一般我们理解应该是返回Response,这里返回一个Promise,引入了一个中间层,实际是为了可以产生异步调用而准备的。Promise可以是同步的,也可以是异步的。

看这个例子:

use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Handler\CurlHandler;

$handler = new CurlHandler();
$stack = HandlerStack::create($handler); // Wrap w/ middleware
$client = new Client(['handler' => $stack]);

可见,hanlder就是底层实际传输http内容的工具,比如curl,stream。(默认的hanlder就是优先使用curl,如果要强制使用curl,可以参考这个例子),可以给halder添加中间件。

发起请求:

$response = $client->get('http://httpbin.org/get');
$response = $client->delete('http://httpbin.org/delete');
$response = $client->head('http://httpbin.org/get');
$response = $client->options('http://httpbin.org/get');
$response = $client->patch('http://httpbin.org/patch');
$response = $client->post('http://httpbin.org/post');
$response = $client->put('http://httpbin.org/put');

#替代
$response = $client->request('GET',"");

也可以先创建一个请求对象,然后通过Client的send的方法发起请求:

use GuzzleHttp\Psr7\Request;

$request = new Request('PUT', 'http://httpbin.org/put');
$response = $client->send($request, ['timeout' => 2]);

对应,可以使用sendAsync()和requestAsync()发起异步请求:

use GuzzleHttp\Psr7\Request;

// Create a PSR-7 request object to send
$headers = ['X-Foo' => 'Bar'];
$body = 'Hello!';
$request = new Request('HEAD', 'http://httpbin.org/head', $headers, $body);

// Or, if you don't need to pass in a request instance:
$promise = $client->requestAsync('GET', 'http://httpbin.org/get');

如果是异步请求,可以使用then方法接收响应,或者异常:

use Psr\Http\Message\ResponseInterface;
use GuzzleHttp\Exception\RequestException;

$promise = $client->requestAsync('GET', 'http://httpbin.org/get');
$promise->then(
    function (ResponseInterface $res) {
        echo $res->getStatusCode() . "\n";
    },
    function (RequestException $e) {
        echo $e->getMessage() . "\n";
        echo $e->getRequest()->getMethod();
    }
);

有了异步的实现,那么就可以并行发起一批请求:

use GuzzleHttp\Client;
use GuzzleHttp\Promise;

$client = new Client(['base_uri' => 'http://httpbin.org/']);

// Initiate each request but do not block
$promises = [
    'image' => $client->getAsync('/image'),
    'png'   => $client->getAsync('/image/png'),
    'jpeg'  => $client->getAsync('/image/jpeg'),
    'webp'  => $client->getAsync('/image/webp')
];

// Wait on all of the requests to complete. Throws a ConnectException
// if any of the requests fail
$results = Promise\unwrap($promises);

// Wait for the requests to complete, even if some of them fail
$results = Promise\settle($promises)->wait();

// You can access each result using the key provided to the unwrap
// function.
echo $results['image']->getHeader('Content-Length');
echo $results['png']->getHeader('Content-Length');

或者使用Pool来进行并发请求:

use GuzzleHttp\Pool;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Request;

$client = new Client();

$requests = function ($total) {
    $uri = 'http://127.0.0.1:8126/guzzle-server/perf';
    for ($i = 0; $i < $total; $i++) {
        yield new Request('GET', $uri);
    }
};

$pool = new Pool($client, $requests(100), [
    'concurrency' => 5,
    'fulfilled' => function ($response, $index) {
        // this is delivered each successful response
    },
    'rejected' => function ($reason, $index) {
        // this is delivered each failed request
    },
]);

// Initiate the transfers and create a promise
$promise = $pool->promise();

// Force the pool of requests to complete.
$promise->wait();

使用响应:

$code = $response->getStatusCode(); // 200
$reason = $response->getReasonPhrase(); // OK

// Check if a header exists.
if ($response->hasHeader('Content-Length')) {
    echo "It exists";
}

// Get a header from the response.
echo $response->getHeader('Content-Length');

// Get all of the response headers.
foreach ($response->getHeaders() as $name => $values) {
    echo $name . ': ' . implode(', ', $values) . "\r\n";
}

$body = $response->getBody();
// Implicitly cast the body to a string and echo it
echo $body;
// Explicitly cast the body to a string
$stringBody = (string) $body;
// Read 10 bytes from the body
$tenBytes = $body->read(10);
// Read the remaining contents of the body as a string
$remainingBytes = $body->getContents();

查询参数:

$response = $client->request('GET', 'http://httpbin.org?foo=bar');

$client->request('GET', 'http://httpbin.org', [
    'query' => ['foo' => 'bar']
]);

$client->request('GET', 'http://httpbin.org', ['query' => 'foo=bar']);

上传数据(数据直接作为body体):

// Provide the body as a string.
$r = $client->request('POST', 'http://httpbin.org/post', [
    'body' => 'raw data'
]);

// Provide an fopen resource.
$body = fopen('/path/to/file', 'r');
$r = $client->request('POST', 'http://httpbin.org/post', ['body' => $body]);

// Use the stream_for() function to create a PSR-7 stream.
$body = \GuzzleHttp\Psr7\stream_for('hello!');
$r = $client->request('POST', 'http://httpbin.org/post', ['body' => $body]);

// 传送JSON数据
$r = $client->request('PUT', 'http://httpbin.org/put', [
    'json' => ['foo' => 'bar']
]);

POST表单请求(如果是GET的表单,就是简单的查询字符串,不是这里讨论的内容)

$response = $client->request('POST', 'http://httpbin.org/post', [
    'form_params' => [
        'field_name' => 'abc',
        'other_field' => '123',
        'nested_field' => [	// 嵌套,checkbox多选
            'nested' => 'hello'
        ]
    ]
]);

// 上传文件
$response = $client->request('POST', 'http://httpbin.org/post', [
    'multipart' => [
        [
            'name'     => 'field_name',
            'contents' => 'abc'
        ],
        [
            'name'     => 'file_name',
            'contents' => fopen('/path/to/file', 'r')
        ],
        [
            'name'     => 'other_file',
            'contents' => 'hello',
            'filename' => 'filename.txt',
            'headers'  => [
                'X-Foo' => 'this is an extra header to include'
            ]
        ]
    ]
]);

Cookies

// Use a specific cookie jar
$jar = new \GuzzleHttp\Cookie\CookieJar;
$r = $client->request('GET', 'http://httpbin.org/cookies', [
    'cookies' => $jar
]);

// 如果要对所有请求使用cookies,可以在Client指定使用cookies
// Use a shared client cookie jar
$client = new \GuzzleHttp\Client(['cookies' => true]);
$r = $client->request('GET', 'http://httpbin.org/cookies');

重定向(GET请求和POST请求的重定向需要注意)
Guzzle自动跟踪重定向,可以明确关闭:

$response = $client->request('GET', 'http://github.com', [
    'allow_redirects' => false
]);
echo $response->getStatusCode();
// 301

异常:

use GuzzleHttp\Exception\ClientException;

try {
    $client->request('GET', 'https://github.com/_abc_123_404');
} catch (ClientException $e) {
    echo $e->getRequest();
    echo $e->getResponse();
}

请求对象可选项:

##############
allow_redirects
默认:
[
    'max'             => 5,
    'strict'          => false,
    'referer'         => true,
    'protocols'       => ['http', 'https'],
    'track_redirects' => false
]

#指定false关闭
$res = $client->request('GET', '/redirect/3', ['allow_redirects' => false]);
echo $res->getStatusCode();

##############
auth
$client->request('GET', '/get', ['auth' => ['username', 'password']]);
$client->request('GET', '/get', [
    'auth' => ['username', 'password', 'digest']
]);

##############
body 请求体内容

##############
cert

##############
cookies 可以在Client设置cookie为true以设置所有请求使用cookie

$jar = new \GuzzleHttp\Cookie\CookieJar();
$client->request('GET', '/get', ['cookies' => $jar]);

##############
connect_timeout
默认为0,表示不超时(无限等待)

##############
debug 可以输出调试信息,或把调试信息写入文件(fopen)

##############
decode_content
默认为ture,表示解码服务端回送的压缩的内容

##############
delay

##############
expect

##############
form_params

##############
headers

$client->request('GET', '/get', [
    'headers' => [
        'User-Agent' => 'testing/1.0',
        'Accept'     => 'application/json',
        'X-Foo'      => ['Bar', 'Baz']
    ]
]);

##############
http_errors
默认为true,表示出错时抛出异常

##############
json

##############
multipart

##############
on_headers

##############
query
$client->request('GET', '/get?abc=123', ['query' => ['foo' => 'bar']]);

##############
sink
保存请求体
$resource = fopen('/path/to/file', 'w');
$client->request('GET', '/stream/20', ['sink' => $resource]);

$resource = fopen('/path/to/file', 'w');
$stream = GuzzleHttp\Psr7\stream_for($resource);
$client->request('GET', '/stream/20', ['save_to' => $stream]);

##############
verify
默认为true,验证SSL证书

##############
timeout
默认为0,不超时。(请求超时)

当使用curl时(默认优先使用,如果指定curl参数,最后可以明确指定使用curl作为hanlder,否则无效):

$client->request('GET', '/', [
    'curl' => [
        CURLOPT_INTERFACE => 'xxx.xxx.xxx.xxx'
    ]
]);

PHP账户登录-拉验证码手动登录实例

<?php

/*
 * 当出现验证码时,拉验证码手动登录一次,解决账户登录需要验证码问题
 * Auth:vfeelit@qq.com
 * At:2016-04-25
 */
namespace App\Console\Commands;

use Illuminate\Console\Command;

class AmazonLogin extends Command {
    protected $signature = 'amazon:login {--vcode=}';
    protected $description = '';
    private $_cookie;
    private $_auth;
    private $_url;
    private $_timeout = 20;
    public function __construct() {
        $this->_url = [ 
            'preLogin' => env ( 'AMZ_PRELOGIN' ),
            'postLogin' => env ( 'AMZ_POSTLOGIN' ) 
        ];
        $this->_cookie = env ( 'AMZ_COOKIE' );
        $this->_auth = [ 
            'username' => env ( 'AMZ_UNA' ),
            'password' => env ( 'AMZ_PWD' ) 
        ];
        
        parent::__construct ();
    }
    public function handle() {
        $vcode = trim ( $this->option ( 'vcode' ) );
        if (! empty ( $vcode )) {
            $loginResult = $this->loginForce ( $vcode );
            if (str_contains ( $loginResult, 'id="ap_password"' )) {
                echo "使用验证码登录失败\n";
                file_put_contents ( "login2.html", $loginResult );
            } else {
                echo "使用验证码登录成功\n";
                
                // 清理垃圾
                @unlink ( "login.html" );
                @unlink ( "login2.html" );
                @unlink ( "vcode.jpg" );
            }
        } else {
            $loginResult = $this->login ();
            
            if (str_contains ( $loginResult, 'id="ap_password"' )) {
                echo "需要输入验证码\n";
                file_put_contents ( "login.html", $loginResult );
                // 取回验证码图片
                $this->getVcode ();
            } else {
                echo "成功登录\n";
                
                // 清理垃圾
                @unlink ( "login.html" );
                @unlink ( "login2.html" );
                @unlink ( "vcode.jpg" );
            }
        }
    }
    
    // 正常登录
    private function login() {
        $ch = curl_init ();
        
        curl_setopt ( $ch, CURLOPT_URL, $this->_url ['preLogin'] );
        curl_setopt ( $ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36' );
        curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, true );
        // curl_setopt($ch, CURLOPT_HEADER, true);
        curl_setopt ( $ch, CURLOPT_HEADER, false );
        curl_setopt ( $ch, CURLOPT_CONNECTTIMEOUT, $this->_timeout );
        curl_setopt ( $ch, CURLOPT_TIMEOUT, $this->_timeout );
        curl_setopt ( $ch, CURLOPT_COOKIEFILE, $this->_cookie );
        curl_setopt ( $ch, CURLOPT_COOKIEJAR, $this->_cookie );
        
        $indexHtml = curl_exec ( $ch );
        
        $reHiddenPostData = '/<input type="hidden"\s*name="([^"]*)"\s*value="([^"]*)"/i';
        $count = preg_match_all ( $reHiddenPostData, $indexHtml, $hiddenPostData );
        
        $postData = array ();
        for($i = 0; $i < $count; $i ++) {
            $postData [$hiddenPostData [1] [$i]] = $hiddenPostData [2] [$i];
        }
        $postData ['username'] = $this->_auth ['username'];
        $postData ['password'] = $this->_auth ['password'];
        
        curl_setopt ( $ch, CURLOPT_URL, $this->_url ['postLogin'] );
        curl_setopt ( $ch, CURLOPT_POST, true );
        curl_setopt ( $ch, CURLOPT_POSTFIELDS, $postData );
        
        return curl_exec ( $ch );
    }
    
    // 拉回验证码图片
    private function getVcode() {
        $indexHtml = file_get_contents ( "login.html" );
        
        $vcodeBase = "https://opfcaptcha-prod.s3.amazonaws.com";
        
        preg_match ( '#src="' . $vcodeBase . '\/([^"]+)#', $indexHtml, $actions );
        $postUrl = $vcodeBase . '/' . $actions [1];
        $fp = fopen ( "vcode.jpg", 'wb' );
        
        $ch = curl_init ();
        
        curl_setopt ( $ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36' );
        curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, true );
        curl_setopt ( $ch, CURLOPT_FILE, $fp );
        curl_setopt ( $ch, CURLOPT_HEADER, false );
        curl_setopt ( $ch, CURLOPT_CONNECTTIMEOUT, $this->_timeout );
        curl_setopt ( $ch, CURLOPT_TIMEOUT, $this->_timeout );
        curl_setopt ( $ch, CURLOPT_COOKIEFILE, $this->_cookie );
        curl_setopt ( $ch, CURLOPT_COOKIEJAR, $this->_cookie );
        
        curl_setopt ( $ch, CURLOPT_URL, $postUrl );
        
        curl_exec ( $ch );
        curl_close ( $ch );
        fclose ( $fp );
    }
    
    // 使用验证码登录
    private function loginForce($vcode) {
        $indexHtml = file_get_contents ( "login.html" );
        
        preg_match ( '/action="https:([^"]+)/', $indexHtml, $actions );
        $postUrl = 'https:' . $actions [1];
        
        $reHiddenPostData = '/<input type="hidden"\s*name="([^"]*)"\s*value="([^"]*)"/i';
        $count = preg_match_all ( $reHiddenPostData, $indexHtml, $hiddenPostData );
        
        $postData = array ();
        for($i = 0; $i < $count; $i ++) {
            $postData [$hiddenPostData [1] [$i]] = $hiddenPostData [2] [$i];
        }
        // 使用验证码登录时用户名对应的是email,不是username
        $postData ['email'] = $this->_auth ['username'];
        $postData ['password'] = $this->_auth ['password'];
        $postData ['guess'] = $vcode;
        // print_r($postData);
        
        $ch = curl_init ();
        curl_setopt ( $ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36' );
        curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, true );
        curl_setopt ( $ch, CURLOPT_HEADER, false );
        curl_setopt ( $ch, CURLOPT_CONNECTTIMEOUT, $this->_timeout );
        curl_setopt ( $ch, CURLOPT_TIMEOUT, $this->_timeout );
        curl_setopt ( $ch, CURLOPT_COOKIEFILE, $this->_cookie );
        curl_setopt ( $ch, CURLOPT_COOKIEJAR, $this->_cookie );
        
        curl_setopt ( $ch, CURLOPT_URL, $postUrl );
        curl_setopt ( $ch, CURLOPT_POST, true );
        curl_setopt ( $ch, CURLOPT_POSTFIELDS, $postData );
        
        return curl_exec ( $ch );
    }
}

XML转换成PHP数组(或JSON)问题

例子:

<root>
   <books>
      <book>1</book>
      <book>2</book>
   </books>
</root>

//
<root>
   <books>
      <book>1</book>
   </books>
</root>

这个情况,可以转换成如下数组:

[
    “root"=>[
        "books" =>[
              ["name" => "book", "value" => 1],
              ["name" => "book", "value" => 2]
         ]
    ]
]
////
[
    “root"=>[
        "books" => ["name" => "book", "value" => 1]
    ]
]

这个转换的结果肯定不能令人满意。第一个XML books对应一个二维数组,第二个XML books对应了一维数据。换句话说,当要遍历数据时,首先要做一个判断,看它是一维数组还是多维数组。如果books只有一个子元素,那么就换成只有一个子元素的二维数组,看起来是比较方便的。不过,有时候类似books这样的元素,仅仅只能有一个子元素的时候,那么取元素还要往二维数组里面提取出来。换句话说,就仅仅从XML结构来看,无法知道books到底是仅包含一个元素,还是多个元素(当然XML本身是有提供描述支持,这里不讨论这个),那么转换函数看到仅包含一个元素的,就直接对上books,遇到多个子元素的,就对上一个二维数组,这个本身是没有问题的,只是使用起来就太不方便了。如果要解决这个问题,必须知道books到底是多元素还是单元素的,这个就是非常特定的数据结构了,无法通用。相比JSON数据结构,XML这个东西实在太笨重。

对于特定的数据结构,预先知道是对应多元素还是单元素,所以可以特别处理。以下的例子就是PHP eBay SDK中把XML转换成数组方式:

$xml = '<GetCategorySpecificsResponse xmlns="urn:ebay:apis:eBLBaseComponents">'.$cat->asXml().'</GetCategorySpecificsResponse>';
$xmlParser = new \DTS\eBaySDK\Parser\XmlParser('\DTS\eBaySDK\Trading\Types\GetCategorySpecificsResponseType');
$xmlParser->parse($xml)->toArray()

这里转换的XML输出就是规范的输出,因为对于每个元素的类型,DTS\eBaySDK\Trading\Types\GetCategorySpecificsResponseType有约定:

class GetCategorySpecificsResponseType extends \DTS\eBaySDK\Trading\Types\AbstractResponseType
{
    private static $propertyTypes = array(
        'Recommendations' => array(
            'type' => 'DTS\eBaySDK\Trading\Types\RecommendationsType',
            'unbound' => true,
            'attribute' => false,
            'elementName' => 'Recommendations'
        ),
        'TaskReferenceID' => array(
            'type' => 'string',
            'unbound' => false,
            'attribute' => false,
            'elementName' => 'TaskReferenceID'
        ),
        'FileReferenceID' => array(
            'type' => 'string',
            'unbound' => false,
            'attribute' => false,
            'elementName' => 'FileReferenceID'
        )
    );
}

这个操作确实费时费力。需要把已知的数据结构做一遍对应。

以下就是我在下载eBay类目属性时,把这个大文件进行拆分,然后按照类目ID进行存储的具体代码:

        $xmlFile = 'ebay/category-specifics-'.$site.'.xml';
        if(!\Storage::has($xmlFile)) {
            echo "文件:$xmlFile 不存在,请先下载并解压\n";
            return;
        }
        $xml = simplexml_load_file(storage_path('app/'.$xmlFile));
        
        foreach($xml->Recommendations as $cat) {
            $cid = $cat->CategoryID;
            $xml = '<GetCategorySpecificsResponse xmlns="urn:ebay:apis:eBLBaseComponents">'.$cat->asXml().'</GetCategorySpecificsResponse>';
            $xmlParser = new \DTS\eBaySDK\Parser\XmlParser('\DTS\eBaySDK\Trading\Types\GetCategorySpecificsResponseType');
            
            $cacheDir = 'ebay/site/'.$siteName."/specifics";
            $cacheFile = $cacheDir.'/'.$cid.".json";
            @mkdir(storage_path('app/'.$cacheDir), 0777, true);
            
            if(Storage::has($cacheFile)) {
                Storage::delete($cacheFile);
            }
            Storage::put($cacheFile,json_encode($xmlParser->parse($xml)->toArray()));
            unset($cid, $xml, $xmlParser, $cacheDir, $cacheFile);
        }

这样转换之后,只需要取回JSON,如果服务器端,再转换成数组就可以方便使用;对于客户端,非常简单的传递JSON字符串就可以了,根本不需要担心可以包含多元素的子元素,当仅包含一个元素时,没有被正确装换成二维数组的问题,因为这个情况都是二维数组(统一了操作)。

如果直接返回JSON,相对简单很多,当前API的开发,大多使用JSON,这个就是趋势。

PHP 图片处理库 intervention/image

https://packagist.org/packages/intervention/image
https://github.com/Intervention/image
http://image.intervention.io/

安装使用:

php composer.phar require intervention/image

或者直接去下载源代码,只要保证能自动装载即可。

例子:

use Intervention\Image\ImageManagerStatic as Image;

//Image::configure(array('driver' => 'imagick'));
$image = Image::make("D:/3.jpg");
$warter = Image::make("D:/w/2.png");

$image->insert($warter,'top-left',15,15);
        
$image->save("D:/o.jpg");

合并两张图片就是如此的简单。make方法可以根据传入的第一参数类型,自动的返回一个Image对象,这是一个强大的工厂方法,当然,默认使用的GD驱动,也可以使用Image::configure(array(‘driver’ => ‘imagick’))来切换到imagick,不过提供的API是一致的。

以上的ImageManagerStatic提供了一个静态用法,实际上它是Intervention\Image\ImageManager的封装,所以以上例子可以改装为:

use Intervention\Image\ImageManager;

$im = new ImageManager();
$im->configure(array('driver' => 'imagick'));

$image = $im->make("D:/3.jpg");
$warter = $im->make("D:/w/2.png");

$image->insert($warter,'top-left',15,15);   
$image->save("D:/o.jpg");

看起来使用静态的方式还是简便一点(这也是其可以存在的理由)。

这个库的基本封装流程:ImageManager管理不同的driver(GD 和 imagick),每个driver都有一个decoder和encoder,decoder用来识别输入,比如直接输入文件路径,base64编码字符串,二进制代码等,具体工作是由一个init方法(在抽象类中)完成的,它返回具体Image对象,这个方法是一个工厂方法;decoder处理输出,比如要正确处理JPG或PNG输出,就需要它来识别并处理,具体来说就是process方法,根据不同fromat,调用不同的处理方法,处理的结果保存在公共的result属性中,Image中的save()方法就是间接调用了process方法。一般,如果要保存一个图片,就调用save()方法,如果需要返回处理的字符串,就调用encode()方法,比如要放回用于URL展示的图片字符流,就用$image->encode(‘data-url’),实际上这个字符串是保存到$image的$encoded字段中的,而它实现了__toString()方法:

    public function __toString()
    {
        return $this->encoded;
    }

比如如下例子:

$image = Image::make("D:/3.jpg");
echo $image->encode('data-url');

data:image/jpeg;base64,/9j/4AAQSkZJRg*************************

不得不说,这确实很便利。这个库对于图片识别,处理,输出识别提供了简单的实现,不需要去直接使用稍微“丑陋”的PHP函数。

另外,这个包提供了适配于Laravel的ServiceProvider,这个个人感觉就可以忽略了。要使用时,直接use一下就可以开始使用了。

以下是方法列表:

/**
 * @method \Intervention\Image\Image backup(string $name = 'default')                                                                                                     Backups current image state as fallback for reset method under an optional name. Overwrites older state on every call, unless a different name is passed.
 * @method \Intervention\Image\Image blur(integer $amount = 1)                                                                                                            Apply a gaussian blur filter with a optional amount on the current image. Use values between 0 and 100.
 * @method \Intervention\Image\Image brightness(integer $level)                                                                                                           Changes the brightness of the current image by the given level. Use values between -100 for min. brightness. 0 for no change and +100 for max. brightness.
 * @method \Intervention\Image\Image cache(\Closure $callback, integer $lifetime = null, boolean $returnObj = false)                                                              Method to create a new cached image instance from a Closure callback. Pass a lifetime in minutes for the callback and decide whether you want to get an Intervention Image instance as return value or just receive the image stream.
 * @method \Intervention\Image\Image canvas(integer $width, integer $height, mixed $bgcolor = null)                                                                       Factory method to create a new empty image instance with given width and height. You can define a background-color optionally. By default the canvas background is transparent.
 * @method \Intervention\Image\Image circle(integer $radius, integer $x, integer $y, \Closure $callback = null)                                                           Draw a circle at given x, y, coordinates with given radius. You can define the appearance of the circle by an optional closure callback.
 * @method \Intervention\Image\Image colorize(integer $red, integer $green, integer $blue)                                                                                Change the RGB color values of the current image on the given channels red, green and blue. The input values are normalized so you have to include parameters from 100 for maximum color value. 0 for no change and -100 to take out all the certain color on the image.
 * @method \Intervention\Image\Image contrast(integer $level)                                                                                                             Changes the contrast of the current image by the given level. Use values between -100 for min. contrast 0 for no change and +100 for max. contrast.
 * @method \Intervention\Image\Image crop(integer $width, integer $height, integer $x = null, integer $y = null)                                                          Cut out a rectangular part of the current image with given width and height. Define optional x,y coordinates to move the top-left corner of the cutout to a certain position.
 * @method void                      destroy()                                                                                                                            Frees memory associated with the current image instance before the PHP script ends. Normally resources are destroyed automatically after the script is finished.
 * @method \Intervention\Image\Image ellipse(integer $width, integer $height, integer $x, integer $y, \Closure $callback = null)                                          Draw a colored ellipse at given x, y, coordinates. You can define width and height and set the appearance of the circle by an optional closure callback.
 * @method mixed                     exif(string $key = null)                                                                                                             Read Exif meta data from current image.
 * @method mixed                     iptc(string $key = null)                                                                                                             Read Iptc meta data from current image.
 * @method \Intervention\Image\Image fill(mixed $filling, integer $x = null, integer $y = null)                                                                           Fill current image with given color or another image used as tile for filling. Pass optional x, y coordinates to start at a certain point.
 * @method \Intervention\Image\Image flip(mixed $mode = 'h')                                                                                                              Mirror the current image horizontally or vertically by specifying the mode.
 * @method \Intervention\Image\Image fit(integer $width, integer $height = null, \Closure $callback = null, string $position = 'center')                                  Combine cropping and resizing to format image in a smart way. The method will find the best fitting aspect ratio of your given width and height on the current image automatically, cut it out and resize it to the given dimension. You may pass an optional Closure callback as third parameter, to prevent possible upsizing and a custom position of the cutout as fourth parameter.
 * @method \Intervention\Image\Image gamma(float $correction)                                                                                                             Performs a gamma correction operation on the current image.
 * @method \Intervention\Image\Image greyscale()                                                                                                                          Turns image into a greyscale version.
 * @method \Intervention\Image\Image heighten(integer $height, \Closure $callback = null)                                                                                 Resizes the current image to new height, constraining aspect ratio. Pass an optional Closure callback as third parameter, to apply additional constraints like preventing possible upsizing.
 * @method \Intervention\Image\Image insert(mixed $source, string $position = 'top-left', integer $x = 0, integer $y = 0)                                                 Paste a given image source over the current image with an optional position and a offset coordinate. This method can be used to apply another image as watermark because the transparency values are maintained.
 * @method \Intervention\Image\Image interlace(boolean $interlace = true)                                                                                                 Determine whether an image should be encoded in interlaced or standard mode by toggling interlace mode with a boolean parameter. If an JPEG image is set interlaced the image will be processed as a progressive JPEG.
 * @method \Intervention\Image\Image invert()                                                                                                                             Reverses all colors of the current image.
 * @method \Intervention\Image\Image limitColors(integer $count, mixed $matte = null)                                                                                     Method converts the existing colors of the current image into a color table with a given maximum count of colors. The function preserves as much alpha channel information as possible and blends transarent pixels against a optional matte color.
 * @method \Intervention\Image\Image line(integer $x1, integer $y1, integer $x2, integer $y2, \Closure $callback = null)                                                  Draw a line from x,y point 1 to x,y point 2 on current image. Define color and/or width of line in an optional Closure callback.
 * @method \Intervention\Image\Image make(mixed $source)                                                                                                                  Universal factory method to create a new image instance from source, which can be a filepath, a GD image resource, an Imagick object or a binary image data.
 * @method \Intervention\Image\Image mask(mixed $source, boolean $mask_with_alpha)                                                                                        Apply a given image source as alpha mask to the current image to change current opacity. Mask will be resized to the current image size. By default a greyscale version of the mask is converted to alpha values, but you can set mask_with_alpha to apply the actual alpha channel. Any transparency values of the current image will be maintained.
 * @method \Intervention\Image\Image opacity(integer $transparency)                                                                                                       Set the opacity in percent of the current image ranging from 100% for opaque and 0% for full transparency.
 * @method \Intervention\Image\Image orientate()                                                                                                                          This method reads the EXIF image profile setting 'Orientation' and performs a rotation on the image to display the image correctly.
 * @method mixed                     pickColor(integer $x, integer $y, string $format = 'array')                                                                          Pick a color at point x, y out of current image and return in optional given format.
 * @method \Intervention\Image\Image pixel(mixed $color, integer $x, integer $y)                                                                                          Draw a single pixel in given color on x, y position.
 * @method \Intervention\Image\Image pixelate(integer $size)                                                                                                              Applies a pixelation effect to the current image with a given size of pixels.
 * @method \Intervention\Image\Image polygon(array $points, \Closure $callback = null)                                                                                    Draw a colored polygon with given points. You can define the appearance of the polygon by an optional closure callback.
 * @method \Intervention\Image\Image rectangle(integer $x1, integer $y1, integer $x2, integer $y2, \Closure $callback = null)                                             Draw a colored rectangle on current image with top-left corner on x,y point 1 and bottom-right corner at x,y point 2. Define the overall appearance of the shape by passing a Closure callback as an optional parameter.
 * @method \Intervention\Image\Image reset(string $name = 'default')                                                                                                      Resets all of the modifications to a state saved previously by backup under an optional name.
 * @method \Intervention\Image\Image resize(integer $width, integer $height, \Closure $callback = null)                                                                   Resizes current image based on given width and/or height. To contraint the resize command, pass an optional Closure callback as third parameter.
 * @method \Intervention\Image\Image resizeCanvas(integer $width, integer $height, string $anchor = 'center', boolean $relative = false, mixed $bgcolor = '#000000')      Resize the boundaries of the current image to given width and height. An anchor can be defined to determine from what point of the image the resizing is going to happen. Set the mode to relative to add or subtract the given width or height to the actual image dimensions. You can also pass a background color for the emerging area of the image.
 * @method mixed                     response(string $format = null, integer $quality = 90)                                                                               Sends HTTP response with current image in given format and quality.
 * @method \Intervention\Image\Image rotate(float $angle, string $bgcolor = '#000000')                                                                                    Rotate the current image counter-clockwise by a given angle. Optionally define a background color for the uncovered zone after the rotation.
 * @method \Intervention\Image\Image sharpen(integer $amount = 10)                                                                                                        Sharpen current image with an optional amount. Use values between 0 and 100.
 * @method \Intervention\Image\Image text(string $text, integer $x = 0, integer $y = 0, \Closure $callback = null)                                                        Write a text string to the current image at an optional x,y basepoint position. You can define more details like font-size, font-file and alignment via a callback as the fourth parameter.
 * @method \Intervention\Image\Image trim(string $base = 'top-left', array $away = array('top', 'bottom', 'left', 'right'), integer $tolerance = 0, integer $feather = 0) Trim away image space in given color. Define an optional base to pick a color at a certain position and borders that should be trimmed away. You can also set an optional tolerance level, to trim similar colors and add a feathering border around the trimed image.
 * @method \Intervention\Image\Image widen(integer $width, \Closure $callback = null)                                                                                     Resizes the current image to new width, constraining aspect ratio. Pass an optional Closure callback as third parameter, to apply additional constraints like preventing possible upsizing.
 * @method StreamInterface           stream(string $format = null, integer $quality = 90)                                                                                 Build PSR-7 compatible StreamInterface with current image in given format and quality.
 * @method ResponseInterface         psrResponse(string $format = null, integer $quality = 90)                                                                            Build PSR-7 compatible ResponseInterface with current image in given format and quality.
 */

PHP ZIP解压缩工具 与 范例

针对ZIP压缩包的操作,PHP中提供了ZIP扩展。具体来说,它提供了一个叫ZipArchive的类,和一系列ZIP函数。ZipArchive类提供了大多操作ZIP压缩包的方法,比如创建压缩包(addFile),获取压缩包的文件名(索引),读取包内的内容等(写和读两个内容展开),对于读取压缩包也可以使用ZIP函数。(ZIP的一系列函数是读取ZIP内文件的工具,在PHP中存在的事件非常长,ZipArchive大概是后期提供的,因为仅仅读取ZIP是远不够的,所以它完整提供了读写功能)

## 创建压缩包addFile

        //生成压缩包下载
        $filename = "./" . date ( 'Ymd' )."_".time() . ".zip";
        // 生成文件
        $zip = new ZipArchive(); 
        if($zip->open($filename, ZIPARCHIVE::CREATE ) !== TRUE) {
            exit('无法打开文件,或者文件创建失败');
        }
        
        foreach($fileNameArr as $val) {
            $zip->addFile($val);
        }
        $zip->close();

New一个ZipArchive,调用其的open方法打开一个zip文件(文件不存在就是创建一个zip),然后非常简单的调用addFile()就可以把文件添加这个压缩包中,最后调用close方法,玛尼压缩包就生成了。So easy。

## 遍历压缩包,获取文件名
需要知道,New一个ZipArchive,那就有一个叫numFiles的属性,它记录了这个压缩包有多少个文件:

#test.php压缩包结构
22222
    4444.txt
33333
fffff.txt

$zip = new ZipArchive();
if ($zip->open($zipFile) == TRUE) {
    for ($i = 0; $i < $zip->numFiles; $i++) {
        $filename = $zip->getNameIndex($i);
        echo $filename."\n";
    }
    $zip->close();
}

输出:
22222/4444.txt
33333/
fffff.txt
22222/

目录是一个文件,实际的文件带目录前缀。

##获取压缩包内的文件

$zip = new ZipArchive();
if ($zip->open($zipFile) == TRUE) {
    $file = $zip->getStream("22222/excel.xls");
    file_put_contents("/var/data/excel.xls", $file);

    $zip->close();
}

这里的getStream()接收一个字符串文件名,就是通过getNameIndex()获取到的文件名,可以首先遍历,判断预期文件是否存在,然后就打开这个文件(getStream()就是打开这个文件),获得一个文件流指针,然后跟操作文件没有什么不一样了。

##获取压缩包内的文件的稍特别的例子 使用copy函数

$zip = new ZipArchive;
if ($zip->open($path) === true) {
    for($i = 0; $i < $zip->numFiles; $i++) {
        $filename = $zip->getNameIndex($i);
        $fileinfo = pathinfo($filename);
        copy("zip://".$path."#".$filename, "/your/new/destination/".$fileinfo['basename']);
    }
    $zip->close();
}

如果要读取压缩包内容,还有如下方法(ZIP系列函数):

            $zip = zip_open($tempZipFile);
            if(is_resource($zip)) {
                $needEntry = '';
                // 取期望的文件
                while($zipf = zip_read($zip)) {
                    $zipfname = zip_entry_name($zipf); 
                    if(iconv("UTF-8","GBK","xxx.xls") == $zipfname) {
                        $needEntry = $zipf;
                    }
                }
                
                if(!empty($needEntry)) {
                    // 打开内文件
                    zip_entry_open($zip, $needEntry, "r");
                    // 在用zip_entry_read()读取内容,务必使用zip_entry_filesize()带上文件大小
                    file_put_contents($savaPath, zip_entry_read($needEntry,zip_entry_filesize($needEntry)));
                    
                    zip_entry_close($needEntry);
                } else {
                    // 不存在预定文件
                }
                zip_close($zip);
            }

这个方法较为繁琐,一般建议不要再使用了。在实际中遇到zip_entry_name()乱码情况(文件名编码实际为GBK),可能跟PHP版本有关(测试环境为PHP 5.6, 而PHP 5.5正确工作)。

PHP 命令行进程控制

exec(‘ps -efH|grep ‘.basename(__FILE__).’|grep -v “grep”‘, $output);
print_r($output);

Array
(
[0] => root 32459 32232 1 16:02 pts/0 00:00:00 php t.php
)

如果多于一个这样的脚本在跑,那么希望希望杀掉前面那个,如何干?两个问题,获取被杀的进程号,然后是能不能杀?

获取前面的进程号可以很容器实现,能不能杀还看你以什么权限的账户运行,由于总是相同用户在运行,所以被自己杀应该总是可以的。还有就是,老进程的ID号一定会比新的小吗,否则老的没杀成,杀了新的就不太好了。