HTML5调用手机摄像拍照、相册等功能样式美化及demo - CSDN博客

mikel阅读(1245)

来源: HTML5调用手机摄像拍照、相册等功能样式美化及demo – CSDN博客

最近用MUI做了个移动端项目,设计中涉及到调用手机摄像头拍照等功能需求,然而在PLUS环境下才能直接调用,这就让人有点头疼了,后经查询资料学习了解到有一个很简单的方法就是input:file标签,不需要复杂代码操作就能实现调用拍照、相册等功能。

//capture -- 设置选择需要调用的功能
//camera -- 照相机
//camcorder -- 摄像机
//microphone -- 录音 
//accept -- 直接打开系统文件目录 

<input type="file" accept="image/*" capture="camera">
<input type="file" accept="video/*" capture="camcorder">
<input type="file" accept="audio/*" capture="microphone">

//input:file标签还有一个属性multiple 用来支持多选 直接呼出原生选项
<input type="file" accept="image/*" multiple>

input [ type=file ] 按钮美化及上传demo

简单说就是:先将原按钮透明度opacity设为0,再外层用标签包裹,最终实现美化功能。

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title> <input[type=file]>上传demo </title>
    <style>
        .file {
            position: relative;
            display: inline-block;
            background: #D0EEFF;
            border: 1px solid #99D3F5;
            border-radius: 4px;
            padding: 4px 12px;
            overflow: hidden;
            color: #1E88C7;
            text-decoration: none;
            text-indent: 0;
            line-height: 20px;
        }
        .file input {
            position: absolute;
            font-size: 100px;
            right: 0;
            top: 0;
            opacity: 0;
        }
        .file:hover {
            background: #AADFFD;
            border-color: #78C3F3;
            color: #004974;
            text-decoration: none;
        }
        p.bottomTitlt{
            margin: 0;
            letter-spacing: 2px;
            padding:  0;
        }
    </style>
</head>
<body>

<a href="javaScript:;" class="file">
    <p class="bottomTitlt">身份证正面</p>
    <img src="" alt=""/>
    <input type="file"  placeholder="身份证正面"  >
</a>
<p class="showFileName"></p>



<script src="https://code.jquery.com/jquery-3.2.0.min.js"></script>

<script type="text/javascript">
    $(".file").on("change","input[type='file']",function(){
        var filePath=$(this).val();
        var fr= new FileReader();
        var imgObj = this.files[0];
        fr.readAsDataURL(imgObj);
        var obj = $(this).prev()[0];
        if(filePath.indexOf("jpg")!=-1 || filePath.indexOf("JPG")!=-1 || filePath.indexOf("PNG")!=-1 || filePath.indexOf("png")!=-1){
            var arr = filePath.split('\\');
            var fileName = arr[arr.length-1];
            $(this).parent().next().html(fileName);
            fr.onload=function( ){
                obj.src=this.result;
            };
        }else{
            $(this).parent().next().html("您未上传文件,或者您上传文件类型有误!").css("color","red");
            return false
        }
    });
</script>

</body>
</html>

input[type=file]样式美化后效果图:

按钮美化效果

图片上传后效果图:

上传文件效果

Golang 常用字符串处理方法汇总,Go语言代码片段分享, - 脚本分享网

mikel阅读(1090)

Golang 常用字符串处理方法汇总,下面的代码主要展示了golang中strings库的用法

来源: Golang 常用字符串处理方法汇总,Go语言代码片段分享, – 脚本分享网

package main

import (
"fmt"
"strings"
//"unicode/utf8"
)

func main() {
fmt.Println("查找子串是否在指定的字符串中")
fmt.Println(" Contains 函数的用法")
fmt.Println(strings.Contains("seafood", "foo")) //true
fmt.Println(strings.Contains("seafood", "bar")) //false
fmt.Println(strings.Contains("seafood", "")) //true
fmt.Println(strings.Contains("", "")) //true 这里要特别注意
fmt.Println(strings.Contains("我是中国人", "我")) //true

fmt.Println("")
fmt.Println(" ContainsAny 函数的用法")
fmt.Println(strings.ContainsAny("team", "i")) // false
fmt.Println(strings.ContainsAny("failure", "u &amp; i")) // true
fmt.Println(strings.ContainsAny("foo", "")) // false
fmt.Println(strings.ContainsAny("", "")) // false

fmt.Println("")
fmt.Println(" ContainsRune 函数的用法")
fmt.Println(strings.ContainsRune("我是中国", '我')) // true 注意第二个参数,用的是字符

fmt.Println("")
fmt.Println(" Count 函数的用法")
fmt.Println(strings.Count("cheese", "e")) // 3
fmt.Println(strings.Count("five", "")) // before &amp; after each rune result: 5 , 源码中有实现

fmt.Println("")
fmt.Println(" EqualFold 函数的用法")
fmt.Println(strings.EqualFold("Go", "go")) //大小写忽略

fmt.Println("")
fmt.Println(" Fields 函数的用法")
fmt.Println("Fields are: %q", strings.Fields(" foo bar baz ")) //["foo" "bar" "baz"] 返回一个列表

//相当于用函数做为参数,支持匿名函数
for _, record := range []string{" aaa*1892*122", "aaa\taa\t", "124|939|22"} {
fmt.Println(strings.FieldsFunc(record, func(ch rune) bool {
switch {
case ch &gt; '5':
return true
}
return false
}))
}

fmt.Println("")
fmt.Println(" HasPrefix 函数的用法")
fmt.Println(strings.HasPrefix("NLT_abc", "NLT")) //前缀是以NLT开头的

fmt.Println("")
fmt.Println(" HasSuffix 函数的用法")
fmt.Println(strings.HasSuffix("NLT_abc", "abc")) //后缀是以NLT开头的

fmt.Println("")
fmt.Println(" Index 函数的用法")
fmt.Println(strings.Index("NLT_abc", "abc")) // 返回第一个匹配字符的位置,这里是4
fmt.Println(strings.Index("NLT_abc", "aaa")) // 在存在返回 -1
fmt.Println(strings.Index("我是中国人", "中")) // 在存在返回 6

fmt.Println("")
fmt.Println(" IndexAny 函数的用法")
fmt.Println(strings.IndexAny("我是中国人", "中")) // 在存在返回 6
fmt.Println(strings.IndexAny("我是中国人", "和")) // 在存在返回 -1

fmt.Println("")
fmt.Println(" Index 函数的用法")
fmt.Println(strings.IndexRune("NLT_abc", 'b')) // 返回第一个匹配字符的位置,这里是4
fmt.Println(strings.IndexRune("NLT_abc", 's')) // 在存在返回 -1
fmt.Println(strings.IndexRune("我是中国人", '中')) // 在存在返回 6

fmt.Println("")
fmt.Println(" Join 函数的用法")
s := []string{"foo", "bar", "baz"}
fmt.Println(strings.Join(s, ", ")) // 返回字符串:foo, bar, baz

fmt.Println("")
fmt.Println(" LastIndex 函数的用法")
fmt.Println(strings.LastIndex("go gopher", "go")) // 3

fmt.Println("")
fmt.Println(" LastIndexAny 函数的用法")
fmt.Println(strings.LastIndexAny("go gopher", "go")) // 4
fmt.Println(strings.LastIndexAny("我是中国人", "中")) // 6

fmt.Println("")
fmt.Println(" Map 函数的用法")
rot13 := func(r rune) rune {
switch {
case r &gt;= 'A' &amp;&amp; r &lt;= 'Z': return 'A' + (r-'A'+13)%26 case r &gt;= 'a' &amp;&amp; r &lt;= 'z':
return 'a' + (r-'a'+13)%26
}
return r
}
fmt.Println(strings.Map(rot13, "'Twas brillig and the slithy gopher..."))

fmt.Println("")
fmt.Println(" Repeat 函数的用法")
fmt.Println("ba" + strings.Repeat("na", 2)) //banana

fmt.Println("")
fmt.Println(" Replace 函数的用法")
fmt.Println(strings.Replace("oink oink oink", "k", "ky", 2))
fmt.Println(strings.Replace("oink oink oink", "oink", "moo", -1))

fmt.Println("")
fmt.Println(" Split 函数的用法")
fmt.Printf("%q\n", strings.Split("a,b,c", ","))
fmt.Printf("%q\n", strings.Split("a man a plan a canal panama", "a "))
fmt.Printf("%q\n", strings.Split(" xyz ", ""))
fmt.Printf("%q\n", strings.Split("", "Bernardo O'Higgins"))

fmt.Println("")
fmt.Println(" SplitAfter 函数的用法")
fmt.Printf("%q\n", strings.SplitAfter("/home/m_ta/src", "/")) //["/" "home/" "m_ta/" "src"]

fmt.Println("")
fmt.Println(" SplitAfterN 函数的用法")
fmt.Printf("%q\n", strings.SplitAfterN("/home/m_ta/src", "/", 2)) //["/" "home/m_ta/src"]
fmt.Printf("%q\n", strings.SplitAfterN("#home#m_ta#src", "#", -1)) //["/" "home/" "m_ta/" "src"]

fmt.Println("")
fmt.Println(" SplitN 函数的用法")
fmt.Printf("%q\n", strings.SplitN("/home/m_ta/src", "/", 1))

fmt.Printf("%q\n", strings.SplitN("/home/m_ta/src", "/", 2)) //["/" "home/" "m_ta/" "src"]
fmt.Printf("%q\n", strings.SplitN("/home/m_ta/src", "/", -1)) //["" "home" "m_ta" "src"]
fmt.Printf("%q\n", strings.SplitN("home,m_ta,src", ",", 2)) //["/" "home/" "m_ta/" "src"]

fmt.Printf("%q\n", strings.SplitN("#home#m_ta#src", "#", -1)) //["/" "home/" "m_ta/" "src"]

fmt.Println("")
fmt.Println(" Title 函数的用法") //这个函数,还真不知道有什么用
fmt.Println(strings.Title("her royal highness"))

fmt.Println("")
fmt.Println(" ToLower 函数的用法")
fmt.Println(strings.ToLower("Gopher")) //gopher

fmt.Println("")
fmt.Println(" ToLowerSpecial 函数的用法")

fmt.Println("")
fmt.Println(" ToTitle 函数的用法")
fmt.Println(strings.ToTitle("loud noises"))
fmt.Println(strings.ToTitle("loud 中国"))

fmt.Println("")
fmt.Println(" Replace 函数的用法")
fmt.Println(strings.Replace("ABAACEDF", "A", "a", 2)) // aBaACEDF
//第四个参数小于0,表示所有的都替换, 可以看下golang的文档
fmt.Println(strings.Replace("ABAACEDF", "A", "a", -1)) // aBaaCEDF

fmt.Println("")
fmt.Println(" ToUpper 函数的用法")
fmt.Println(strings.ToUpper("Gopher")) //GOPHER

fmt.Println("")
fmt.Println(" Trim 函数的用法")
fmt.Printf("[%q]", strings.Trim(" !!! Achtung !!! ", "! ")) // ["Achtung"]

fmt.Println("")
fmt.Println(" TrimLeft 函数的用法")
fmt.Printf("[%q]", strings.TrimLeft(" !!! Achtung !!! ", "! ")) // ["Achtung !!! "]

fmt.Println("")
fmt.Println(" TrimSpace 函数的用法")
fmt.Println(strings.TrimSpace(" \t\n a lone gopher \n\t\r\n")) // a lone gopher

}

//该代码片段来自于: http://www.sharejs.com/codes/go/7304

Dephi时间戳与日期互转 - CSDN博客

mikel阅读(1589)

1、日期转换成时间戳function Gettimestamp: string; begin    Result := inttostr(DateTimeToUnix(IncHour(Now,-8)));    //  本地时间减8小时end;2、时间戳转换成日期function UnixDateToDateTime(const USec: Longin

来源: 时间戳与日期互转 – CSDN博客

1、日期转换成时间戳

function Gettimestamp: string;
begin
Result := inttostr(DateTimeToUnix(IncHour(Now,-8)));    //  本地时间减8小时
end;

2、时间戳转换成日期

function UnixDateToDateTime(const USec: Longint): TDateTime;
const
UnixStartDate: TDateTime = 25569.0;      // 1970/01/01
begin
Result := (Usec / 86400) + UnixStartDate;
Result  := IncHour(Result,8);
end;

Deprecated: Automatically populating $HTTP_RAW_POST_DATA is deprecated and will be removed in a futu - CSDN博客

mikel阅读(1058)

Deprecated: Automatically populating $HTTP_RAW_POST_DATA is deprecated and will be removed in a future version. To avoid this warning set ” to ‘-1’ in php.ini and use the php://input stream instead.

来源: Deprecated: Automatically populating $HTTP_RAW_POST_DATA is deprecated and will be removed in a futu – CSDN博客

Deprecated: Automatically populating $HTTP_RAW_POST_DATA is deprecated and will be removed in a future version. To avoid this warning set ” to ‘-1’ in php.ini and use the php://input stream instead. in Unknown on line 0`

报这样的错误很多情况下都是php版本的问题, 我是在5.6版本中使用ajax时遇到的,原因是$HTTP_RAW_POST_DATA 要逐渐弃用,在php5.6版本中找到php.ini文件,将always_populate_raw_post_data改成-1,并且打开注释,OK,解决

wampserver 最新版本 mysql修改数据库密码 - CSDN博客

mikel阅读(1245)

第一步 进入mySQL 控制台 wamp安装,数据库是没有密码 进入控制台直接回车就可以了第二步 使用 update user set password=PASSWORD(‘你所要修改的密码’) where user=’root’; 注 : 分号、引号 都不要省去 如果报错了 不要急 新版本有些变动 前面两步完成了 密码还没有生效 需要使用下面命令

来源: wampserver 最新版本 mysql修改数据库密码 – CSDN博客

第一步 进入mySQL 控制台
这里写图片描述
wamp安装,数据库是没有密码 进入控制台直接回车就可以了

第二步 使用 update user set password=PASSWORD(‘你所要修改的密码’) where user=’root’;
注 : 分号、引号 都不要省去
这里写图片描述

如果报错了 不要急  新版本有些变动  
  • 1
  • 2

这里写图片描述

前面两步完成了 密码还没有生效 需要使用下面命令 刷新表 flush privileges; 从而使密码生效

最后 使用quit;退出 
  • 1
  • 2

如果你是用 phpmyadmin 访问数据库的话 还需要 修改 wamp/apps/phpmyadmin 中的配置文件
config.inc.php中

$cfg['Servers'][$i]['password'] = '你修改的数据库密码';
  • 1

保存就可以了

Using PHP OCI8 with 32-bit PHP on Windows 64-bit | Oracle Scripting and Oracle: Christopher Jones Blog

mikel阅读(956)

来源: Using PHP OCI8 with 32-bit PHP on Windows 64-bit | Oracle Scripting and Oracle: Christopher Jones Blog

The world migration from 32-bit to 64-bit operating systems is gaining pace. However I’ve seen a couple of customers having difficulty with the PHP OCI8 extension and Oracle DB on Windows 64-bit platforms. The errors vary depending how PHP is run. They may appear in the Apache or PHP log:

Unable to load dynamic library 'C:\Program Files
(x86)\PHP\ext\php_oci8_11g.dll' - %1 is not a valid Win32
application.

or

Warning oci_connect(): OCIEnvNlsCreate() failed. There is
something wrong with your system - please check that PATH includes the
directory with Oracle Instant Client libraries

Other than IIS permission issues a common cause seems to be trying to use PHP with libraries from an Oracle 64-bit database on the same machine. There is currently no 64-bit version of PHP on http://php.net/ so there is a library mismatch.

A solution is to install Oracle Instant Client 32-bit and make sure that PHP uses these libraries, while not interferring with the 64-bit database on the same machine. Warning: The following hacky steps come untested from a Linux user:

  1. Unzip Oracle Instant Client 32-bit and move it to C:\WINDOWS\SYSWOW64\INSTANTCLIENT_11_2. You may need to do this in a console with elevated permissions.
  2. Edit your PATH environment variable and insert C:\WINDOWS\SYSTEM32\INSTANTCLIENT_11_2in the directory list before the entry for the Oracle Home library.

    Windows makes it so all 32-bit applications that reference C:\WINDOWS\SYSTEM32 actually see the contents of the C:\WINDOWS\SYSWOW64 directory. Your 64-bit database won’t find an Instant Client in the real, physical C:\WINDOWS\SYSTEM32 directory and will continue to use the database libraries.

Some of our Windows team are concerned about this hack and prefer a more “correct” solution that (i) doesn’t require changing the Windows system directory (ii) doesn’t add to the “memory” burden about what was configured on the system (iii) works when there are multiple database versions installed. The solution is to write a script which will set the 64-bit (or 32-bit) Oracle libraries in the path as needed before invoking the relevant bit-ness application. This does have a weakness when the application is started as a service.

As a footnote: If you don’t have a local database and simply need to have 32-bit and 64-bit Instant Client accessible at the same time, try the “symbolic” link approach covered in the hack in this OTN forum thread.

Reminder warning: This blog post came untested from a Linux user.

新安装的wampserver怎么使用本机已有的mysql作为数据库 - gccbuaa - 博客园

mikel阅读(783)

来源: 新安装的wampserver怎么使用本机已有的mysql作为数据库 – gccbuaa – 博客园

一般在一台没有安装mySQL的机器上安装好wamp后,能够直接在wamp的phpMyAdmin中打开集成的mySQL并设置用户信息。

而假设之前已经安装配置好mySQL(实usernamepassword),之后才安装wamp的话,wamp的mysql会与本机mysql产生冲突,
导致顶开phpMyAdmin的时候会报错,错误信息大致是“用户root未使用password进行登录”。

分析一下原因能够知道,phpMyAdmin会读电脑环境变量中的mysql的路径,而环境变量中已经存在了一条老mysql的路径。
在安装wamp后,wamp就会读取这条老的mysql路径而不会读取自带集成mysql的路径。可是wamp仍然会使用自己集成mysql的配置信息(新安装的wamp的mysql配置信息没有password),
终于导致用集成mysql的配置信息去訪问老mysql的数据库,所以就会报错。

网上其它的解决的方法大致有两种:一种是备份好老的mysql信息,然后卸载掉,把数据转移到集成mysql中。还有一种是改变老mysql的port号。防止两个mysql冲突

而笔者给大家带来的方法是直接改变集成Mysql的配置信息,用以訪问本机已经存在的Mysql,经笔者实验,这样的方法相当方便。
方法例如以下
在D:\wamp\apps\phpmyadmin4.1.14下编辑config.inc.php
找到以下两句话

$cfg['Servers'][$i]['user'] = 'root';
$cfg['Servers'][$i]['password'] = '';

将他们的值改成本机mysql的username和password。

重新启动wamp全部服务,再次打开phpMyAdmin的时候就能訪问mysql了

PHP: 可变变量 - Manual

mikel阅读(831)

来源: PHP: 可变变量 – Manual

有时候使用可变变量名是很方便的。就是说,一个变量的变量名可以动态的设置和使用。一个普通的变量通过声明来设置,例如:

<?php
$a = 'hello';
?>

一个可变变量获取了一个普通变量的值作为这个可变变量的变量名。在上面的例子中 hello 使用了两个美元符号($)以后,就可以作为一个可变变量的变量了。例如:

<?php
$$a = 'world';
?>

这时,两个变量都被定义了:$a 的内容是“hello”并且 $hello 的内容是“world”。因此,以下语句:

<?php
echo "$a ${$a}";
?>

与以下语句输出完全相同的结果:

<?php
echo "$a $hello";
?>

它们都会输出:hello world

要将可变变量用于数组,必须解决一个模棱两可的问题。这就是当写下 $$a[1] 时,解析器需要知道是想要 $a[1]作为一个变量呢,还是想要 $$a 作为一个变量并取出该变量中索引为 [1] 的值。解决此问题的语法是,对第一种情况用 ${$a[1]},对第二种情况用 ${$a}[1]

类的属性也可以通过可变属性名来访问。可变属性名将在该调用所处的范围内被解析。例如,对于 $foo->$bar 表达式,则会在本地范围来解析 $bar 并且其值将被用于 $foo 的属性名。对于 $bar 是数组单元时也是一样。

也可使用花括号来给属性名清晰定界。最有用是在属性位于数组中,或者属性名包含有多个部分或者属性名包含有非法字符时(例如来自 json_decode() 或 SimpleXML)。

Example #1 可变属性示例

<?php
class foo {
var $bar = 'I am bar.';
var $arr = array('I am A.', 'I am B.', 'I am C.');
var $r   = 'I am r.';
}

$foo = new foo();
$bar = 'bar';
$baz = array('foo', 'bar', 'baz', 'quux');
echo $foo->$bar . "\n";
echo $foo->$baz[1] . "\n";

$start = 'b';
$end   = 'ar';
echo $foo->{$start . $end} . "\n";

$arr = 'arr';
echo $foo->$arr[1] . "\n";
echo $foo->{$arr}[1] . "\n";

?>

以上例程会输出:

I am bar.
I am bar.
I am bar.
I am r.
I am B.

Blob、FileReader全面解析 - CSDN博客

mikel阅读(1385)

上传图片获取base64图片或2进制图片base64:var uploadFile = document.getElementById(‘uploadFile’); // uploadFile.onchange = function() { var fr = new FileReader(); fr.onloadend = function(res)

来源: Blob、FileReader全面解析 – CSDN博客

历史上,JavaScript无法处理二进制数据。如果一定要处理的话,只能使用charCodeAt()方法,一个个字节地从文字编码转成二进制数据,还有一种办法是将二进制数据转成Base64编码,再进行处理。这两种方法不仅速度慢,而且容易出错。ECMAScript 5引入了Blob对象,允许直接操作二进制数据。

Blob对象是一个代表二进制数据的基本对象,在它的基础上,又衍生出一系列相关的API,用来操作文件。

  • File对象:负责处理那些以文件形式存在的二进制数据,也就是操作本地文件;
  • FileList对象:File对象的网页表单接口;
  • FileReader对象:负责将二进制数据读入内存内容;
  • URL对象:用于对二进制数据生成URL。

Blob对象

Blob(Binary Large Object)对象代表了一段二进制数据,提供了一系列操作接口。其他操作二进制数据的API(比如File对象),都是建立在Blob对象基础上的,继承了它的属性和方法。

生成Blob对象有两种方法:一种是使用Blob构造函数,另一种是对现有的Blob对象使用slice方法切出一部分。

(1)Blob构造函数,接受两个参数。第一个参数是一个包含实际数据的数组,第二个参数是数据的类型,这两个参数都不是必需的。

var htmlParts = ["<a id=\"a\"><b id=\"b\">hey!<\/b><\/a>"];
var myBlob = new Blob(htmlParts, { "type" : "text\/xml" });

下面是一个利用Blob对象,生成可下载文件的例子。

var blob = new Blob(["Hello World"]);

var a = document.createElement("a");
a.href = window.URL.createObjectURL(blob);
a.download = "hello-world.txt";
a.textContent = "Download Hello World!";

body.appendChild(a);

上面的代码生成了一个超级链接,点击后提示下载文本文件hello-world.txt,文件内容为“Hello World”。

(2)Blob对象的slice方法,将二进制数据按照字节分块,返回一个新的Blob对象。

var newBlob = oldBlob.slice(startingByte, endindByte);

下面是一个使用XMLHttpRequest对象,将大文件分割上传的例子。

function upload(blobOrFile) {
  var xhr = new XMLHttpRequest();
  xhr.open('POST', '/server', true);
  xhr.onload = function(e) { ... };
  xhr.send(blobOrFile);
}

document.querySelector('input[type="file"]').addEventListener('change', function(e) {
  var blob = this.files[0];

  const BYTES_PER_CHUNK = 1024 * 1024; // 1MB chunk sizes.
  const SIZE = blob.size;

  var start = 0;
  var end = BYTES_PER_CHUNK;

  while(start < SIZE) {
    upload(blob.slice(start, end));

    start = end;
    end = start + BYTES_PER_CHUNK;
  }
}, false);

})();

(3)Blob对象有两个只读属性:

  • size:二进制数据的大小,单位为字节。
  • type:二进制数据的MIME类型,全部为小写,如果类型未知,则该值为空字符串。

在Ajax操作中,如果xhr.responseType设为blob,接收的就是二进制数据。

FileList对象

FileList对象针对表单的file控件。当用户通过file控件选取文件后,这个控件的files属性值就是FileList对象。它在结构上类似于数组,包含用户选取的多个文件。

<input type="file" id="input" onchange="console.log(this.files.length)" multiple />

当用户选取文件后,就可以读取该文件。

var selected_file = document.getElementById('input').files[0];

采用拖放方式,也可以得到FileList对象。

var dropZone = document.getElementById('drop_zone');
dropZone.addEventListener('drop', handleFileSelect, false);

function handleFileSelect(evt) {
    evt.stopPropagation();
    evt.preventDefault();

    var files = evt.dataTransfer.files; // FileList object.

    // ...
}

上面代码的 handleFileSelect 是拖放事件的回调函数,它的参数evt是一个事件对象,该参数的dataTransfer.files属性就是一个FileList对象,里面包含了拖放的文件。

File API

File API提供File对象,它是FileList对象的成员,包含了文件的一些元信息,比如文件名、上次改动时间、文件大小和文件类型。

var selected_file = document.getElementById('input').files[0];

var fileName = selected_file.name;
var fileSize = selected_file.size;
var fileType = selected_file.type;

File对象的属性值如下。

  • name:文件名,该属性只读。
  • size:文件大小,单位为字节,该属性只读。
  • type:文件的MIME类型,如果分辨不出类型,则为空字符串,该属性只读。
  • lastModified:文件的上次修改时间,格式为时间戳。
  • lastModifiedDate:文件的上次修改时间,格式为Date对象实例。
$('#upload-file').files[0]
// {
//   lastModified: 1449370355682,
//   lastModifiedDate: Sun Dec 06 2015 10:52:35 GMT+0800 (CST),
//   name: "HTTP 2 is here Goodbye SPDY Not quite yet.png",
//   size: 17044,
//   type: "image/png"
// }

FileReader API

FileReader API用于读取文件,即把文件内容读入内存。它的参数是File对象或Blob对象。

对于不同类型的文件,FileReader提供不同的方法读取文件。

  • readAsBinaryString(Blob|File):返回二进制字符串,该字符串每个字节包含一个0到255之间的整数。
  • readAsText(Blob|File, opt_encoding):返回文本字符串。默认情况下,文本编码格式是’UTF-8’,可以通过可选的格式参数,指定其他编码格式的文本。
  • readAsDataURL(Blob|File):返回一个基于Base64编码的data-uri对象。
  • readAsArrayBuffer(Blob|File):返回一个ArrayBuffer对象。

readAsText方法用于读取文本文件,它的第一个参数是FileBlob对象,第二个参数是前一个参数的编码方法,如果省略就默认为UTF-8编码。该方法是异步方法,一般监听onload件,用来确定文件是否加载结束,方法是判断FileReader实例的result属性是否有值。其他三种读取方法,用法与readAsText方法类似。

var reader = new FileReader();
reader.onload = function(e) {
  var text = reader.result;
}

reader.readAsText(file, encoding);

readAsDataURL方法返回一个data URL,它的作用基本上是将文件数据进行Base64编码。你可以将返回值设为图像的src属性。

var file = document.getElementById('destination').files[0];
if(file.type.indexOf('image') !== -1) {
  var reader = new FileReader();
  reader.onload = function (e) {
    var dataURL = reader.result;
  }
  reader.readAsDataURL(file);
}

readAsBinaryString方法可以读取任意类型的文件,而不仅仅是文本文件,返回文件的原始的二进制内容。这个方法与XMLHttpRequest.sendAsBinary方法结合使用,就可以使用JavaScript上传任意文件到服务器。

var reader = new FileReader();
reader.onload = function(e) {
  var rawData = reader.result;
}
reader.readAsBinaryString(file);

readAsArrayBuffer方法读取文件,返回一个类型化数组(ArrayBuffer),即固定长度的二进制缓存数据。在文件操作时(比如将JPEG图像转为PNG图像),这个方法非常方便。

var reader = new FileReader();
reader.onload = function(e) {
  var arrayBuffer = reader.result;
}

reader.readAsArrayBuffer(file);

除了以上四种不同的读取文件方法,FileReader API还有一个abort方法,用于中止文件上传。

var reader = new FileReader();
reader.abort();

FileReader对象采用异步方式读取文件,可以为一系列事件指定回调函数。

  • onabort方法:读取中断或调用reader.abort()方法时触发。
  • onerror方法:读取出错时触发。
  • onload方法:读取成功后触发。
  • onloadend方法:读取完成后触发,不管是否成功。触发顺序排在 onload 或 onerror 后面。
  • onloadstart方法:读取将要开始时触发。
  • onprogress方法:读取过程中周期性触发。

下面的代码是如何展示文本文件的内容。

var reader = new FileReader();
reader.onload = function(e) {
  console.log(e.target.result);
}
reader.readAsText(blob);

onload事件的回调函数接受一个事件对象,该对象的target.result就是文件的内容。

下面是一个使用readAsDataURL方法,为img元素添加src属性的例子。

var reader = new FileReader();
reader.onload = function(e) {
  document.createElement('img').src = e.target.result;
};
reader.readAsDataURL(f);

下面是一个onerror事件回调函数的例子。

var reader = new FileReader();
reader.onerror = errorHandler;

function errorHandler(evt) {
  switch(evt.target.error.code) {
    case evt.target.error.NOT_FOUND_ERR:
      alert('File Not Found!');
      break;
    case evt.target.error.NOT_READABLE_ERR:
      alert('File is not readable');
      break;
    case evt.target.error.ABORT_ERR:
      break;
    default:
      alert('An error occurred reading this file.');
  };
}

下面是一个onprogress事件回调函数的例子,主要用来显示读取进度。

var reader = new FileReader();
reader.onprogress = updateProgress;

function updateProgress(evt) {
  if (evt.lengthComputable) {
    var percentLoaded = Math.round((evt.loaded / evt.totalEric Bidelman) * 100);
    var progress = document.querySelector('.percent');
    if (percentLoaded < 100) {
      progress.style.width = percentLoaded + '%';
      progress.textContent = percentLoaded + '%';
    }
  }
}

读取大文件的时候,可以利用Blob对象的slice方法,将大文件分成小段,逐一读取,这样可以加快处理速度。

综合实例:显示用户选取的本地图片

假设有一个表单,用于用户选取图片。

<input type="file" name="picture" accept="image/png, image/jpeg"/>

一旦用户选中图片,将其显示在canvas的函数可以这样写:

document.querySelector('input[name=picture]').onchange = function(e){
     readFile(e.target.files[0]);
}

function readFile(file){

  var reader = new FileReader();

  reader.onload = function(e){
    applyDataUrlToCanvas( reader.result );
  };

  reader.reaAsDataURL(file);
}

还可以在canvas上面定义拖放事件,允许用户直接拖放图片到上面。

// stop FireFox from replacing the whole page with the file.
canvas.ondragover = function () { return false; };

// Add drop handler
canvas.ondrop = function (e) {
  e.stopPropagation();
  e.preventDefault(); 
  e = e || window.event;
  var files = e.dataTransfer.files;
  if(files){
    readFile(files[0]);
  }
};

所有的拖放事件都有一个dataTransfer属性,它包含拖放过程涉及的二进制数据。

还可以让canvas显示剪贴板中的图片。

document.onpaste = function(e){
  e.preventDefault();
  if(e.clipboardData&&e.clipboardData.items){
    // pasted image
    for(var i=0, items = e.clipboardData.items;i<items.length;i++){
      if( items[i].kind==='file' && items[i].type.match(/^image/) ){
        readFile(items[i].getAsFile());
        break;
      }
    }
  }
  return false;
};

URL对象

URL对象用于生成指向File对象或Blob对象的URL。

var objecturl =  window.URL.createObjectURL(blob);

上面的代码会对二进制数据生成一个URL,类似于“blob:http%3A//test.com/666e6730-f45c-47c1-8012-ccc706f17191”。这个URL可以放置于任何通常可以放置URL的地方,比如img标签的src属性。需要注意的是,即使是同样的二进制数据,每调用一次URL.createObjectURL方法,就会得到一个不一样的URL。

这个URL的存在时间,等同于网页的存在时间,一旦网页刷新或卸载,这个URL就失效。除此之外,也可以手动调用URL.revokeObjectURL方法,使URL失效。

window.URL.revokeObjectURL(objectURL);

下面是一个利用URL对象,在网页插入图片的例子。

var img = document.createElement("img");

img.src = window.URL.createObjectURL(files[0]);

img.height = 60;

img.onload = function(e) {
    window.URL.revokeObjectURL(this.src);
}

body.appendChild(img);

var info = document.createElement("span");

info.innerHTML = files[i].name + ": " + files[i].size + " bytes";

body.appendChild(info);

还有一个本机视频预览的例子。

var video = document.getElementById('video');
var obj_url = window.URL.createObjectURL(blob);
video.src = obj_url;
video.play()
window.URL.revokeObjectURL(obj_url);

【转】Oracle + PHP Cookbook(php oracle clob 长度超过4000如何写入) - yzyssg1的博客 - CSDN博客

mikel阅读(759)

 

来源: 【转】Oracle + PHP Cookbook(php oracle clob 长度超过4000如何写入) – yzyssg1的博客 – CSDN博客

在甲骨文LOB和PHP工作
由哈里Fuecks

达到4,000字节的限制?输入LOB …

 

在这个“Oracle + PHP Cookbook”HowTo中,您将学习可用的LOB类型和与之相关的问题,然后探索PHP中常见的LOB操作的示例。使用像VARCHAR2这样的Oracle类型是很好的,但是如果你需要能够一次存储超过4,000字节的限制呢?对于此任务,您需要一个Oracle的长对象(LOB)类型,这反过来要求您了解如何使用PHP API来处理LOB。这本身对于那些不熟悉它的人来说是令人生畏的。

Oracle中的长对象

Oracle提供以下LOB类型:

  • BLOB,用于存储二进制数据
  • CLOB,用于存储使用数据库字符集编码的字符数据
  • NCLOB,用于存储使用国家字符集的Unicode字符数据。需要注意的是NCLOBs是,目前不被支持PHP OCI8扩展,你会在这里使用。
  • BFILE,用于引用操作系统文件系统下的外部文件

LOB的另一个子类是临时LOB,它可以是BLOB,CLOB或NCLOB,但存储在临时表空间中,直到您释放它。请注意,旧版本的Oracle分别为字符和二进制数据提供了LONG和LONG RAW类型。随着Oracle9需要这些赞成的LOB被弃用。

LOB存储。对于BLOB,CLOB和NCLOB类型,Oracle数据库10 ğ能够在一个单一的值存储高达128TB,这取决于你的数据库块大小和“块”设置,为LOB定义的。

LOB本身包括两个元素:LOB内容和LOB定位符,其是到LOB内容的“指针”。这种分离需要允许Oracle存储和有效地管理LOB和它反映在使用到PHP的API INSERT, 数据库,和 选择的LOB(见下文)。

对于内部LOB类型(即不是BFILE),如果LOB的大小小于4KB,则Oracle将在表中存储LOB的内容以及行的其余部分。大于4KB的LOB在默认情况下在表的表空间中存储为“out-of-line”。此方法允许快速检索小LOB,而对于大LOB,访问时间将较慢,但是在扫描表时的整体性能被保留。

还有LOB存储和访问的其他选项,例如内存缓存和缓冲 – 这可以提高性能,具体取决于您的应用程序的具体情况。欲了解更多信息,请参阅 LOB性能指南和 Oracle数据库应用程序开发人员指南-大型对象在Oracle文档中获得。

LOB的限制。许多限制适用于使用LOB类型,最重要的是它们在SQL语句中的使用。您不能在以下任何查询中使用LOB类型。

SELECT DISTINCT <lob_type>
ORDER BY <lob_type>
GROUP BY <lob_col>

这也是非法的表连接,使用LOB类型列 UNION, INTERSECTION和 MINUS语句。其他限制适用于LOB使用的其他方面,例如,您不能将LOB用作主键列。再次,看 Oracle数据库应用程序开发人员指南-大型对象的详细信息。

CLOB和字符集

数据库的默认字符集由参数NLS_CHARACTERSET定义,并且放置在CLOB中的文本应使用此字符集进行编码。使用此SQL来确定您的数据库字符集编码:

SELECT value FROM nls_database_parameters WHERE parameter ='NLS_CHARACTERSET'

由于缺乏对PHP中NCLOB的支持,您可能需要考虑使用Unicode编码作为数据库字符,例如UTF-8,可以使用以下语句完成(给予足够的权限):

ALTER数据库字符集UTF8

注意:不要在不理解影响的情况下尝试此操作,尤其是如果现有数据或应用程序代码使用不同的字符集。看到 甲骨文全球化支持指南对全球化的Oracle PHP应用程序概述了解更多信息。使用LOB

这里的讨论将集中在PHP的OCI8扩展。还值得注意的是,Oracle提供了DBMS_LOB包,其中包含使用PL / SQL处理LOB的并行过程和函数。

PHP OCI8扩展在全局PHP命名空间中注册了一个名为“OCI-Lob”的PHP类。当您执行 SELECT语句,例如,其中一列是一个LOB类型,PHP将自动绑定这个到OCI-LOB对象实例。一旦你有一个OCI高球对象的引用,你可以调用方法一样 的load()和 save()方法来访问或修改LOB的内容。

可用的OCI高球的方法将取决于你的PHP版本,PHP5特别是具有像获得方法 的read() , 求()和 追加() 。在 PHP手册是有点不清楚,在这种情况下,版本号,所以如果有疑问,您可以验证使用下面的脚本。

<?php
foreach(get_class_methods('OCI-Lob')as $ method){
    print“OCI-Lob :: $ method()\ n”;
}}
?>

在我的系统上,运行PHP 5.0.5,我得到以下列表的方法:

OCI-Lob :: load()
OCI-Lob :: tell()
OCI-Lob :: truncate()
OCI-Lob :: erase()
OCI-Lob :: flush()
OCI-Lob :: setbuffering()
OCI-Lob :: getbuffering()
OCI-Lob :: rewind()
OCI-Lob :: read()
OCI-Lob :: eof()
OCI-Lob :: seek()
OCI-Lob :: write()
OCI-Lob :: append()
OCI-Lob :: size()
OCI-Lob :: writetofile()
OCI-Lob :: writetemporary()
OCI-Lob :: close()
OCI-Lob :: save()
OCI-Lob :: savefile()
OCI-Lob :: free()

在实践中,PHP 4.x OCI8扩展仅支持读取或写入完整的LOB,这是Web应用程序中最常见的用例。PHP5扩展是为了让LOB的“块”的阅读和写作,以及支持LOB缓冲的方法 setBuffering()和 getBuffering() 。PHP5还提供了独立的功能 oci_lob_is_equal()和 oci_lob_copy() 。此处的示例将使用新的PHP5 OCI函数名(如 oci_parse而不是 OCIParse)。示例使用以下序列和表:

CREATE SEQUENCE mylobs_id_seq
    NOMINVALUE
    NOMAXVALUE
    NOCYCLE
    CACHE 20
    NOORDER
增量1;

CREATE TABLE mylobs(
    id NUMBER PRIMARY KEY,
    mylob CLOB
)

注意,这里的大多数示例使用CLOB,但是相同的逻辑也可以几乎完全应用于BLOB。插入LOB

要 INSERT内部LOB,首先需要使用相应的Oracle初始化LOB EMPTY_BLOB或 EMPTY_CLOB功能,您无法更新一个包含NULL值的LOB。

初始化后,你再绑定列到PHP OCI-LOB对象和更新通过对象的LOB的内容 保存()方法。

下面的脚本提供了一个示例,返回从该LOB类型 INSERT查询:

<?php
//连接到DB等...

$ sql =“INSERT INTO
        mylobs
          ((
            ID,
            mylob
          )
       值
          ((
            mylobs_id_seq.NEXTVAL,
            - 初始化为空CLOB
            EMPTY_CLOB()
          )
       返回
          - 返回LOB定位器
          mylob INTO:mylob_loc“;

$ stmt = oci_parse($ conn,$ sql);

//创建一个“空”OCI-Lob对象以绑定到定位器
$ myLOB = oci_new_descriptor($ conn,OCI_D_LOB);

//将返回的Oracle LOB定位符绑定到PHP LOB对象
oci_bind_by_name($ stmt,“:mylob_loc”,$ myLOB,-1,OCI_B_CLOB);

//使用OCI_DEFAULT作为事务执行语句
oci_execute($ stmt,OCI_DEFAULT)
    或死亡(“无法执行查询\ n”);
    
//现在保存一个值到LOB
if(!$ myLOB-> save('INSERT:'.date('H:i:s',time()))){
    
    //出错时,回滚事务
    oci_rollback($ conn);
    
} else {

    //成功后,提交事务
    oci_commit($ conn);
    
}}

//免费资源
oci_free_statement($ stmt);
$ myLOB-> free();


//断开与DB的连接等。
?>

请注意这个例子中如何使用事务,指示 oci_execute与 OCI_DEFAULT常量等待 oci_commit或 oci_rollback。这一点很重要,因为我有两个阶段发生在 INSERT -首先创建行和第二次更新的LOB。需要注意的是,如果我是用BLOB类型,唯一需要的变化(假设BLOB列)的工作是对 oci_bind_by_name调用:

oci_bind_by_name($ stmt,“:mylob_loc”,$ myLOB,-1,OCI_B_BLOB);

或者,您可以直接绑定字符串,而不指定LOB类型;

<?php
//等等。

$ sql =“INSERT INTO
          mylobs
          ((
            ID,
            mylob
          )
        值
          ((
            mylobs_id_seq.NEXTVAL,
            :串
          )
“;

$ stmt = oci_parse($ conn,$ sql);


oci_bind_by_name($ stmt,':string',$ string);

oci_execute($ stmt)
    或死亡(“无法执行查询\ n”);

//等等。
?>

这种方法大大简化了代码,适合当你想要写入到LOB的数据相对较小。相反,如果你想大文件的内容流成LOB,你可以通过文件的内容循环调用 write()方法和 冲洗()的PHP LOB对象写较小的块上,而不是整个文件在单个实例中保持在内存中。选择LOB

当 SELECT查询包含一个LOB列,PHP将自动绑定列到OCI-LOB对象。例如:

<?php
//等等。

$ sql =“SELECT
          *:
        从
          mylobs
        ORDER BY
          ID
“;

$ stmt = oci_parse($ conn,$ sql);

oci_execute($ stmt)
    或死亡(“无法执行查询\ n”);

while($ row = oci_fetch_assoc($ stmt)){
    print“ID:{$ row ['ID']},”;
    
    //调用load()方法来获取LOB的内容
    print $ row ['MYLOB']  - > load()。“\ n”;
}}

//等等。
?>

这可以通过使用进一步简化 OCI_RETURN_LOBS恒定,与所用 oci_fetch_array() ,指示它替换为它们的值LOB对象:

while($ row = oci_fetch_array($ stmt,OCI_ASSOC + OCI_RETURN_LOBS)){
    print“ID:{$ row ['ID']},{$ row ['MYLOB']} \ n”;
}}

更新LOB要 UPDATE一个LOB,它也可以使用 “回归”中的SQL命令,与上面 的INSERT的例子,但一个更简单的方法是 SELECT ... FOR UPDATE

<?php
//等等。

$ sql =“SELECT
           mylob
        从
           mylobs
        哪里
           id = 3
        FOR UPDATE / *锁定行* /
“;

$ stmt = oci_parse($ conn,$ sql);

//使用OCI_DEFAULT执行语句(开始事务)
oci_execute($ stmt,OCI_DEFAULT) 
    或死亡(“无法执行查询\ n”);

//获取SELECTed行
if(FALSE ===($ row = oci_fetch_assoc($ stmt))){
    oci_rollback($ conn);
    die(“无法获取行\ n”);
}}

//丢弃现有的LOB内容
if(!$ row ['MYLOB']  - > truncate()){
    oci_rollback($ conn);
    die(“Failed to truncate LOB \ n”);
}}

//现在保存一个值到LOB
if(!$ row ['MYLOB']  - > save('UPDATE:'.date('H:i:s',time()))){
    
    //出错时,回滚事务
    oci_rollback($ conn);
    
} else {

    //成功后,提交事务
    oci_commit($ conn);
    
}}

//免费资源
oci_free_statement($ stmt);
$ row ['MYLOB']  - > free();


//等等。
?>

如同 INSERT,我需要执行 UPDATE使用事务。一个重要的附加步骤是将呼叫 截断() 。当更新与一个LOB 保存() ,将取代的LOB的从启动开始新的数据的长度的内容。这意味着较旧的内容(如果它比新的内容更长)可能仍然留在LOB中。对于PHP 4.x中,在 截断()是不可用的,下面的替代解决方案使用Oracle的 EMPTY_CLOB()函数的新数据保存到前擦除在LOB任何现有内容。

$ sql =“UPDATE
           mylobs
        组
            mylob = EMPTY_CLOB()
        哪里
           id = 2403
        返回
            mylob INTO:mylob
“;

$ stmt = OCIParse($ conn,$ sql);

$ mylob = OCINewDescriptor($ conn,OCI_D_LOB);

OCIBindByName($ stmt,':mylob',$ mylob,-1,OCI_B_CLOB);

//使用OCI_DEFAULT执行语句(开始事务)
OCIExecute($ stmt,OCI_DEFAULT) 
    或死亡(“无法执行查询\ n”);
    
if(!$ mylob-> save('UPDATE:'.date('H:i:s',time()))){
    
    OCIRollback($ conn);
    die(“无法更新lob \ n”);
    
}}

OCICommit($ conn);
$ mylob-> free();
OCIFreeStatement($ stmt);

使用BFILES当使用BFILE类型, INSERT S和 UPDATE刻薄告诉甲骨文在该文件所在的数据库服务器(可能不是同一台机器作为Web服务器)的文件系统中,而不是传递文件内容。使用 SELECT语句,您可以通过Oracle读取BFILE的内容,应该你愿意的话,或致电函数和过程从DBMS_LOB包,以获取有关文件的信息。

BFILE的主要优点是能够直接从文件系统访问原始文件,同时仍然能够使用SQL查找文件。这意味着,例如,图像可以直接由Web服务器提供,而我可以跟踪包含BFILES的表格和“用户”表之间的关系,告诉我谁上传了文件。

例如,我首先需要更新上面使用的表模式;

ALTER TABLE mylobs ADD(mybfile BFILE)

接下来,我需要向Oracle注册目录别名(这需要管理权限),并授予读取它的权限:

CREATE DIRECTORY IMAGES_DIR AS'/ home / harryf / public_html / images'

我现在可以 INSERT像一些BFILE的名称:

<?php
//等等。

//为BFILE名称构建一个INSERT
$ sql =“INSERT INTO
        mylobs
          ((
            ID,
            mybfile
          )
       值
          ((
            mylobs_id_seq.NEXTVAL,
            / *
            使用Oracle目录引用传递文件名
            我创建了名为IMAGES_DIR
            * /
            BFILENAME('IMAGES_DIR',:filename)
          )“;

$ stmt = oci_parse($ conn,$ sql);

//打开目录
$ dir ='/ home / harryf / public_html / images';
$ dh = opendir($ dir)
    或死亡(“无法打开$ dir”);

//循环遍历目录的内容
while(false!==($ entry = readdir($ dh))){
    
    //仅匹配扩展名为.jpg,.gif或.png的文件
    if(is_file($ dir。'/'。$ entry)&& preg_match('/ \。(jpg | gif | png)$ /',$ entry)
        
        //绑定语句的文件名
        oci_bind_by_name($ stmt,“:filename”,$ entry);
        
        //执行语句
        if(oci_execute($ stmt)){
            print“$ entry added \ n”;
        }}        
    }}
    
}}

如果我需要,我可以通过Oracle读取BFILE内容使用与上面相同的方法,我选择CLOB。或者,如果我需要获得文件名后面,所以我可以从文件系统中直接访问它们,我可以调用 DBMS_LOB.FILEGETNAME程序,如:

<?php
//等等。

$ sql =“SELECT
          ID
        从
          mylobs
        哪里
          - 只选择不为空的BFILES
          mybfile IS NOT NULL;

$ stmt1 = oci_parse($ conn,$ sql);

oci_execute($ stmt1)
    或死亡(“无法执行查询\ n”);

$ sql =“DECLARE
          定位器BFILE;
          diralias VARCHAR2(30);
          filename VARCHAR2(30);
           
        开始
          
          选择
            mybfile INTO locator
          从
            mylobs
          哪里
            id =:id;
          
          - 从BFILE获取文件名
          DBMS_LOB.FILEGETNAME(locator,diralias,filename);
          
          - 分配OUT参数以绑定参数
          :diralias:= diralias;
          :filename:= filename;
          
       结束;”;

$ stmt2 = oci_parse($ conn,$ sql);

    
    oci_bind_by_name($ stmt2,“:id”,$ row ['ID']);
    oci_bind_by_name($ stmt2,“:diralias”,$ diralias,30);
    oci_bind_by_name($ stmt2,“:filename”,$ filename,30);
    
    oci_execute($ stmt2);
    print“{$ row ['ID']}:$ diralias / $ filename \ n”;
    
}}
//等等。
?>

此外,还可以使用 DBMS_LOB.FILEEXISTS功能来发现哪些文件已通过操作系统被删除,但在数据库中仍引用。结论

在此方法文档中,你已经被介绍给不同类型的Oracle数据库10个可用的LOB ,希望现在了解他们在允许大型数据实体高效地存储在数据库中的作用。您还学习了如何使用PHP的OCI8 API来处理LOB,涵盖了在使用Oracle和PHP开发时遇到的常见用例。

 


哈利Fuecks [ http://www.phppatterns.com ]是一个知名的PHP开发人员和作家,自1999年发现PHP他发表了许多介绍,并通过中间PHP文章 Sitepoint网站开发者的网络,以及写作 的PHP文集(SitePoint)。

原文地址:http://www.oracle.com/technetwork/articles/fuecks-lobs-095315.html