上善若水

上善若水 查看完整档案

填写现居城市池州学院  |  计算机科学与技术 编辑杭州多麦电子商务  |  PHP 编辑填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 该用户太懒什么也没留下

个人动态

上善若水 提出了问题 · 9月24日

为什么七牛资源偶尔性访问不了,ping地址 也ping不通,然后过一会又能访问了

经常遇到七牛的资源突然访问不了,ping地址的话 也ping不通,过一会后,又能访问,然后多一会又访问不了,怎么回事啊,有么有大佬解释下

关注 2 回答 1

上善若水 赞了文章 · 4月1日

PHP7新特性的介绍

截止到目前为止,PHP官方已经发布了php7的RC7版本,距离发布第一个正式版本不会很远了!现在来说php7的重大特性肯定已经是定型了,不会再有什么变动了。后续一些版本的迭代主要也就是修修bug,优化之类的。下面就来说话我们一直期待的php7会有那些主要的变化了。。。

关于PHP

  • 20年的发展历史;

  • 迄今为止最流行的WEB开发语言;

  • 超过82%的网站都会使用PHP作为他们的服务端开发语言;

新特性介绍

  • PHP NG – Zend Engine 3

  • 抽象语法树

  • 64位的 INT 支持

  • 统一的变量语法

  • 新增Closure::call()

  • 一致性foreach循环

  • 匿名类的支持

  • 新增 <=>**??\u{xxxx}操作符

  • 增加了返回类型的声明

  • 增加了标量类型的声明

  • 核心错误可以通过异常捕获

  • 增加了上下文敏感的词法分析

PHP NG

  • ZVAL

大小从24字节减少到16字节

  • Zend Array(HashTable)

HashTable大小从72字节减少到56字节
HashTable bucket大小从72字节减少到32字节

  • 函数调用的优化

  • 使用了新的内存分配,管理方式,减少了内存的浪费

  • Immutable array optimization

    $arr = [];
    
    for($i=0; $i<100000; $i++) {
        $arr[] = ['php'];
    }
    
    p(memory_get_usage(true));

PHP5: 45M
PHP7: 10M

  • 一些非常常用,开销不大的的函数直接变成了引擎支持的opcode

call_user_function(_array) => ZEND_INIT_USER_CALL
is_intis_stringis_array、... => ZEND_TYPE_CHECK
strlen => ZEND_STRLEN
defined => ZEND+DEFINED

  • 核心排序的优化

PHP5(zend_qsort
快速排序(非稳定排序)

array(1 => 0, 0 => 0)

PHP7(zend_sort
快速排序+选择排序(稳定排序)

array(0 => 0, 1 => 0)

小于16个元素的使用选择排序,大于16个按照16个为单位去分割,分别使用选择排序,然后再全部合起来使用快速排序。排序较之前相比,内部元素由非稳定排序变成稳定排序,减少元素的交换次数,减少对内存的操作次数,性能提升40%

抽象语法树

clipboard.png

假如现在我们有这样的需求,要对php源文件就行语法检测,实现编码规范。php5之前的话,没有AST,直接从parser就生成了opcodes!就需要借助一些外部的php语法解析器来实现;而php7增加了AST,我们可以自己去实现这样一个扩展,利用扩展提供的函数可以直接获取文件对应的的AST结构,而这样的结构正是我们可以识别的,所以就可以在这个基础上去做一些优化和判断了。

64位的INT支持

  • 支持存储大于2GB的字符串

  • 支持上传大小大于2GB的文件

  • 保证字符串在所有平台上【64位】都是64bit

统一的语法变量

$$foo['bar']['baz'] 

PHP5: ${$foo[‘bar’]['baz']}
PHP7: ($$foo)[‘bar’][‘baz']【从左至右法则】

(function() {})();
$foo()();
[$obj, 'method']();

class A {
    public static function a1() {}
}

[new A, 'a1']();

新增Closure::call()

$f = function() {
    p($this->name);
};

class F {
    private $name = 'F';
}

$f->call(new F);

匿名类的支持

function getAnonymousClass($config) {
    return new class(config) {};
 }

p(getAnonymousClass(array()));

一致性的foreach循环

//PHP5
$a =  array(1, 2, 3);foreach ($a as $v){var_dump(current($a));}
int(2)
int(2)
int(2)

$a =  array(1, 2, 3);$b=&$a;foreach ($a as $v){var_dump(current($a));}
int(2)
int(3)
bool(false)

$a =  array(1, 2, 3);$b=$a;foreach ($a as $v){var_dump(current($a));}
int(1)
int(1)
int(1)

//PHP7:不再操作数据的内部指针了
$a =  array(1, 2, 3);foreach ($a as $v){var_dump(current($a))}
int(1)
int(1)
int(1)

$a =  array(1, 2, 3);$b=&$a;foreach ($a as $v){var_dump(current($a))
int(1)
int(1)
int(1)

$a =  array(1, 2, 3);$b=$a;foreach ($a as $v){var_dump(current($a))}
int(1)
int(1)
int(1)

新增的几个操作符

<=>

//PHP5
function compare($a, $b) {
    return ($a < $b) ? -1 : (($a >$b) ? 1 : 0);
}
//PHP7
function compare($a, $b) {
    return $a <=> $b;
}

**

2 ** 2; // 2 * 2 = 4
2 ** -1; // 1 / 2 = 0.5
3 ** -2; // 1 / 9 = 0.111111111

??

$a = null;
$b = 1;
$c = 2;
echo $a ?? $b , ‘,’ , $c ?? $b; // 1,2
echo $a ?? $b ?? $c  , ‘,’ , $a ?? $d ?? $c; // 1,2

\u{xxxx}

echo "\u{4f60}";//你
echo "\u{65b0}";//新
// 从右至左强制
echo"\u{202E}iabgnay\u{1F602}";;
? yangbai

返回类型的声明

function getInt() : int {
    return 'test';
}; 

getInt();

//返回值为DateTime的函数
function getDateTime() : DateTime {
    return new DateTime();
}; 

标量类型的声明

function getAmount(int $num) : int {
    return $num;
}; 

getAmount('test');

//PHP5
#PHP Catchable fatal error:  Argument 1 passed to getInt() must be an instance of int, string given…

//PHP7
#Fatal error: Uncaught TypeError: Argument 1 passed to getInt() must be of the type integer, string given…

getAmount('123');
#PHP7新增的严格模式选项开启下也会报错【declare(strict_types=1),注意要放到代码的第一行】

核心错误可以通过异常捕获了

try {
    non_exists_func();
} catch(EngineException $e) {
    echo "Exception: {$e->getMessage();}\n";
} finally {
    echo "undefined function…";
}

//这里用php7试了一下没有捕获成功【但是确实抛出了异常】。。。
#Exception: Call to undefined function non_exists_func()

上下问敏感的词法分析

//PHP5
class Collection {public function foreach($arr) {}}
#Parse error:  parse error, expecting `"identifier (T_STRING)”’...

//PHP7
class Collection {
    public function foreach($arr) {
        return $this;
    }
    public function in($arr){
        return $this;
    }
    public function sort($condition){
        return $this;
    }
    public function echo($condition){
        return 'ok';
    }
}
$collection = new Collection();
$collection->in()->foreach()->sort()->echo();

打破的一些东西

  • mysql、ereg

mysql 移到了 ext/pecl 中去了,ereg 移到了 ext/pcre

  • isapi、tux etc SAPIs

  •  <?和<? language=“php”这样的标签被移除了

  • HTTP_RAW_POST_DATA移除了(可以使用php://input替代)

  • $o = & new className(),不再支持这样的写法

  • mktime()gmmktime() 函数的$is_dst 参数被移除了

  • setlocale()函数的$category参数不支持字符串了,必须是LC开头的常量

  • php.ini文件移除了#作为注释,统一用;去注释

  • 函数定义同名参数不支持了

  • 类的同名构造函数不推荐(目前没有移除,后续会去掉)

  • Stringintfloat等这些关键字不能被作为类、接口、trait的名称使用了

  • func_get_arg/func_get_args获取的是当前变量的值

  • 无效的八进制数字会产生编译错误

  • preg_replace()不再支持匹配模式/e

  • 16进制的字符串数字转换被移除了

  • 不再支持静态调用一个不兼容的$this上下文的非静态调用

  • Unsafe curl file uploads (use CurlFile instead)

    //PHP5
    curl_setopt(ch, CURLOPT_POSTFIELDS, array(
        'file' => '@'.realpath('image.png'), 
    )); 
    
    //PHP7
    curl_setopt(ch, CURLOPT_POSTFIELDS, [
        'file' => new CURLFile(realpath('image.png')), 
    ]); 
    
  • 一些移除的函数和选项

    set_magic_quotes_runtime();
    magic_quotes_runtime();
    
    //(use stream_set_blocking() instead)
    set_socket_blocking();
    
    //(use mcrypt_generic_deinit() instead)
    mcrypt_generic_end();
    
    //(use mcrypt_encrypt() and mcrypt_decrypt() instead)
    mcrypt_ecb();
    mcrypt_cbc();
    mcrypt_cfb();
    mcrypt_ofb();
    
    //(use datefmt_set_timezone() or IntlDateFormatter::setTimeZone() instead)
    datefmt_set_timezone_id();
    IntlDateFormatter::setTimeZoneID();
    
    //(use XsltProcessor::setSecurityPrefs() instead)
    xsl.security_prefs;//php.ini 
    
    //(use php.input_encoding、php.internal_encoding and php.output_encoding instead)
    iconv.input_encoding;
    iconv.output_encoding;
    iconv.internal_encoding;
    mbstring.http_input;
    mbstring.http_output;
    mbstring.internal_encoding;
    
    (use PDO::ATTR_EMULATE_PREPARES instead)
    PDO::PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT;//driver option 
     
    //(use peer_name instead)
    CN_match;//SSL context options 
    SNI_server_name;//SSL context options 
    

差不多了,夜已深、人已困!
更多、更详细的介绍请猛戳 这里

查看原文

赞 11 收藏 80 评论 1

上善若水 收藏了文章 · 4月1日

PHP7新特性的介绍

截止到目前为止,PHP官方已经发布了php7的RC7版本,距离发布第一个正式版本不会很远了!现在来说php7的重大特性肯定已经是定型了,不会再有什么变动了。后续一些版本的迭代主要也就是修修bug,优化之类的。下面就来说话我们一直期待的php7会有那些主要的变化了。。。

关于PHP

  • 20年的发展历史;

  • 迄今为止最流行的WEB开发语言;

  • 超过82%的网站都会使用PHP作为他们的服务端开发语言;

新特性介绍

  • PHP NG – Zend Engine 3

  • 抽象语法树

  • 64位的 INT 支持

  • 统一的变量语法

  • 新增Closure::call()

  • 一致性foreach循环

  • 匿名类的支持

  • 新增 <=>**??\u{xxxx}操作符

  • 增加了返回类型的声明

  • 增加了标量类型的声明

  • 核心错误可以通过异常捕获

  • 增加了上下文敏感的词法分析

PHP NG

  • ZVAL

大小从24字节减少到16字节

  • Zend Array(HashTable)

HashTable大小从72字节减少到56字节
HashTable bucket大小从72字节减少到32字节

  • 函数调用的优化

  • 使用了新的内存分配,管理方式,减少了内存的浪费

  • Immutable array optimization

    $arr = [];
    
    for($i=0; $i<100000; $i++) {
        $arr[] = ['php'];
    }
    
    p(memory_get_usage(true));

PHP5: 45M
PHP7: 10M

  • 一些非常常用,开销不大的的函数直接变成了引擎支持的opcode

call_user_function(_array) => ZEND_INIT_USER_CALL
is_intis_stringis_array、... => ZEND_TYPE_CHECK
strlen => ZEND_STRLEN
defined => ZEND+DEFINED

  • 核心排序的优化

PHP5(zend_qsort
快速排序(非稳定排序)

array(1 => 0, 0 => 0)

PHP7(zend_sort
快速排序+选择排序(稳定排序)

array(0 => 0, 1 => 0)

小于16个元素的使用选择排序,大于16个按照16个为单位去分割,分别使用选择排序,然后再全部合起来使用快速排序。排序较之前相比,内部元素由非稳定排序变成稳定排序,减少元素的交换次数,减少对内存的操作次数,性能提升40%

抽象语法树

clipboard.png

假如现在我们有这样的需求,要对php源文件就行语法检测,实现编码规范。php5之前的话,没有AST,直接从parser就生成了opcodes!就需要借助一些外部的php语法解析器来实现;而php7增加了AST,我们可以自己去实现这样一个扩展,利用扩展提供的函数可以直接获取文件对应的的AST结构,而这样的结构正是我们可以识别的,所以就可以在这个基础上去做一些优化和判断了。

64位的INT支持

  • 支持存储大于2GB的字符串

  • 支持上传大小大于2GB的文件

  • 保证字符串在所有平台上【64位】都是64bit

统一的语法变量

$$foo['bar']['baz'] 

PHP5: ${$foo[‘bar’]['baz']}
PHP7: ($$foo)[‘bar’][‘baz']【从左至右法则】

(function() {})();
$foo()();
[$obj, 'method']();

class A {
    public static function a1() {}
}

[new A, 'a1']();

新增Closure::call()

$f = function() {
    p($this->name);
};

class F {
    private $name = 'F';
}

$f->call(new F);

匿名类的支持

function getAnonymousClass($config) {
    return new class(config) {};
 }

p(getAnonymousClass(array()));

一致性的foreach循环

//PHP5
$a =  array(1, 2, 3);foreach ($a as $v){var_dump(current($a));}
int(2)
int(2)
int(2)

$a =  array(1, 2, 3);$b=&$a;foreach ($a as $v){var_dump(current($a));}
int(2)
int(3)
bool(false)

$a =  array(1, 2, 3);$b=$a;foreach ($a as $v){var_dump(current($a));}
int(1)
int(1)
int(1)

//PHP7:不再操作数据的内部指针了
$a =  array(1, 2, 3);foreach ($a as $v){var_dump(current($a))}
int(1)
int(1)
int(1)

$a =  array(1, 2, 3);$b=&$a;foreach ($a as $v){var_dump(current($a))
int(1)
int(1)
int(1)

$a =  array(1, 2, 3);$b=$a;foreach ($a as $v){var_dump(current($a))}
int(1)
int(1)
int(1)

新增的几个操作符

<=>

//PHP5
function compare($a, $b) {
    return ($a < $b) ? -1 : (($a >$b) ? 1 : 0);
}
//PHP7
function compare($a, $b) {
    return $a <=> $b;
}

**

2 ** 2; // 2 * 2 = 4
2 ** -1; // 1 / 2 = 0.5
3 ** -2; // 1 / 9 = 0.111111111

??

$a = null;
$b = 1;
$c = 2;
echo $a ?? $b , ‘,’ , $c ?? $b; // 1,2
echo $a ?? $b ?? $c  , ‘,’ , $a ?? $d ?? $c; // 1,2

\u{xxxx}

echo "\u{4f60}";//你
echo "\u{65b0}";//新
// 从右至左强制
echo"\u{202E}iabgnay\u{1F602}";;
? yangbai

返回类型的声明

function getInt() : int {
    return 'test';
}; 

getInt();

//返回值为DateTime的函数
function getDateTime() : DateTime {
    return new DateTime();
}; 

标量类型的声明

function getAmount(int $num) : int {
    return $num;
}; 

getAmount('test');

//PHP5
#PHP Catchable fatal error:  Argument 1 passed to getInt() must be an instance of int, string given…

//PHP7
#Fatal error: Uncaught TypeError: Argument 1 passed to getInt() must be of the type integer, string given…

getAmount('123');
#PHP7新增的严格模式选项开启下也会报错【declare(strict_types=1),注意要放到代码的第一行】

核心错误可以通过异常捕获了

try {
    non_exists_func();
} catch(EngineException $e) {
    echo "Exception: {$e->getMessage();}\n";
} finally {
    echo "undefined function…";
}

//这里用php7试了一下没有捕获成功【但是确实抛出了异常】。。。
#Exception: Call to undefined function non_exists_func()

上下问敏感的词法分析

//PHP5
class Collection {public function foreach($arr) {}}
#Parse error:  parse error, expecting `"identifier (T_STRING)”’...

//PHP7
class Collection {
    public function foreach($arr) {
        return $this;
    }
    public function in($arr){
        return $this;
    }
    public function sort($condition){
        return $this;
    }
    public function echo($condition){
        return 'ok';
    }
}
$collection = new Collection();
$collection->in()->foreach()->sort()->echo();

打破的一些东西

  • mysql、ereg

mysql 移到了 ext/pecl 中去了,ereg 移到了 ext/pcre

  • isapi、tux etc SAPIs

  •  <?和<? language=“php”这样的标签被移除了

  • HTTP_RAW_POST_DATA移除了(可以使用php://input替代)

  • $o = & new className(),不再支持这样的写法

  • mktime()gmmktime() 函数的$is_dst 参数被移除了

  • setlocale()函数的$category参数不支持字符串了,必须是LC开头的常量

  • php.ini文件移除了#作为注释,统一用;去注释

  • 函数定义同名参数不支持了

  • 类的同名构造函数不推荐(目前没有移除,后续会去掉)

  • Stringintfloat等这些关键字不能被作为类、接口、trait的名称使用了

  • func_get_arg/func_get_args获取的是当前变量的值

  • 无效的八进制数字会产生编译错误

  • preg_replace()不再支持匹配模式/e

  • 16进制的字符串数字转换被移除了

  • 不再支持静态调用一个不兼容的$this上下文的非静态调用

  • Unsafe curl file uploads (use CurlFile instead)

    //PHP5
    curl_setopt(ch, CURLOPT_POSTFIELDS, array(
        'file' => '@'.realpath('image.png'), 
    )); 
    
    //PHP7
    curl_setopt(ch, CURLOPT_POSTFIELDS, [
        'file' => new CURLFile(realpath('image.png')), 
    ]); 
    
  • 一些移除的函数和选项

    set_magic_quotes_runtime();
    magic_quotes_runtime();
    
    //(use stream_set_blocking() instead)
    set_socket_blocking();
    
    //(use mcrypt_generic_deinit() instead)
    mcrypt_generic_end();
    
    //(use mcrypt_encrypt() and mcrypt_decrypt() instead)
    mcrypt_ecb();
    mcrypt_cbc();
    mcrypt_cfb();
    mcrypt_ofb();
    
    //(use datefmt_set_timezone() or IntlDateFormatter::setTimeZone() instead)
    datefmt_set_timezone_id();
    IntlDateFormatter::setTimeZoneID();
    
    //(use XsltProcessor::setSecurityPrefs() instead)
    xsl.security_prefs;//php.ini 
    
    //(use php.input_encoding、php.internal_encoding and php.output_encoding instead)
    iconv.input_encoding;
    iconv.output_encoding;
    iconv.internal_encoding;
    mbstring.http_input;
    mbstring.http_output;
    mbstring.internal_encoding;
    
    (use PDO::ATTR_EMULATE_PREPARES instead)
    PDO::PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT;//driver option 
     
    //(use peer_name instead)
    CN_match;//SSL context options 
    SNI_server_name;//SSL context options 
    

差不多了,夜已深、人已困!
更多、更详细的介绍请猛戳 这里

查看原文

上善若水 关注了专栏 · 4月1日

技术总结

路漫漫其修远,要走的路还很长

关注 56

上善若水 收藏了文章 · 3月31日

Phalcon模型

使用模型(Working with Models)

模型表示应用程序信息(数据)以及这些数据的处理规则,主要用于管理与对应数据表的交互规则。大多数情况下,数据库中的每一张表都有对应的模型。应用程序中的大部分业务逻辑集中在模型中。

Phalcon应用中,Phalcon\Mvc\Model是所有模型的基类。它提供了数据库独立、基础CRUD、高级查找、模型关联以及其他服务。

Phalcon\Mvc\Model将调用的方法动态转换为相应的数据库操作,避免了直接使用SQL。

模型使用数据库高级抽象层,如果想要使用更为底层的方式操作数据库,请参考Phalcon\Db组件文档。

创建模型(Creating Models)

模型需继承Phalcon\Mvc\Model类,以大驼峰格式命名。

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class RobotParts extends Model
{

}

如果使用PHP 5.4、5.5版本,建议在模型中声明对应数据表的所有字段,以节约内存。

模型Store\Toys\RobotParts默认映射robot_parts表,可以调用setSource()方法手动指定映射表:

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class RobotParts extends Model
{
    public function initialize()
    {
        $this->setSource('toys_robot_parts');
    }
}

模型RobotParts现在映射toys_robot_parts表。initialize()方法有助于在模型中创建自定义行为,如为模型指定映射表。

initialize()方法在请求期间只调用一次,目的是为该模型的所有实例执行初始化操作。如果每次实例化模型的时候都需要进行初始化,可以使用onConstruct()方法:

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class RobotParts extends Model
{
    public function onConstruct()
    {
        // ...
    }
}

公共属性和Setters、Getters方法(Public properties vs. Setters/Getters)

模型可以定义公共属性,在任何获取了模型实例的地方都可以读写模型的公共属性:

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    public $id;

    public $name;

    public $price;
}

另一种实现方式是getterssetters方法,控制哪些模型属性可以公开访问。这种方式的好处是,开发者可以在对模型属性进行写操作时执行转换和验证,这是使用公共属性方式无法实现的。此外,getterssetters可以在不改动模型和接口的前提下,应对未来可能的改动。如果字段名称改变,唯一需要的改动的地方是getterssetters中引用的模型私有属性。

<?php

namespace Store\Toys;

use InvalidArgumentException;
use Phalcon\Mvc\Model;

class Robots extends Model
{
    protected $id;

    protected $name;

    protected $price;

    public function getId()
    {
        return $this->id;
    }

    public function setName($name)
    {
        // name不能太短
        if (strlen($name) < 10) {
            throw new InvalidArgumentException(
                'The name is too short'
            );
        }

        $this->name = $name;
    }

    public function getName()
    {
        return $this->name;
    }

    public function setPrice($price)
    {
        // price不能为负
        if ($price < 0) {
            throw new InvalidArgumentException(
                "Price can't be negative"
            );
        }

        $this->price = $price;
    }

    public function getPrice()
    {
        // 返回前,将该值转换为double类型
        return (double) $this->price;
    }
}

虽然公共属性在开发中的复杂度更低,但是getterssetters可以大大提高程序的测试性、扩展性和可维护性。开发者可以根据需求决定哪种方式更适合他们的应用。ORM兼容这两种方式。

使用getterssetters时,属性名中的下划线可能会导致问题。

如果在属性名中使用下划线,在声明getterssetters魔术方法时,仍然要使用驼峰格式($model->getPropertyName()代替$model->getProperty_name()$model->findByPropertyName()代替$model->findByProperty_name()等)。大多数系统推荐驼峰写法,而不是下划线写法,所以建议按照文档中的写法为属性命名。可以使用字段映射(如上所述)以确保属性正确映射到数据表中对应字段。

理解记录对象(Understanding Records To Objects)

模型的每一个实例代表数据表中的一条记录,可以通过读取模型对象属性来访问记录数据。例如,表robots有如下记录:

mysql> select * from robots;
+----+------------+------------+------+
| id | name       | type       | year |
+----+------------+------------+------+
|  1 | Robotina   | mechanical | 1972 |
|  2 | Astro Boy  | mechanical | 1952 |
|  3 | Terminator | cyborg     | 2029 |
+----+------------+------------+------+
3 rows in set (0.00 sec)

通过主键查找某条记录:

<?php

use Store\Toys\Robots;

// 查找id = 3的记录
$robot = Robots::findFirst(3);

// 输出'Terminator'
echo $robot->name;

一旦记录存储在内存中,就可以修改其中的数据并保存:

<?php

use Store\Toys\Robots;

$robot = Robots::findFirst(3);

$robot->name = 'RoboCop';

$robot->save();

Phalcon\Mvc\Model为web应用提供了数据库高级抽象层,不需要使用原生SQL语句。

查找记录(Finding Records)

Phalcon\Mvc\Model提供了多种查询记录的方法,下面例子演示如何用模型查找一条或多条记录:

<?php

use Store\Toys\Robots;

// 查询所有记录
$robots = Robots::find();
echo 'There are ', count($robots), "\n";

// 查询type = 'mechanical'的记录
$robots = Robots::find("type = 'mechanical'");
echo 'There are ', count($robots), "\n";

// 获取type = 'virtual'的记录,根据name排序
$robots = Robots::find(
    [
        "type = 'virtual'",
        'order' => 'name',
    ]
);
foreach ($robots as $robot) {
    echo $robot->name, "\n";
}

// 获取type = 'virtual'的前100条记录,根据name排序
$robots = Robots::find(
    [
        "type = 'virtual'",
        'order' => 'name',
        'limit' => 100,
    ]
);
foreach ($robots as $robot) {
    echo $robot->name, "\n";
}

如果要使用外部数据(如用户输入)或变量查找记录,必须进行参数绑定。

使用findFirst()方法,获取满足给定条件的第一条记录:

<?php

use Store\Toys\Robots;

// 获取robots表的第一条记录
$robot = Robots::findFirst();
echo 'The robot name is ', $robot->name, "\n";

// 获取robots表中type = 'mechanical'的第一条记录
$robot = Robots::findFirst("type = 'mechanical'");
echo 'The first mechanical robot name is ', $robot->name, "\n";

// 获取robots表中type = 'virtual'的第一条记录,根据name排序
$robot = Robots::findFirst(
    [
        "type = 'virtual'",
        'order' => 'name',
    ]
);

echo 'The first virtual robot name is ', $robot->name, "\n";

find()方法和findFirst()方法都接受一个包含指定搜索条件的关联数组:

<?php

use Store\Toys\Robots;

$robots = Robots::findFirst(
    [
        "type = 'virtual'",
        'order' => 'name DESC',
        'limit' => 30,
    ]
);

$robots = Robots::find(
    [
        'conditions' => 'type = ?1',
        'bind'       => [
            1 => 'virtual',
        ],
    ]
);

有下列查询选项:

参数说明示例
conditions 查询操作的搜索条件,用于筛选符合指定条件的记录。默认情况下,PhalconMvcModel假定第一个参数就是搜索条件 'conditions' => "name LIKE 'steve%'"
columns 获取模型中的指定字段,而不是所有字段。使用此选项时,返回不完整对象。 'columns' => 'id, name'
bind 参数绑定与conditions一起使用,通过替换占位符、转义特殊字符从而提高安全性 'bind' => ['status' => 'A', 'type' => 'some-time']
bindTypes 使用参数绑定时,可以使用这个参数为绑定参数定义额外的类型限制,从而提高安全性 'bindTypes' => [Column::BIND_PARAM_STR, Column::BIND_PARAM_INT]
order 用于对结果集进行排序,使用逗号分隔多个字段 'order' => 'name DESC, status'
limit 将查询结果的数量限制在一定范围内 'limit' => 10
offset 设定查询结果偏移量 'offset' => 5
group 允许跨记录收集数据,并将结果集按一个或多个字段分组 'group' => 'name, status'
for_update 使用此选项,PhalconMvcModel将读取最新的可用数据,并为读取到的每一条记录设置独占锁 'for_update' => true
shared_lock 使用此选项,PhalconMvcModel将读取最新的可用数据,并为读取到的每一条记录设置共享锁 'shared_lock' => true
cache 缓存结果集,减少对数据库的持续访问 'cache' => ['lifetime' => 3600, 'key' => 'my-find-key']
hydration 设置结果集返回模式 'hydration' => Resultset::HYDRATE_OBJECTS

除了使用参数数组,还可以使用面向对象的方式创建查询:

<?php

use Store\Toys\Robots;

$robots = Robots::query()
    ->where('type = :type:')
    ->addWhere('year < 2000')
    ->bind(['type' => 'machanical'])
    ->order('name')
    ->execute();

静态方法query()返回一个IDE自动完成友好的Phalcon\Mvc\Model\Criteria对象。

所有查询在内部都以PHQL查询的方式处理。PHQL是一种高级的、面向对象的类SQL语言,这种语言提供了多种功能来执行查询,如join其他模型,定义分组,添加聚合等。

最后,还有一个findFirstBy<property-name>()方法,该方法扩展了findFirst()方法,它允许通过使用方法中的属性名称并向它传递一个包含要在该字段中搜索数据的参数,从表中快速执行检索。以上面的Robots模型为例:

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    public $id;

    public $name;

    public $price;
}

这里有三个属性:$id$name$price,假设要检索name为'Terminator'的第一条记录,代码如下:

<?php

use Store\Toys\Robots;

$name = 'Terminator';

$robot = Robots::findFirstByName($name);

if ($robot) {
    echo 'The first robot with the name ', $name . ' cost ' . $robot->price, '.';
} else {
    echo 'There were no robots found in our table with the name ' . $name . '.';
}

请注意,我们在调用的方法中使用了Name并传递了变量$name,表中name字段值为$name的记录就是我们要查找的。

模型结果集(Model Resultsets)

findFirst()方法返回被调用类的实例(如果有结果返回),而find()方法返回Phalcon\Mvc\Model\Resultsets\Simple对象,该对象封装了结果集应有的所有功能,如遍历,查找特定记录,统计等。

这些对象比一般数组功能强大,Phalcon\Mvc\Model\Resultset一个最大的特点是,任何时刻,只有一条记录保存在内存中。这对内存管理有极大帮助,特别是在处理大批量数据时。

<?php

use Store\Toys\Robots;

// 获取所有记录
$robots = Robots::find();

// foreach遍历
foreach ($robots as $robot) {
    echo $robot->name, "\n";
}

// while遍历
while ($robots->valid()) {
    $robot = $robots->current();

    echo $robot->name, "\n";

    $robots->next();
}

// 结果集计数
echo count($robots);

// 另一种结果集计数方法
echo $robots->count();

// 移动游标到第三条记录
$robots->seek(2);

$robot = $robots->current();

// 通过位置访问结果集中的记录
$robot = $robots[5];

// 检查指定位置是否有记录
if (isset($robots[3])) {
    $robot = $robots[3];
}

// 获取结果集中第一条记录
$robot = $robots->getFirst();

// 获取结果集中最后一条记录
$robot = $robots->getLast();

Phalcon结果集模拟游标,可以通过访问其位置或内部指针获取任一条记录。注意,某些数据库系统不支持游标,这会导致查询被反复执行,以重置游标到初始位置并获取被请求位置的记录。同样的,遍历多少次结果集,查询便要执行多少次。

将大量查询结果保存在内存中会消耗太多资源,因此,在某些情况下,以32条记录为一块从数据库中获取记录,可以降低重复执行请求的内存消耗。

注意,结果集可以序列化后存储在缓存中,Phalcon\Cache可以实现该需求。但是序列化数据会导致Phalcon\Mvc\Model以数组形式保存从数据库中检索到的数据,这会导致更多的内存消耗。

<?php

// 查询表parts所有记录
$parts = Parts::find();

// 将结果集保存到文件中
file_put_contents(
    'cache.txt',
    serialize($parts)
);

// 从文件中获取结果集
$parts = unserialize(
    file_get_contents('cache.txt')
);

// 遍历
foreach ($parts as $part) {
    echo $part->id;
}

自定义结果集(Custom Resultsets)

有时候应用程序需要对数据库中检索到的数据进行额外处理。以前,我们只需要扩展模型或将功能封装在模型或trait当中,然后返回一组经过转换的数据。

通过自定义结果集,不需要再如此处理。自定义结果集将封装原本封装在模型中并能被其他模型重用的功能,这样可以简化代码。如此,find()方法返回自定义对象,而不再返回Phalcon\Mvc\Model\Resultset对象。Phalcon允许在模型中定义getResultsetClass()方法来实现此操作。

首先,声明resultset类:

<?php

namespace Application\Mvc\Model\Resultset;

use Phalcon\Mvc\Model\Resultset\Simple;

class Custom extends Simple
{
    public function getSomeData()
    {
        /** CODE */
    }
}

模型中,在getResultsetClass()方法里设置resultset类,如下:

<?php

namespace Phalcon\Test\Models\Statistics;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    public function getSource()
    {
        return 'robots';
    }

    public function getResultsetClass()
    {
        return 'Application\Mvc\Model\Resultset\Custom';
    }
}

最后,代码里应包含如下内容:

<?php

/**
 * 查找robots表记录
 */
$robots = Robots::find(
    [
        'conditions' => 'date between "2017-01-01" AND "2017-12-31"',
        'order'      => 'date',
    ]
);

/**
 * 传递数据到视图
 */
$this->view->mydata = $robots->getSomeData();

过滤结果集(Filtering Resultsets)

过滤数据最有效的方法之一是设置搜索条件,数据库将使用表索引以更快的返回数据。Phalcon允许使用PHP函数或是数据库不支持的方式过滤数据:

<?php

$customers = Customers::find();

$customers = $customers->filter(
    function ($customer) {
        // 只返回e-mail合法的记录
        if (filter_var($customer->email, FILTER_VALIDATE_EMAIL)) {
            return $customer;
        }
    }
);

参数绑定(Binding Parameters)

Phalcon\Mvc\Model支持参数绑定,建议使用这种方法以避免SQL注入,字符串占位符和数字占位符均被支持。参数绑定简单实现如下:

<?php

use Store\Toys\Robots;

// 字符串占位符
$robots = Robots::find(
    [
        'name = :name: AND type = :type:',
        'bind' => [
            'name' => 'Robotina',
            'type' => 'maid',
        ],
    ]
);

// 数字占位符
$robots = Robots::find(
    [
        'name = ?1 AND type = ?2',
        'bind' => [
            1 => 'Robotina',
            2 => 'maid',
        ],
    ]
);

// 同时使用字符串占位符和数字占位符
$robots = Robots::find(
    [
        'name = :name: AND type = ?1',
        'bind' => [
            'name' => 'Robotina',
            1      => 'maid',
        ],
    ]
);

使用数字占位符时,需要将它们定义成整数形式,即1或2。而'1'或'2'会被当成字符串,所以占位符不能被成功替换。

字符串会自动使用PDO转义,该功能会考虑连接字符集,因此建议在连接参数或数据库配置中定义正确字符集,因为错误字符集会在存储和检索数据时产生不良影响。

还可以设置bindTypes参数,定义参数如何根据其类型绑定:

<?php

use Phalcon\Db\Column;
use Store\Toys\Robots;

// 绑定参数
$parameters = [
    'name' => 'Robotina',
    'year' => 2008,
];

// 参数类型转换
$types = [
    'name' => Column::BIND_PARAM_STR,
    'year' => Column::BIND_PARAM_INT,
];

// 字符串占位符
$robots = Robots::find(
    [
        'name = :name: AND year = :year:',
        'bind'      => $parameters,
        'bindTypes' => $types,
    ]
);

由于默认的绑定类型是Phalcon\Db\Column::BIND_PARAM_STR,如果所有字段都是字符串类型,则没有必要指定bindTypes参数。

如果使用数组作为绑定参数,则数组必须是键名从0开始的索引数组:

<?php

use Store\Toys\Robots;

$array = ['a', 'b', 'c']; // $array: [[0] => 'a', [1] => 'b', [2] => 'c']

unset($array[1]); // $array: [[0] => 'a', [2] => 'c']

// 现在必须重建数组索引
$array = array_values($array); // $array: [[0] => 'a', [1] => 'c']

$robots = Robots::find(
    [
        'letter IN ({letter:array})',
        'bind' => [
            'letter' => $array,
        ],
    ]
);

参数绑定除了可用于所有查询方法,如find()findFirst()外,还可用于count()sum()average()等统计方法。

使用finders时,会自动使用参数绑定:

<?php

use Store\Toys\Robots;

// 显式使用参数绑定
$robots = Robots::find(
    [
        'name = ?0',
        'bind' => [
            'Ultron',
        ],
    ]
);

// 隐式使用参数绑定
$robots = Robots::findByName('Ultron');

初始化已获取记录(Initializing / Preparing fetched records)

有时从数据库获取记录之后,在数据被应用程序使用之前,需要对数据进行初始化。可以在模型中实现afterFetch()方法,实例化模型时会执行该方法,并将数据传递给它:

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    public $id;

    public $name;

    public $status;

    public function beforeSave()
    {
        // 将数组转换成字符串
        $this->status = join(',', $this->status);
    }

    public function afterFetch()
    {
        // 将字符串转换成数组
        $this->status = explode(',', $this->status);
    }

    public function afterSave()
    {
        // 将字符串转换成数组
        $this->status = explode(',', $this->status);
    }
}

如果使用getters/setters代替公共属性,或同时使用它们,可以在字段被访问时初始化字段:

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    public $id;

    public $name;

    public $status;

    public function getStatus()
    {
        return explode(',', $this->status);
    }
}

生成运算(Generating Calculations)

运算(或聚合)是数据库中常用的辅助方法,如COUNTSUMMAXMINAVGPhalcon\Mvc\Model可以直接使用这些方法。
Count示例:

<?php

// 表employees总记录数
$rowcount = Employees::count();

// 表employees共有多少不同areas值
$rowcount = Employees::count(
    [
        'distinct' => 'area',
    ]
);

// 表employees共有多少area为'Testing'的记录
$rowcount = Employees::count(
    'area = "Testing"'
);

// 按area分组统计表employees记录
$group = Employees::count(
    [
        'group' => 'area',
    ]
);
foreach ($group as $row) {
    echo 'There are ', $row->rowcount, ' in ', $row->area;
}

// 按area分组统计表employees记录,并根据数目排序
$group = Employees::count(
    [
        'group' => 'area',
        'order' => 'rowcount',
    ]
);

// 使用参数绑定避免SQL注入
$group = Employees::count(
    [
        'type > ?0',
        'bind' => [
            $type,
        ],
    ]
);

Sum示例:

<?php

// 所有employees的salaries总和
$total = Employees::sum(
    [
        'column' => 'salary',
    ]
);

// area = 'Sales'的所有employees的salaries总和
$total = Employees::sum(
    [
        'column'     => 'salary',
        'conditions' => 'area = "Sales"',
    ]
);

// 根据area分组统计salaries
$group = Employees::sum(
    [
        'column' => 'salary',
        'group'  => 'area',
    ]
);
foreach ($group as $row) {
    echo 'The sum of salaries of the ', $row->area, ' is ', $row->sumatory;
}

// 根据area分组统计salaries,salaries由高到低排序
$group = Employees::sum(
    [
        'column' => 'salary',
        'group'  => 'area',
        'order'  => 'sumatory DESC',
    ]
);

// 使用参数绑定避免参数绑定
$group = Employees::sum(
    [
        'conditions' => 'area > ?0',
        'bind'       => [
            $area,
        ],
    ]
);

Average示例:

<?php

// 所有employees的平均salary
$average = Employees::average(
    [
        'column' => 'salary',
    ]
);

// area = 'Sales'的employees的平均salary
$average = Employees::average(
    [
        'column'     => 'salary',
        'conditions' => 'area = "Sales"',
    ]
);

// 使用参数绑定避免SQL注
$average = Employees::average(
    [
        'column'     => 'age',
        'conditions' => 'area > ?0',
        'bind'       => [
            $area,
        ],
    ]
);

Max / Min示例:

<?php

// 所有employees中age最大的
$age = Employees::maximum(
    [
        'column' => 'age',
    ]
);

// area = 'Sales'的employees中age最大的
$age = Employees::maximum(
    [
        'column'     => 'age',
        'conditions' => 'area = "Sales"',
    ]
);

// 所有employees中salary最低的
$salary = Employees::minimum(
    [
        'column' => 'salary',
    ]
);

创建 / 更新记录(Creating / Updating Records)

Phalcon\Mvc\Model::save()方法会根据记录是否存在于模型映射表中而创建 / 更新记录,Phalcon\Mvc\Model的创建和更新方法会在内部调用该方法。为此,必须在实体中正确定义主键,以确定是创建记录还是更新记录。

该方法会执行相关验证器,虚拟外键和模型中定义的事件:

<?php

use Store\Toys\Robots;

$robot = new Robots();

$robot->type = 'mechanical';
$robot->name = 'Astro Boy';
$robot->year = 1952;

if ($robot->save() === false) {
    echo "Umh, We can't store robots right now: \n";

    $messages = $robot->getMessages();

    foreach ($messages as $message) {
        echo $message, "\n";
    }
} else {
    echo 'Great, a new robot was saved successfully!';
}

直接传递或者通过属性数组传递的值会根据其数据类型自动被转义 / 过滤,所以可以传递一个不安全的数组而不用担心SQL注入:

<?php

use Store\Toys\Robots;

$robot = new Robots();

$robot->save($_POST);

毫无防护的批量传值可能会允许攻击者设置任意字段的值,仅在允许用户插入 / 更新模型中所有字段的情况下使用上述功能,即使这些字段不是使用表单提交的。

可以在save()方法中设置额外参数,以设置批量传值时,执行插入 / 更新操作的白名单字段。

<?php

use Store\Toys\Robots;

$robot = new Robots();

$robot->save(
    $_POST,
    [
        'name',
        'type',
    ]
);

创建 / 更新执行结果(Create / Update with Confidence)

应用程序高并发时,创建记录操作可能会变成更新操作。使用Phalcon\Mvc\Model::save()方法保存记录时,可能发生这种情况。如果想确保执行创建或更新,可以使用create()update()方法替换save()

<?php

use Store\Toys\Robots;

$robot = new Robots();

$robot->type = 'mechanical';
$robot->name = 'Astro Boy';
$robot->year = 1952;

// 仅创建记录
if ($robot->create() === false) {
    echo "Umh, We can't store robots right now: \n";

    $messages = $robot->getMessages();

    foreach ($messages as $message) {
        echo $message, "\n";
    }
} else {
    echo 'Great, a new robot was created successfully!';
}

create()方法和update()方法同样接受一个数组作为参数。

删除记录(Deleting Records)

Phalcon\Mvc\Model::delete()方法允许删除记录,使用示例:

<?php

use Store\Toys\Robots;

$robot = Robots::findFirst(11);

if ($robot !== false) {
    if ($robot->delete() === false) {
        echo "Sorry, we can't delete the robot right now: \n";

        $messages = $robot->getMessages();

        foreach ($messages as $message) {
            echo $message, "\n";
        }
    } else {
        echo 'The robots was deleted successfully!';
    }
}

也可以通过使用foreach遍历结果集来删除多条记录:

<?php

use Store\Toys\Robots;

$robots = Robots::find(
    "type = 'mechanical'"
);

foreach ($robots as $robot) {
    if ($robot->delete() === false) {
        echo "Sorry, we can't delete the robot right now: \n";

        $messages = $robot->getMessages();

        foreach ($messages as $message) {
            echo $message, "\n";
        }
    } else {
        echo 'The robot was deleted successfully!';
    }
}

以下事件可以用于定义在执行删除操作时,要执行的自定义业务规则:

操作事件名称能否终止操作说明
删除 afterDelete 删除操作后执行
删除 beforeDelete 删除操作前执行

通过上述事件,可以在模型中定义业务规则:

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    public function beforeDelete()
    {
        if ($this->status === 'A') {
            echo "The robot is active, it can't be deleted";

            return false;
        }

        return true;
    }
}

Hydrations模式(Hydration Modes)

如前所述,结果集是完整对象的集合,这意味着每条返回结果都是一个对象,代表数据表中的一行。这些对象可以修改并永久保存:

<?php

use Store\Toys\Robots;

$robots = Robots::find();

// 操作完整对象结果集
foreach ($robots as $robot) {
    $robot->year = 2000;

    $robot->save();
}

有时记录只能以只读模式呈现给用户,这种情况下,改变记录的展现方式有助于用户处理数据。用于表示结果集中返回的对象的策略称为'hydration mode':

<?php

use Phalcon\Mvc\Model\Resultset;
use Store\Toys\Robots;

$robots = Robots::find();

// 返回数组
$robots->setHydrateMode(
    Resultset::HYDRATE_ARRAYS
);

foreach ($robots as $robot) {
    echo $robot['year'], PHP_EOL;
}

// 返回stdClass对象
$robots->setHydrateMode(
    Resultset::HYDRATE_OBJECTS
);

foreach ($robots as $robot) {
    echo $robot->year, PHP_EOL;
}

// 返回模型实例
$robots->setHydrateMode(
    Resultset::HYDRATE_RECORDS
);

foreach ($robots as $robot) {
    echo $robot->year, PHP_EOL;
}

Hydration mode也可以作为find()方法的参数传递:

<?php

use Phalcon\Mvc\Model\Resultset;
use Store\Toys\Robots;

$robots = Robots::find(
    [
        'hydration' => Resultset::HYDRATE_ARRAYS,
    ]
);

foreach ($robots as $robot) {
    echo $robot['year'], PHP_EOL;
}

表前缀(Table prefixes)

如果希望所有表名称都有特定前缀,并且不想在每个模型中都调用setSource()方法,则可以调用Phalcon\Mvc\Model\ManagersetModelprefix()方法:

<?php

use Phalcon\Mvc\Model;
use Phalcon\Mvc\Model\Manager;

class Robots extends Model
{

}

$manager = new Manager();
$manager->setModelPrefix('wp_');
$robots = new Robots(null, null, $manager);
echo $robots->getSource(); // 返回wp_robots

自动生成的标识字段(Auto-generated identity columns)

某些模型有标识字段,这些字段通常是映射表的主键。Phalcon\Mvc\Model能够识别标识字段,并在生成INSERT语句时忽略它,所以数据库能够自动为它生成一个值。创建记录之后,标识字段的值会被注册为数据库为其生成的值:

<?php

$robot->save();

echo 'The generated id is: ', $robot->id;

Phalcon\Mvc\Model能够识别标识字段,根据数据库系统,这些字段可能是PostgreSQL的串行列,或者是MySQL的自增列。

PostgreSQL使用序列生成自增值,默认情况下,Phalcon试图从序列table_field_seq中获取生成的值,例如:robots_id_seq,如果序列具有其他名称,则需要实现getSequenceName()方法:

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    public function getSequenceName()
    {
        return 'robots_sequence_name';
    }
}

忽略字段(Skipping Columns)

Phalcon\Mvc\Model指定创建 / 更新记录时需要被忽略的字段,以便数据库为其赋默认值:

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    public function initialize()
    {
        // INSERT / UPDATE操作均忽略字段
        $this->skipAttributes(
            [
                'year',
                'price',
            ]
        );

        // INSERT操作忽略字段
        $this->skipAttributes(
            [
                'created_at',
            ]
        );

        // UPDATE操作忽略字段
        $this->skipAttributes(
            [
                'modified_in',
            ]
        );
    }
}

这将全局忽略应用程序中每个INSERT / UPDATE操作的这些字段。如果想在不同的INSERT / UPDATE操作时忽略不同字段,可以传递第二个参数(布尔值) - true。强制使用默认值,实现方式如下:

<?php

use Phalcon\Db\RawValue;
use Store\Toys\Robots;

$robot = new Robots();

$robot->name       = 'Bender';
$robot->year       = 1999;
$robot->created_at = new RawValue('default');

$robot->create();

回调函数也可以用于为默认值创建分配条件:

<?php

namespace Store\Toys;

use Phalcon\Db\RawValue;
use Phalcon\Mvc\Model;

class Robots extends Model
{
    public function beforeCreate()
    {
        if ($this->price > 10000) {
            $this->type = new RawValue('default');
        }
    }
}

切勿使用Phalcon\Db\RawValue传递外部数据(如用户输入)或可变数据,因为参数绑定时,这些字段的值也会被忽略,所以有可能会被用来实施注入攻击。

动态更新(Dynamic Updates)

UPDATE语句默认使用模型中定义的所有字段创建(全字段更新SQL),可以更改特定模型以进行动态更新,用需要更新的字段创建SQL语句。

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    public function initialize()
    {
        $this->useDynamicUpdate(true);
    }
}

独立列映射(Independent Column Mapping)

ORM支持独立的列映射,它允许开发者在模型中定义与映射表列名称不相同的字段名,Phalcon会识别新的字段名称,并重命名字段以匹配数据库中相应的列。这是一项很棒的功能,当需要重命名数据库中的列名称时,不需要更改查询代码,模型中的映射列会处理好这一切。例如:

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    public $code;

    public $theName;

    public $theType;

    public $theYear;

    public function columnMap()
    {
        return [
            'id'       => 'code',
            'the_name' => 'theName',
            'the_type' => 'theType',
            'the_year' => 'theYear',
        ];
    }
}

然后,可以使用新的字段名:

<?php

use Store\Toys\Robots;

// 根据name查找一个记录
$robot = Robots::findFirst(
    'theName = "Voltron"'
);

echo $robot->theName, "\n";

// 根据type排序
$robot = Robots::find(
    [
        'order' => 'theType DESC',
    ]
);

foreach ($robots as $robot) {
    echo 'Code: ', $robot->code, "\n";
}

// 添加记录
$robot = new Robots();

$robot->code    = '10101';
$robot->theName = 'Bender';
$robot->theType = 'Industrial';
$robot->theYear = 2999;

$robot->save();

重命名字段时需要注意以下事项:

  • 关系 / 验证器中对属性的引用必须使用新名称
  • 引用实际列名称将导致ORM异常

独立列映射允许:

  • 使用自定义约定编写应用程序
  • 清除代码中列的前后缀
  • 改变列名称时,无需更改应用代码

记录快照(Record Snapshots)

查询时可以设置特定的模型以保持记录快照。可以使用此功能来实现审计,或是根据持久性查询的数据了解哪些字段发生了改变:

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    public function initialize()
    {
        $this->keepSnapshots(true);
    }
}

当激活此功能时,应用程序会消耗更多的内存来与持久性查询的原始数据保持同步。在激活此功能的模型中,可以按如下方式检查发生改变的字段:

<?php

use Store\Toys\Robots;

// 查找一条记录
$robot = Robots::findFirst();

// 改变列值
$robot->name = 'Other name';

var_dump($robot->getChangedFields()); // ['name']

var_dump($robot->hasChanged('name')); // true

var_dump($robot->hasChanged('type')); // false

快照会在模型创建 / 更新时自动更新,使用hasUpdated()方法和getUploadedFields()方法检查create / save / update操作后,字段是否更新。但是在afterUpdate()afterSave()afterCreate()方法中调用getChangedFields()方法,会导致应用程序出问题。

可以禁用此功能:

<?php

Phalcon\Mvc\Model::setup(
    [
        'updateSnapshotOnSave' => false,
    ]
);

也可以在php.ini中设置:

phalcon.orm.update_snapshot_on_save = 0

使用此功能会产生以下效果:

<?php

use Phalcon\Mvc\Model;

class User extends Model
{
    public function initialize()
    {
        $this->keepSnapshots(true);
    }
}

$user       = new User();
$user->name = 'Test User';
$user->create();
var_dump($user->getChangedFields());
$user->login = 'testuser';
var_dump($user->getChangedFields());
$user->update();
var_dump($user->getChangedFields());

在Phalcon 3.1.0及之后的版本中:

array(0) {
}
array(1) {
[0] => string(5) "login"
}
array(0) {
}

getUpdatedFields()方法将正确返回更新的字段,或如上所述,可以通过设置相关的ini值回到先前的行为。

指向不同模式(Pointing to a different schema)

如果模型映射到非默认的模式 / 数据库,可以使用setSchema()方法重新定义它:

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    public function initialize()
    {
        $this->setSchema('toys');
    }
}

多数据库配置(Setting multiple databases)

Phalcon应用中,所有模型可以属于相同的数据库连接,或具有独立的数据库连接。实际上,当Phalcon\Mvc\Model需要连接数据库时,它会在应用程序的服务容器中请求数据库服务。可以在initialize()方法中设置,覆盖该服务:

<?php

use Phalcon\Db\Adapter\Pdo\Mysql as MysqlPdo;
use Phalcon\Db\Adapter\Pdo\PostgreSQL as PostgreSQLPdo;

// 注册MySQL数据库服务
$di->set(
    'dbMysql',
    function () {
        return new MysqlPdo(
            [
                'host'     => 'localhost',
                'username' => 'root',
                'password' => 'secret',
                'dbname'   => 'invo',
            ]
        );
    }
);

// 注册PostgreSQL数据库服务
$di->set(
    'dbPostgres',
    function () {
        return new PostgreSQLPdo(
            [
                'host'     => 'localhost',
                'username' => 'postgres',
                'password' => '',
                'dbname'   => 'invo',
            ]
        );
    }
);

然后,在initialize()方法中为模型定义连接服务:

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    public function initialize()
    {
        $this->setConnectionService('dbPostgres');
    }
}

Phalcon提供了更灵活的操作,可以定义只读连接或写连接,这对于负载均衡和主从架构的的数据库非常有用:

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    public function initialize()
    {
        $this->setReadConnectionService('dbSlave');

        $this->setWriteConnectionService('dbMaster');
    }
}

ORM还支持分片功能,允许根据当前查询条件实施分片选择:

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    /**
     * 动态选择分片
     * @param  array $intermediate
     * @param  array $bindParams
     * @param  array $bindTypes
     */
    public function selectReadConnection($intermediate, $bindParams, $bindTypes)
    {
        // 检查select语句中是否有where子句
        if (isset($intermediate['where'])) {
            $conditions = $intermediate['where'];

            // 根据条件选择可能的分片
            if ($conditions['left']['name'] === 'id') {
                $id = $conditions['right']['value'];

                if ($id > 0 && $id < 10000) {
                    return $this->getDI()->get('dbShard1');
                }

                if ($id > 10000) {
                    return $this->getDI()->get('dbShard2');
                }
            }
        }

        // 使用默认分片
        return $this->getDI()->get('dbShard0');
    }
}

selectReadConnection()方法选择合适的连接,会影响任何新执行的查询:

<?php

use Store\Toys\Robots;

$robot = Robots::findFirst('id = 101');

注入服务到模型(Injecting services into Models)

有时可能需要在模型中访问应用程序服务,以下示例介绍了如何执行此操作:

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    public function notSaved()
    {
        // 从DI容器中获取flash服务
        $flash = $this->getDI()->getFlash();

        $messages = $this->getMessages();

        // 显示验证消息
        foreach ($messages as $message) {
            $flash->error($message);
        }
    }
}

创建或更新操作执行失败时会触发notSaved事件,因此我们从DI容器中获取flash服务,然后闪存验证消息。这样,我们不必在每次保存后打印消息。

禁用 / 启用功能(Disabling / Enabling Features)

在ORM中,我们实现了一种机制,允许在全局范围内启用 / 禁用特定功能或选项,根据ORM的使用情况,可以禁用那些你没有使用到的功能。如有需要,下面这些选项可以暂时禁用:

<?php

use Phalcon\Mvc\Model;

Model::setup(
    [
        'events'         => false,
        'columnRenaming' => false,
    ]
);

可用选项:

选项说明默认值
astCache启用 / 禁用模型中的回调、钩子和事件通知null
cacheLevel设置ORM的缓存级别3
castOnHydratefalse
columnRenaming启用 / 禁用字段重命名true
disableAssignSetters模型中禁用setterfalse
enableImplicitJoinstrue
enableLiteralstrue
escapeIdentifierstrue
events启用 / 禁用模型中的回调、钩子和事件通知true
exceptionOnFailedSave启用 / 禁用save()操作失败时抛出异常false
forceCastingfalse
ignoreUnknownColumns启用 / 禁用忽略模型上的未知字段false
lateStateBinding启用 / 禁用PhalconMvcModel::cloneResultMap()方法的延迟绑定false
notNullValidationsORM自动验证映射表中的非空列true
parserCachenull
phqlLiterals启用 / 禁用PHQL解析器中的字面量true
uniqueCacheId3
updateSnapshotOnSave启用 / 禁用save()方法的更新快照true
virtualForeignKeys启用 / 禁用虚拟外键true

注意,给Phalcon\Mvc\Model::assign()方法(创建 / 更新 / 保存模型中常用到它)传递参数时,setters始终会被调用,这会给应用程序增加额外开销。可以在php.ini文件中添加配置phalcon.orm.disable_assign_setters = 1,这样就只会简单的使用$this->property = value

独立组件(Stand-Along component)

下面演示以独立组件模式使用Phalcon\Mvc\Model

<?php

use Phalcon\Di;
use Phalcon\Mvc\Db\Adapter\Pdo\Sqlite as Connection;
use Phalcon\Mvc\Model;
use Phalcon\Mvc\Model\Manager as ModelsManager;
use Phalcon\Mvc\Model\Metadata\Memory as MetaData;

$di = new Di();

// 建立连接
$di->set(
    'db',
    new Connection(
        [
            'dbname' => 'sample.db',
        ]
    )
);

// 创建模型管理器
$di->set(
    'modelsManager',
    new ModelsManager()
);

// 使用内存元数据适配器或其他
$di->set(
    'modelsMetadata',
    new MetaData()
);

// 创建模型
class Robots extends Model
{

}

// 使用模型
echo Robots::count();
查看原文

上善若水 关注了用户 · 2018-12-14

黄小斜 @h2pl

微信公众号「程序员黄小斜」致力于让自学编程这件事变得更简单,授人以鱼也要授人以渔。作者自学编程转行互联网,目前是阿里巴巴Java工程师,专注于分享程序员前沿技术干货和编程学习心得,期待你的关注,和我们一起进步!

关注 44

上善若水 评论了文章 · 2018-12-14

从Java小白到收获BAT等offer,分享我这两年的经验和感悟

我写过很多篇秋招总结,这篇文章应该是最后一篇总结,当然也是最完整,最详细的一篇总结。秋招是我人生中一段宝贵的经历,不仅是我研究生生涯交出的一份答卷,也是未来职业生涯的开端。仅以此文,献给自己,以及各位在求职路上的,或者是已经经历过校招的朋友们。不忘初心,方得始终。

前言

在下本是跨专业渣考研的985渣硕一枚,经历研究生两年的学习积累,有幸于2019秋季招聘中拿到几个公司的研发岗offer,包括百度,阿里,腾讯,今日头条,网易,华为等。

(在秋招末期,有幸又拿到了滴滴和亚马逊的offer,那时已经11月份了,所以之前的文章里都没有提到过)

一路走来也遇到很多困难,也踩了很多坑,同时我自己也探索了很多的学习方法,总结了很多心得体会,并且,我对校园招聘也做了一些研究和相应的准备。在今年的秋季招聘结束以后,我也决定把这些东西全部都写成文字,做成专题,以便分享给更多未来将要参加校招的同学。

大学时期的迷茫与坚定

我的本科专业是电子信息工程,基本没有接触过计算机专业的课程,只学过c语言,然后在大三的时候接触过java,Android,以及前端开发。这时候我只是一个刚刚入门的菜鸟,还不知道软件开发的水有多深,抱着试一试的态度去应聘了很多公司。结果可想而知,连简历筛选都没有通过。

当年我对游戏开发很有兴趣,特别是对网易游戏情有独钟,但是当我看到网易游戏研发工程师的招聘要求时,我只能望而却步,因为它要求学历至少是985的硕士。

也因为这个契机,我在大三的暑假开始准备考研,花了一个月的时间深思熟虑之后,选择了华科作为我的目标院校。

于是,2016年的下半年,我成为了“两耳不闻窗外事,一心只读圣贤书”的考研党,回想起来那确实是玩命学习的半年时间,每天稳定泡在图书馆8个小时以上,有时候学到宿舍都能学到晚上12点,那时候感觉自己完全变了一个人似的,可能当一个人为了某个目标而努力时,真的会变得不一样。最终我顺利地考上了,令我意外的是,成绩还挺不错。

研究生时期的方向选择

对于即将读研的同学来说,一般有两件事很重要,一件事是选择导师,一件事是选择方向。

我在刚读研的时候最头疼的也是这两件事情。首先说明一下,我读的是专硕,所以实验室一般不搞科研,有部分导师会带项目,由于我不打算在实验室做项目(因为我更希望去大公司里锻炼几年),所以我当时本着想要找实习的想法选择了导师,事实证明我的选择还是很正确的,我在研二有大段时间去参加实习,让我在大厂里有足够的时间去锻炼和学习。

而选择方向这件事,我倒是折腾了好久。研一期间我做的最多的事情就是看书了,当时自己的方向还不明确,所以找了很多书来看。当别人都在专研数据挖掘和机器学习时,我还在各种方向之间摇摆不定。

我在读研之前想做游戏开发和Android开发,但我以前也学过Java Web开发。于是我在网上了解对应方向的资讯,发现游戏研发的就业面比较窄。

最后,我综合公司的岗位情况,个人兴趣,以及我之前的学习经历等因素,选定了Java开发方向。

于是,我在学校的实训项目中选择了Java Web项目,从此也真正意义上地踏上了Java的学习之路。

笨鸟先飞,勤能补拙

尽管我的入学成绩是全学院的top3,但是,我发现,作为非科班出身的我,和很多科班同学相比,还是有一定差距的。

大部分同学本科都上过计算机专业的相关课程,比如计算机网络,操作系统,数据结构等等,而我以前连听都没听过,除此之外,他们一般都会几段比较完整的项目经验,至少在Java Web方面已经算是比较熟悉了。而我在当时,只学了数据结构,另外接触过一些Java基础,有一部分项目经验,基本上就是入门水平。

于是我痛定思痛,决定好好弥补我的不足,平时一有空就去图书馆找些书来看,不论是操作系统,计算机网络,还是数据库等本科课程,我都会找一些对应的书籍来看,当时不太清楚其实有些课程其实不需要特地去补,以至于我连计算机组成原理,编译原理,软件测试等方面的书都特地找来看,现在想想也是挺逗比的。

由于我们上的课比较水,所以上课时间反而变成了我自学基础课程的大好时光了。所以我平时上课的时候都会带两三本书,一到两周内看完一本,虽然可能吸收的不是特别好,但是对当时的我来说还是有很大帮助的。

除此之外,有时候我还会偷偷去旁听有一些本科生的课程,这也是因为我在自学一些课程的时候遇到了困难,比如《操作系统》,《数据库原理》等等。于是我花时间研究了一下本科生的课表,趁着自己没课的时候赶紧去旁听课程。有时候感觉自己在课堂中显得非常突兀,尴尬地想要逃跑,但总算是坚持地听完了一门数据库的课程。

此外,我还在各种视频网站上看网课,比亦或是看中国MOOC的计算机基础课程,里面的操作系统,数据库等课程也让我印象深刻。

就这样,每天我都把自己的时间填满,愣是在研一上学期看了好几本书,当时书的版本现在有的记不清了,主要是计算机网络,操作系统,计算机组成原理,另外还有软件工程,软件测试,设计模式,等书籍。就这样,我靠着这段时间的坚持把计算机基础课程补上来了一些。

历尽艰辛,终得实习

时间来到研一下半页,这时候我刚刚结束了学校的Java Web的项目实训课程,在做这个项目期间,我发现自己暴露出了很多问题,技术实践能力不足,Java基础不扎实。这件事情也给我自己敲响了警钟,因为我计划在春招期间找一份大厂的实习,但是目前看来我的水平还远远不够。

压力之下,只有努力一条出路。于是,从那时候起,我开始了“留守“实验室的学习生活。为什么要在实验室学习,一是因为学习气氛好,二是因为平时大家也可以互相交流问题。

每天早上9点到实验室打开电脑,晚上9点背电脑回寝室。大部分时间我会花在看书上,这段时间主要看的都是Java相关的书籍,借鉴的是江南白衣大佬的“Java后端书架”,比如《深入理解JVM虚拟机》,《Java并发编程艺术》,《深入分析Java Web技术内幕》,《深入剖析Spring源码》等等。

另外一部分时间我会用来看一些技术博客,我主要是根据面经上的知识点按图索骥,找到对应讲解该知识点的文章,那时候主要还是通过搜索引擎来找文章,当然有时候看到一些重点难点也会自己写一些博客。不过这个时期并不是我大量写博客的阶段,主要还是看一些讲解面试知识点的技术博客为主。

除此之外在面试前几天我会花时间去看这家公司的面经,搞懂每一个面经上的知识点,并且记录在我的笔记上,光是面经相关的笔记我就记了100多篇,这样的学习习惯我一直坚持到了秋招,确保每个面试知识点都能被我记住,消化,直至完全理解。

慢慢的,笔记越来越多,我参加面试的公司也越来越多,于是我开始不断完善自己的简历,总结自己的面试技巧,选择合适的网申时机。从头到尾我大概花了3个月的时间在找实习上,期间大大小小参加了20多次面试,我也从一开始面试一问三不知的菜鸟,逐渐变成了面霸,到复习末期,我对Java常见面试知识点已经了然于胸,同时也越来越自信,不管面什么大厂都不慌不忙。

这样的日子持续了好几个月,所谓世上无难事,只怕有心人。到最后,实验室里每个人都拿到了心仪的实习offer。

实习路上,我明白了很多

踏出学校大门,我的实习之路才刚刚开始。8个月左右的实习时间,说长也长,说短也短。但经历过这段实习之后,我才明白了很多事情。

在猪场实习的日子里,我第一次了解大公司的开发流程,亲自参与项目代码的开发,我的导师会和我提需求,会指导我怎么做得更好。在这里的成长无疑是非常快速的,但我很快意识到我的问题所在,不熟悉部门技术栈,对很多Java Web的技术原理都不太熟悉,这段时间我意识到了自己的知识深度和广度都可能都有待提高。

不过由于家里的一些事情。我提前离职了,所以在猪场呆的时间很短,以致于我没来得及搞懂部门项目的技术架构就走了,这也让我在离开以后感觉很遗憾,所以我下定决心在下个实习单位要好好做。

离开猪场后我来到了熊厂。部门给我提的需求不算太难,大部分都是一些CURD的工作,但是这次很快就意识到了问题所在,就是我不太熟悉部门的整体技术栈,所以在需要借鉴别人代码时偶尔会看不懂。后来部门又给了新的重构需求,此时的我开始焦虑起来,是不是应该做出一些改变呢。

终于,我找到了新的目标,我要搞懂部门的项目架构,了解相关技术栈(我们部门做的是私有云),一开始,我会请教我的导师,尽量去了解项目的架构设计,除此之外,我还会利用一些时间去看其他同事负责的代码,并且通过一些文档和PPT去了解这些代码的功能和意义。结合代码和文档,再加上和同时的交流,我对部门项目的架构逐渐熟悉起来,为了更好地理解每一块代码的作用,我还为一些模块的代码写了注释。

当然,光看代码和文档还不能解决所有问题,因为这个项目的重点难点不在Web应用,而是在底层技术,这个项目中包含了两套架构,分别是一套OpenStack集群和一套docker集群。为了学习这两块内容,我先是看了很多博客,然后在平台上跑虚拟机和容器来做实践,最后又看了这方面的一些书籍,主要是《OpenStack设计与实现》,《docker技术入门实战》。

但是这还不够,虚拟化技术与Linux内核息息相关,又需要学习者对操作系统和计算网络非常熟悉,我自知这些内容我学得还不够深入,于是我花大量时间看这方面的书,当时也遇到了几本确实不错的书,分别是《深入理解计算机系统》,《计算机网络:自顶向下方法》,还有一本没来得及看完的《Linux内核设计与实现》。虽然以后不一定会作云计算方向的开发,但是学完这些东西我还是非常开心的。

到后来,我工位上的书越来越多,我对部门的技术栈也越来越熟悉,有时候我还会去听公司内各个团队组织的技术分享,有空的时候看看内网的技术课程,真正地实现了自己在技术广度上的拓展。

有时候我觉得,实习生活是会骗人的,你佩戴着和正式员工一样的工牌,和他们做着类似的事情,会让你觉得你的水平已经和他们差不多了,但事实上是,在转正之前,你和他们还差得远,所以不要停下自己前进的脚步,抓紧时间学习吧,把握好你在公司里的机会,合理利用公司给你提供的资源。

秋招前的积累与沉淀

研究生期间我有一件事情一直在坚持,那就是做笔记和写博客。

做笔记,就是记录学习中大大小小的事情,可能是面试问题,可能是一周的学习计划,也可能知识一个知识点,总归都是值得记录的东西,对我来说,就是一种积累。而对于博客,我从一开始只用于记录项目,到后来做转载,再到后来写原创,整理系列文章,则更像是一种沉淀。

但是在春招刚刚结束的这段时间,我发现一个问题,之前学过的东西忘记了很多,特别是那些理解的不够深的知识点,总是特别容易忘记。另外我发现,虽然我在笔记中记录了很多的知识点和面试题,但是往往我只看过一次,不会再去看第二次。

这也意味着,虽然记录的内容很多,但是真正消化吸收的内容很少,脑子里充斥着总是那些零碎的知识点和面试问题,对于完整的知识体系知之甚少。这些问题在春招期间也不断地暴露出来,让我思考了很久。

面对如此窘境,我想做出改变,趁着现在时间充裕,我想要为这些内容做一次减法,并且借此机会,推翻自己原有的知识体系,重建新的知识框架。简单说来,就是重新开始学习Java后端,这次我要用一种更高效的方式,避免走之前走的弯路,要用最高效,最合理的方式去复习。由于我之前已经有基础,所以我对完成这一目标有信心,相应地我也为此做出了明确且详细的学习计划。

我打算用几个关键词来形容这三个月的秋招复习。

“具体可靠的学习计划”

在三个月的时间里,我首先按照Java后端路线图安排好复习计划,每个知识点都会对应安排一段时间,比如我可能花一天时间复习“Java反射”,两天时间复习“设计模式”,一周的时间用于复习"JVM虚拟机”。我一般会在月初做好整个月的计划,然后根据进度做一些微调,但是基本上我都可以跟上进度,并且是在复习到位的前提下。

所以我觉得,对于秋招这一场苦战,指定计划尤为重要,一旦计划定下来,战略目标清晰,对应的战术制定也会变得清晰,执行力也会随之变强。

“写博客整合知识点”

至于复习方法,我主要通过看高质量博客,并且结合代码实践的方式巩固这部分知识点,比如今天学习“concurrenthashmap”,我会去找两三篇比较好的博客先看看,主要是源码解读方面的,然后我会把它们进行整合,如果有遗漏的知识点我会再进行补充,有时候我还会自己去看看JDK源码,以便更好地理解博客内容,完成知识整合之后,我就会对应地整理出一篇博客出来,发在我的个人博客上。

除此之外,当我完成了一整个专题的复习之后,我会把这些文章整理成一个专题,比如上面说的“concurrenthashmap”,实际上属于Java并发包,所以我会专门做一个博客专栏,用来完成Java并发系列的文章专题。对于每一个文章专题,我都会先理清这个专题一共有哪些内容,然后再开始整理。比如对于Java并发包,我会先写Java多线程基础的文章,再写JMM内存模型的文章,接着一步步着手写Java线程池,阻塞队列,工具类,原子类等等。这样一来这部分内容就复习完毕了,写系列文章的好处就在于,我可以从头到尾理清脉络,并且对于每一部分的知识点都做了比较好的总结。

对于博客的选择,我吸取了之前的教训,宁愿花半小时看一篇高质量文章,也不花10分钟看5篇烂文章。深度阅读的好处,就是可以让这部分内容更好地融入你脑内的知识体系,而不是像其他快餐文章一样转瞬即逝。

“做项目巩固实践能力”

由于之前在实习期间参加的项目都比较大,我接触的模块也比较单一,没有对整体项目有一个很好的把握,所以我决定趁这段时间再巩固一下我的项目实践能力,这里的能力主要是指的是对项目架构的把握能力,以及对业务开发的熟练度,当然也包括对各种常用后端技术的熟悉程度。

我花了大概一个月的时间完成了两个项目的开发,当然主要也是模仿两个开源项目做了,这两个项目都使用SpringBoot快速开发,并且用到一些常用的后端技术比如redis,云存储,以及一些常见Web框架,除此之外还涉及到了solr,爬虫等技术。虽然项目不算很难,但是我在这段时间里很快地熟悉了完整项目开发的流程,并且每天做迭代,通过Git来跟进版本,每个版本都会写清所做的内容,这也让我对项目的架构非常熟悉。

在项目之余,我也找一些常用的后端组件来跑一跑demo,以便让我对这些技术有一个直观的了解,比如面试常问的dubbo,zookeeper,消息队列等组件。这些尝试也让我在理解它们的原理时更加得心应手了。

“坚持刷题,注重方法”

算法题是秋招笔试面试中的重头戏,每个研发同学都免不了经历算法题的摧残,对我这么一个非科班同学来说,更是让人头大。正因为如此,我放弃了刷大量LeetCode题目的方法,选择了更加行之有效的刷题方式。

首先我重新刷了一遍剑指offer,并且对每道题目进行总结,尽量保证每一道题都可以记在脑子里,众所周知剑指offer中的题是面试时非常喜欢考的,所以先搞定这部分题目是最为关键的。

搞定剑指offer之后,当然还要刷LeetCode了,LeetCode题目这么多,怎么选择呢,我没有按照tag刷,也没有按照顺序刷,而是参考当时一个大佬的LeetCode刷题指南来进行刷题的,他把每个类型的题目都做了归纳,每部分只放一些比较经典的题目。所以我前后大概刷了100多道LeetCode的题目,并且在第二遍刷题复习的时候,我也对这些题目做了一份总结。

除了上面两个经典题库,我还着重刷了大厂的历年真题,这部分我主要是通过牛客网的历年真题题库来完成刷题的。说实话,真题是非常重要的,因为公司出的题目不像平时的那些算法题,可能会出得非常奇葩,所以你如果不提前适应的话会比较吃亏。完成这部分题目之后,我对算法题的复习也基本告一段落了。

当我完成所有内容的复习时,提前批已经开始了。终于要上战场了,因为战前准备比较充分,所以我对秋招还是比较乐观的,但事实上,秋招不仅是攻坚战,而且是持久战,要笑到最后,确实也不是那么容易的事情。

重建知识体系,对学过的东西做减法

前面提到我在秋招前完成了知识体系重建,那在这里我也想跟大家分享一下我当时大致的知识体系构成。就跟我前面说的一样,我选择重新再学一遍Java后端相关的技术内容,因为我知道大致的学习方向,并且有一定的基础,所以看很多文章变得更加得心应手,写文章和做总结也更加有底气了。

首先在Java基础方面,我写了20多篇原创博客,主要是对Java核心技术的解析,比如“Java反射”,“Java序列化和反序列化”,“Java异常体系”等等。

在Java集合类方面,我原创了部分文章,另外整合了一些比较好的技术文章,其中最主要的就是关于hashmap的文章,当时我整合的文章几乎没有遗漏任何一个知识点。

在Java并发编程方面,我主要参考了并发编程网以及一些优质博客的文章,先搞懂了Java并发原理,再一步步学习JUC并发包的组件,其中重点看了chm,并发工具类以及阻塞队列等JDK源码的解析文章,除此之外,我还会在IDE中跑JUC相关的emo,毕竟这方面的内容非常需要实践。

在Java网络编程方面,我先从最基础的socket入手,再讲到NIO,AIO,并且加入了几篇对Linux IO模型解析的文章,让整个知识体系更加完整(因为NIO是基于Linux Epoll实现的),接着我又加入了对Netty的探讨,以及Tomcat中对NIO的应用,可以说是把Java网络编程一些比较重要的部分都囊括进来了。为了更好理解这部分内容,我也在网上参考了很多客户端和服务端通信的demo,最后我分别用Socket,NIO,AIO以及Netty把C/S 通信的demo都写了一遍。

在JVM虚拟机方面,我则按照《深入理解JVM虚拟机》这本书的行文脉络进行文章的整理。在搞定JVM基本原理以后,我着重了解了JVM调优和实践中常遇到的问题,并且整理了常用的JVM调优工具,场景问题以及调优实践的案例,这也是因为面试中对JVM调优实践越来越重视了。

在JavaWeb方面,我从Java Web相关技术的发展入手,一步步了解了每种技术存在的意义,比如JSP,Servlet,JDBC,Spring等等,然后对每种技术进行了比较全面的了解,并且着重地看了Spring和SpringMVC的源码分析文章,另外一方面,我花了很多时间去研究Tomcat的工作原理。除此之外,JavaWeb项目中常用的maven,日志组件,甚至是单测试组件,也纳入了我的系列文章里。

在数据库和缓存方面,我主要学习了MySQL和Redis这两种最常用的数据库。对于Mysql,我从简单的sql开始了解,然后开始了解sql优化,MySQL的存储引擎和索引,事务及锁,还有更复杂的主从复制,分库分表等内容。对于Redis,我也是从简单的api入手,然后去了解每一种数据结构的底层实现原理,接着尝试去学习Redis的持久化方式,以及作为缓存常需要考虑的技术点,当然,也包括Redis的分布式锁实现,以及它的分布式集群方案。

最后一部分就是分布式相关的理论和技术了,这个也是困扰我很久的一块内容,我主要把这块内容分为两个部分,分别是分布式理论和分布式技术,理论方面,我先了解CAP,BASE等基本知识,然后开始学习一致性协议和算法,接着探讨分布式事务。对于分布式技术,涉及的东西就更多了,例如分布式session,负载均衡,分布式锁等内容,这些知识点我都会用一到两篇文章去总结,对于分布式缓存,消息队列,以及分布式服务等内容,我会花比较多的时间去全面学习,然后总结出一个系列的文章出来。当然,对于这些技术的学习主要还是停留在理论方面,在自己的项目中能用到的比较少。

至此,我的知识体系基本构建完成,这也是我在秋招中能够成功闯过那么多面试的原因。

秋招之路,砥砺前行

不管前期做了多少准备,到秋招的时候也不能掉以轻心,从七月底第一次面试到9月基本佛系,中间经历了大大小小的面试。

在完成知识体系重建以后,我把重点转向了另外几件事,一是完善和熟悉我的简历,以便在面试中能够比较好地发挥,二是持续刷题,保持对算法题和笔试真题的手感和熟练度,三则是看面经查缺补漏,我一直认为看面经是很重要的一项复习内容。

就这样,我一边继续复习,以便开始了一场接一场的面试接力。

起初,我面了几家小公司练手,接着阿里的提前批接踵而至,我战战兢兢地参加了阿里中间件部门的面试,面难难度还算适中,一共四轮面试,当时我的表现也还不错,问题基本都答上来了。面完不到一周以后我就收到了通过的消息,当时还有点懵。没想到第一个offer这么快就来了。

这段时间内,蚂蚁金服的两个部门也给了我面试机会,我都参加了它们的面试,并且顺利地拿到了其中一个部门的offer。由于我对蚂蚁这边的业务比较感兴趣,最终选择了蚂蚁金服的offer。

阿里提前批的胜利确实是意外之喜,但也大大地鼓舞了我,于是我又参加了百度和腾讯的提前批面试,由于百度的提前批不走流程,一共有四个部门面试了我,每个部门都有2到3轮面试,总计约为12次面试,到后来我已经快晕了,看到百度的电话就害怕,由于面试次数太多,有时候发挥确实也不是很好,我也没有特别在意,只当是在锻炼自己了。

百度的面试难度每个部门不一样,但是每次面试必写算法题,一写算法题,时间至少就是一个小时以上,每次面试完都有一种身体被掏空的感觉。

经历了百度面试的摧残以后,我手写算法的速度也变快了,很多坑也被我填上了。接下来面对腾讯的面试,我也是既激动又担心,腾讯的面试难度比较大,对于操作系统和网络的知识喜欢深挖,问的东西也很有深度,面完前三面以后,第四面拖了3周才进行。当时三面面试官对我的评价比较好,也让我信心爆棚了好久。

在等待腾讯终面的期间,我参加了今日头条的面试,当时有幸拿到了一个白金码,免去笔试,事实证明白金码作用真的很大。头条的面试难度和腾讯差不多,三轮面试,同样需要写各种算法,由于是视频面试,我可以清楚地看到,头条的面试官真的非常高冷啊。面完头条我的第一感觉就是应该挂了吧。没想到最后还是给了offer。

结束这几家大厂的面试之后,我觉得我的秋招已经接近尾声了,不过由于之前投的比较多,所以我又面了几家大公司,如网易,华为,快手等。到9月上旬的时候,我接连收到了bat和头条,网易的意向书,阿里最早,腾讯最晚,每收到一封意向书我都很开心,没想到最后我真的可以集齐bat等大厂的offer。

9月以后,除了偶尔和同学做几场大厂的笔试,我基本就佛系了。直到后来一些外企例如亚马逊,大摩开始笔试面试,我才又重新回到了状态。

截止目前,我基本上把该拒绝的offer都拒绝了,综合各方面因素的考虑,最后应该会签阿里,原因是部门是我自己喜欢的,同时给的评级也比较高。虽然腾讯也给了sp,但是最后还是忍痛割爱啦。至于百度和头条,给的offer并不是很令人满意,所以就没有考虑了。

至此,我的秋招之旅总算圆满结束。


Java后端技术专栏

对于校园招聘来说,最重要的还是基础知识。下面的博客专栏出自我的技术博客 https://blog.csdn.net/a724888

这些专栏中有一些文章是我自己原创的,也有一些文章是转载自技术大牛的,基本都是是我在学习Java后端的两年时间内陆续完成的。

总的来说算是比较全面了,做后端方向的同学可以参考一下。

1.深入浅出Java核心技术

本专栏主要介绍Java基础,并且会结合实现原理以及具体实例来讲解。同时还介绍了Java集合类,设计模式以及Java8的相关知识。

2.深入理解JVM虚拟机

带你走进JVM的世界,整合高质量文章以阐述虚拟机的原理及相关技术,让开发者更好地了解Java的底层运行原理以及相应的调优方法。

3.Java并发指南

本专栏主要介绍Java并发编程相关的基本原理以及进阶知识。主要包括Java多线程基础,Java并发编程基本原理以及JUC并发包的使用和源码解析。

4.Java网络编程与NIO

Java网络编程一直是很重要的一部分内容,其中涉及了socket的使用,以及Java网络编程的IO模型,譬如BIO,NIO,AIO,当然也包括Linux的网络编程模型。

了解这部分知识对于理解网络编程有很多帮助。另外还补充了两个涉及NIO的重要技术:Tomcat和Netty。

5.JavaWeb技术世界

从这里开始打开去往JavaWeb世界的大门。什么是J2EE,什么是JavaWeb,以及这个生态中常用的一些技术:Maven,Spring,Tomcat,Junit,log4j等等。

我们不仅要了解怎么使用它们,更要去了解它们为什么出现,其中一些技术的实现原理是什么。

6.Spring与SpringMVC源码解析

本专栏主要讲解Spring和SpringMVC的实现原理。

7.Spring是最流行的Java框架之一。

本专栏文章主要包括IOC的实现原理分析,AOP的实现原理分析,事务的实现源码分析等,当然也有SpringMVC的源码解析文章。

8.重新学习MySQL与Redis

本专栏介绍MySQL的基本知识,比如基本架构,存储引擎,索引原理,主从复制,事务等内容。当然也会讲解一些和sql语句优化有关的知识。

同时本专栏里也介绍了Redis的基本实现原理,包括数据结构,主从复制,集群方案,分布式锁等实现。

9.分布式系统理论与实践

本专栏介绍分布式的基本理论和相关技术,比如CAP和BASE理论,一致性算法,以及ZooKeeper这类的分布式协调服务。

在分布式实践方面,我们会讲到负载均衡,缓存,分布式事务,分布式锁,以及Dubbo这样的微服务,也包括消息队列,数据库中间件等等。

10.后端开技术杂谈

本专栏涵盖了大后端的众多技术文章,当你在Java后端方面有一定基础以后,再多了解一些相关技术总是有好处的。

除了Java后端的文章以外,还会涉及Hadoop生态,云计算技术,搜索引擎,甚至包括一些数据挖掘和AI的文章。

总的来说选取了一些不错的基础类文章,能让你对大后端有一个更直观的认识。


面经分享

具体的面经都比较长,这里大概介绍一下面试的情况,具体的面经请大家关注我的公众号并回复“面经”即可查看。

1 阿里面经

阿里中间件研发面经

蚂蚁金服研发面经

岗位是研发工程师,直接找蚂蚁金服的大佬进行内推。

我参与了阿里巴巴中间件部门的提前批面试,一共经历了四次面试,拿到了口头offer。

然后我也参加了蚂蚁金服中间件部门的面试,经历了三次面试,但是没有走流程,所以面试中止了。

最后我走的是蚂蚁金服财富事业群的流程,经历了四次面试,包括一次交叉面,最终拿到了蚂蚁金服的意向书,评级为A。

阿里的面试体验还是比较好的,至少不要求手写算法,但是非常注重Java基础,中间件部门还会特别安排Java基础笔试。

2 腾讯面经

腾讯研发面经

岗位是后台开发工程师,我没有选择意向事业群。

SNG的部门捞了我的简历,开始了面试,他们的技术栈主要是Java,所以比较有的聊。

一共经历了四次技术面试和一次HR面试,目前正在等待结果。

腾讯的面试一如既往地注重考查网络和操作系统,并且喜欢问Linux底层的一些知识,在这方面我还是有很多不足的。

3 百度面经

百度研发面经

百度研发面经整合版

岗位是研发工程师岗位,部门包括百度智能云的三个分部门以及大搜索部门。

百度的提前批面试不走流程,所以可以同时面试好多个部门,所以我参加百度面试的次数大概有12次左右,最终应该是拿了两个部门的offer。

百度的面试风格非常统一,每次面试基本都要到电脑上写算法,所以那段时间写算法写的头皮发麻。

4 网易面经

网易研发面经

面试部门是网易云音乐,岗位是Java开发工程师。

网易是唯一一家我去外地面试的公司,也是我最早去实习的老东家。

一共三轮面试,耗时一个下午。

网易的面试比我想象中的要难,面试官会问的问题都比较深,并且会让你写一些结合实践的代码。

5 头条面经

今日头条研发面经

岗位是后台研发工程师,地点选择了上海。

我参加的是字节跳动的内推面试,当时找了一个牛友要到了白金码,再次感谢这位头条大佬。

然后就开始了一下午的视频面试,一共三轮技术面试,每一轮都要写代码,问问题的风格有点像腾讯,也喜欢问一些底层知识,让我有点懵逼。

后记

秋招结束以后,我就把主要精力花在做公众号【程序员江湖】上了。当然,剩下要处理的事情还有很多,毕业论文,毕业旅行,还有工作前的知识储备等等。果然,人的一生需要不断的修行,刚刚闯过了一关又马上要迎接下一轮挑战,你不能停下脚步,毕竟大家都在往前走。

希望还在求职路上的各位少侠好好加油,在未来也能够顺利地拿到自己想要的offer!

微信公众号【程序员江湖】

作者黄小斜,斜杠青年,某985硕士,阿里研发工程师,于2018 年秋招拿到 BAT 头条、网易、滴滴等 8 个大厂 offer

个人擅长领域 :自学编程、技术校园招聘、软件工程考研(关注公众号后回复”资料“即可领取 3T 免费技术学习资源)

查看原文

上善若水 关注了标签 · 2018-12-14

linux

Linux是一种自由和开放源代码的类Unix计算机操作系统。目前存在着许多不同的Linux,但它们全都使用了Linux内核。Linux可安装在各种各样的计算机硬件设备,从手机、平板电脑、路由器和视频游戏控制台,到台式计算机,大型机和超级计算机。

Linux家族家谱图,很全很强大!! 图中可以清楚的看出各个Linux发行版的血缘关系。无水印原图:http://url.cn/5ONhQb

关注 63652

上善若水 关注了标签 · 2018-12-14

postgresql

PostgreSQL 是自由的对象-关系型数据库服务器(数据库管理系统),在灵活的 BSD 风格许可证下发行。

关注 529

上善若水 关注了标签 · 2018-12-14

memcached

Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载。它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提供动态、数据库驱动网站的速度。Memcached基于一个存储键/值对的hashmap。其守护进程(daemon )是用C写的,但是客户端可以用任何语言来编写,并通过memcached协议与守护进程通信。

关注 966

认证与成就

  • 获得 0 次点赞
  • 获得 1 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 1 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2018-12-14
个人主页被 117 人浏览