Web
Web

__autoload 内部实现原理

__autoload  

官方:尝试加载未定义的类。我的理解是在New 对象的时候,会将对象名传递。

<?php
function __autoload($class_name) { 
    echo '加载的类名:'.$class_name;
} 

@$a = new iwonmo(); 
@$a = new iwonmo1();

运行结果输出:加载的类名:iwonmo

当New的类不存在时,可以通过这个函数进行include/require。

不过注意官方的警告:This feature has been DEPRECATED as of PHP 7.2.0. Relying on this feature is highly discouraged. 在7.2.0以后的版本被弃用

SPL

SPL的全称是:Standard PHP Library (PHP标准库),版本:PHP 5 >= 5.1.0, PHP 7,这个库是做什么用的,在官网也有解释,只不过需要看英文版,因为中文版的介绍实际是不全的,不单单是这个解释。所以看PHP的文档尽量看英文版的。

The Standard PHP Library (SPL) is a collection of interfaces and classes that are meant to solve common problems.

No external libraries are needed to build this extension and it is available and compiled by default in PHP 5.0.0.

SPL provides a set of standard datastructure, a set of iterators to traverse over objects, a set of interfaces, a set of standard Exceptions, a number of classes to work with files and it provides a set of functions like spl_autoload_register()


大致的意思是SPL是一个解决常见问题接口或者的集合。然后里面提供了一组叫做SPL的函数。另外(英文里不包含):SPL函数也取代了__autoload。

spl_autoload_register的官方解释里有这么一句话:如果在你的程序中已经实现了__autoload()函数,它必须显式注册(explicitly registered )到__autoload()队列中。因为 spl_autoload_register()函数会将Zend Engine中的__autoload()函数取代为spl_autoload()或spl_autoload_call()。

对于这组函数可以翻阅PHP的源代码(php_spl.c):

int spl_autoload(const char *class_name, const char * lc_name, int class_name_len, const char * file_extension TSRMLS_DC) /* {{{ */
{
    char *class_file;
    int class_file_len;
    int dummy = 1;
    zend_file_handle file_handle;
    zend_op_array *new_op_array;
    zval *result = NULL;

    class_file_len = spprintf(&class_file, 0, "%s%s", lc_name, file_extension);

    if (zend_stream_open(class_file, &file_handle TSRMLS_CC) == SUCCESS) {
        if (!file_handle.opened_path) {
            file_handle.opened_path = estrndup(class_file, class_file_len);
        }
        if (zend_hash_add(&EG(included_files), file_handle.opened_path, strlen(file_handle.opened_path)+1, (void *)&dummy, sizeof(int), NULL)==SUCCESS) {
            new_op_array = zend_compile_file(&file_handle, ZEND_REQUIRE TSRMLS_CC);
            zend_destroy_file_handle(&file_handle TSRMLS_CC);
        } else {
            new_op_array = NULL;
            zend_file_handle_dtor(&file_handle);
        }
        if (new_op_array) {
            EG(return_value_ptr_ptr) = &result;
            EG(active_op_array) = new_op_array;
    
            zend_execute(new_op_array TSRMLS_CC);
    
            destroy_op_array(new_op_array TSRMLS_CC);
            efree(new_op_array);
            if (!EG(exception)) {
                if (EG(return_value_ptr_ptr)) {
                    zval_ptr_dtor(EG(return_value_ptr_ptr));
                }
            }

            efree(class_file);
            return zend_hash_exists(EG(class_table), (char*)lc_name, class_name_len+1);
        }
    }
    efree(class_file);
    return 0;
} /* }}} */

大致就是找到这个文件,然后编译成OP代码,然后执行,释放。实际这个过程也是__autoload的过程。这里有一个地方是会将对象名转换为小写,不知道这段代码的意义。不过明白大致意思就可以。file_extension由于被宏定义,所以这里它是一个上下文。


个人理解,如有错误还望指出。

七牛 大文件上传

大文件报错:invalid multipart format: multipart: message too large

UploadManager->put 改为 UploadManager->putFile


原生与WebView传递超大文件

方法一

原生实现 分块读取接口,由JS去调用,读取本地文件。

方法二

将大文件转成特定格式如base64,以对象形式存储生成JS文件,动态加载JS文件,调用相应接口,获取base64数据。

js文件如下

function load(){
    var ob = new Object(); /** 这里内部会创建一个Handle<JSOBJECT>的对象 是一个地址 */
    ob.data="亿万数据"; /** 由于是内存地址所以可以存储小于当前内存的一块空间,理论上无限大 */
    return ob; /** 返回这一个对象 */
}

原生可以将文件base64写入到手机,然后webview的JS通过动态加载JS获取load函数,从而调用load函数实现得到大文件的数据。

调用方法(jQuery)

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <button style="width:100px;height:30px;" onclick="getJs()">获取</button>
    <script>
        function getJs(){
            $.getScript("hotLoad.js",(e)=>{
                console.log(load());
            });
        };
    </script>
</body>
</html>

由于getScript是Ajax实现的,所以注意跨域问题。使用其他方式实现JS的加载也可。

微信小程序附近人

代码很简单,需要前端不断的传入经纬度并且更新数据库用户表里的经纬度字段,然后重数据表中提取所有用户信息,在获取自身的经纬度,进行排序就可以了。

<?php
//自己的经纬度,对方的经纬度 ,返回单位 米 
function getdistance($lng1, $lat1, $lng2, $lat2) {
    $radLat1 = deg2rad($lat1);
    $radLat2 = deg2rad($lat2);
    $radLng1 = deg2rad($lng1);
    $radLng2 = deg2rad($lng2);
    $a = $radLat1 - $radLat2;
    $b = $radLng1 - $radLng2;
    $s = 2 * asin(sqrt(pow(sin($a / 2), 2) + cos($radLat1) * cos($radLat2) * pow(sin($b / 2), 2))) * 6370.996 * 1000;
    return $s;
}
//模拟数据库查找出的数据  j:经度 w:纬度
$array=[]; 
array_push($array,[
'j'=>36.6675927719,
'w'=>98.4400749207,
's'=>1
]);
array_push($array,[
'j'=>36.6708284639,
'w'=>98.4741497040,
's'=>2
]);
array_push($array,[
'j'=>36.6711038356,
'w'=>98.4590435028,
's'=>3
]);
//自己的经纬度
$j=36.6684189189;
$w=98.4375000000;
//上述数组正确的顺序 s: 1 3 2
function s($j,$w,&$array){
$len=count($array);
    for($i=1;$i<$len; $i++) {
        $tmp = $array[$i];
        for($j=$i-1;$j>=0;$j--) {
            if(getdistance($j,$w,$tmp['j'],$tmp['w'])<getdistance($j,$w,$array[$j]['j'],$array[$j]['w'])) {
                $array[$j+1] = $array[$j];
                $array[$j] = $tmp;
            } else {
                break;
            }
        }
    }
    return $array;
}
var_dump(s($j,$w,$array));


微信开发者工具插件开发

前天看了下HBuilder发现用它开发的微信小程序,在调试的时候可以直接进行微信开发者工具展示,觉得这个功能挺有意思,随机看了下微信开发者工具的文档,发现它提供了两种与微信开发者工具沟通的方法。

1、CLI

2、HTTP

文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/cli.html

不过并没有提供微信开发者工具公众号开发的一些“接口”,比如说刷新。如果说使用PhpStorm开发公众号,PhpStorm保存后,微信开发者工具可以自动刷新,那么是非常有意思的事情,所以我决定去实现它。

首先要明白微信开发者工具是用什么软件进行开发的,才能决定使用哪种方式进行插件开发。

一波三折后,你会发现这款工具是使用的nw.js开发的,https://nwjs.io/

1、阅读nw.js的文档,你会发现在编译的时候总会有一个叫做package.nw的文件夹,但是我在微信开发者工具的根目录并没有发现这个文件夹。这个思路又断了,

2、然后开始查看微信开发者工具,你会发现菜单上就有一个刷新的按钮,并且它存在快捷键,随即考虑另一种方案,发送组合键,这是win下的一种编程方式,在win中,界面是一种庞大的消息结构,是否可以尝试发送CTRL+V这种快捷键消息来进行操作,在真实的操作过程中发现存在一个问题,我的scancode每次发送后总是显示114,而真实的应该是18。后来认为是CTRL是系统键,微信开发者工具响应的模式不一样。如果你用osk进行屏幕键盘操作,你会发现是可以的,但是这种方法是无法准确的投递到窗口,因为需要WM_ACTIVE是你的开发者工具窗口,然后才能发送按键,这个时候scancode就是18。这种方法感觉不好,因为phpstorm保存后在激活开发者工具 ,然后在投递,整个展示的过程是不友好的。

3、后来决定换种思路,直接替换掉微信开发者工具的菜单,然后把click事件进行一个绑定拦截,但是在我动态调试的时候,没有拦截到菜单对应的几个api。而后重载的时候又发现老是出现nw.js框架的界面。昨晚调试的,由于太晚就没有按照这个思路折腾下去,因为这种的话,你要实现一个外部代码动态插入到微信开发者工具中,是很繁琐的一个步骤,并且不会保证软件的稳定性。

4、在菜单当中发现了一个叫做调试微信开发者工具的功能,在整个html的最底部有一个index.js的文件,这个可是一个新大陆,js代码如下:

const path = require('path')
const tools = require('../js/84b183688a46c9e2626d3e6f83365e13.js')
const locales = require('../js/common/locales/index.js')
const isMac = (process.platform === 'darwin')
const query = tools.getQuery(location.search)
// 记录工具开始运行的时间
global.beginTime = Date.now()
function hack() {
  // to prevent drag image or html
  document.body.addEventListener('dragover', function (e) {
    e.preventDefault()
    e.stopPropagation()
  }, false)
  document.body.addEventListener('drop', function (e) {
    e.preventDefault()
    e.stopPropagation()
  }, false)
  // 禁用滚轮缩放
  document.addEventListener('mousewheel', (event) => {
    if (event.ctrlKey) event.preventDefault()
  })
}
function initGlobal() {
  global.appVersion = nw.App.manifest.version
  global.useChromeRemoteDebugProtocal = false
  // mac 从application 启动时带的环境变量里没有 :/usr/local/bin
  isMac && (process.env.PATH += ':/usr/local/bin')
  // 在非 new instance 的窗口内可以共享
  global.shareData = {}
  global.appConfig = tools.getAppConfig()
  // global.appConfig.isDev = false
  // global.appConfig.isGamma = true
  // nw & foreground variables
  const Win = nw.Window.get()
  global.Win = Win
  global.contentDocument = document
  global.contentDocumentBody = document.body
  global.contentWindow = window
  global.windowMap = new Map()
  global.windowMap.set('LOGIN', global.Win)
  // worker 懒加载
  global.worker = {}
  Object.defineProperties(global.worker, {
    bbsLogWorker: {
      get() {
        if (!this._bbsLogWoker) {
          this._bbsLogWoker = new Worker('../js/2bc74df4df155a7d0d1c4df1e947d57d.js')
        }
        return this._bbsLogWoker
      },
    },
  })
  // 提供一个全局 reload 的方法
  global.reload = () => {
    for (key in require.cache) {
      require.cache[key] = undefined
    }
    location.reload()
  }
  global.devInfo = {}
  if (location.search) {
    global.isDevWindow = true
    global.devType = location.search.match(/devtype=(.*?)(&|$)/)[1]
    switch (global.devType) {
      case 'webdebugger': {
        global.devInfo.id = query.devid
        break
      }
      default: {
        global.devInfo.id = query.devid
        global.devInfo.appid = query.appid
        global.devInfo.projectname = query.projectname
        global.devInfo.projectpath = query.projectpath
        global.devInfo.projectid = `${global.devInfo.appid}_${encodeURIComponent(global.devInfo.projectname)}`
        global.devInfo.isTemp = Boolean(query.isTemp)
        global.devInfo.isOnline = Boolean(query.isOnline)
        if (global.devInfo.isTemp) {
          const tempLocalStorageKey = `temp_${global.devInfo.appid}_${global.devInfo.projectname}`
          global.devInfo.project = JSON.parse(localStorage[tempLocalStorageKey])
          delete localStorage[tempLocalStorageKey]
        }
      }
    }
    if (query.simple) {
      // 多账号模式登录
      global.isSimple = true
      global.userInfo = {
        openid: query.openid,
        nickName: query.nickName,
        headUrl: query.headUrl,
        contry: query.contry,
        city: query.city,
        loginStatus: query.loginStatus,
        province: query.province,
        sex: query.sex,
        newticket: query.newticket,
        ticketExpiredTime: parseInt(query.ticketExpiredTime),
        signature: query.signature,
        signatureExpiredTime: parseInt(query.signatureExpiredTime)
      }
    }
  }
  // parse cli
  global.CLI = {}
  try {
    global.CLI.isTestMode = nw.App.argv.indexOf('--test-mode') > -1
    global.autoTest = global.CLI.isTestMode
    if (nw.App.argv.indexOf('--only-simulator') > -1) {
      global.onlySimulator = true
    }
    if (nw.App.argv.indexOf('--online') > -1) {
      global.online = true
    }
    if (global.CLI.isTestMode) {
      const ind = nw.App.argv.indexOf('--id')
      if (ind > -1) {
        const raw = nw.App.argv[ind + 1]
        if (raw) {
          global.CLI.id = raw
        }
      }
    }
  } catch (err) {}
}
function initMenu() {
  // init initial menu in case of failure
  try {
    if (global.isDevWindow || isMac) {
      const menu = new nw.Menu({type: 'menubar'})
      const ideMenu = new nw.Menu()
      const debugMenu = new nw.Menu()
      if (global.isDevWindow) {
        debugMenu.append(new nw.MenuItem({
          label: locales.config.MENU_INSPECT_APP,
          click: () => global.Win.showDevTools(),
        }))
      } else {
        debugMenu.append(new nw.MenuItem({
          label: locales.config.MENU_INSPECT_APP,
          click: () => {
            chrome.developerPrivate.openDevTools({
              renderViewId: -1,
              renderProcessId: -1,
              extensionId: chrome.runtime.id
            })
          },
        }))
      }
      ideMenu.append(new nw.MenuItem({
        label: locales.config.MENU_INSPECT,
        submenu: debugMenu,
      }))
      ideMenu.append(new nw.MenuItem({
        label: locales.config.CLOSE_WINDOW,
        click: () => global.Win.close(true),
      }))
      ideMenu.append(new nw.MenuItem({
        label: locales.config.MENU_EXIT,
        click: () => nw.App.quit(),
      }))
      menu.append(new nw.MenuItem({
        label: locales.config.MENU_TITLE_APP,
        submenu: ideMenu,
      }))
      global.Win.menu = menu
    }
  } catch (err) {}
}
function init() {
  const Win = global.Win
  Win.on('new-win-policy', (frame, url, policy) => {
    policy.ignore()
  })
  Win.on('close', () => {
    console.log(222222);
    alert(1111)
    // make all webviews invisible
    const webviews = document.querySelectorAll('webview')
    for (const webview of webviews) {
      try {
        webview.style.display = 'none'
      } catch (e) {
        // nothing to do
      }
    }
    global.windowMap.forEach((win) => {
      try {
        if (win !== Win) {
          win.close(true)
        }
      } catch (e) {}
    })
    global.windowMap.clear()
    if (global.isDevWindow) {
      // dev window
      const clientWindowSync = require('../js/881e653f19d837f2408386047cb8c38c.js')
      clientWindowSync.notifyCloseWindow()
      Win.close(true)
    } else {
      // original main window
      const serverWindowSync = require('../js/b543ae2da406cea63b3ad8951f17b6c0.js')
      if (serverWindowSync.clientWindows.size > 0) {
        Win.hide()
      } else {
        // 几秒之后项目窗口数还是 0 说明没有正在打开的窗口了,可以正常退出
        Win.hide()
        setTimeout(() => {
          if (serverWindowSync.clientWindows.size === 0) {
            tools.quit()
          }
        }, 2000)
      }
    }
  })
  // 打开 inspect 窗口
  if (nw.App.argv.indexOf('inspect') !== -1) {
    tools.openInspectWin()
  }
  // enter background
  if (query.simple) {
    require('../js/8524207e9ea0bd06cec5e97c74bd6b7d.js')
  } else {
    require('../js/29cbb96f0d87ca0a3ee63c5dbbd8107c.js')
  }
}
hack()
initGlobal()
initMenu()
if (!global.isDevWindow && !global.online) {
  const checkUpdate = require('../js/e5184416014aff2809a9dee32cc447c8.js')
  checkUpdate.loop()
  // 检查是否需要更新
  tools.checkUpdateApp()
    .then(() => {
      init()
    })
} else {
  init()
}

在这里你会发现一个叫做initMenu的函数,不过查看之下会发现只是一个简单的Menu,不过你可以看到有一个关键文件,84b183688a46c9e2626d3e6f83365e13.js,打开后你会失望,因为它混淆并且压缩了,代码基本不可读,原本想着直接调用刷新的函数,现在想来也不好使,没办法继续回到index.js的查看,会发下一个叫做global的对象,这个对象我打印了一下,并没有找到存在刷新的关键字例如ref,在要放弃的时候发现了新大陆,win对象。这个对象如果在win里那就是窗口的意思,然后把win打印一下,出现了一个另人激动的子对象Menu,逐级查找,最终确定了刷新按钮的事件:global.Win.menu.items[2].submenu.items[2].click();。不过即使有了这个事件,还是无法与外界程序沟通更别提phpstorm了,因为这个global对象是在微信开发者工具里的,外部是无法调用的,即使你在往里写一个js那还是无法调用,后来思考改造一下微信开发者工具的cli和http接口调用的方式,加入一个自己的接口,先说cli,这个我实现了一下,发现不可行,因为其中的global并没有在cli进行实现,调用的时候直接抛出错误,然后我又想着改微信开发者工具http的调用,发现依然不行,因为提示没有我新新增的命令,在查找的过程中发现http又被微信混淆了,代码基本没法读,没办法。所以采用了一种折中的方式,自己实现http,然后在自己的端口进行监听,并且刷新。然后你就可以开发phpstorm插件,调用这个路径,进行刷新了,phpstorm插件的编写方法,搜索本博客也有记载。

http.createServer(function(request, response){
  global.Win.menu.items[2].submenu.items[2].click();
    response.writeHead(200, { 'Content-Type': 'text-plain' });
    response.write('<head><meta charset="utf-8"/></head>');
    response.end('我刷新了微信开发者工具...\n');
}).listen(2019);


最终的效果图:


Untitled1.gif