使用 satis 搭建一个私有的 Composer 包仓库

在日常php开发中可能需要使用大量的composer包,大部份都可以直接使用,但公司内部有一些与业务相关的包,是不能公开的,这时候我们就需要搭建一个公司内部使用的composer仓库。

composer官方提供的工具有satis和toran proxy。 satis的搭建相对简单一些,于是我选用satis进行搭建。它的文档在composer 以及 github

1. 建立项目

使用 Composer 自带的建项目功能,这个相当于 git clone + composer install + 运行 post-install 脚本。

composer create-project composer/satis my-satis --stability=dev --keep-vcs

2. 建立配置文件

在 satis 项目目录下建立 satis.json 文件

{
  "name": "仓库名称",
  "homepage": "http://satis仓库地址",
  "repositories": [
    { "type": "vcs", "url": "https://github.com/mycompany/privaterepo" },
    { "type": "vcs", "url": "http://svn.example.org/private/repo" },
    { "type": "vcs", "url": "https://github.com/mycompany/privaterepo2" }
  ],
  "require-all": true
}

注意:仓库名称需要和仓库里 composer.json 的 name 定义一致,和路径没什么关系,不然就会找不到。

因为加入私有源的仓库本身可能也有依赖,require-all 会把这些依赖的信息也抓进来。如果不需要的话,可以指定某个仓库,甚至某个版本:

{
  "name": "仓库名称",
  "homepage": "http://satis仓库地址/",
  "repositories": [
    { "type": "vcs", "url": "https://github.com/mycompany/privaterepo" },
    { "type": "vcs", "url": "http://svn.example.org/private/repo" },
    { "type": "vcs", "url": "https://github.com/mycompany/privaterepo2" }
  ],
  "require": {
    "company/package": "*",
    "company/package2": "*",
    "company/package3": "2.0.0"
  }
}

3. 生成仓库列表

php bin/satis build satis.json public/

4. 配置nginx

总之把访问地址的根目录放在public文件夹下。

5. 在其它项目中使用私有源

只需要在项目的 composer.json 文件的根上添加

{
  "repositories": [
    {
      "type": "composer",
      "url": "http://satis仓库地址/"
    }
  ],
  "require": {
    "company/package": "1.2.0",
    "company/package2": "1.5.2",
    "company/package3": "dev-master"
  }
}

然后执行composer update即可

注意:源里面只有“仓库列表”,并没有真的同步代码仓库过来,所以下载还要走托管代码的机器,比如 GitHub,内部 GitLab 等。

如果从 clone 速度太慢了,我们也可以缓存在我们的仓库中。

在satis.json中增加

{
    "archive": {
        "directory": "dist",
        "format": "tar",
        "prefix-url": "http://packages.dev.com/",
        "skip-dev": true
    }
}

directory: 必需要的,表示生成的压缩包存放的目录,会在我们build时的目录中
format: 压缩包格式, zip(默认) tar
prefix-url: 下载链接的前缀的Url,默认会从homepage中取
skip-dev: 默认为假,是否跳过开发分支
absolute-directory: 绝对目录
whitelist: 白名单,只下载哪些
blacklist: 黑名单,不下载哪些
checksum: 可选,是否验证sha1

再次生成

php bin/satis build satis.json public/

会发现public目录多了一个dist目录,里面有很多tar的压缩包,这就是我们的package。 之后再执行composer update就会发现快了很多。

参考资料


selenium2 入门 - 基于php

Selenium 用于自动化测试,它有助于自动化Web应用程序测试。本文介绍它的快速入门配置(php版本)与一个很简单的php爬虫应用。

Composer 安装 Selenium:

composer require facebook/webdriver

安装selenium

本机安装

  • 下载Selenium Server并启动:

    下载Selenium Standalone Server:http://www.seleniumhq.org/download/

    安装java jdk后,运行如下命令:

    java -jar selenium-server-standalone-3.0.1.jar
    
  • 下载浏览器插件

    selenium 支持多种浏览器,我目前使用的是chrome。http://chromedriver.storage.googleapis.com/index.html?path=2.27/

    下载解压,你会得到一个 chromedriver.exe 文件,放到 chrome 的安装目录下(我的:C:\Program Files (x86)\Google\Chrome\Application\),然后在 path 环境变量中添加 chrome 的安装目录。

docker安装

docker run -d --name chrome --rm -p 4444:4444 -p 5900:5900 selenium/standalone-chrome-debug

上面这条命令的参数含义如下:

  • -d, 表示不附着容器,即容器在后台运行
  • –name 是给容器定义一个名称;
  • –rm 容器运行结束后即删除,方便调试使用
  • -p 4444:4444 暴露宿主机的4444端口到容器的4444端口,提供 webdriver 服务;
  • -p 5900:5900 暴露宿主机的5900端口,提供 VNC 远程桌面服务,如果是在 Mac 上运行,有可能5900端口已使用,可以换为5901端口,写法是 -p 5901:5900

可以用 VNC viewer 观看浏览器运行的情况。

运行测试代码

打开命令提示符,运行后边的文件 php bilibili.php 命令,最后打印了哔哩哔哩顶部的8个视频。

// bilibili.php
<?php
namespace Facebook\WebDriver;

use Facebook\WebDriver\Remote\DesiredCapabilities;
use Facebook\WebDriver\Remote\RemoteWebDriver;

require_once('vendor/autoload.php');

header("Content-Type: text/html; charset=UTF-8");
$waitSeconds = 15;  //需等待加载的时间,一般加载时间在0-15秒,如果超过15秒,报错。
$host = 'http://localhost:4444/wd/hub'; // this is the default
$capabilities = DesiredCapabilities::chrome();
$driver = RemoteWebDriver::create($host, $capabilities, 5000);
$baseUrl = 'http://www.bilibili.com/';
$driver->get($baseUrl);

echo consoleText($driver->getTitle()) . "\n";    //cmd.exe中文乱码,所以需转码

$topLists = $driver->findElement(WebDriverBy::className('container-top-wrapper'))->findElement(WebDriverBy::className('top-list-wrapper'))->findElements(WebDriverBy::tagName('li'));

foreach ($topLists as $topLi) {
    $itemContent = $topLi->findElement(WebDriverBy::tagName('a'));
    echo consoleText($itemContent->getAttribute('title')) . ' : ' . consoleText($itemContent->getAttribute('href')) . "\n";
}


​ //关闭浏览器 ​ $driver->quit(); ​
function consoleText($text, $pageEncoding = ‘’, $consoleEncoding = ‘’) { // windows if (!$consoleEncoding) { if (stristr(php_uname(‘s’), ‘win’)) { $consoleEncoding = “GBK”; } else { $consoleEncoding = ‘UTF-8’; } } return exchangeEncoding($text, $pageEncoding, $consoleEncoding); }

function exchangeEncoding($text, $pageEncoding = '', $targetEncoding = 'UTF-8')
{
    if (!$pageEncoding) {
        $pageEncoding = mb_detect_encoding($text, array("ASCII", 'UTF-8', "GB2312", "GBK", 'BIG5'));
    }

    if ($pageEncoding != $targetEncoding) {
        return mb_convert_encoding($text,$targetEncoding,$pageEncoding);
    }

    return $text;
}

//切换至最后一个window
function switchToEndWindow($driver)
{

    $arr = $driver->getWindowHandles();
    foreach ($arr as $k => $v) {
        if ($k == (count($arr) - 1)) {
            $driver->switchTo()->window($v);
        }
    }
}

?>

参考资料


php 获取编码和转换编码

做爬虫抓取页面的时候,常常有非utf-8的混入,包括gbk gb2312 甚至是 Big5,需要转换成我们期望的格式。 同时,Windows与其他操作系统也不同。普通的Linux和Mac都是原生使用utf-8的编码格式,而中文的windows用的则是gbk格式。因此针对不同系统的终端输出(console,cmd),我们也需要进行编码转换。

看代码说话:

function exchangeEncoding($text, $pageEncoding = '', $targetEncoding = 'UTF-8')
{
    if (!$pageEncoding) {
        $pageEncoding = mb_detect_encoding($text, array("ASCII", 'UTF-8', "GB2312", "GBK", 'BIG5'));
    }

    if ($pageEncoding != $targetEncoding) {
        return mb_convert_encoding($text,$targetEncoding,$pageEncoding);
    }

    return $text;
}

function consoleText($text, $pageEncoding = '', $consoleEncoding = '')
{
    // windows
    if (!$consoleEncoding) {
        if (stristr(php_uname('s'), 'win')) {
            $consoleEncoding = "GBK";
        } else {
            $consoleEncoding = 'UTF-8';
        }
    }
    return exchangeEncoding($text, $pageEncoding, $consoleEncoding);
}

使用 php_uname() 判断操作系统类型

/**
 *       'a':  返回所有信息
 *       's':  操作系统的名称,如FreeBSD
 *       'n':  主机的名称,如cnscn.org
 *       'r':  版本名,如5.1.2-RELEASE
 *       'v':  操作系统的版本号
 *       'm': 核心类型,如i386
 */
function php_uname ($mode = null) {}

运行 php_uname(), 在我本机 Windows 中,返回类似如下的数据

Windows NT KELU-PC 10.0 build 10586 (Windows 10) AMD64

在 Linux 服务器中则如下

Linux debian 4.8.6-x86_64 #1 SMP Tue Nov 1 14:51:21 EDT 2016 x86_64

使用 mb_detect_encoding 判断字符编码

function mb_detect_encoding ($str, $encoding_list = null, $strict = null) {}

使用 mb_convert_encoding 转换字符编码

function mb_convert_encoding ($str, $to_encoding, $from_encoding = null) {}

参考资料


Chrome 插件vimium —— vim党的福音

使用vimium已经很长时间了,然而每次应用仅限于hjkl几个上下左右键和KJ标签页操作这种最低级的操作。实际上vimium有很多实用的快捷键。 这几天换了新的办公地点,除了熟悉vimium这个插件外,索性给自己再来点新的感觉——使用左手鼠标。三天过去了,慢慢习惯了,确实不错。倒不是因为效率上带来的提高,而是由此带来的新鲜感,将平日的疲惫和懈怠感一扫而空。

最常用

hjkldu 页面内移动 JK 标签页切换 HL 后退前进

Command Note
O 在新标签页中打开 URl、书签或历史记录
t 创建一个新标签页
x 关闭当前标签页

页面导航

Command Note
j, <c-e> 向下滚动
k, <c-y> 向上滚动
h 向左滚动
l 向右滚动
gg 滚动到页面顶部
G 滚动到页面底部
zH 滚动到页面最左边
zL 滚动到页面最右边
d 向下滚动(相当于 PageDown 键)
u 向上滚动(相当于 PageUp 键)
r 重载页面
gs 查看页面源代码
yy 复制当前 URL 到剪贴板
yf 复制一个 URL 链接到剪贴板
p 打开剪贴板中的 URL 到当前标签页
P 打开剪贴板中的 URL 到新标签页
gu 跳转到当前 URL 的上一层
gU 跳转到当前 URL 的最高层
i 进入插入模式
v Enter visual mode (beta feature) (enterVisualMode)
V Enter visual line mode (beta feature) (enterVisualLineMode)
gi 焦点第一个文本输入框。使用 tab 可以在文本输入框之间循环跳转
f 在当前标签页中打开一个链接
F 在新标签页中打开一个链接
<a-f> 在新标签页中打开多个链接
[[ Follow the link labeled previous or < (goPrevious)
]] Follow the link labeled next or > (goNext)
gf Cycle forward to the next frame on the page (nextFrame)
gF Select the tab’s main/top frame (mainFrame)
m 创建新标记

使用 Vomnibar

Command Note
o 打开 URl、书签或历史记录
O 在新标签页中打开 URl、书签或历史记录
T 搜索已打开的标签页
b 打开书签
B 在新窗口中打开书签
ge 编辑当前 URL
gE 编辑当前 URL 并在新标签页中打开

使用查询

Command Note
/ 进入查询模式
n 下一个已查询到的值(页面内循环查找)
N 上一个已查询到的值(页面内循环查找)

导航历史

Command Note
H 根据浏览历史后退一页
L 根据浏览历史前进一页

标签操纵

Command Note
K, gt 向右移动一个标签页
J, gT 向左移动一个标签页
g0 移动到第一个标签页
g$ 移动到最后一个标签页
t 创建一个新标签页
yt 复制当前标签页
x 关闭当前标签页
X 恢复已关闭的标签页
W 将标签页移动到新窗口
<a-p> 固定/取消固定当前标签页
« 向左移动标签页
» 向右移动标签页

其他

Command Note
? 显示帮助

参考资料


postgresql 连接报错 - no pg_hba_conf entry for host

最近换了办公环境,发现内部测试时使用 postgres 无法正常连接数据库。 使用工具连接时报错

connect to PostgreSQL server: FATAL: no pg_hba.conf entry for host "4X.XXX.XX.XXX", user "userXXX", database "dbXXX".

在维护 PostgreSQL 库时,pg_hba.conf 属于认证文件,在业务服务器出现调整,或增加应用服务器时,需要增加 pg_hba.conf 的 IP 签权信息。 另外pg_hba.conf 文件的更改对当前连接不影响,仅影响更改配置之后的新的连接。

如果 Postgresql 在 windows 下安装,使用安装时附带的 pgAdmin 进行修改即可。

一般该文件位于

C:\my_pp\PostgreSQL\9.4\data\pg_hba.conf

如果是 Linux 环境,使用 find 全局查找也能查出来。按照文件example修改即可。因为内网环境,也就没所谓了,让所有链接都能通过,即 0.0.0.0/0

local   all             all                                     peer
# IPv4 local connections:
host    all             all             0.0.0.0/0            trust
host    all             all             samenet                 trust
# IPv6 local connections:
host    all             all             ::1/128                 trust

pg 的几个基本概念

pg 的几个基本概念

  • 实例(instance):一台机器上可以跑多个实例,每个实例要占用单独的端口,默认只跑一个实例,默认端口是5432
  • 数据库(database):一个实例可以包含多个数据库
  • 模式(schema):一个数据库可以包含多个模式,模式之间的命名不会冲突,默认模式是public
  • 对象(object):模式里包含的所有东西都是对象,包括表、视图、索引、函数等等

参考资料


composer install 无法下载

如下图。

your configuration does not allow to connection to `http://xxxxxxx`,see the https://getcomposer.org/doc/06-config.md#secure-http for details.

按照提示打开 https://getcomposer.org/doc/06-config.md#secure-http

secure-http#

Defaults to true. If set to true only HTTPS URLs are allowed to be downloaded via Composer. If you really absolutely need HTTP access to something then you can disable it, but using Let's Encrypt to get a free SSL certificate is generally a better alternative.

默认是必须使用https的,而默认配置使用的镜像是http的。

所以,解决办法有两个,一个是,在本项目的composer.json 中添加 secure-http 配置:

  "config": {
    "secure-http": false
  }

composer的配置分为全局和各个项目的。针对当前项目是可以以第一种方式实现。而如果我们需要使用 composer 创建项目时当然不可行了。例如 composer create-project --prefer-dist laravel/lumen blog

于是第二个解决的办法就是在 composer 的全局配置文件 config.json 中添加 secure-http 配置。 在 win10 中这个配置文件位于 C:\Users\xxx\AppData\Roaming\Composer\config.json 中。

    "config": {
	    "secure-http": false
	},
    "repositories": {
        "packagist": {
            "type": "composer",
            "url": "http://packagist.phpcomposer.com"
        }
    }

参考资料


php include()和 require() 的区别说明

这也是个老生常谈的东西了,今天在阅读laravel的源代码,顺手记一下。

在php中引用文件有两种办法,require(_once) 及 include(_once)。

他们最主要的不同点在于

  • 使用include时,一般是放在流程控制的处理部分中。PHP 程序网页在读到 include 的文件时,才将它读进来。这种方式,可以把程序执行时的流程简单化。当包含的文件不存在时,系统会报出警告级别的错误(warning),程序会继续往下执行。
  • 使用require时,通常放在 PHP 程序的最前面,使得 PHP 程序在执行前,就会先读入 require 所指定引入的文件,使它变成 PHP 程序网页的一部份。当包含的文件不存在时,系统会先报出警告级别的错误(warning),接着又报一个致命级别的错误(Fatal error)。程序终止执行。
  • incluce在用到时加载,require在一开始就加载,_once后缀表示已加载的不加载
  • require()通常来导入静态的内容,而include()则适合用导入动态的程序代码。

      对include()语句来说,在执行文件时每次都要进行读取和评估;
      而对于require()来说,文件只处理一次(实际上,文件内容替换require()语句)。
      这就意味着如果可能执行多次的代码,则使用require()效率比较高,如果每次执行代码时是读取不同的文件,或者有通过一组文件迭代的循环,就使用include()语句。
    
  • include有返回值,而require没有。

    例如laravel的index.php文件中通过 require_once 对 app 进行赋值:

      // index.php
          <?php
          require __DIR__.'/../bootstrap/autoload.php';
          $app = require_once __DIR__.'/../bootstrap/app.php';
    
      // app.php
    
          <?php
          $app = new Illuminate\Foundation\Application(
              realpath(__DIR__.'/../')
          );
            
          ...
    
          return $app;
    

参考资料