Commit 3e329016 by Carsten Brandt

Merge remote-tracking branch 'origin/master' into redis

* origin/master: (104 commits) Fixed typo in query builder and reverted changes to DbCache.php added note about enabling APC cache for CLI more assertions for cache test fixed dbcache multiget fixed Html test under Windows (line endings) fixed DB quoting typo create a webapp in test bootstrap updated expected exception message Added a basic app. initial implementation of asset command. script WIP script command WIP Fixed bug in yiic.php. Refactoring AssetConverter. adjusted default app layout refactoring and documentation for asset/script management. Finished asset converter. removed unnecessary code, added comment about displaying errors better handling of errors during rendering an error safer exception rendering turn asset manager into a getter. ...
parents 34b66244 15b9f97c
<?php
defined('YII_DEBUG') or define('YII_DEBUG', true);
require(__DIR__ . '/../framework/yii.php');
$config = require(__DIR__ . '/protected/config/main.php');
$application = new yii\web\Application($config);
$application->run();
<?php
return array(
'id' => 'hello',
'basePath' => dirname(__DIR__),
'components' => array(
'cache' => array(
'class' => 'yii\caching\FileCache',
),
'user' => array(
'class' => 'yii\web\User',
'identityClass' => 'app\models\User',
)
),
);
\ No newline at end of file
<?php
class SiteController extends \yii\web\Controller
{
public function actionIndex()
{
echo $this->render('index');
}
public function actionLogin()
{
$user = app\models\User::findIdentity(100);
Yii::$app->getUser()->login($user);
Yii::$app->getResponse()->redirect(array('site/index'));
}
public function actionLogout()
{
Yii::$app->getUser()->logout();
Yii::$app->getResponse()->redirect(array('site/index'));
}
}
\ No newline at end of file
<?php
namespace app\models;
class User extends \yii\base\Object implements \yii\web\Identity
{
public $id;
public $name;
public $authKey;
private static $users = array(
'100' => array(
'id' => '100',
'authKey' => 'test100key',
'name' => 'admin',
),
'101' => array(
'id' => '101',
'authKey' => 'test101key',
'name' => 'demo',
),
);
public static function findIdentity($id)
{
return isset(self::$users[$id]) ? new self(self::$users[$id]) : null;
}
public function getId()
{
return $this->id;
}
public function getAuthKey()
{
return $this->authKey;
}
public function validateAuthKey($authKey)
{
return $this->authKey === $authKey;
}
}
\ No newline at end of file
<?php
/**
* @var $this \yii\base\View
* @var $content string
*/
use yii\helpers\Html;
?>
<!DOCTYPE html>
<html>
<?php $this->beginPage(); ?>
<head>
<title><?php echo Html::encode($this->title); ?></title>
<?php $this->head(); ?>
</head>
<body>
<h1>Welcome</h1>
<?php $this->beginBody(); ?>
<?php echo $content; ?>
<?php $this->endBody(); ?>
</body>
<?php $this->endPage(); ?>
</html>
<?php
/** @var $this \yii\base\View */
use yii\helpers\Html;
$this->title = 'Hello World';
$user = Yii::$app->getUser();
if ($user->isGuest) {
echo Html::a('login', array('login'));
} else {
echo "You are logged in as " . $user->identity->name . "<br/>";
echo Html::a('logout', array('logout'));
}
?>
deny from all
#!/usr/bin/env php
<?php
/**
* build script file.
*
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
// fcgi doesn't have STDIN defined by default
defined('STDIN') or define('STDIN', fopen('php://stdin', 'r'));
require(__DIR__ . '/../framework/yii.php');
$id = 'yiic-build';
$basePath = __DIR__;
$application = new yii\console\Application($id, $basePath);
$application->run();
@echo off
rem -------------------------------------------------------------
rem build script for Windows.
rem
rem This is the bootstrap script for running build on Windows.
rem
rem @author Qiang Xue <qiang.xue@gmail.com>
rem @link http://www.yiiframework.com/
rem @copyright 2008 Yii Software LLC
rem @license http://www.yiiframework.com/license/
rem @version $Id$
rem -------------------------------------------------------------
@setlocal
set BUILD_PATH=%~dp0
if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe
%PHP_COMMAND% "%BUILD_PATH%build" %*
@endlocal
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* Phing build file for Yii.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright 2008-2009 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
-->
<project name="yii" basedir="." default="help">
<!-- task definitions -->
<taskdef name="yii-init-build" classname="YiiInitTask" classpath="tasks" />
<!--
<taskdef name="yii-pear" classname="YiiPearTask" classpath="tasks"/>
-->
<!-- init yii.version, yii.revision and yii.winbuild -->
<yii-init-build />
<!-- these are required external commands -->
<property name="php" value="php" /> <!-- PHP parser -->
<property name="hhc" value="hhc" /> <!-- compile phpdoc into CHM -->
<property name="pdflatex" value="pdflatex" /> <!-- generates PDF from LaTex -->
<property name="pkgname" value="${phing.project.name}-${yii.version}.${yii.revision}"/>
<property name="docname" value="${phing.project.name}-docs-${yii.version}.${yii.revision}"/>
<property name="pearname" value="${phing.project.name}-${yii.release}.tgz" />
<!-- directory definitions -->
<property name="build.base.dir" value="release"/>
<property name="build.dist.dir" value="${build.base.dir}/dist"/>
<property name="build.src.dir" value="${build.base.dir}/${pkgname}"/>
<property name="build.pear.src.dir" value="${build.src.dir}/framework" />
<property name="build.doc.dir" value="${build.base.dir}/${docname}"/>
<property name="build.web.dir" value="${build.base.dir}/web"/>
<tstamp>
<format property="DATE" pattern="%b %e %Y" />
</tstamp>
<if>
<equals arg1="${yii.winbuild}" arg2="true"/>
<then>
<property name="build" value="build"/>
</then>
<else>
<property name="build" value="php build"/>
</else>
</if>
<!-- source files in the framework -->
<fileset dir=".." id="framework">
<exclude name="**/.gitignore"/>
<exclude name="**/*.bak"/>
<exclude name="**/*~"/>
<include name="framework/**/*"/>
<include name="requirements/**/*"/>
<include name="demos/**/*"/>
<include name="CHANGELOG"/>
<include name="UPGRADE"/>
<include name="LICENSE"/>
<include name="README"/>
</fileset>
<!-- doc files -->
<fileset dir="../docs" id="docs">
<exclude name="**/.gitignore"/>
<exclude name="**/*.bak"/>
<exclude name="**/*~"/>
<include name="guide/**/*"/>
<include name="blog/**/*"/>
</fileset>
<fileset dir="../docs/guide" id="docs-guide">
<exclude name="**/.gitignore"/>
<exclude name="**/*.bak"/>
<exclude name="**/*~"/>
<include name="**/*"/>
</fileset>
<fileset dir="../docs/blog" id="docs-blog">
<exclude name="**/.gitignore"/>
<exclude name="**/*.bak"/>
<exclude name="**/*~"/>
<include name="**/*"/>
</fileset>
<fileset dir="." id="writables">
<include name="${build.src.dir}/**/runtime" />
<include name="${build.src.dir}/**/assets" />
<include name="${build.src.dir}/demos/**/data" />
</fileset>
<fileset dir="." id="executables">
<include name="${build.src.dir}/**/yiic" />
</fileset>
<target name="src" depends="sync">
<echo>Building package ${pkgname}...</echo>
<echo>Copying files to build directory...</echo>
<copy todir="${build.src.dir}">
<fileset refid="framework"/>
</copy>
<echo>Changing file permissions...</echo>
<chmod mode="0777">
<fileset refid="writables" />
</chmod>
<chmod mode="0755">
<fileset refid="executables" />
</chmod>
<echo>Generating source release file...</echo>
<mkdir dir="${build.dist.dir}" />
<if>
<equals arg1="${yii.winbuild}" arg2="true"/>
<then>
<tar destfile="${build.dist.dir}/${pkgname}.tar.gz" compression="gzip">
<fileset dir="${build.base.dir}">
<include name="${pkgname}/**/*"/>
</fileset>
</tar>
</then>
<else>
<exec command="tar czpf ${pkgname}.tar.gz ${pkgname}" dir="${build.base.dir}"/>
<move file="${build.base.dir}/${pkgname}.tar.gz" todir="${build.dist.dir}" />
</else>
</if>
<zip destfile="${build.dist.dir}/${pkgname}.zip">
<fileset dir="${build.base.dir}">
<include name="${pkgname}/**/*"/>
</fileset>
</zip>
</target>
<target name="doc" depends="sync">
<echo>Building documentation...</echo>
<echo>Building Guide PDF...</echo>
<exec command="${build} guideLatex" dir="." passthru="true" />
<exec command="${pdflatex} guide.tex -interaction=nonstopmode -max-print-line=120" dir="commands/guide" passthru="true"/>
<exec command="${pdflatex} guide.tex -interaction=nonstopmode -max-print-line=120" dir="commands/guide" passthru="true"/>
<exec command="${pdflatex} guide.tex -interaction=nonstopmode -max-print-line=120" dir="commands/guide" passthru="true"/>
<move file="commands/guide/guide.pdf" tofile="${build.doc.dir}/yii-guide-${yii.version}.pdf" />
<echo>Building Blog PDF...</echo>
<exec command="${build} blogLatex" dir="." passthru="true" />
<exec command="${pdflatex} blog.tex -interaction=nonstopmode -max-print-line=120" dir="commands/blog" passthru="true"/>
<exec command="${pdflatex} blog.tex -interaction=nonstopmode -max-print-line=120" dir="commands/blog" passthru="true"/>
<exec command="${pdflatex} blog.tex -interaction=nonstopmode -max-print-line=120" dir="commands/blog" passthru="true"/>
<move file="commands/blog/blog.pdf" tofile="${build.doc.dir}/yii-blog-${yii.version}.pdf" />
<echo>Building API...</echo>
<exec command="${build} api ${build.doc.dir}" dir="." passthru="true" />
<!--
<echo>Building API CHM...</echo>
<exec command="${hhc} ${build.doc.dir}/api/manual.hhp" />
<move file="${build.doc.dir}/api/manual.chm" tofile="${build.doc.dir}/yii-api-${yii.version}.chm" />
<delete>
<fileset dir="${build.doc.dir}/api">
<include name="manual.*" />
</fileset>
</delete>
-->
<echo>Generating doc release file...</echo>
<mkdir dir="${build.dist.dir}" />
<tar destfile="${build.dist.dir}/${docname}.tar.gz" compression="gzip">
<fileset dir="${build.base.dir}">
<include name="${docname}/**/*"/>
</fileset>
</tar>
<zip destfile="${build.dist.dir}/${docname}.zip">
<fileset dir="${build.base.dir}">
<include name="${docname}/**/*"/>
</fileset>
</zip>
</target>
<target name="web" depends="sync">
<echo>Building online API...</echo>
<mkdir dir="${build.web.dir}/common/data/${yii.version}" />
<exec command="${build} api ${build.web.dir}/common/data/${yii.version} online" dir="." passthru="true" />
<echo>Copying tutorials...</echo>
<copy todir="${build.web.dir}/common/data/${yii.version}/tutorials/guide">
<fileset refid="docs-guide"/>
</copy>
<copy todir="${build.web.dir}/common/data/${yii.version}/tutorials/blog">
<fileset refid="docs-blog"/>
</copy>
<echo>Copying release text files...</echo>
<mkdir dir="${build.web.dir}/frontend/www/files" />
<copy file="../CHANGELOG" tofile="${build.web.dir}/frontend/www/files/CHANGELOG-${yii.version}.txt" />
<copy file="../UPGRADE" tofile="${build.web.dir}/frontend/www/files/UPGRADE-${yii.version}.txt" />
<echo>
Finished building Web files.
Please update yiisite/common/data/versions.php file with the following code:
'1.1'=>array(
'version'=>'${yii.version}',
'revision'=>'${yii.revision}',
'date'=>'${yii.date}',
'latest'=>true,
),
</echo>
</target>
<target name="sync">
<echo>Synchronizing code changes for ${pkgname}...</echo>
<echo>Building autoload map...</echo>
<exec command="${build} autoload" dir="." passthru="true"/>
<echo>Building yiilite.php...</echo>
<exec command="${build} lite" dir="." passthru="true"/>
</target>
<target name="message">
<echo>Extracting i18n messages...</echo>
<exec command="${build} message ../framework/messages/config.php" dir="." passthru="true"/>
</target>
<!--
<target name="pear" depends="clean,build">
<echo>Generating pear package for ${phing.project.name}-${yii.release}</echo>
<mkdir dir="${build.dist.dir}" />
<yii-pear pkgdir="${build.pear.src.dir}"
channel="pear.php.net"
version="${yii.release}"
state="stable"
category="framework"
package="${phing.project.name}"
summary="Yii PHP Framework"
pkgdescription="Yii PHP Framework: Best for Web 2.0 Development"
notes="http://www.yiiframework.com/files/CHANGELOG-${yii.release}.txt"
license="BSD"
/>
<exec command="pear package" dir="${build.pear.src.dir}" passthru="true" />
<move file="${build.pear.src.dir}/${pearname}" tofile="${build.dist.dir}/${pearname}" />
</target>
-->
<target name="clean">
<echo>Cleaning up the build...</echo>
<delete dir="${build.base.dir}"/>
</target>
<target name="help">
<echo>
Welcome to use Yii build script!
--------------------------------
You may use the following command format to build a target:
phing &lt;target name&gt;
where &lt;target name&gt; can be one of the following:
- sync : synchronize yiilite.php and YiiBase.php
- message : extract i18n messages of the framework
- src : build source release
- doc : build documentation release (Windows only)
- clean : clean up the build
</echo>
</target>
</project>
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
use yii\console\Controller;
use yii\helpers\FileHelper;
/**
* http://www.unicode.org/cldr/charts/supplemental/language_plural_rules.html
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class LocaleController extends Controller
{
public $defaultAction = 'plural';
/**
* Generates the plural rules data.
*
* This command will parse the plural rule XML file from CLDR and convert them
* into appropriate PHP representation to support Yii message translation feature.
* @param string $xmlFile the original plural rule XML file (from CLDR). This file may be found in
* http://www.unicode.org/Public/cldr/latest/core.zip
* Extract the zip file and locate the file "common/supplemental/plurals.xml".
* @throws Exception
*/
public function actionPlural($xmlFile)
{
if (!is_file($xmlFile)) {
throw new Exception("The source plural rule file does not exist: $xmlFile");
}
$xml = simplexml_load_file($xmlFile);
$allRules = array();
$patterns = array(
'/n in 0..1/' => '(n==0||n==1)',
'/\s+is\s+not\s+/i' => '!=', //is not
'/\s+is\s+/i' => '==', //is
'/n\s+mod\s+(\d+)/i' => 'fmod(n,$1)', //mod (CLDR's "mod" is "fmod()", not "%")
'/^(.*?)\s+not\s+in\s+(\d+)\.\.(\d+)/i' => '!in_array($1,range($2,$3))', //not in
'/^(.*?)\s+in\s+(\d+)\.\.(\d+)/i' => 'in_array($1,range($2,$3))', //in
'/^(.*?)\s+not\s+within\s+(\d+)\.\.(\d+)/i' => '($1<$2||$1>$3)', //not within
'/^(.*?)\s+within\s+(\d+)\.\.(\d+)/i' => '($1>=$2&&$1<=$3)', //within
);
foreach ($xml->plurals->pluralRules as $node) {
$attributes = $node->attributes();
$locales = explode(' ', $attributes['locales']);
$rules = array();
if (!empty($node->pluralRule)) {
foreach ($node->pluralRule as $rule) {
$expr_or = preg_split('/\s+or\s+/i', $rule);
foreach ($expr_or as $key_or => $val_or) {
$expr_and = preg_split('/\s+and\s+/i', $val_or);
$expr_and = preg_replace(array_keys($patterns), array_values($patterns), $expr_and);
$expr_or[$key_or] = implode('&&', $expr_and);
}
$expr = preg_replace('/\\bn\\b/', '$n', implode('||', $expr_or));
$rules[] = preg_replace_callback('/range\((\d+),(\d+)\)/', function ($matches) {
if ($matches[2] - $matches[1] <= 5) {
return 'array(' . implode(',', range($matches[1], $matches[2])) . ')';
} else {
return $matches[0];
}
}, $expr);
}
foreach ($locales as $locale) {
$allRules[$locale] = $rules;
}
}
}
// hard fix for "br": the rule is too complex
$allRules['br'] = array(
0 => 'fmod($n,10)==1&&!in_array(fmod($n,100),array(11,71,91))',
1 => 'fmod($n,10)==2&&!in_array(fmod($n,100),array(12,72,92))',
2 => 'in_array(fmod($n,10),array(3,4,9))&&!in_array(fmod($n,100),array_merge(range(10,19),range(70,79),range(90,99)))',
3 => 'fmod($n,1000000)==0&&$n!=0',
);
if (preg_match('/\d+/', $xml->version['number'], $matches)) {
$revision = $matches[0];
} else {
$revision = -1;
}
echo "<?php\n";
echo <<<EOD
/**
* Plural rules.
*
* This file is automatically generated by the "yiic locale/plural" command under the "build" folder.
* Do not modify it directly.
*
* The original plural rule data used for generating this file has the following copyright terms:
*
* Copyright © 1991-2007 Unicode, Inc. All rights reserved.
* Distributed under the Terms of Use in http://www.unicode.org/copyright.html.
*
* @revision $revision (of the original plural file)
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
EOD;
echo "\nreturn " . var_export($allRules, true) . ';';
}
}
Component is the base class that implements the *property*, *event* and *behavior* features.
Component provides the *event* and *behavior* features, in addition to the *property* feature which is implemented in
its parent class [[Object]].
......@@ -7,20 +9,20 @@ is triggered (i.e. comment will be added), our custom code will be executed.
An event is identified by a name that should be unique within the class it is defined at. Event names are *case-sensitive*.
One or multiple PHP callbacks, called *event handlers*, could be attached to an event. You can call [[trigger()]] to
One or multiple PHP callbacks, called *event handlers*, can be attached to an event. You can call [[trigger()]] to
raise an event. When an event is raised, the event handlers will be invoked automatically in the order they were
attached.
To attach an event handler to an event, call [[on()]]:
~~~
$comment->on('add', function($event) {
$post->on('update', function($event) {
// send email notification
});
~~~
In the above, we attach an anonymous function to the "add" event of the comment.
Valid event handlers include:
In the above, an anonymous function is attached to the "update" event of the post. You may attach
the following types of event handlers:
- anonymous function: `function($event) { ... }`
- object method: `array($object, 'handleAdd')`
......@@ -35,8 +37,8 @@ function foo($event)
where `$event` is an [[Event]] object which includes parameters associated with the event.
You can also attach an event handler to an event when configuring a component with a configuration array. The syntax is
like the following:
You can also attach a handler to an event when configuring a component with a configuration array.
The syntax is like the following:
~~~
array(
......@@ -46,15 +48,13 @@ array(
where `on add` stands for attaching an event to the `add` event.
You can call [[getEventHandlers()]] to retrieve all event handlers that are attached to a specified event. Because this
method returns a [[Vector]] object, we can manipulate this object to attach/detach event handlers, or adjust their
relative orders.
Sometimes, you may want to associate extra data with an event handler when you attach it to an event
and then access it when the handler is invoked. You may do so by
~~~
$handlers = $comment->getEventHandlers('add');
$handlers->insertAt(0, $callback); // attach a handler as the first one
$handlers[] = $callback; // attach a handler as the last one
unset($handlers[0]); // detach the first handler
$post->on('update', function($event) {
// the data can be accessed via $event->data
}, $data);
~~~
......
Object is the base class that implements the *property* feature.
A property is defined by a getter method (e.g. `getLabel`), and/or a setter method (e.g. `setLabel`). For example,
the following getter and setter methods define a property named `label`:
......@@ -30,4 +32,30 @@ $object->label = 'abc';
If a property has only a getter method and has no setter method, it is considered as *read-only*. In this case, trying
to modify the property value will cause an exception.
One can call [[hasProperty]], [[canGetProperty]] and/or [[canSetProperty]] to check the existence of a property.
One can call [[hasProperty()]], [[canGetProperty()]] and/or [[canSetProperty()]] to check the existence of a property.
Besides the property feature, Object also introduces an important object initialization life cycle. In particular,
creating an new instance of Object or its derived class will involve the following life cycles sequentially:
1. the class constructor is invoked;
2. object properties are initialized according to the given configuration;
3. the `init()` method is invoked.
In the above, both Step 2 and 3 occur at the end of the class constructor. It is recommended that
you perform object initialization in the `init()` method because at that stage, the object configuration
is already applied.
In order to ensure the above life cycles, if a child class of Object needs to override the constructor,
it should be done like the following:
~~~
public function __construct($param1, $param2, ..., $config = array())
{
...
parent::__construct($config);
}
~~~
That is, a `$config` parameter (defaults to `array()`) should be declared as the last parameter
of the constructor, and the parent implementation should be called at the end of the constructor.
Yii2 code standard
==================
This code standard is used for all the Yii2 core classes and can be applied to
your application in order to achieve consistency among your team. Also it will
help in case you want to opensource code.
PHP file formatting
-------------------
### General
- Do not end file with `?>` if it contains PHP code only.
- Do not use `<?`. Use `<?php` instead.
- Files should be encoded in UTF-8.
- Any file that contains PHP code should end with the extension `.php`.
- Do not add trailing spaces to the end of the lines.
#### Indentation
All code must be indented with tabs. That includes both PHP and JavaScript code.
#### Maximum Line Length
We're not strictly limiting maximum line length but sticking to 80 characters
where possible.
### PHP types
All PHP types and values should be used lowercase. That includes `true`, `false`,
`null` and `array`.
### Strings
- If string doesn't contain variables or single quotes, use single quotes.
~~~
$str = 'Like this.';
~~~
- If string contains single quotes you can use double quotes to avoid extra escaping.
- You can use the following forms of variable substitution:
~~~
$str1 = "Hello $username!";
$str2 = "Hello {$username}!";
~~~
The following is not permitted:
~~~
$str3 = "Hello ${username}!";
~~~
### String concatenation
Add spaces around dot when concatenating strings:
~~~
$name = 'Yii' . ' Framework';
~~~
When string is long format is the following:
~~~
$sql = "SELECT *"
. "FROM `post` "
. "WHERE `id` = 121 ";
~~~
### Numerically indexed arrays
- Do not use negative numbers as array indexes.
Use the following formatting when declaring array:
~~~
$arr = array(3, 14, 15, 'Yii', 'Framework');
~~~
If there are too many elements for a single line:
~~~
$arr = array(
3, 14, 15,
92, 6, $test,
'Yii', 'Framework',
);
~~~
### Associative arrays
Use the following format for associative arrays:
~~~
$config = array(
'name' => 'Yii',
'options' => array(
'usePHP' => true,
),
);
~~~
### Classes
- Classes should be named using `CamelCase`.
- The brace should always be written on the line underneath the class name.
- Every class must have a documentation block that conforms to the PHPDoc.
- All code in a class must be indented with a single tab.
- There should be only one class in a single PHP file.
- All classes should be namespaced.
- Class name should match file name. Class namespace should match directory structure.
~~~
/**
* Documentation
*/
class MyClass extends \yii\Object implements MyInterface
{
// code
}
~~~
### Class members and variables
- When declaring public class members specify `public` keyword explicitly.
- Variables should be declared at the top of the class before any method declarations.
- Private and protected variables should be named like `$_varName`.
- Public class members and standalone variables should be named using `$camelCase`
with first letter lowercase.
- Use descriptive names. Variables such as `$i` and `$j` are better not to be used.
### Constants
Both class level constants and global constants should be named in uppercase. Words
are separated by underscore.
~~~
class User {
const STATUS_ACTIVE = 1;
const STATUS_BANNED = 2;
}
~~~
It's preferable to define class level constants rather than global ones.
### Functions and methods
- Functions and methods should be named using `camelCase` with first letter lowercase.
- Name should be descriptive by itself indicating the purpose of the function.
- Class methods should always declare visibility using `private`, `protected` and
`public` modifiers. `var` is not allowed.
- Opening brace of a function should be on the line after the function declaration.
~~~
/**
* Documentation
*/
class Foo
{
/**
* Documentation
*/
public function bar()
{
// code
return $value;
}
}
~~~
Use type hinting where possible:
~~~
public function __construct(CDbConnection $connection)
{
$this->connection = $connection;
}
~~~
### Function and method calls
~~~
doIt(2, 3);
doIt(array(
'a' => 'b',
));
doIt('a', array(
'a' => 'b',
));
~~~
### Control statements
- Control statement condition must have single space before and after parenthesis.
- Operators inside of parenthesis should be separated by spaces.
- Opening brace is on the same line.
- Closing brace is on a new line.
- Always use braces for single line statements.
~~~
if ($event === null) {
return new Event();
} elseif ($event instanceof CoolEvent) {
return $event->instance();
} else {
return null;
}
// the following is NOT allowed:
if(!$model)
throw new Exception('test');
~~~
### Switch
Use the following formatting for switch:
~~~
switch ($this->phpType) {
case 'string':
$a = (string)$value;
break;
case 'integer':
case 'int':
$a = (integer)$value;
break;
case 'boolean':
$a = (boolean)$value;
break;
default:
$a = null;
}
~~~
### Code documentation
- Refer ot [phpDoc](http://phpdoc.org/) for documentation syntax.
- Code without documentation is not allowed.
- All class files must contain a "file-level" docblock at the top of each file
and a "class-level" docblock immediately above each class.
- There is no need to use `@return` if method does return nothing.
#### File
~~~
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
~~~
#### Class
~~~
/**
* Component is the base class that provides the *property*, *event* and *behavior* features.
*
* @include @yii/docs/base-Component.md
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Component extends \yii\base\Object
~~~
#### Function / method
~~~
/**
* Returns the list of attached event handlers for an event.
* You may manipulate the returned [[Vector]] object by adding or removing handlers.
* For example,
*
* ~~~
* $component->getEventHandlers($eventName)->insertAt(0, $eventHandler);
* ~~~
*
* @param string $name the event name
* @return Vector list of attached event handlers for the event
* @throws Exception if the event is not defined
*/
public function getEventHandlers($name)
{
if (!isset($this->_e[$name])) {
$this->_e[$name] = new Vector;
}
$this->ensureBehaviors();
return $this->_e[$name];
}
~~~
#### Comments
- One-line comments should be started with `//` and not `#`.
- One-line comment should be on its own line.
Yii application naming conventions
----------------------------------
Other library and framework standards
-------------------------------------
It's good to be consistent with other frameworks and libraries whose components
will be possibly used with Yii2. That's why when there are no objective reasons
to use different style we should use one that's common among most of the popular
libraries and frameworks.
That's not only about PHP but about JavaScript as well. Since we're using jQuery
a lot it's better to be consistent with its style as well.
Application style consistency is much more important than consistency with other frameworks and libraries.
- [Symfony 2](http://symfony.com/doc/current/contributing/code/standards.html)
- [Zend Framework 1](http://framework.zend.com/manual/en/coding-standard.coding-style.html)
- [Zend Framework 2](http://framework.zend.com/wiki/display/ZFDEV2/Coding+Standards)
- [Pear](http://pear.php.net/manual/en/standards.php)
- [jQuery](http://docs.jquery.com/JQuery_Core_Style_Guidelines)
\ No newline at end of file
Alex's Code Review, 2011.11.12
==============================
Overall hierarchy
------------------
Generally is OK. Like that `Object` and `Component` are now separated.
I've generated 2 diagrams under `docs/` to see it better as a whole.
> The purpose of separating `Object` from `Component` is to make `Object`
> a super-light base class that supports properties defined by getter/setters.
> Note that `Component` is a bit of heavy because it uses two extra member
> variables to support events and behaviors.
Object
------
### property feature
Is it OK that `canGetProperty` and `canSetProperty` will return `false` for real
class members?
> Added $checkVar parameter
### callbacks and expressions
We're using 5.3. What's the reason to support `eval()` in `evaluateExpression` if
we have anonymous functions? Is that for storing code as string inside of DB (RBAC)?
If we're going to get rid of `eval()`, cosider remaning method to something about callback.
If not then we definitely need to use anonymous functions in API docs and the guide
where possible.
> The purpose of evaluateExpression() is to provide a way of evaluating a PHP expression
> in the context of an object. Will remove it before release if we find no use of it.
>> mdomba:
>> As eval() is controversial, and anonymous functions can replace all Yii 1 usage of eval()
>> how about removing it from the beginning and add it only if we find it necessary.
>> This way we would not be tempted to stick with eval() and will be forced to first try to find alternatives
### Object::create()
#### `__construct` issue
Often a class doesn't have `__construct` implementation and `stdClass` doesn't have
default one either but Object::create() always expects constructor to be
defined. See `ObjectTest`. Either `method_exists` call or `Object::__construct` needed.
> Added Object::__construct.
#### How to support object factory like we do with CWidgetFactory?
~~~
class ObjectConfig
{
public function configure($class)
{
$config = $this->load($class);
// apply config to $class
}
private function load($class)
{
// get class properties from a config file
// in this method we need to walk all the
// inheritance hierarchy down to Object itself
return array(
'property' => 'value',
// …
);
}
}
~~~
Then we need to add `__construct` to `Object` (or implement `Initalbe`):
~~~
class Object
{
public function __construct()
{
$conf = new ObjectConfig();
$conf->configure($this);
}
}
~~~
This way we'll be able to set defaults for any object.
> The key issue here is about how to process the config file. Clearly, we cannot
> do this for every type of component because it would mean an extra file access
> for every component type
#### Do we need to support lazy class injection?
Currently there's no way to lazy-inject class into another class property via
config. Do we need it? If yes then we can probably extend component config to support
the following:
~~~
class Foo extends Object
{
public $prop;
}
class Bar extends Object
{
public $prop;
}
$config = array(
'prop' => array(
'class' => 'Bar',
'prop' => 'Hello!',
),
);
$foo = Foo::create($config);
echo $foo->bar->prop;
// will output Hello!
~~~
Should it support infinite nesting level?
> I don't think we need this. Foo::$prop cannot be an object unless it needs it to be.
> In that case, it can be defined with a setter in which it can handle the object creation
> based on a configuration array. This is a bit inconvenient, but I think such usage is
> not very common.
### Why `Event` is `Object`?
There's no need to extend from `Object`. Is there a plan to use `Object` features
later?
> To use properties defined via getter/setter.
Behaviors
---------
Overall I wasn't able to use behaviors. See `BehaviorTest`.
### Should behaviors be able to define events for owner components?
Why not? Should be a very good feature in order to make behaviors customizable.
> It's a bit hard to implement it efficiently. I tend not to support it for now
> unless enough people are requesting for it.
### Multiple behaviors can be attached to the same component
What if we'll have multiple methods / properties / events with the same name?
> The first one takes precedence. This is the same as we do in 1.1.
### How to use Behavior::attach?
Looks like it is used by `Component::attachBehavior` but can't be used without it.
Why it's public then? Can we move it to `Component?`
> It's public because it is called by Component. It is in Behavior such that
> it can be overridden by behavior classes to customize the attach process.
Events
------
Class itself looks OK. Component part is OK as well but I've not tested
it carefully. Overall it seems concept is the same as in Yii1.
### Event declaration: the on-method is mostly repetitive for every event. Should we choose a different way of declaring events?
Maybe. People complained previously about too many code for event declaration.
### Should we implement some additional event mechanism, such as global events?
Why use two different implementations in a single application?
Exceptions
----------
- Should we convert all errors, warnings and notices to exceptions?
> I think not. We used to do this in early versions of 1.0. We found sometimes
> very mysterious things would happen which makes error fixing harder rather than
> easier.
Coding style
------------
See `docs/code_style.md`.
\ No newline at end of file
<?php
return array(
'basePath' => __DIR__ . '/web/assets',
);
\ No newline at end of file
......@@ -22,7 +22,9 @@ class ActionEvent extends Event
*/
public $action;
/**
* @var boolean whether to continue running the action.
* @var boolean whether to continue running the action. Event handlers of
* [[Controller::EVENT_BEFORE_ACTION]] may set this property to decide whether
* to continue running the current action.
*/
public $isValid = true;
......
......@@ -47,6 +47,11 @@ class Controller extends Component
* by [[run()]] when it is called by [[Application]] to run an action.
*/
public $action;
/**
* @var View the view object that can be used to render views or view files.
*/
private $_view;
/**
* @param string $id the ID of this controller
......@@ -135,7 +140,7 @@ class Controller extends Component
} elseif ($pos > 0) {
return $this->module->runAction($route, $params);
} else {
return \Yii::$app->runAction(ltrim($route, '/'), $params);
return Yii::$app->runAction(ltrim($route, '/'), $params);
}
}
......@@ -293,6 +298,37 @@ class Controller extends Component
/**
* Renders a view and applies layout if available.
*
* The view to be rendered can be specified in one of the following formats:
*
* - path alias (e.g. "@app/views/site/index");
* - absolute path within application (e.g. "//site/index"): the view name starts with double slashes.
* The actual view file will be looked for under the [[Application::viewPath|view path]] of the application.
* - absolute path within module (e.g. "/site/index"): the view name starts with a single slash.
* The actual view file will be looked for under the [[Module::viewPath|view path]] of [[module]].
* - relative path (e.g. "index"): the actual view file will be looked for under [[viewPath]].
*
* To determine which layout should be applied, the following two steps are conducted:
*
* 1. In the first step, it determines the layout name and the context module:
*
* - If [[layout]] is specified as a string, use it as the layout name and [[module]] as the context module;
* - If [[layout]] is null, search through all ancestor modules of this controller and find the first
* module whose [[Module::layout|layout]] is not null. The layout and the corresponding module
* are used as the layout name and the context module, respectively. If such a module is not found
* or the corresponding layout is not a string, it will return false, meaning no applicable layout.
*
* 2. In the second step, it determines the actual layout file according to the previously found layout name
* and context module. The layout name can be
*
* - a path alias (e.g. "@app/views/layouts/main");
* - an absolute path (e.g. "/main"): the layout name starts with a slash. The actual layout file will be
* looked for under the [[Application::layoutPath|layout path]] of the application;
* - a relative path (e.g. "main"): the actual layout layout file will be looked for under the
* [[Module::viewPath|view path]] of the context module.
*
* If the layout name does not contain a file extension, it will use the default one `.php`.
*
* @param string $view the view name. Please refer to [[findViewFile()]] on how to specify a view name.
* @param array $params the parameters (name-value pairs) that should be made available in the view.
* These parameters will not be available in the layout.
......@@ -301,10 +337,11 @@ class Controller extends Component
*/
public function render($view, $params = array())
{
$output = Yii::$app->getView()->render($view, $params, $this);
$viewFile = $this->findViewFile($view);
$output = $this->getView()->renderFile($viewFile, $params, $this);
$layoutFile = $this->findLayoutFile();
if ($layoutFile !== false) {
return Yii::$app->getView()->renderFile($layoutFile, array('content' => $output), $this);
return $this->getView()->renderFile($layoutFile, array('content' => $output), $this);
} else {
return $output;
}
......@@ -313,14 +350,15 @@ class Controller extends Component
/**
* Renders a view.
* This method differs from [[render()]] in that it does not apply any layout.
* @param string $view the view name. Please refer to [[findViewFile()]] on how to specify a view name.
* @param string $view the view name. Please refer to [[render()]] on how to specify a view name.
* @param array $params the parameters (name-value pairs) that should be made available in the view.
* @return string the rendering result.
* @throws InvalidParamException if the view file does not exist.
*/
public function renderPartial($view, $params = array())
{
return Yii::$app->getView()->render($view, $params, $this);
$viewFile = $this->findViewFile($view);
return $this->getView()->renderFile($viewFile, $params, $this);
}
/**
......@@ -332,7 +370,30 @@ class Controller extends Component
*/
public function renderFile($file, $params = array())
{
return Yii::$app->getView()->renderFile($file, $params, $this);
return $this->getView()->renderFile($file, $params, $this);
}
/**
* Returns the view object that can be used to render views or view files.
* The [[render()]], [[renderPartial()]] and [[renderFile()]] methods will use
* this view object to implement the actual view rendering.
* @return View the view object that can be used to render views or view files.
*/
public function getView()
{
if ($this->_view === null) {
$this->_view = Yii::$app->getView();
}
return $this->_view;
}
/**
* Sets the view object to be used by this controller.
* @param View $view the view object that can be used to render views or view files.
*/
public function setView($view)
{
$this->_view = $view;
}
/**
......@@ -347,30 +408,33 @@ class Controller extends Component
}
/**
* Finds the view file based on the given view name.
* @param string $view the view name or the path alias of the view file. Please refer to [[render()]]
* on how to specify this parameter.
* @return string the view file path. Note that the file may not exist.
*/
protected function findViewFile($view)
{
if (strncmp($view, '@', 1) === 0) {
// e.g. "@app/views/main"
$file = Yii::getAlias($view);
} elseif (strncmp($view, '//', 2) === 0) {
// e.g. "//layouts/main"
$file = Yii::$app->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
} elseif (strncmp($view, '/', 1) === 0) {
// e.g. "/site/index"
$file = $this->module->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
} else {
$file = $this->getViewPath() . DIRECTORY_SEPARATOR . $view;
}
return pathinfo($file, PATHINFO_EXTENSION) === '' ? $file . '.php' : $file;
}
/**
* Finds the applicable layout file.
*
* This method locates an applicable layout file via two steps.
*
* In the first step, it determines the layout name and the context module:
*
* - If [[layout]] is specified as a string, use it as the layout name and [[module]] as the context module;
* - If [[layout]] is null, search through all ancestor modules of this controller and find the first
* module whose [[Module::layout|layout]] is not null. The layout and the corresponding module
* are used as the layout name and the context module, respectively. If such a module is not found
* or the corresponding layout is not a string, it will return false, meaning no applicable layout.
*
* In the second step, it determines the actual layout file according to the previously found layout name
* and context module. The layout name can be
*
* - a path alias (e.g. "@app/views/layouts/main");
* - an absolute path (e.g. "/main"): the layout name starts with a slash. The actual layout file will be
* looked for under the [[Application::layoutPath|layout path]] of the application;
* - a relative path (e.g. "main"): the actual layout layout file will be looked for under the
* [[Module::viewPath|view path]] of the context module.
*
* If the layout name does not contain a file extension, it will use the default one `.php`.
*
* @return string|boolean the layout file path, or false if layout is not needed.
* Please refer to [[render()]] on how to specify this parameter.
* @throws InvalidParamException if an invalid path alias is used to specify the layout
*/
protected function findLayoutFile()
......@@ -399,7 +463,7 @@ class Controller extends Component
$file = $module->getLayoutPath() . DIRECTORY_SEPARATOR . $view;
}
if (FileHelper::getExtension($file) === '') {
if (pathinfo($file, PATHINFO_EXTENSION) === '') {
$file .= '.php';
}
return $file;
......
......@@ -7,6 +7,8 @@
namespace yii\base;
use Yii;
/**
* ErrorException represents a PHP error.
*
......@@ -33,6 +35,32 @@ class ErrorException extends Exception
$this->severity = $severity;
$this->file = $filename;
$this->line = $lineno;
if (function_exists('xdebug_get_function_stack')) {
$trace = array_slice(array_reverse(xdebug_get_function_stack()), 3, -1);
foreach ($trace as &$frame) {
if (!isset($frame['function'])) {
$frame['function'] = 'unknown';
}
// XDebug < 2.1.1: http://bugs.xdebug.org/view.php?id=695
if (!isset($frame['type']) || $frame['type'] === 'static') {
$frame['type'] = '::';
} elseif ($frame['type'] === 'dynamic') {
$frame['type'] = '->';
}
// XDebug has a different key name
$frame['args'] = array();
if (isset($frame['params']) && !isset($frame['args'])) {
$frame['args'] = $frame['params'];
}
}
$ref = new \ReflectionProperty('Exception', 'trace');
$ref->setAccessible(true);
$ref->setValue($this, $trace);
}
}
/**
......@@ -51,7 +79,7 @@ class ErrorException extends Exception
* @param array $error error got from error_get_last()
* @return bool if error is one of fatal type
*/
public static function isFatalErorr($error)
public static function isFatalError($error)
{
return isset($error['type']) && in_array($error['type'], array(E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING));
}
......@@ -62,20 +90,20 @@ class ErrorException extends Exception
public function getName()
{
$names = array(
E_ERROR => \Yii::t('yii|Fatal Error'),
E_PARSE => \Yii::t('yii|Parse Error'),
E_CORE_ERROR => \Yii::t('yii|Core Error'),
E_COMPILE_ERROR => \Yii::t('yii|Compile Error'),
E_USER_ERROR => \Yii::t('yii|User Error'),
E_WARNING => \Yii::t('yii|Warning'),
E_CORE_WARNING => \Yii::t('yii|Core Warning'),
E_COMPILE_WARNING => \Yii::t('yii|Compile Warning'),
E_USER_WARNING => \Yii::t('yii|User Warning'),
E_STRICT => \Yii::t('yii|Strict'),
E_NOTICE => \Yii::t('yii|Notice'),
E_RECOVERABLE_ERROR => \Yii::t('yii|Recoverable Error'),
E_DEPRECATED => \Yii::t('yii|Deprecated'),
E_ERROR => Yii::t('yii|Fatal Error'),
E_PARSE => Yii::t('yii|Parse Error'),
E_CORE_ERROR => Yii::t('yii|Core Error'),
E_COMPILE_ERROR => Yii::t('yii|Compile Error'),
E_USER_ERROR => Yii::t('yii|User Error'),
E_WARNING => Yii::t('yii|Warning'),
E_CORE_WARNING => Yii::t('yii|Core Warning'),
E_COMPILE_WARNING => Yii::t('yii|Compile Warning'),
E_USER_WARNING => Yii::t('yii|User Warning'),
E_STRICT => Yii::t('yii|Strict'),
E_NOTICE => Yii::t('yii|Notice'),
E_RECOVERABLE_ERROR => Yii::t('yii|Recoverable Error'),
E_DEPRECATED => Yii::t('yii|Deprecated'),
);
return isset($names[$this->getCode()]) ? $names[$this->getCode()] : \Yii::t('yii|Error');
return isset($names[$this->getCode()]) ? $names[$this->getCode()] : Yii::t('yii|Error');
}
}
......@@ -16,8 +16,6 @@ namespace yii\base;
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
use yii\helpers\VarDumper;
class ErrorHandler extends Component
{
/**
......@@ -53,6 +51,7 @@ class ErrorHandler extends Component
/**
* Handles exception
* @param \Exception $exception
*/
public function handle($exception)
......@@ -63,10 +62,14 @@ class ErrorHandler extends Component
$this->clearOutput();
}
$this->render($exception);
$this->renderException($exception);
}
protected function render($exception)
/**
* Renders exception
* @param \Exception $exception
*/
protected function renderException($exception)
{
if ($this->errorAction !== null) {
\Yii::$app->runAction($this->errorAction);
......@@ -78,13 +81,19 @@ class ErrorHandler extends Component
if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') {
\Yii::$app->renderException($exception);
} else {
// if there is an error during error rendering it's useful to
// display PHP error in debug mode instead of a blank screen
if(YII_DEBUG) {
ini_set('display_errors', 1);
}
$view = new View;
if (!YII_DEBUG || $exception instanceof UserException) {
$viewName = $this->errorView;
} else {
$viewName = $this->exceptionView;
}
echo $view->render($viewName, array(
echo $view->renderFile($viewName, array(
'exception' => $exception,
), $this);
}
......@@ -198,6 +207,10 @@ class ErrorHandler extends Component
echo '<div class="code"><pre>' . $output . '</pre></div>';
}
/**
* Renders calls stack trace
* @param array $trace
*/
public function renderTrace($trace)
{
$count = 0;
......@@ -235,6 +248,11 @@ class ErrorHandler extends Component
echo '</table>';
}
/**
* Converts special characters to HTML entities
* @param string $text text to encode
* @return string
*/
public function htmlEncode($text)
{
return htmlspecialchars($text, ENT_QUOTES, \Yii::$app->charset);
......@@ -255,7 +273,7 @@ class ErrorHandler extends Component
{
$view = new View;
$name = !YII_DEBUG || $exception instanceof HttpException ? $this->errorView : $this->exceptionView;
echo $view->render($name, array(
echo $view->renderFile($name, array(
'exception' => $exception,
), $this);
}
......
......@@ -15,12 +15,14 @@ namespace yii\base;
* And the [[handled]] property indicates if the event is handled.
* If an event handler sets [[handled]] to be true, the rest of the
* uninvoked handlers will no longer be called to handle the event.
* Additionally, an event may specify extra parameters via the [[data]] property.
*
* Additionally, when attaching an event handler, extra data may be passed
* and be available via the [[data]] property when the event handler is invoked.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Event extends \yii\base\Object
class Event extends Object
{
/**
* @var string the event name. This property is set by [[Component::trigger()]].
......@@ -39,7 +41,8 @@ class Event extends \yii\base\Object
*/
public $handled = false;
/**
* @var mixed extra custom data associated with the event.
* @var mixed the data that is passed to [[Component::on()]] when attaching an event handler.
* Note that this varies according to which event handler is currently executing.
*/
public $data;
}
......@@ -29,11 +29,12 @@ class HttpException extends UserException
* @param integer $status HTTP status code, such as 404, 500, etc.
* @param string $message error message
* @param integer $code error code
* @param \Exception $previous The previous exception used for the exception chaining.
*/
public function __construct($status, $message = null, $code = 0)
public function __construct($status, $message = null, $code = 0, \Exception $previous = null)
{
$this->statusCode = $status;
parent::__construct($message, $code);
parent::__construct($message, $code, $previous);
}
/**
......
......@@ -8,8 +8,8 @@
namespace yii\base;
use yii\helpers\StringHelper;
use yii\validators\Validator;
use yii\validators\RequiredValidator;
use yii\validators\Validator;
/**
* Model is the base class for data models.
......@@ -169,6 +169,26 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess
}
/**
* Returns the form name that this model class should use.
*
* The form name is mainly used by [[\yii\web\ActiveForm]] to determine how to name
* the input fields for the attributes in a model. If the form name is "A" and an attribute
* name is "b", then the corresponding input name would be "A[b]". If the form name is
* an empty string, then the input name would be "b".
*
* By default, this method returns the model class name (without the namespace part)
* as the form name. You may override it when the model is used in different forms.
*
* @return string the form name of this model class.
*/
public function formName()
{
$class = get_class($this);
$pos = strrpos($class, '\\');
return $pos === false ? $class : substr($class, $pos + 1);
}
/**
* Returns the list of attribute names.
* By default, this method returns all public non-static properties of the class.
* You may override this method to change the default behavior.
......@@ -541,7 +561,7 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess
public function onUnsafeAttribute($name, $value)
{
if (YII_DEBUG) {
\Yii::info("Failed to set unsafe attribute '$name' in '" . get_class($this) . "'.", __CLASS__);
\Yii::info("Failed to set unsafe attribute '$name' in '" . get_class($this) . "'.", __METHOD__);
}
}
......
......@@ -170,7 +170,6 @@ abstract class Module extends Component
*/
public function init()
{
Yii::setAlias('@' . $this->id, $this->getBasePath());
$this->preloadComponents();
}
......@@ -208,11 +207,17 @@ abstract class Module extends Component
* Sets the root directory of the module.
* This method can only be invoked at the beginning of the constructor.
* @param string $path the root directory of the module. This can be either a directory name or a path alias.
* @throws Exception if the directory does not exist.
* @throws InvalidParamException if the directory does not exist.
*/
public function setBasePath($path)
{
$this->_basePath = FileHelper::ensureDirectory($path);
$path = Yii::getAlias($path);
$p = realpath($path);
if ($p !== false && is_dir($p)) {
$this->_basePath = $p;
} else {
throw new InvalidParamException("The directory does not exist: $path");
}
}
/**
......@@ -237,7 +242,7 @@ abstract class Module extends Component
*/
public function setControllerPath($path)
{
$this->_controllerPath = FileHelper::ensureDirectory($path);
$this->_controllerPath = Yii::getAlias($path);
}
/**
......@@ -260,7 +265,7 @@ abstract class Module extends Component
*/
public function setViewPath($path)
{
$this->_viewPath = FileHelper::ensureDirectory($path);
$this->_viewPath = Yii::getAlias($path);
}
/**
......@@ -283,20 +288,7 @@ abstract class Module extends Component
*/
public function setLayoutPath($path)
{
$this->_layoutPath = FileHelper::ensureDirectory($path);
}
/**
* Imports the specified path aliases.
* This method is provided so that you can import a set of path aliases when configuring a module.
* The path aliases will be imported by calling [[Yii::import()]].
* @param array $aliases list of path aliases to be imported
*/
public function setImport($aliases)
{
foreach ($aliases as $alias) {
Yii::import($alias);
}
$this->_layoutPath = Yii::getAlias($path);
}
/**
......@@ -346,7 +338,7 @@ abstract class Module extends Component
if ($this->_modules[$id] instanceof Module) {
return $this->_modules[$id];
} elseif ($load) {
Yii::trace("Loading module: $id", __CLASS__);
Yii::trace("Loading module: $id", __METHOD__);
return $this->_modules[$id] = Yii::createObject($this->_modules[$id], $id, $this);
}
}
......@@ -452,7 +444,7 @@ abstract class Module extends Component
if ($this->_components[$id] instanceof Component) {
return $this->_components[$id];
} elseif ($load) {
Yii::trace("Loading component: $id", __CLASS__);
Yii::trace("Loading component: $id", __METHOD__);
return $this->_components[$id] = Yii::createObject($this->_components[$id]);
}
}
......@@ -580,8 +572,9 @@ abstract class Module extends Component
* instance of it.
*
* @param string $route the route consisting of module, controller and action IDs.
* @return array|boolean if the controller is created successfully, it will be returned together
* with the remainder of the route which represents the action ID. Otherwise false will be returned.
* @return array|boolean If the controller is created successfully, it will be returned together
* with the requested action ID. Otherwise false will be returned.
* @throws InvalidConfigException if the controller class and its file do not match.
*/
public function createController($route)
{
......@@ -605,16 +598,16 @@ abstract class Module extends Component
$controller = Yii::createObject($this->controllerMap[$id], $id, $this);
} elseif (preg_match('/^[a-z0-9\\-_]+$/', $id)) {
$className = StringHelper::id2camel($id) . 'Controller';
$classFile = $this->controllerPath . DIRECTORY_SEPARATOR . $className . '.php';
if (is_file($classFile)) {
$className = $this->controllerNamespace . '\\' . $className;
if (!class_exists($className, false)) {
require($classFile);
}
if (class_exists($className, false) && is_subclass_of($className, '\yii\base\Controller')) {
$controller = new $className($id, $this);
}
if (!is_file($classFile)) {
return false;
}
$className = ltrim($this->controllerNamespace . '\\' . $className, '\\');
Yii::$classMap[$className] = $classFile;
if (is_subclass_of($className, 'yii\base\Controller')) {
$controller = new $className($id, $this);
} elseif (YII_DEBUG) {
throw new InvalidConfigException("Controller class must extend from \\yii\\base\\Controller.");
}
}
......
......@@ -8,10 +8,7 @@
namespace yii\base;
/**
* Object is the base class that provides the *property* feature.
*
* @include @yii/base/Object.md
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
......
......@@ -13,28 +13,38 @@ namespace yii\base;
*/
class Response extends Component
{
/**
* Starts output buffering
*/
public function beginOutput()
{
ob_start();
ob_implicit_flush(false);
}
/**
* Returns contents of the output buffer and discards it
* @return string output buffer contents
*/
public function endOutput()
{
return ob_get_clean();
}
/**
* Returns contents of the output buffer
* @return string output buffer contents
*/
public function getOutput()
{
return ob_get_contents();
}
public function cleanOutput()
{
ob_clean();
}
public function removeOutput($all = true)
/**
* Discards the output buffer
* @param boolean $all if true recursively discards all output buffers used
*/
public function cleanOutput($all = true)
{
if ($all) {
for ($level = ob_get_level(); $level > 0; --$level) {
......
......@@ -33,11 +33,17 @@ use yii\helpers\FileHelper;
class Theme extends Component
{
/**
* @var string the root path of this theme.
* @var string the root path or path alias of this theme. All resources of this theme are located
* under this directory. This property must be set if [[pathMap]] is not set.
* @see pathMap
*/
public $basePath;
/**
* @var string the base URL (or path alias) for this theme. All resources of this theme are considered
* to be under this base URL. This property must be set. It is mainly used by [[getUrl()]].
*/
public $baseUrl;
/**
* @var array the mapping between view directories and their corresponding themed versions.
* If not set, it will be initialized as a mapping from [[Application::basePath]] to [[basePath]].
* This property is used by [[applyTo()]] when a view is trying to apply the theme.
......@@ -45,7 +51,6 @@ class Theme extends Component
*/
public $pathMap;
private $_baseUrl;
/**
* Initializes the theme.
......@@ -56,10 +61,10 @@ class Theme extends Component
parent::init();
if (empty($this->pathMap)) {
if ($this->basePath !== null) {
$this->basePath = FileHelper::ensureDirectory($this->basePath);
$this->basePath = Yii::getAlias($this->basePath);
$this->pathMap = array(Yii::$app->getBasePath() => $this->basePath);
} else {
throw new InvalidConfigException("Theme::basePath must be set.");
throw new InvalidConfigException('The "basePath" property must be set.');
}
}
$paths = array();
......@@ -69,25 +74,11 @@ class Theme extends Component
$paths[$from . DIRECTORY_SEPARATOR] = $to . DIRECTORY_SEPARATOR;
}
$this->pathMap = $paths;
}
/**
* Returns the base URL for this theme.
* The method [[getUrl()]] will prefix this to the given URL.
* @return string the base URL for this theme.
*/
public function getBaseUrl()
{
return $this->_baseUrl;
}
/**
* Sets the base URL for this theme.
* @param string $value the base URL for this theme.
*/
public function setBaseUrl($value)
{
$this->_baseUrl = rtrim(Yii::getAlias($value), '/');
if ($this->baseUrl === null) {
throw new InvalidConfigException('The "baseUrl" property must be set.');
} else {
$this->baseUrl = rtrim(Yii::getAlias($this->baseUrl), '/');
}
}
/**
......@@ -112,7 +103,7 @@ class Theme extends Component
}
/**
* Converts a relative URL into an absolute URL using [[basePath]].
* Converts a relative URL into an absolute URL using [[baseUrl]].
* @param string $url the relative URL to be converted.
* @return string the absolute URL
*/
......
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\base;
/**
* UnknownClassException represents an exception caused by accessing an unknown class.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class UnknownClassException extends Exception
{
/**
* @return string the user-friendly name of this exception
*/
public function getName()
{
return \Yii::t('yii|Unknown Class');
}
}
......@@ -8,7 +8,7 @@
namespace yii\base;
/**
* UnknownMethodException represents an exception caused by accessing unknown object methods.
* UnknownMethodException represents an exception caused by accessing an unknown object method.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
......
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\base;
/**
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class ViewEvent extends Event
{
/**
* @var string the rendering result of [[View::renderFile()]].
* Event handlers may modify this property and the modified output will be
* returned by [[View::renderFile()]]. This property is only used
* by [[View::EVENT_AFTER_RENDER]] event.
*/
public $output;
/**
* @var string the view file path that is being rendered by [[View::renderFile()]].
*/
public $viewFile;
/**
* @var boolean whether to continue rendering the view file. Event handlers of
* [[View::EVENT_BEFORE_RENDER]] may set this property to decide whether
* to continue rendering the current view file.
*/
public $isValid = true;
/**
* Constructor.
* @param string $viewFile the view file path that is being rendered by [[View::renderFile()]].
* @param array $config name-value pairs that will be used to initialize the object properties
*/
public function __construct($viewFile, $config = array())
{
$this->viewFile = $viewFile;
parent::__construct($config);
}
}
\ No newline at end of file
......@@ -19,9 +19,11 @@ use yii\helpers\FileHelper;
class Widget extends Component
{
/**
* @var Widget|Controller the owner/creator of this widget. It could be either a widget or a controller.
* @var View the view object that is used to create this widget.
* This property is automatically set by [[View::createWidget()]].
* This property is required by [[render()]] and [[renderFile()]].
*/
public $owner;
public $view;
/**
* @var string id of the widget.
*/
......@@ -32,17 +34,6 @@ class Widget extends Component
private static $_counter = 0;
/**
* Constructor.
* @param Widget|Controller $owner owner/creator of this widget.
* @param array $config name-value pairs that will be used to initialize the object properties
*/
public function __construct($owner, $config = array())
{
$this->owner = $owner;
parent::__construct($config);
}
/**
* Returns the ID of the widget.
* @param boolean $autoGenerate whether to generate an ID if it is not set previously
* @return string ID of the widget.
......@@ -73,6 +64,18 @@ class Widget extends Component
/**
* Renders a view.
* The view to be rendered can be specified in one of the following formats:
*
* - path alias (e.g. "@app/views/site/index");
* - absolute path within application (e.g. "//site/index"): the view name starts with double slashes.
* The actual view file will be looked for under the [[Application::viewPath|view path]] of the application.
* - absolute path within module (e.g. "/site/index"): the view name starts with a single slash.
* The actual view file will be looked for under the [[Module::viewPath|view path]] of the currently
* active module.
* - relative path (e.g. "index"): the actual view file will be looked for under [[viewPath]].
*
* If the view name does not contain a file extension, it will use the default one `.php`.
* @param string $view the view name. Please refer to [[findViewFile()]] on how to specify a view name.
* @param array $params the parameters (name-value pairs) that should be made available in the view.
* @return string the rendering result.
......@@ -80,7 +83,7 @@ class Widget extends Component
*/
public function render($view, $params = array())
{
return Yii::$app->getView()->render($view, $params, $this);
return $this->view->render($view, $params, $this);
}
/**
......@@ -92,7 +95,7 @@ class Widget extends Component
*/
public function renderFile($file, $params = array())
{
return Yii::$app->getView()->renderFile($file, $params, $this);
return $this->view->renderFile($file, $params, $this);
}
/**
......@@ -106,4 +109,28 @@ class Widget extends Component
$class = new \ReflectionClass($className);
return dirname($class->getFileName()) . DIRECTORY_SEPARATOR . 'views';
}
/**
* Finds the view file based on the given view name.
* @param string $view the view name or the path alias of the view file. Please refer to [[render()]]
* on how to specify this parameter.
* @return string the view file path. Note that the file may not exist.
*/
protected function findViewFile($view)
{
if (strncmp($view, '@', 1) === 0) {
// e.g. "@app/views/main"
$file = Yii::getAlias($view);
} elseif (strncmp($view, '//', 2) === 0) {
// e.g. "//layouts/main"
$file = Yii::$app->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
} elseif (strncmp($view, '/', 1) === 0 && Yii::$app->controller !== null) {
// e.g. "/site/index"
$file = Yii::$app->controller->module->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
} else {
$file = $this->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
}
return pathinfo($file, PATHINFO_EXTENSION) === '' ? $file . '.php' : $file;
}
}
\ No newline at end of file
......@@ -11,6 +11,7 @@ namespace yii\caching;
* ApcCache provides APC caching in terms of an application component.
*
* To use this application component, the [APC PHP extension](http://www.php.net/apc) must be loaded.
* In order to enable APC for CLI you should add "apc.enable_cli = 1" to your php.ini.
*
* See [[Cache]] for common cache operations that ApcCache supports.
*
......
......@@ -22,11 +22,10 @@ namespace yii\caching;
class ChainedDependency extends Dependency
{
/**
* @var array list of dependencies that this dependency is composed of.
* Each array element should be a dependency object or a configuration array
* that can be used to create a dependency object via [[\Yii::createObject()]].
* @var Dependency[] list of dependencies that this dependency is composed of.
* Each array element must be a dependency object.
*/
public $dependencies = array();
public $dependencies;
/**
* @var boolean whether this dependency is depending on every dependency in [[dependencies]].
* Defaults to true, meaning if any of the dependencies has changed, this dependency is considered changed.
......@@ -37,9 +36,8 @@ class ChainedDependency extends Dependency
/**
* Constructor.
* @param array $dependencies list of dependencies that this dependency is composed of.
* Each array element should be a dependency object or a configuration array
* that can be used to create a dependency object via [[\Yii::createObject()]].
* @param Dependency[] $dependencies list of dependencies that this dependency is composed of.
* Each array element should be a dependency object.
* @param array $config name-value pairs that will be used to initialize the object properties
*/
public function __construct($dependencies = array(), $config = array())
......@@ -54,10 +52,7 @@ class ChainedDependency extends Dependency
public function evaluateDependency()
{
foreach ($this->dependencies as $dependency) {
if (!$dependency instanceof Dependency) {
$dependency = \Yii::createObject($dependency);
}
$dependency->evalulateDependency();
$dependency->evaluateDependency();
}
}
......@@ -79,10 +74,7 @@ class ChainedDependency extends Dependency
*/
public function getHasChanged()
{
foreach ($this->dependencies as $i => $dependency) {
if (!$dependency instanceof Dependency) {
$this->dependencies[$i] = $dependency = \Yii::createObject($dependency);
}
foreach ($this->dependencies as $dependency) {
if ($this->dependOnAll && $dependency->getHasChanged()) {
return true;
} elseif (!$this->dependOnAll && !$dependency->getHasChanged()) {
......
......@@ -99,7 +99,7 @@ class DbCache extends Cache
$query = new Query;
$query->select(array('data'))
->from($this->cacheTable)
->where('id = :id AND (expire = 0 OR expire >' . time() . ')', array(':id' => $key));
->where('[[id]] = :id AND ([[expire]] = 0 OR [[expire]] >' . time() . ')', array(':id' => $key));
if ($this->db->enableQueryCache) {
// temporarily disable and re-enable query caching
$this->db->enableQueryCache = false;
......@@ -125,7 +125,7 @@ class DbCache extends Cache
$query->select(array('id', 'data'))
->from($this->cacheTable)
->where(array('id' => $keys))
->andWhere('(expire = 0 OR expire > ' . time() . ')');
->andWhere('([[expire]] = 0 OR [[expire]] > ' . time() . ')');
if ($this->db->enableQueryCache) {
$this->db->enableQueryCache = false;
......@@ -227,7 +227,7 @@ class DbCache extends Cache
{
if ($force || mt_rand(0, 1000000) < $this->gcProbability) {
$this->db->createCommand()
->delete($this->cacheTable, 'expire > 0 AND expire < ' . time())
->delete($this->cacheTable, '[[expire]] > 0 AND [[expire]] < ' . time())
->execute();
}
}
......
......@@ -52,6 +52,7 @@ class DbDependency extends Dependency
/**
* Generates the data needed to determine if dependency has been changed.
* This method returns the value of the global state.
* @throws InvalidConfigException
* @return mixed the data needed to determine if dependency has been changed.
*/
protected function generateDependencyData()
......
......@@ -7,7 +7,7 @@
namespace yii\caching;
use yii\base\InvalidConfigException;
use Yii;
/**
* FileCache implements a cache component using files.
......@@ -51,7 +51,7 @@ class FileCache extends Cache
public function init()
{
parent::init();
$this->cachePath = \Yii::getAlias($this->cachePath);
$this->cachePath = Yii::getAlias($this->cachePath);
if (!is_dir($this->cachePath)) {
mkdir($this->cachePath, 0777, true);
}
......
......@@ -106,7 +106,7 @@ class MemCache extends Cache
/**
* Returns the underlying memcache (or memcached) object.
* @return \Memcache|\Memcached the memcache (or memcached) object used by this cache component.
* @throws Exception if memcache or memcached extension is not loaded
* @throws InvalidConfigException if memcache or memcached extension is not loaded
*/
public function getMemcache()
{
......
......@@ -10,7 +10,7 @@ namespace yii\caching;
/**
* ZendDataCache provides Zend data caching in terms of an application component.
*
* To use this application component, the [Zend Data Cache PHP extensionn](http://www.zend.com/en/products/server/)
* To use this application component, the [Zend Data Cache PHP extension](http://www.zend.com/en/products/server/)
* must be loaded.
*
* See [[Cache]] for common cache operations that ZendDataCache supports.
......
......@@ -129,6 +129,7 @@ class Application extends \yii\base\Application
'migrate' => 'yii\console\controllers\MigrateController',
'app' => 'yii\console\controllers\AppController',
'cache' => 'yii\console\controllers\CacheController',
'asset' => 'yii\console\controllers\AssetController',
);
}
......
......@@ -24,7 +24,6 @@ use yii\base\InvalidRouteException;
* ~~~
*
* @author Qiang Xue <qiang.xue@gmail.com>
*
* @since 2.0
*/
class Controller extends \yii\base\Controller
......
......@@ -86,7 +86,7 @@ class AppController extends Controller
$sourceDir = $this->getSourceDir();
$config = $this->getConfig();
$list = FileHelper::buildFileList($sourceDir, $path);
$list = $this->buildFileList($sourceDir, $path);
if(is_array($config)) {
foreach($config as $file => $settings) {
......@@ -96,7 +96,7 @@ class AppController extends Controller
}
}
FileHelper::copyFiles($list);
$this->copyFiles($list);
if(is_array($config)) {
foreach($config as $file => $settings) {
......@@ -159,7 +159,7 @@ class AppController extends Controller
* @param string $pathTo path to file we want to get relative path for
* @param string $varName variable name w/o $ to replace value with relative path for
*
* @return string target file contetns
* @return string target file contents
*/
public function replaceRelativePath($source, $pathTo, $varName)
{
......@@ -204,4 +204,121 @@ class AppController extends Controller
return '__DIR__.\''.$up.'/'.basename($path1).'\'';
}
/**
* Copies a list of files from one place to another.
* @param array $fileList the list of files to be copied (name=>spec).
* The array keys are names displayed during the copy process, and array values are specifications
* for files to be copied. Each array value must be an array of the following structure:
* <ul>
* <li>source: required, the full path of the file/directory to be copied from</li>
* <li>target: required, the full path of the file/directory to be copied to</li>
* <li>callback: optional, the callback to be invoked when copying a file. The callback function
* should be declared as follows:
* <pre>
* function foo($source,$params)
* </pre>
* where $source parameter is the source file path, and the content returned
* by the function will be saved into the target file.</li>
* <li>params: optional, the parameters to be passed to the callback</li>
* </ul>
* @see buildFileList
*/
protected function copyFiles($fileList)
{
$overwriteAll = false;
foreach($fileList as $name=>$file) {
$source = strtr($file['source'], '/\\', DIRECTORY_SEPARATOR);
$target = strtr($file['target'], '/\\', DIRECTORY_SEPARATOR);
$callback = isset($file['callback']) ? $file['callback'] : null;
$params = isset($file['params']) ? $file['params'] : null;
if(is_dir($source)) {
if (!is_dir($target)) {
mkdir($target, 0777, true);
}
continue;
}
if($callback !== null) {
$content = call_user_func($callback, $source, $params);
}
else {
$content = file_get_contents($source);
}
if(is_file($target)) {
if($content === file_get_contents($target)) {
echo " unchanged $name\n";
continue;
}
if($overwriteAll) {
echo " overwrite $name\n";
}
else {
echo " exist $name\n";
echo " ...overwrite? [Yes|No|All|Quit] ";
$answer = trim(fgets(STDIN));
if(!strncasecmp($answer, 'q', 1)) {
return;
}
elseif(!strncasecmp($answer, 'y', 1)) {
echo " overwrite $name\n";
}
elseif(!strncasecmp($answer, 'a', 1)) {
echo " overwrite $name\n";
$overwriteAll = true;
}
else {
echo " skip $name\n";
continue;
}
}
}
else {
if (!is_dir(dirname($target))) {
mkdir(dirname($target), 0777, true);
}
echo " generate $name\n";
}
file_put_contents($target, $content);
}
}
/**
* Builds the file list of a directory.
* This method traverses through the specified directory and builds
* a list of files and subdirectories that the directory contains.
* The result of this function can be passed to {@link copyFiles}.
* @param string $sourceDir the source directory
* @param string $targetDir the target directory
* @param string $baseDir base directory
* @param array $ignoreFiles list of the names of files that should
* be ignored in list building process.
* @param array $renameMap hash array of file names that should be
* renamed. Example value: array('1.old.txt'=>'2.new.txt').
* @return array the file list (see {@link copyFiles})
*/
protected function buildFileList($sourceDir, $targetDir, $baseDir='', $ignoreFiles=array(), $renameMap=array())
{
$list = array();
$handle = opendir($sourceDir);
while(($file = readdir($handle)) !== false) {
if(in_array($file, array('.', '..', '.svn', '.gitignore', '.hgignore')) || in_array($file, $ignoreFiles)) {
continue;
}
$sourcePath = $sourceDir.DIRECTORY_SEPARATOR.$file;
$targetPath = $targetDir.DIRECTORY_SEPARATOR.strtr($file, $renameMap);
$name = $baseDir === '' ? $file : $baseDir.'/'.$file;
$list[$name] = array(
'source' => $sourcePath,
'target' => $targetPath,
);
if(is_dir($sourcePath)) {
$list = array_merge($list, self::buildFileList($sourcePath, $targetPath, $name, $ignoreFiles, $renameMap));
}
}
closedir($handle);
return $list;
}
}
\ No newline at end of file
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\console\controllers;
use Yii;
use yii\console\Exception;
use yii\console\Controller;
/**
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class AssetController extends Controller
{
public $defaultAction = 'compress';
public $bundles = array();
public $extensions = array();
/**
* @var array
* ~~~
* 'all' => array(
* 'css' => 'all.css',
* 'js' => 'js.css',
* 'depends' => array( ... ),
* )
* ~~~
*/
public $targets = array();
public $assetManager = array();
public $jsCompressor = 'java -jar compiler.jar --js {from} --js_output_file {to}';
public $cssCompressor = 'java -jar yuicompressor.jar {from} -o {to}';
public function actionCompress($configFile, $bundleFile)
{
$this->loadConfiguration($configFile);
$bundles = $this->loadBundles($this->bundles, $this->extensions);
$targets = $this->loadTargets($this->targets, $bundles);
$this->publishBundles($bundles, $this->publishOptions);
$timestamp = time();
foreach ($targets as $target) {
if (!empty($target->js)) {
$this->buildTarget($target, 'js', $bundles, $timestamp);
}
if (!empty($target->css)) {
$this->buildTarget($target, 'css', $bundles, $timestamp);
}
}
$targets = $this->adjustDependency($targets, $bundles);
$this->saveTargets($targets, $bundleFile);
}
protected function loadConfiguration($configFile)
{
foreach (require($configFile) as $name => $value) {
if (property_exists($this, $name)) {
$this->$name = $value;
} else {
throw new Exception("Unknown configuration option: $name");
}
}
if (!isset($this->assetManager['basePath'])) {
throw new Exception("Please specify 'basePath' for the 'assetManager' option.");
}
if (!isset($this->assetManager['baseUrl'])) {
throw new Exception("Please specify 'baseUrl' for the 'assetManager' option.");
}
}
protected function loadBundles($bundles, $extensions)
{
$result = array();
foreach ($bundles as $name => $bundle) {
$bundle['class'] = 'yii\\web\\AssetBundle';
$result[$name] = Yii::createObject($bundle);
}
foreach ($extensions as $path) {
$manifest = $path . '/assets.php';
if (!is_file($manifest)) {
continue;
}
foreach (require($manifest) as $name => $bundle) {
if (!isset($result[$name])) {
$bundle['class'] = 'yii\\web\\AssetBundle';
$result[$name] = Yii::createObject($bundle);
}
}
}
return $result;
}
protected function loadTargets($targets, $bundles)
{
// build the dependency order of bundles
$registered = array();
foreach ($bundles as $name => $bundle) {
$this->registerBundle($bundles, $name, $registered);
}
$bundleOrders = array_combine(array_keys($registered), range(0, count($bundles) - 1));
// fill up the target which has empty 'depends'.
$referenced = array();
foreach ($targets as $name => $target) {
if (empty($target['depends'])) {
if (!isset($all)) {
$all = $name;
} else {
throw new Exception("Only one target can have empty 'depends' option. Found two now: $all, $name");
}
} else {
foreach ($target['depends'] as $bundle) {
if (!isset($referenced[$bundle])) {
$referenced[$bundle] = $name;
} else {
throw new Exception("Target '{$referenced[$bundle]}' and '$name' cannot contain the bundle '$bundle' at the same time.");
}
}
}
}
if (isset($all)) {
$targets[$all]['depends'] = array_diff(array_keys($registered), array_keys($referenced));
}
// adjust the 'depends' order for each target according to the dependency order of bundles
// create an AssetBundle object for each target
foreach ($targets as $name => $target) {
if (!isset($target['basePath'])) {
throw new Exception("Please specify 'basePath' for the '$name' target.");
}
if (!isset($target['baseUrl'])) {
throw new Exception("Please specify 'baseUrl' for the '$name' target.");
}
usort($target['depends'], function ($a, $b) use ($bundleOrders) {
if ($bundleOrders[$a] == $bundleOrders[$b]) {
return 0;
} else {
return $bundleOrders[$a] > $bundleOrders[$b] ? 1 : -1;
}
});
$target['class'] = 'yii\\web\\AssetBundle';
$targets[$name] = Yii::createObject($target);
}
return $targets;
}
/**
* @param \yii\web\AssetBundle[] $bundles
* @param array $options
*/
protected function publishBundles($bundles, $options)
{
if (!isset($options['class'])) {
$options['class'] = 'yii\\web\\AssetManager';
}
$am = Yii::createObject($options);
foreach ($bundles as $bundle) {
$bundle->publish($am);
}
}
/**
* @param \yii\web\AssetBundle $target
* @param string $type either "js" or "css"
* @param \yii\web\AssetBundle[] $bundles
* @param integer $timestamp
* @throws Exception
*/
protected function buildTarget($target, $type, $bundles, $timestamp)
{
$outputFile = strtr($target->$type, array(
'{ts}' => $timestamp,
));
$inputFiles = array();
foreach ($target->depends as $name) {
if (isset($bundles[$name])) {
foreach ($bundles[$name]->$type as $file) {
$inputFiles[] = $bundles[$name]->basePath . '/' . $file;
}
} else {
throw new Exception("Unknown bundle: $name");
}
}
if ($type === 'js') {
$this->compressJsFiles($inputFiles, $target->basePath . '/' . $outputFile);
} else {
$this->compressCssFiles($inputFiles, $target->basePath . '/' . $outputFile);
}
$target->$type = array($outputFile);
}
protected function adjustDependency($targets, $bundles)
{
$map = array();
foreach ($targets as $name => $target) {
foreach ($target->depends as $bundle) {
$map[$bundle] = $name;
}
}
foreach ($targets as $name => $target) {
$depends = array();
foreach ($target->depends as $bn) {
foreach ($bundles[$bn]->depends as $bundle) {
$depends[$map[$bundle]] = true;
}
}
unset($depends[$name]);
$target->depends = array_keys($depends);
}
// detect possible circular dependencies
foreach ($targets as $name => $target) {
$registered = array();
$this->registerBundle($targets, $name, $registered);
}
foreach ($map as $bundle => $target) {
$targets[$bundle] = Yii::createObject(array(
'class' => 'yii\\web\\AssetBundle',
'depends' => array($target),
));
}
return $targets;
}
protected function registerBundle($bundles, $name, &$registered)
{
if (!isset($registered[$name])) {
$registered[$name] = false;
$bundle = $bundles[$name];
foreach ($bundle->depends as $depend) {
$this->registerBundle($bundles, $depend, $registered);
}
unset($registered[$name]);
$registered[$name] = true;
} elseif ($registered[$name] === false) {
throw new Exception("A circular dependency is detected for target '$name'.");
}
}
protected function saveTargets($targets, $bundleFile)
{
$array = array();
foreach ($targets as $name => $target) {
foreach (array('js', 'css', 'depends', 'basePath', 'baseUrl') as $prop) {
if (!empty($target->$prop)) {
$array[$name][$prop] = $target->$prop;
}
}
}
$array = var_export($array, true);
$version = date('Y-m-d H:i:s', time());
file_put_contents($bundleFile, <<<EOD
<?php
/**
* This file is generated by the "yiic script" command.
* DO NOT MODIFY THIS FILE DIRECTLY.
* @version $version
*/
return $array;
EOD
);
}
protected function compressJsFiles($inputFiles, $outputFile)
{
if (is_string($this->jsCompressor)) {
$tmpFile = $outputFile . '.tmp';
$this->combineJsFiles($inputFiles, $tmpFile);
$log = shell_exec(strtr($this->jsCompressor, array(
'{from}' => $tmpFile,
'{to}' => $outputFile,
)));
@unlink($tmpFile);
} else {
$log = call_user_func($this->jsCompressor, $this, $inputFiles, $outputFile);
}
}
protected function compressCssFiles($inputFiles, $outputFile)
{
if (is_string($this->cssCompressor)) {
$tmpFile = $outputFile . '.tmp';
$this->combineCssFiles($inputFiles, $tmpFile);
$log = shell_exec(strtr($this->cssCompressor, array(
'{from}' => $inputFiles,
'{to}' => $outputFile,
)));
} else {
$log = call_user_func($this->cssCompressor, $this, $inputFiles, $outputFile);
}
}
public function combineJsFiles($files, $tmpFile)
{
$content = '';
foreach ($files as $file) {
$content .= "/*** BEGIN FILE: $file ***/\n"
. file_get_contents($file)
. "/*** END FILE: $file ***/\n";
}
file_put_contents($tmpFile, $content);
}
public function combineCssFiles($files, $tmpFile)
{
// todo: adjust url() references in CSS files
$content = '';
foreach ($files as $file) {
$content .= "/*** BEGIN FILE: $file ***/\n"
. file_get_contents($file)
. "/*** END FILE: $file ***/\n";
}
file_put_contents($tmpFile, $content);
}
public function actionTemplate($configFile)
{
$template = <<<EOD
<?php
return array(
//
'bundles' => require('path/to/bundles.php'),
//
'extensions' => require('path/to/namespaces.php'),
//
'targets' => array(
'all' => array(
'basePath' => __DIR__,
'baseUrl' => '/test',
'js' => 'all-{ts}.js',
'css' => 'all-{ts}.css',
),
),
'assetManager' => array(
'basePath' => __DIR__,
'baseUrl' => '/test',
),
);
EOD;
file_put_contents($configFile, $template);
}
}
\ No newline at end of file
......@@ -9,9 +9,9 @@ namespace yii\console\controllers;
use Yii;
use yii\base\Application;
use yii\console\Exception;
use yii\base\InlineAction;
use yii\console\Controller;
use yii\console\Exception;
use yii\console\Request;
use yii\helpers\StringHelper;
......@@ -128,7 +128,7 @@ class HelpController extends Controller
$files = scandir($module->getControllerPath());
foreach ($files as $file) {
if(strcmp(substr($file,-14),'Controller.php') === 0 && is_file($file)) {
if (strcmp(substr($file, -14), 'Controller.php') === 0) {
$commands[] = $prefix . lcfirst(substr(basename($file), 0, -14));
}
}
......
<?php
define('YII_DEBUG', true);
$yii = __DIR__.'/../framework/yii.php';
require $yii;
require __DIR__.'/../framework/yii.php';
$config = require dirname(__DIR__).'/protected/config/main.php';
$config['basePath'] = dirname(__DIR__).'/protected';
$basePath = dirname(__DIR__).'/protected';
$app = new \yii\web\Application('webapp', $basePath, $config);
$app = new \yii\web\Application($config);
$app->run();
\ No newline at end of file
<?php
return array(
'id' => 'webapp',
'name' => 'My Web Application',
'components' => array(
......@@ -12,5 +13,8 @@ return array(
'password' => '',
),
*/
'cache' => array(
'class' => 'yii\caching\DummyCache',
),
),
);
\ No newline at end of file
<?php use yii\helpers\Html as Html; ?>
<!doctype html>
<html lang="en">
<html lang="<?php \Yii::$app->language?>">
<head>
<meta charset="utf-8" />
<title><?php echo $this->context->pageTitle?></title>
<title><?php echo Html::encode($this->title)?></title>
</head>
<body>
<h1><?php echo $this->context->pageTitle?></h1>
<h1><?php echo Html::encode($this->title)?></h1>
<div class="content">
<?php echo $content?>
</div>
......
......@@ -191,15 +191,12 @@ class ActiveRecord extends Model
*/
public static function updateAllCounters($counters, $condition = '', $params = array())
{
$db = static::getDb();
$n = 0;
foreach ($counters as $name => $value) {
$quotedName = $db->quoteColumnName($name);
$counters[$name] = new Expression("$quotedName+:bp{$n}");
$params[":bp{$n}"] = $value;
$counters[$name] = new Expression("[[$name]]+:bp{$n}", array(":bp{$n}" => $value));
$n++;
}
$command = $db->createCommand();
$command = static::getDb()->createCommand();
$command->update(static::tableName(), $counters, $condition, $params);
return $command->execute();
}
......@@ -280,6 +277,34 @@ class ActiveRecord extends Model
}
/**
* Returns the name of the column that stores the lock version for implementing optimistic locking.
*
* Optimistic locking allows multiple users to access the same record for edits and avoids
* potential conflicts. In case when a user attempts to save the record upon some staled data
* (because another user has modified the data), a [[StaleObjectException]] exception will be thrown,
* and the update or deletion is skipped.
*
* Optimized locking is only supported by [[update()]] and [[delete()]].
*
* To use optimized locking:
*
* 1. Create a column to store the version number of each row. The column type should be `BIGINT DEFAULT 0`.
* Override this method to return the name of this column.
* 2. In the Web form that collects the user input, add a hidden field that stores
* the lock version of the recording being updated.
* 3. In the controller action that does the data updating, try to catch the [[StaleObjectException]]
* and implement necessary business logic (e.g. merging the changes, prompting stated data)
* to resolve the conflict.
*
* @return string the column name that stores the lock version of a table row.
* If null is returned (default implemented), optimistic locking will not be supported.
*/
public function optimisticLock()
{
return null;
}
/**
* PHP getter magic method.
* This method is overridden so that attributes and related objects can be accessed like properties.
* @param string $name property name
......@@ -530,8 +555,8 @@ class ActiveRecord extends Model
*/
public function isAttributeChanged($name)
{
if (isset($this->_attribute[$name], $this->_oldAttributes[$name])) {
return $this->_attribute[$name] !== $this->_oldAttributes[$name];
if (isset($this->_attributes[$name], $this->_oldAttributes[$name])) {
return $this->_attributes[$name] !== $this->_oldAttributes[$name];
} else {
return isset($this->_attributes[$name]) || isset($this->_oldAttributes);
}
......@@ -590,7 +615,11 @@ class ActiveRecord extends Model
*/
public function save($runValidation = true, $attributes = null)
{
return $this->getIsNewRecord() ? $this->insert($runValidation, $attributes) : $this->update($runValidation, $attributes);
if ($this->getIsNewRecord()) {
return $this->insert($runValidation, $attributes);
} else {
return $this->update($runValidation, $attributes) !== false;
}
}
/**
......@@ -692,11 +721,26 @@ class ActiveRecord extends Model
* $customer->update();
* ~~~
*
* Note that it is possible the update does not affect any row in the table.
* In this case, this method will return 0. For this reason, you should use the following
* code to check if update() is successful or not:
*
* ~~~
* if ($this->update() !== false) {
* // update successful
* } else {
* // update failed
* }
* ~~~
*
* @param boolean $runValidation whether to perform validation before saving the record.
* If the validation fails, the record will not be inserted into the database.
* @param array $attributes list of attributes that need to be saved. Defaults to null,
* meaning all attributes that are loaded from DB will be saved.
* @return boolean whether the attributes are valid and the record is updated successfully.
* @return integer|boolean the number of rows affected, or false if validation fails
* or [[beforeSave()]] stops the updating process.
* @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data
* being updated is outdated.
*/
public function update($runValidation = true, $attributes = null)
{
......@@ -706,15 +750,31 @@ class ActiveRecord extends Model
if ($this->beforeSave(false)) {
$values = $this->getDirtyAttributes($attributes);
if ($values !== array()) {
$condition = $this->getOldPrimaryKey(true);
$lock = $this->optimisticLock();
if ($lock !== null) {
if (!isset($values[$lock])) {
$values[$lock] = $this->$lock + 1;
}
$condition[$lock] = $this->$lock;
}
// We do not check the return value of updateAll() because it's possible
// that the UPDATE statement doesn't change anything and thus returns 0.
$this->updateAll($values, $this->getOldPrimaryKey(true));
$rows = $this->updateAll($values, $condition);
if ($lock !== null && !$rows) {
throw new StaleObjectException('The object being updated is outdated.');
}
foreach ($values as $name => $value) {
$this->_oldAttributes[$name] = $this->_attributes[$name];
}
$this->afterSave(false);
return $rows;
} else {
return 0;
}
return true;
} else {
return false;
}
......@@ -763,17 +823,28 @@ class ActiveRecord extends Model
* In the above step 1 and 3, events named [[EVENT_BEFORE_DELETE]] and [[EVENT_AFTER_DELETE]]
* will be raised by the corresponding methods.
*
* @return boolean whether the deletion is successful.
* @return integer|boolean the number of rows deleted, or false if the deletion is unsuccessful for some reason.
* Note that it is possible the number of rows deleted is 0, even though the deletion execution is successful.
* @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data
* being deleted is outdated.
*/
public function delete()
{
if ($this->beforeDelete()) {
// we do not check the return value of deleteAll() because it's possible
// the record is already deleted in the database and thus the method will return 0
$this->deleteAll($this->getPrimaryKey(true));
$condition = $this->getOldPrimaryKey(true);
$lock = $this->optimisticLock();
if ($lock !== null) {
$condition[$lock] = $this->$lock;
}
$rows = $this->deleteAll($condition);
if ($lock !== null && !$rows) {
throw new StaleObjectException('The object being deleted is outdated.');
}
$this->_oldAttributes = null;
$this->afterDelete();
return true;
return $rows;
} else {
return false;
}
......
......@@ -84,39 +84,51 @@ class Command extends \yii\base\Component
/**
* Specifies the SQL statement to be executed.
* Any previous execution will be terminated or cancelled.
* The previous SQL execution (if any) will be cancelled, and [[params]] will be cleared as well.
* @param string $sql the SQL statement to be set.
* @return Command this command instance
*/
public function setSql($sql)
{
if ($sql !== $this->_sql) {
if ($this->db->enableAutoQuoting && $sql != '') {
$sql = $this->expandSql($sql);
}
$this->cancel();
$this->_sql = $sql;
$this->_sql = $this->db->quoteSql($sql);
$this->_params = array();
}
return $this;
}
/**
* Expands a SQL statement by quoting table and column names and replacing table prefixes.
* @param string $sql the SQL to be expanded
* @return string the expanded SQL
* Returns the raw SQL by inserting parameter values into the corresponding placeholders in [[sql]].
* Note that the return value of this method should mainly be used for logging purpose.
* It is likely that this method returns an invalid SQL due to improper replacement of parameter placeholders.
* @return string the raw SQL
*/
protected function expandSql($sql)
public function getRawSql()
{
$db = $this->db;
return preg_replace_callback('/(\\{\\{(.*?)\\}\\}|\\[\\[(.*?)\\]\\])/', function($matches) use($db) {
if (isset($matches[3])) {
return $db->quoteColumnName($matches[3]);
if ($this->_params === array()) {
return $this->_sql;
} else {
$params = array();
foreach ($this->_params as $name => $value) {
if (is_string($value)) {
$params[$name] = $this->db->quoteValue($value);
} elseif ($value === null) {
$params[$name] = 'NULL';
} else {
$params[$name] = $value;
}
}
if (isset($params[1])) {
$sql = '';
foreach (explode('?', $this->_sql) as $i => $part) {
$sql .= (isset($params[$i]) ? $params[$i] : '') . $part;
}
return $sql;
} else {
$name = str_replace('%', $db->tablePrefix, $matches[2]);
return $db->quoteTableName($name);
return strtr($this->_sql, $params);
}
}, $sql);
}
}
/**
......@@ -134,7 +146,7 @@ class Command extends \yii\base\Component
try {
$this->pdoStatement = $this->db->pdo->prepare($sql);
} catch (\Exception $e) {
Yii::error($e->getMessage() . "\nFailed to prepare SQL: $sql", __CLASS__);
Yii::error($e->getMessage() . "\nFailed to prepare SQL: $sql", __METHOD__);
$errorInfo = $e instanceof \PDOException ? $e->errorInfo : null;
throw new Exception($e->getMessage(), $errorInfo, (int)$e->getCode());
}
......@@ -243,6 +255,7 @@ class Command extends \yii\base\Component
'boolean' => \PDO::PARAM_BOOL,
'integer' => \PDO::PARAM_INT,
'string' => \PDO::PARAM_STR,
'resource' => \PDO::PARAM_LOB,
'NULL' => \PDO::PARAM_NULL,
);
$type = gettype($data);
......@@ -260,21 +273,18 @@ class Command extends \yii\base\Component
{
$sql = $this->getSql();
if ($this->_params === array()) {
$paramLog = '';
} else {
$paramLog = "\nParameters: " . var_export($this->_params, true);
}
$rawSql = $this->getRawSql();
Yii::trace("Executing SQL: {$sql}{$paramLog}", __CLASS__);
Yii::trace("Executing SQL: $rawSql", __METHOD__);
if ($sql == '') {
return 0;
}
try {
$token = "SQL: $sql";
if ($this->db->enableProfiling) {
Yii::beginProfile(__METHOD__ . "($sql)", __CLASS__);
Yii::beginProfile($token, __METHOD__);
}
$this->prepare();
......@@ -282,16 +292,16 @@ class Command extends \yii\base\Component
$n = $this->pdoStatement->rowCount();
if ($this->db->enableProfiling) {
Yii::endProfile(__METHOD__ . "($sql)", __CLASS__);
Yii::endProfile($token, __METHOD__);
}
return $n;
} catch (\Exception $e) {
if ($this->db->enableProfiling) {
Yii::endProfile(__METHOD__ . "($sql)", __CLASS__);
Yii::endProfile($token, __METHOD__);
}
$message = $e->getMessage();
Yii::error("$message\nFailed to execute SQL: {$sql}{$paramLog}", __CLASS__);
Yii::error("$message\nFailed to execute SQL: $rawSql", __METHOD__);
$errorInfo = $e instanceof \PDOException ? $e->errorInfo : null;
throw new Exception($message, $errorInfo, (int)$e->getCode());
......@@ -377,13 +387,9 @@ class Command extends \yii\base\Component
{
$db = $this->db;
$sql = $this->getSql();
if ($this->_params === array()) {
$paramLog = '';
} else {
$paramLog = "\nParameters: " . var_export($this->_params, true);
}
$rawSql = $this->getRawSql();
Yii::trace("Querying SQL: {$sql}{$paramLog}", __CLASS__);
Yii::trace("Querying SQL: $rawSql", __METHOD__);
/** @var $cache \yii\caching\Cache */
if ($db->enableQueryCache && $method !== '') {
......@@ -395,18 +401,18 @@ class Command extends \yii\base\Component
__CLASS__,
$db->dsn,
$db->username,
$sql,
$paramLog,
$rawSql,
));
if (($result = $cache->get($cacheKey)) !== false) {
Yii::trace('Query result served from cache', __CLASS__);
Yii::trace('Query result served from cache', __METHOD__);
return $result;
}
}
try {
$token = "SQL: $sql";
if ($db->enableProfiling) {
Yii::beginProfile(__METHOD__ . "($sql)", __CLASS__);
Yii::beginProfile($token, __METHOD__);
}
$this->prepare();
......@@ -423,21 +429,21 @@ class Command extends \yii\base\Component
}
if ($db->enableProfiling) {
Yii::endProfile(__METHOD__ . "($sql)", __CLASS__);
Yii::endProfile($token, __METHOD__);
}
if (isset($cache, $cacheKey) && $cache instanceof Cache) {
$cache->set($cacheKey, $result, $db->queryCacheDuration, $db->queryCacheDependency);
Yii::trace('Saved query result in cache', __CLASS__);
Yii::trace('Saved query result in cache', __METHOD__);
}
return $result;
} catch (\Exception $e) {
if ($db->enableProfiling) {
Yii::endProfile(__METHOD__ . "($sql)", __CLASS__);
Yii::endProfile($token, __METHOD__);
}
$message = $e->getMessage();
Yii::error("$message\nCommand::$method() failed: {$sql}{$paramLog}", __CLASS__);
Yii::error("$message\nCommand::$method() failed: $rawSql", __METHOD__);
$errorInfo = $e instanceof \PDOException ? $e->errorInfo : null;
throw new Exception($message, $errorInfo, (int)$e->getCode());
}
......@@ -541,7 +547,7 @@ class Command extends \yii\base\Component
*/
public function delete($table, $condition = '', $params = array())
{
$sql = $this->db->getQueryBuilder()->delete($table, $condition);
$sql = $this->db->getQueryBuilder()->delete($table, $condition, $params);
return $this->setSql($sql)->bindValues($params);
}
......
......@@ -223,21 +223,10 @@ class Connection extends Component
* @var string the common prefix or suffix for table names. If a table name is given
* as `{{%TableName}}`, then the percentage character `%` will be replaced with this
* property value. For example, `{{%post}}` becomes `{{tbl_post}}` if this property is
* set as `"tbl_"`. Note that this property is only effective when [[enableAutoQuoting]]
* is true.
* @see enableAutoQuoting
* set as `"tbl_"`.
*/
public $tablePrefix;
/**
* @var boolean whether to enable automatic quoting of table names and column names.
* Defaults to true. When this property is true, any token enclosed within double curly brackets
* (e.g. `{{post}}`) in a SQL statement will be treated as a table name and will be quoted
* accordingly when the SQL statement is executed; and any token enclosed within double square
* brackets (e.g. `[[name]]`) will be treated as a column name and quoted accordingly.
* @see tablePrefix
*/
public $enableAutoQuoting = true;
/**
* @var array mapping between PDO driver names and [[Schema]] classes.
* The keys of the array are PDO driver names while the values the corresponding
* schema class name or configuration. Please refer to [[\Yii::createObject()]] for
......@@ -248,15 +237,15 @@ class Connection extends Component
* [[Schema]] class to support DBMS that is not supported by Yii.
*/
public $schemaMap = array(
'pgsql' => 'yii\db\pgsql\Schema', // PostgreSQL
'mysqli' => 'yii\db\mysql\Schema', // MySQL
'mysql' => 'yii\db\mysql\Schema', // MySQL
'sqlite' => 'yii\db\sqlite\Schema', // sqlite 3
'pgsql' => 'yii\db\pgsql\Schema', // PostgreSQL
'mysqli' => 'yii\db\mysql\Schema', // MySQL
'mysql' => 'yii\db\mysql\Schema', // MySQL
'sqlite' => 'yii\db\sqlite\Schema', // sqlite 3
'sqlite2' => 'yii\db\sqlite\Schema', // sqlite 2
'mssql' => 'yi\db\dao\mssql\Schema', // Mssql driver on windows hosts
'dblib' => 'yii\db\mssql\Schema', // dblib drivers on linux (and maybe others os) hosts
'sqlsrv' => 'yii\db\mssql\Schema', // Mssql
'oci' => 'yii\db\oci\Schema', // Oracle driver
'sqlsrv' => 'yii\db\mssql\Schema', // Mssql
'oci' => 'yii\db\oci\Schema', // Oracle driver
'dblib' => 'yii\db\mssql\Schema', // dblib drivers on linux (and maybe others os) hosts
);
/**
* @var Transaction the currently active transaction
......@@ -324,12 +313,12 @@ class Connection extends Component
throw new InvalidConfigException('Connection::dsn cannot be empty.');
}
try {
\Yii::trace('Opening DB connection: ' . $this->dsn, __CLASS__);
\Yii::trace('Opening DB connection: ' . $this->dsn, __METHOD__);
$this->pdo = $this->createPdoInstance();
$this->initConnection();
}
catch (\PDOException $e) {
\Yii::error("Failed to open DB connection ({$this->dsn}): " . $e->getMessage(), __CLASS__);
\Yii::error("Failed to open DB connection ({$this->dsn}): " . $e->getMessage(), __METHOD__);
$message = YII_DEBUG ? 'Failed to open DB connection: ' . $e->getMessage() : 'Failed to open DB connection.';
throw new Exception($message, $e->errorInfo, (int)$e->getCode());
}
......@@ -343,7 +332,7 @@ class Connection extends Component
public function close()
{
if ($this->pdo !== null) {
\Yii::trace('Closing DB connection: ' . $this->dsn, __CLASS__);
\Yii::trace('Closing DB connection: ' . $this->dsn, __METHOD__);
$this->pdo = null;
$this->_schema = null;
$this->_transaction = null;
......@@ -518,6 +507,27 @@ class Connection extends Component
}
/**
* Processes a SQL statement by quoting table and column names that are enclosed within double brackets.
* Tokens enclosed within double curly brackets are treated as table names, while
* tokens enclosed within double square brackets are column names. They will be quoted accordingly.
* Also, the percentage character "%" in a table name will be replaced with [[tablePrefix]].
* @param string $sql the SQL to be quoted
* @return string the quoted SQL
*/
public function quoteSql($sql)
{
$db = $this;
return preg_replace_callback('/(\\{\\{([\w\-\. ]+)\\}\\}|\\[\\[([\w\-\. ]+)\\]\\])/',
function($matches) use($db) {
if (isset($matches[3])) {
return $db->quoteColumnName($matches[3]);
} else {
return str_replace('%', $db->tablePrefix, $db->quoteTableName($matches[2]));
}
}, $sql);
}
/**
* Returns the name of the DB driver for the current [[dsn]].
* @return string name of the DB driver
*/
......
......@@ -83,7 +83,7 @@ abstract class Schema extends \yii\base\Object
}
$db = $this->db;
$realName = $this->getRealTableName($name);
$realName = $this->getRawTableName($name);
if ($db->enableSchemaCache && !in_array($name, $db->schemaCacheExclude, true)) {
/** @var $cache Cache */
......@@ -248,7 +248,7 @@ abstract class Schema extends \yii\base\Object
/**
* Quotes a table name for use in a query.
* If the table name contains schema prefix, the prefix will also be properly quoted.
* If the table name is already quoted or contains special characters including '(', '[[' and '{{',
* If the table name is already quoted or contains '(' or '{{',
* then this method will do nothing.
* @param string $name table name
* @return string the properly quoted table name
......@@ -256,7 +256,7 @@ abstract class Schema extends \yii\base\Object
*/
public function quoteTableName($name)
{
if (strpos($name, '(') !== false || strpos($name, '[[') !== false || strpos($name, '{{') !== false) {
if (strpos($name, '(') !== false || strpos($name, '{{') !== false) {
return $name;
}
if (strpos($name, '.') === false) {
......@@ -273,7 +273,7 @@ abstract class Schema extends \yii\base\Object
/**
* Quotes a column name for use in a query.
* If the column name contains prefix, the prefix will also be properly quoted.
* If the column name is already quoted or contains special characters including '(', '[[' and '{{',
* If the column name is already quoted or contains '(', '[[' or '{{',
* then this method will do nothing.
* @param string $name column name
* @return string the properly quoted column name
......@@ -318,15 +318,15 @@ abstract class Schema extends \yii\base\Object
}
/**
* Returns the real name of a table name.
* Returns the actual name of a given table name.
* This method will strip off curly brackets from the given table name
* and replace the percentage character in the name with [[Connection::tablePrefix]].
* and replace the percentage character '%' with [[Connection::tablePrefix]].
* @param string $name the table name to be converted
* @return string the real name of the given table name
*/
public function getRealTableName($name)
public function getRawTableName($name)
{
if ($this->db->enableAutoQuoting && strpos($name, '{{') !== false) {
if (strpos($name, '{{') !== false) {
$name = preg_replace('/\\{\\{(.*?)\\}\\}/', '\1', $name);
return str_replace('%', $this->db->tablePrefix, $name);
} else {
......
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db;
/**
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class StaleObjectException extends Exception
{
/**
* @return string the user-friendly name of this exception
*/
public function getName()
{
return \Yii::t('yii|Stale Object Exception');
}
}
\ No newline at end of file
......@@ -66,7 +66,7 @@ class Transaction extends \yii\base\Object
if ($this->db === null) {
throw new InvalidConfigException('Transaction::db must be set.');
}
\Yii::trace('Starting transaction', __CLASS__);
\Yii::trace('Starting transaction', __METHOD__);
$this->db->open();
$this->db->pdo->beginTransaction();
$this->_active = true;
......@@ -80,7 +80,7 @@ class Transaction extends \yii\base\Object
public function commit()
{
if ($this->_active && $this->db && $this->db->isActive) {
\Yii::trace('Committing transaction', __CLASS__);
\Yii::trace('Committing transaction', __METHOD__);
$this->db->pdo->commit();
$this->_active = false;
} else {
......@@ -95,7 +95,7 @@ class Transaction extends \yii\base\Object
public function rollback()
{
if ($this->_active && $this->db && $this->db->isActive) {
\Yii::trace('Rolling back transaction', __CLASS__);
\Yii::trace('Rolling back transaction', __METHOD__);
$this->db->pdo->rollBack();
$this->_active = false;
} else {
......
......@@ -9,9 +9,6 @@
namespace yii\helpers;
use yii\base\Exception;
use yii\base\InvalidConfigException;
/**
* Filesystem helper
*
......@@ -19,256 +16,6 @@ use yii\base\InvalidConfigException;
* @author Alex Makarov <sam@rmcreative.ru>
* @since 2.0
*/
class FileHelper
class FileHelper extends base\FileHelper
{
/**
* Returns the extension name of a file path.
* For example, the path "path/to/something.php" would return "php".
* @param string $path the file path
* @return string the extension name without the dot character.
*/
public static function getExtension($path)
{
return pathinfo($path, PATHINFO_EXTENSION);
}
/**
* Checks the given path and ensures it is a directory.
* This method will call `realpath()` to "normalize" the given path.
* If the given path does not refer to an existing directory, an exception will be thrown.
* @param string $path the given path. This can also be a path alias.
* @return string the normalized path
* @throws InvalidConfigException if the path does not refer to an existing directory.
*/
public static function ensureDirectory($path)
{
$p = \Yii::getAlias($path);
if (($p = realpath($p)) !== false && is_dir($p)) {
return $p;
} else {
throw new InvalidConfigException('Directory does not exist: ' . $path);
}
}
/**
* Normalizes a file/directory path.
* After normalization, the directory separators in the path will be `DIRECTORY_SEPARATOR`,
* and any trailing directory separators will be removed. For example, '/home\demo/' on Linux
* will be normalized as '/home/demo'.
* @param string $path the file/directory path to be normalized
* @param string $ds the directory separator to be used in the normalized result. Defaults to `DIRECTORY_SEPARATOR`.
* @return string the normalized file/directory path
*/
public static function normalizePath($path, $ds = DIRECTORY_SEPARATOR)
{
return rtrim(strtr($path, array('/' => $ds, '\\' => $ds)), $ds);
}
/**
* Returns the localized version of a specified file.
*
* The searching is based on the specified language code. In particular,
* a file with the same name will be looked for under the subdirectory
* whose name is same as the language code. For example, given the file "path/to/view.php"
* and language code "zh_cn", the localized file will be looked for as
* "path/to/zh_cn/view.php". If the file is not found, the original file
* will be returned.
*
* If the target and the source language codes are the same,
* the original file will be returned.
*
* For consistency, it is recommended that the language code is given
* in lower case and in the format of LanguageID_RegionID (e.g. "en_us").
*
* @param string $file the original file
* @param string $language the target language that the file should be localized to.
* If not set, the value of [[\yii\base\Application::language]] will be used.
* @param string $sourceLanguage the language that the original file is in.
* If not set, the value of [[\yii\base\Application::sourceLanguage]] will be used.
* @return string the matching localized file, or the original file if the localized version is not found.
* If the target and the source language codes are the same, the original file will be returned.
*/
public static function localize($file, $language = null, $sourceLanguage = null)
{
if ($language === null) {
$language = \Yii::$app->language;
}
if ($sourceLanguage === null) {
$sourceLanguage = \Yii::$app->sourceLanguage;
}
if ($language === $sourceLanguage) {
return $file;
}
$desiredFile = dirname($file) . DIRECTORY_SEPARATOR . $sourceLanguage . DIRECTORY_SEPARATOR . basename($file);
return is_file($desiredFile) ? $desiredFile : $file;
}
/**
* Determines the MIME type of the specified file.
* This method will first try to determine the MIME type based on
* [finfo_open](http://php.net/manual/en/function.finfo-open.php). If this doesn't work, it will
* fall back to [[getMimeTypeByExtension()]].
* @param string $file the file name.
* @param string $magicFile name of the optional magic database file, usually something like `/path/to/magic.mime`.
* This will be passed as the second parameter to [finfo_open](http://php.net/manual/en/function.finfo-open.php).
* @param boolean $checkExtension whether to use the file extension to determine the MIME type in case
* `finfo_open()` cannot determine it.
* @return string the MIME type (e.g. `text/plain`). Null is returned if the MIME type cannot be determined.
*/
public static function getMimeType($file, $magicFile = null, $checkExtension = true)
{
if (function_exists('finfo_open')) {
$info = finfo_open(FILEINFO_MIME_TYPE, $magicFile);
if ($info && ($result = finfo_file($info, $file)) !== false) {
return $result;
}
}
return $checkExtension ? self::getMimeTypeByExtension($file) : null;
}
/**
* Determines the MIME type based on the extension name of the specified file.
* This method will use a local map between extension names and MIME types.
* @param string $file the file name.
* @param string $magicFile the path of the file that contains all available MIME type information.
* If this is not set, the default file aliased by `@yii/util/mimeTypes.php` will be used.
* @return string the MIME type. Null is returned if the MIME type cannot be determined.
*/
public static function getMimeTypeByExtension($file, $magicFile = null)
{
if ($magicFile === null) {
$magicFile = \Yii::getAlias('@yii/util/mimeTypes.php');
}
$mimeTypes = require($magicFile);
if (($ext = pathinfo($file, PATHINFO_EXTENSION)) !== '') {
$ext = strtolower($ext);
if (isset($mimeTypes[$ext])) {
return $mimeTypes[$ext];
}
}
return null;
}
/**
* Copies a list of files from one place to another.
* @param array $fileList the list of files to be copied (name=>spec).
* The array keys are names displayed during the copy process, and array values are specifications
* for files to be copied. Each array value must be an array of the following structure:
* <ul>
* <li>source: required, the full path of the file/directory to be copied from</li>
* <li>target: required, the full path of the file/directory to be copied to</li>
* <li>callback: optional, the callback to be invoked when copying a file. The callback function
* should be declared as follows:
* <pre>
* function foo($source,$params)
* </pre>
* where $source parameter is the source file path, and the content returned
* by the function will be saved into the target file.</li>
* <li>params: optional, the parameters to be passed to the callback</li>
* </ul>
* @see buildFileList
*/
public static function copyFiles($fileList)
{
$overwriteAll = false;
foreach($fileList as $name=>$file) {
$source = strtr($file['source'], '/\\', DIRECTORY_SEPARATOR);
$target = strtr($file['target'], '/\\', DIRECTORY_SEPARATOR);
$callback = isset($file['callback']) ? $file['callback'] : null;
$params = isset($file['params']) ? $file['params'] : null;
if(is_dir($source)) {
try {
self::ensureDirectory($target);
}
catch (Exception $e) {
mkdir($target, true, 0777);
}
continue;
}
if($callback !== null) {
$content = call_user_func($callback, $source, $params);
}
else {
$content = file_get_contents($source);
}
if(is_file($target)) {
if($content === file_get_contents($target)) {
echo " unchanged $name\n";
continue;
}
if($overwriteAll) {
echo " overwrite $name\n";
}
else {
echo " exist $name\n";
echo " ...overwrite? [Yes|No|All|Quit] ";
$answer = trim(fgets(STDIN));
if(!strncasecmp($answer, 'q', 1)) {
return;
}
elseif(!strncasecmp($answer, 'y', 1)) {
echo " overwrite $name\n";
}
elseif(!strncasecmp($answer, 'a', 1)) {
echo " overwrite $name\n";
$overwriteAll = true;
}
else {
echo " skip $name\n";
continue;
}
}
}
else {
try {
self::ensureDirectory(dirname($target));
}
catch (Exception $e) {
mkdir(dirname($target), true, 0777);
}
echo " generate $name\n";
}
file_put_contents($target, $content);
}
}
/**
* Builds the file list of a directory.
* This method traverses through the specified directory and builds
* a list of files and subdirectories that the directory contains.
* The result of this function can be passed to {@link copyFiles}.
* @param string $sourceDir the source directory
* @param string $targetDir the target directory
* @param string $baseDir base directory
* @param array $ignoreFiles list of the names of files that should
* be ignored in list building process. Argument available since 1.1.11.
* @param array $renameMap hash array of file names that should be
* renamed. Example value: array('1.old.txt'=>'2.new.txt').
* @return array the file list (see {@link copyFiles})
*/
public static function buildFileList($sourceDir, $targetDir, $baseDir='', $ignoreFiles=array(), $renameMap=array())
{
$list = array();
$handle = opendir($sourceDir);
while(($file = readdir($handle)) !== false) {
if(in_array($file, array('.', '..', '.svn', '.gitignore')) || in_array($file, $ignoreFiles)) {
continue;
}
$sourcePath = $sourceDir.DIRECTORY_SEPARATOR.$file;
$targetPath = $targetDir.DIRECTORY_SEPARATOR.strtr($file, $renameMap);
$name = $baseDir === '' ? $file : $baseDir.'/'.$file;
$list[$name] = array(
'source' => $sourcePath,
'target' => $targetPath,
);
if(is_dir($sourcePath)) {
$list = array_merge($list, self::buildFileList($sourcePath, $targetPath, $name, $ignoreFiles, $renameMap));
}
}
closedir($handle);
return $list;
}
}
\ No newline at end of file
......@@ -7,11 +7,6 @@
namespace yii\helpers;
use Yii;
use yii\base\Exception;
use yii\base\InvalidConfigException;
use yii\base\InvalidParamException;
/**
* SecurityHelper provides a set of methods to handle common security-related tasks.
*
......@@ -29,244 +24,6 @@ use yii\base\InvalidParamException;
* @author Tom Worster <fsb@thefsb.org>
* @since 2.0
*/
class SecurityHelper
class SecurityHelper extends base\SecurityHelper
{
/**
* Encrypts data.
* @param string $data data to be encrypted.
* @param string $key the encryption secret key
* @return string the encrypted data
* @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized
* @see decrypt()
*/
public static function encrypt($data, $key)
{
$module = static::openCryptModule();
$key = StringHelper::substr($key, 0, mcrypt_enc_get_key_size($module));
srand();
$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($module), MCRYPT_RAND);
mcrypt_generic_init($module, $key, $iv);
$encrypted = $iv . mcrypt_generic($module, $data);
mcrypt_generic_deinit($module);
mcrypt_module_close($module);
return $encrypted;
}
/**
* Decrypts data
* @param string $data data to be decrypted.
* @param string $key the decryption secret key
* @return string the decrypted data
* @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized
* @see encrypt()
*/
public static function decrypt($data, $key)
{
$module = static::openCryptModule();
$key = StringHelper::substr($key, 0, mcrypt_enc_get_key_size($module));
$ivSize = mcrypt_enc_get_iv_size($module);
$iv = StringHelper::substr($data, 0, $ivSize);
mcrypt_generic_init($module, $key, $iv);
$decrypted = mdecrypt_generic($module, StringHelper::substr($data, $ivSize, StringHelper::strlen($data)));
mcrypt_generic_deinit($module);
mcrypt_module_close($module);
return rtrim($decrypted, "\0");
}
/**
* Prefixes data with a keyed hash value so that it can later be detected if it is tampered.
* @param string $data the data to be protected
* @param string $key the secret key to be used for generating hash
* @param string $algorithm the hashing algorithm (e.g. "md5", "sha1", "sha256", etc.). Call PHP "hash_algos()"
* function to see the supported hashing algorithms on your system.
* @return string the data prefixed with the keyed hash
* @see validateData()
* @see getSecretKey()
*/
public static function hashData($data, $key, $algorithm = 'sha256')
{
return hash_hmac($algorithm, $data, $key) . $data;
}
/**
* Validates if the given data is tampered.
* @param string $data the data to be validated. The data must be previously
* generated by [[hashData()]].
* @param string $key the secret key that was previously used to generate the hash for the data in [[hashData()]].
* @param string $algorithm the hashing algorithm (e.g. "md5", "sha1", "sha256", etc.). Call PHP "hash_algos()"
* function to see the supported hashing algorithms on your system. This must be the same
* as the value passed to [[hashData()]] when generating the hash for the data.
* @return string the real data with the hash stripped off. False if the data is tampered.
* @see hashData()
*/
public static function validateData($data, $key, $algorithm = 'sha256')
{
$hashSize = StringHelper::strlen(hash_hmac($algorithm, 'test', $key));
$n = StringHelper::strlen($data);
if ($n >= $hashSize) {
$hash = StringHelper::substr($data, 0, $hashSize);
$data2 = StringHelper::substr($data, $hashSize, $n - $hashSize);
return $hash === hash_hmac($algorithm, $data2, $key) ? $data2 : false;
} else {
return false;
}
}
/**
* Returns a secret key associated with the specified name.
* If the secret key does not exist, a random key will be generated
* and saved in the file "keys.php" under the application's runtime directory
* so that the same secret key can be returned in future requests.
* @param string $name the name that is associated with the secret key
* @param integer $length the length of the key that should be generated if not exists
* @return string the secret key associated with the specified name
*/
public static function getSecretKey($name, $length = 32)
{
static $keys;
$keyFile = Yii::$app->getRuntimePath() . '/keys.php';
if ($keys === null) {
$keys = is_file($keyFile) ? require($keyFile) : array();
}
if (!isset($keys[$name])) {
// generate a 32-char random key
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
$keys[$name] = substr(str_shuffle(str_repeat($chars, 5)), 0, $length);
file_put_contents($keyFile, "<?php\nreturn " . var_export($keys, true) . ";\n");
}
return $keys[$name];
}
/**
* Opens the mcrypt module.
* @return resource the mcrypt module handle.
* @throws InvalidConfigException if mcrypt extension is not installed
* @throws Exception if mcrypt initialization fails
*/
protected static function openCryptModule()
{
if (!extension_loaded('mcrypt')) {
throw new InvalidConfigException('The mcrypt PHP extension is not installed.');
}
$module = @mcrypt_module_open('rijndael-256', '', MCRYPT_MODE_CBC, '');
if ($module === false) {
throw new Exception('Failed to initialize the mcrypt module.');
}
return $module;
}
/**
* Generates a secure hash from a password and a random salt.
*
* The generated hash can be stored in database (e.g. `CHAR(64) CHARACTER SET latin1` on MySQL).
* Later when a password needs to be validated, the hash can be fetched and passed
* to [[validatePassword()]]. For example,
*
* ~~~
* // generates the hash (usually done during user registration or when the password is changed)
* $hash = SecurityHelper::hashPassword($password);
* // ...save $hash in database...
*
* // during login, validate if the password entered is correct using $hash fetched from database
* if (PasswordHelper::verifyPassword($password, $hash) {
* // password is good
* } else {
* // password is bad
* }
* ~~~
*
* @param string $password The password to be hashed.
* @param integer $cost Cost parameter used by the Blowfish hash algorithm.
* The higher the value of cost,
* the longer it takes to generate the hash and to verify a password against it. Higher cost
* therefore slows down a brute-force attack. For best protection against brute for attacks,
* set it to the highest value that is tolerable on production servers. The time taken to
* compute the hash doubles for every increment by one of $cost. So, for example, if the
* hash takes 1 second to compute when $cost is 14 then then the compute time varies as
* 2^($cost - 14) seconds.
* @throws Exception on bad password parameter or cost parameter
* @return string The password hash string, ASCII and not longer than 64 characters.
* @see validatePassword()
*/
public static function generatePasswordHash($password, $cost = 13)
{
$salt = static::generateSalt($cost);
$hash = crypt($password, $salt);
if (!is_string($hash) || strlen($hash) < 32) {
throw new Exception('Unknown error occurred while generating hash.');
}
return $hash;
}
/**
* Verifies a password against a hash.
* @param string $password The password to verify.
* @param string $hash The hash to verify the password against.
* @return boolean whether the password is correct.
* @throws InvalidParamException on bad password or hash parameters or if crypt() with Blowfish hash is not available.
* @see generatePasswordHash()
*/
public static function validatePassword($password, $hash)
{
if (!is_string($password) || $password === '') {
throw new InvalidParamException('Password must be a string and cannot be empty.');
}
if (!preg_match('/^\$2[axy]\$(\d\d)\$[\./0-9A-Za-z]{22}/', $hash, $matches) || $matches[1] < 4 || $matches[1] > 30) {
throw new InvalidParamException('Hash is invalid.');
}
$test = crypt($password, $hash);
$n = strlen($test);
if (strlen($test) < 32 || $n !== strlen($hash)) {
return false;
}
// Use a for-loop to compare two strings to prevent timing attacks. See:
// http://codereview.stackexchange.com/questions/13512
$check = 0;
for ($i = 0; $i < $n; ++$i) {
$check |= (ord($test[$i]) ^ ord($hash[$i]));
}
return $check === 0;
}
/**
* Generates a salt that can be used to generate a password hash.
*
* The PHP [crypt()](http://php.net/manual/en/function.crypt.php) built-in function
* requires, for the Blowfish hash algorithm, a salt string in a specific format:
* "$2a$", "$2x$" or "$2y$", a two digit cost parameter, "$", and 22 characters
* from the alphabet "./0-9A-Za-z".
*
* @param integer $cost the cost parameter
* @return string the random salt value.
* @throws InvalidParamException if the cost parameter is not between 4 and 30
*/
protected static function generateSalt($cost = 13)
{
$cost = (int)$cost;
if ($cost < 4 || $cost > 30) {
throw new InvalidParamException('Cost must be between 4 and 31.');
}
// Get 20 * 8bits of pseudo-random entropy from mt_rand().
$rand = '';
for ($i = 0; $i < 20; ++$i) {
$rand .= chr(mt_rand(0, 255));
}
// Add the microtime for a little more entropy.
$rand .= microtime();
// Mix the bits cryptographically into a 20-byte binary string.
$rand = sha1($rand, true);
// Form the prefix that specifies Blowfish algorithm and cost parameter.
$salt = sprintf("$2y$%02d$", $cost);
// Append the random salt data in the required base64 format.
$salt .= str_replace('+', '.', substr(base64_encode($rand), 0, 22));
return $salt;
}
}
\ No newline at end of file
......@@ -14,112 +14,6 @@ namespace yii\helpers;
* @author Alex Makarov <sam@rmcreative.ru>
* @since 2.0
*/
class StringHelper
class StringHelper extends base\StringHelper
{
/**
* Returns the number of bytes in the given string.
* This method ensures the string is treated as a byte array.
* It will use `mb_strlen()` if it is available.
* @param string $string the string being measured for length
* @return integer the number of bytes in the given string.
*/
public static function strlen($string)
{
return function_exists('mb_strlen') ? mb_strlen($string, '8bit') : strlen($string);
}
/**
* Returns the portion of string specified by the start and length parameters.
* This method ensures the string is treated as a byte array.
* It will use `mb_substr()` if it is available.
* @param string $string the input string. Must be one character or longer.
* @param integer $start the starting position
* @param integer $length the desired portion length
* @return string the extracted part of string, or FALSE on failure or an empty string.
* @see http://www.php.net/manual/en/function.substr.php
*/
public static function substr($string, $start, $length)
{
return function_exists('mb_substr') ? mb_substr($string, $start, $length, '8bit') : substr($string, $start, $length);
}
/**
* Converts a word to its plural form.
* Note that this is for English only!
* For example, 'apple' will become 'apples', and 'child' will become 'children'.
* @param string $name the word to be pluralized
* @return string the pluralized word
*/
public static function pluralize($name)
{
static $rules = array(
'/(m)ove$/i' => '\1oves',
'/(f)oot$/i' => '\1eet',
'/(c)hild$/i' => '\1hildren',
'/(h)uman$/i' => '\1umans',
'/(m)an$/i' => '\1en',
'/(s)taff$/i' => '\1taff',
'/(t)ooth$/i' => '\1eeth',
'/(p)erson$/i' => '\1eople',
'/([m|l])ouse$/i' => '\1ice',
'/(x|ch|ss|sh|us|as|is|os)$/i' => '\1es',
'/([^aeiouy]|qu)y$/i' => '\1ies',
'/(?:([^f])fe|([lr])f)$/i' => '\1\2ves',
'/(shea|lea|loa|thie)f$/i' => '\1ves',
'/([ti])um$/i' => '\1a',
'/(tomat|potat|ech|her|vet)o$/i' => '\1oes',
'/(bu)s$/i' => '\1ses',
'/(ax|test)is$/i' => '\1es',
'/s$/' => 's',
);
foreach ($rules as $rule => $replacement) {
if (preg_match($rule, $name)) {
return preg_replace($rule, $replacement, $name);
}
}
return $name . 's';
}
/**
* Converts a CamelCase name into space-separated words.
* For example, 'PostTag' will be converted to 'Post Tag'.
* @param string $name the string to be converted
* @param boolean $ucwords whether to capitalize the first letter in each word
* @return string the resulting words
*/
public static function camel2words($name, $ucwords = true)
{
$label = trim(strtolower(str_replace(array('-', '_', '.'), ' ', preg_replace('/(?<![A-Z])[A-Z]/', ' \0', $name))));
return $ucwords ? ucwords($label) : $label;
}
/**
* Converts a CamelCase name into an ID in lowercase.
* Words in the ID may be concatenated using the specified character (defaults to '-').
* For example, 'PostTag' will be converted to 'post-tag'.
* @param string $name the string to be converted
* @param string $separator the character used to concatenate the words in the ID
* @return string the resulting ID
*/
public static function camel2id($name, $separator = '-')
{
if ($separator === '_') {
return trim(strtolower(preg_replace('/(?<![A-Z])[A-Z]/', '_\0', $name)), '_');
} else {
return trim(strtolower(str_replace('_', $separator, preg_replace('/(?<![A-Z])[A-Z]/', $separator . '\0', $name))), $separator);
}
}
/**
* Converts an ID into a CamelCase name.
* Words in the ID separated by `$separator` (defaults to '-') will be concatenated into a CamelCase name.
* For example, 'post-tag' is converted to 'PostTag'.
* @param string $id the ID to be converted
* @param string $separator the character used to separate the words in the ID
* @return string the resulting CamelCase name
*/
public static function id2camel($id, $separator = '-')
{
return str_replace(' ', '', ucwords(implode(' ', explode($separator, $id))));
}
}
......@@ -23,112 +23,6 @@ namespace yii\helpers;
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class CVarDumper
class VarDumper extends base\VarDumper
{
private static $_objects;
private static $_output;
private static $_depth;
/**
* Displays a variable.
* This method achieves the similar functionality as var_dump and print_r
* but is more robust when handling complex objects such as Yii controllers.
* @param mixed $var variable to be dumped
* @param integer $depth maximum depth that the dumper should go into the variable. Defaults to 10.
* @param boolean $highlight whether the result should be syntax-highlighted
*/
public static function dump($var, $depth = 10, $highlight = false)
{
echo self::dumpAsString($var, $depth, $highlight);
}
/**
* Dumps a variable in terms of a string.
* This method achieves the similar functionality as var_dump and print_r
* but is more robust when handling complex objects such as Yii controllers.
* @param mixed $var variable to be dumped
* @param integer $depth maximum depth that the dumper should go into the variable. Defaults to 10.
* @param boolean $highlight whether the result should be syntax-highlighted
* @return string the string representation of the variable
*/
public static function dumpAsString($var, $depth = 10, $highlight = false)
{
self::$_output = '';
self::$_objects = array();
self::$_depth = $depth;
self::dumpInternal($var, 0);
if ($highlight) {
$result = highlight_string("<?php\n" . self::$_output, true);
self::$_output = preg_replace('/&lt;\\?php<br \\/>/', '', $result, 1);
}
return self::$_output;
}
/*
* @param mixed $var variable to be dumped
* @param integer $level depth level
*/
private static function dumpInternal($var, $level)
{
switch (gettype($var)) {
case 'boolean':
self::$_output .= $var ? 'true' : 'false';
break;
case 'integer':
self::$_output .= "$var";
break;
case 'double':
self::$_output .= "$var";
break;
case 'string':
self::$_output .= "'" . addslashes($var) . "'";
break;
case 'resource':
self::$_output .= '{resource}';
break;
case 'NULL':
self::$_output .= "null";
break;
case 'unknown type':
self::$_output .= '{unknown}';
break;
case 'array':
if (self::$_depth <= $level) {
self::$_output .= 'array(...)';
} elseif (empty($var)) {
self::$_output .= 'array()';
} else {
$keys = array_keys($var);
$spaces = str_repeat(' ', $level * 4);
self::$_output .= "array\n" . $spaces . '(';
foreach ($keys as $key) {
self::$_output .= "\n" . $spaces . ' ';
self::dumpInternal($key, 0);
self::$_output .= ' => ';
self::dumpInternal($var[$key], $level + 1);
}
self::$_output .= "\n" . $spaces . ')';
}
break;
case 'object':
if (($id = array_search($var, self::$_objects, true)) !== false) {
self::$_output .= get_class($var) . '#' . ($id + 1) . '(...)';
} elseif (self::$_depth <= $level) {
self::$_output .= get_class($var) . '(...)';
} else {
$id = self::$_objects[] = $var;
$className = get_class($var);
$members = (array)$var;
$spaces = str_repeat(' ', $level * 4);
self::$_output .= "$className#$id\n" . $spaces . '(';
foreach ($members as $key => $value) {
$keyDisplay = strtr(trim($key), array("\0" => ':'));
self::$_output .= "\n" . $spaces . " [$keyDisplay] => ";
self::dumpInternal($value, $level + 1);
}
self::$_output .= "\n" . $spaces . ')';
}
break;
}
}
}
\ No newline at end of file
<?php
/**
* Filesystem helper class file.
*
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\helpers\base;
use Yii;
/**
* Filesystem helper
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Alex Makarov <sam@rmcreative.ru>
* @since 2.0
*/
class FileHelper
{
/**
* Normalizes a file/directory path.
* After normalization, the directory separators in the path will be `DIRECTORY_SEPARATOR`,
* and any trailing directory separators will be removed. For example, '/home\demo/' on Linux
* will be normalized as '/home/demo'.
* @param string $path the file/directory path to be normalized
* @param string $ds the directory separator to be used in the normalized result. Defaults to `DIRECTORY_SEPARATOR`.
* @return string the normalized file/directory path
*/
public static function normalizePath($path, $ds = DIRECTORY_SEPARATOR)
{
return rtrim(strtr($path, array('/' => $ds, '\\' => $ds)), $ds);
}
/**
* Returns the localized version of a specified file.
*
* The searching is based on the specified language code. In particular,
* a file with the same name will be looked for under the subdirectory
* whose name is the same as the language code. For example, given the file "path/to/view.php"
* and language code "zh_CN", the localized file will be looked for as
* "path/to/zh_CN/view.php". If the file is not found, the original file
* will be returned.
*
* If the target and the source language codes are the same,
* the original file will be returned.
*
* @param string $file the original file
* @param string $language the target language that the file should be localized to.
* If not set, the value of [[\yii\base\Application::language]] will be used.
* @param string $sourceLanguage the language that the original file is in.
* If not set, the value of [[\yii\base\Application::sourceLanguage]] will be used.
* @return string the matching localized file, or the original file if the localized version is not found.
* If the target and the source language codes are the same, the original file will be returned.
*/
public static function localize($file, $language = null, $sourceLanguage = null)
{
if ($language === null) {
$language = Yii::$app->language;
}
if ($sourceLanguage === null) {
$sourceLanguage = Yii::$app->sourceLanguage;
}
if ($language === $sourceLanguage) {
return $file;
}
$desiredFile = dirname($file) . DIRECTORY_SEPARATOR . $sourceLanguage . DIRECTORY_SEPARATOR . basename($file);
return is_file($desiredFile) ? $desiredFile : $file;
}
/**
* Determines the MIME type of the specified file.
* This method will first try to determine the MIME type based on
* [finfo_open](http://php.net/manual/en/function.finfo-open.php). If this doesn't work, it will
* fall back to [[getMimeTypeByExtension()]].
* @param string $file the file name.
* @param string $magicFile name of the optional magic database file, usually something like `/path/to/magic.mime`.
* This will be passed as the second parameter to [finfo_open](http://php.net/manual/en/function.finfo-open.php).
* @param boolean $checkExtension whether to use the file extension to determine the MIME type in case
* `finfo_open()` cannot determine it.
* @return string the MIME type (e.g. `text/plain`). Null is returned if the MIME type cannot be determined.
*/
public static function getMimeType($file, $magicFile = null, $checkExtension = true)
{
if (function_exists('finfo_open')) {
$info = finfo_open(FILEINFO_MIME_TYPE, $magicFile);
if ($info) {
$result = finfo_file($info, $file);
finfo_close($info);
if ($result !== false) {
return $result;
}
}
}
return $checkExtension ? self::getMimeTypeByExtension($file) : null;
}
/**
* Determines the MIME type based on the extension name of the specified file.
* This method will use a local map between extension names and MIME types.
* @param string $file the file name.
* @param string $magicFile the path of the file that contains all available MIME type information.
* If this is not set, the default file aliased by `@yii/util/mimeTypes.php` will be used.
* @return string the MIME type. Null is returned if the MIME type cannot be determined.
*/
public static function getMimeTypeByExtension($file, $magicFile = null)
{
static $mimeTypes = array();
if ($magicFile === null) {
$magicFile = __DIR__ . '/mimeTypes.php';
}
if (!isset($mimeTypes[$magicFile])) {
$mimeTypes[$magicFile] = require($magicFile);
}
if (($ext = pathinfo($file, PATHINFO_EXTENSION)) !== '') {
$ext = strtolower($ext);
if (isset($mimeTypes[$magicFile][$ext])) {
return $mimeTypes[$magicFile][$ext];
}
}
return null;
}
/**
* Copies a whole directory as another one.
* The files and sub-directories will also be copied over.
* @param string $src the source directory
* @param string $dst the destination directory
* @param array $options options for directory copy. Valid options are:
*
* - dirMode: integer, the permission to be set for newly copied directories. Defaults to 0777.
* - fileMode: integer, the permission to be set for newly copied files. Defaults to the current environment setting.
* - beforeCopy: callback, a PHP callback that is called before copying each sub-directory or file.
* If the callback returns false, the copy operation for the sub-directory or file will be cancelled.
* The signature of the callback should be: `function ($from, $to)`, where `$from` is the sub-directory or
* file to be copied from, while `$to` is the copy target.
* - afterCopy: callback, a PHP callback that is called after a sub-directory or file is successfully copied.
* The signature of the callback is similar to that of `beforeCopy`.
*/
public static function copyDirectory($src, $dst, $options = array())
{
if (!is_dir($dst)) {
mkdir($dst, isset($options['dirMode']) ? $options['dirMode'] : 0777, true);
}
$handle = opendir($src);
while (($file = readdir($handle)) !== false) {
if ($file === '.' || $file === '..') {
continue;
}
$from = $src . DIRECTORY_SEPARATOR . $file;
$to = $dst . DIRECTORY_SEPARATOR . $file;
if (!isset($options['beforeCopy']) || call_user_func($options['beforeCopy'], $from, $to)) {
if (is_file($from)) {
copy($from, $to);
if (isset($options['fileMode'])) {
@chmod($to, $options['fileMode']);
}
} else {
static::copyDirectory($from, $to, $options);
}
if (isset($options['afterCopy'])) {
call_user_func($options['afterCopy'], $from, $to);
}
}
}
closedir($handle);
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment