diff --git a/apps/advanced/common/models/User.php b/apps/advanced/common/models/User.php index ace9324..d51d7ec 100644 --- a/apps/advanced/common/models/User.php +++ b/apps/advanced/common/models/User.php @@ -16,7 +16,6 @@ use yii\web\IdentityInterface; * @property string $password_reset_token * @property string $email * @property string $auth_key - * @property integer $role * @property integer $status * @property integer $created_at * @property integer $updated_at @@ -26,7 +25,6 @@ class User extends ActiveRecord implements IdentityInterface { const STATUS_DELETED = 0; const STATUS_ACTIVE = 10; - const ROLE_USER = 10; /** * @inheritdoc @@ -54,9 +52,6 @@ class User extends ActiveRecord implements IdentityInterface return [ ['status', 'default', 'value' => self::STATUS_ACTIVE], ['status', 'in', 'range' => [self::STATUS_ACTIVE, self::STATUS_DELETED]], - - ['role', 'default', 'value' => self::ROLE_USER], - ['role', 'in', 'range' => [self::ROLE_USER]], ]; } diff --git a/apps/advanced/console/migrations/m130524_201442_init.php b/apps/advanced/console/migrations/m130524_201442_init.php index 5119f33..38d0cfe 100644 --- a/apps/advanced/console/migrations/m130524_201442_init.php +++ b/apps/advanced/console/migrations/m130524_201442_init.php @@ -20,7 +20,6 @@ class m130524_201442_init extends Migration 'password_hash' => Schema::TYPE_STRING . ' NOT NULL', 'password_reset_token' => Schema::TYPE_STRING, 'email' => Schema::TYPE_STRING . ' NOT NULL', - 'role' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 10', 'status' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 10', 'created_at' => Schema::TYPE_INTEGER . ' NOT NULL', diff --git a/apps/advanced/frontend/models/SignupForm.php b/apps/advanced/frontend/models/SignupForm.php index 17bcdeb..c071f83 100644 --- a/apps/advanced/frontend/models/SignupForm.php +++ b/apps/advanced/frontend/models/SignupForm.php @@ -48,8 +48,9 @@ class SignupForm extends Model $user->email = $this->email; $user->setPassword($this->password); $user->generateAuthKey(); - $user->save(); - return $user; + if ($user->save()) { + return $user; + } } return null; diff --git a/apps/advanced/frontend/widgets/Alert.php b/apps/advanced/frontend/widgets/Alert.php index fa755da..5b20488 100644 --- a/apps/advanced/frontend/widgets/Alert.php +++ b/apps/advanced/frontend/widgets/Alert.php @@ -58,12 +58,12 @@ class Alert extends \yii\bootstrap\Widget foreach ($flashes as $type => $data) { if (isset($this->alertTypes[$type])) { $data = (array) $data; - foreach ($data as $message) { + foreach ($data as $i => $message) { /* initialize css class for each alert box */ $this->options['class'] = $this->alertTypes[$type] . $appendCss; /* assign unique id to each alert box */ - $this->options['id'] = $this->getId() . '-' . $type; + $this->options['id'] = $this->getId() . '-' . $type . '-' . $i; echo \yii\bootstrap\Alert::widget([ 'body' => $message, diff --git a/apps/basic/tests/README.md b/apps/basic/tests/README.md index f14db26..8134fb3 100644 --- a/apps/basic/tests/README.md +++ b/apps/basic/tests/README.md @@ -59,5 +59,53 @@ codecept run functional codecept run unit ``` +Code coverage support +--------------------- + +By default, code coverage is disabled in `codeception.yml` configuration file, you should uncomment needed rows to be able +to collect code coverage. You can run your tests and collect coverage with the following command: + +``` +#collect coverage for all tests +codecept run --coverage-html --coverage-xml + +#collect coverage only for unit tests +codecept run unit --coverage-html --coverage-xml + +#collect coverage for unit and functional tests +codecept run functional,unit --coverage-html --coverage-xml +``` + +You can see code coverage output under the `tests/_output` directory. + +###Remote code coverage + +When you run your tests not in the same process where code coverage is collected, then you should uncomment `remote` option and its +related options, to be able to collect code coverage correctly. To setup remote code coverage you should follow [instructions](http://codeception.com/docs/11-Codecoverage) +from codeception site. + +1. install `Codeception c3` remote support `composer require "codeception/c3:*"`; + +2. copy `c3.php` file under your `web` directory; + +3. include `c3.php` file in your `index-test.php` file before application run, so it can catch needed requests. + +Configuration options that are used by remote code coverage: + +- c3_url: url pointing to entry script that includes `c3.php` file, so `Codeception` will be able to produce code coverage; +- remote: whether to enable remote code coverage or not; +- remote_config: path to the `codeception.yml` configuration file, from the directory where `c3.php` file is located. This is needed + so that `Codeception` can create itself instance and collect code coverage correctly. + +By default `c3_url` and `remote_config` setup correctly, you only need to copy and include `c3.php` file in your `index-test.php` + +After that you should be able to collect code coverage from tests that run through `PhpBrowser` or `WebDriver` with same command +as for other tests: + +``` +#collect coverage from remote +codecept run acceptance --coverage-html --coverage-xml +``` + Please refer to [Codeception tutorial](http://codeception.com/docs/01-Introduction) for more details about writing and running acceptance, functional and unit tests. diff --git a/apps/basic/tests/codeception.yml b/apps/basic/tests/codeception.yml index 0a45a0c..b71ba31 100644 --- a/apps/basic/tests/codeception.yml +++ b/apps/basic/tests/codeception.yml @@ -1,4 +1,24 @@ actor: Tester +#coverage: +# #c3_url: http://localhost:8080/index-test.php/ +# enabled: true +# #remote: true +# #remote_config: '../tests/codeception.yml' +# white_list: +# include: +# - ../models/* +# - ../controllers/* +# - ../commands/* +# - ../mail/* +# blacklist: +# include: +# - ../assets/* +# - ../config/* +# - ../runtime/* +# - ../vendor/* +# - ../views/* +# - ../web/* +# - ../tests/* paths: tests: codeception log: codeception/_output diff --git a/build/controllers/ReleaseController.php b/build/controllers/ReleaseController.php index ffe19ec..6e5f65f 100644 --- a/build/controllers/ReleaseController.php +++ b/build/controllers/ReleaseController.php @@ -8,6 +8,7 @@ namespace yii\build\controllers; use Yii; +use yii\base\Exception; use yii\console\Controller; /** @@ -30,6 +31,8 @@ class ReleaseController extends Controller */ public function actionPrepare($version) { + $this->resortChangelogs($version); + $this->mergeChangelogs($version); $this->closeChangelogs($version); $this->composerSetStability($version); $this->updateYiiVersion($version); @@ -77,9 +80,95 @@ class ReleaseController extends Controller } } + protected function resortChangelogs($version) + { + foreach($this->getChangelogs() as $file) { + // split the file into relevant parts + list($start, $changelog, $end) = $this->splitChangelog($file, $version); + $changelog = $this->resortChangelog($changelog); + file_put_contents($file, implode("\n", array_merge($start, $changelog, $end))); + } + } + + protected function mergeChangelogs($version) + { + $file = $this->getFrameworkChangelog(); + // split the file into relevant parts + list($start, $changelog, $end) = $this->splitChangelog($file, $version); + + $changelog = $this->resortChangelog($changelog); + + $changelog[] = ''; + $extensions = $this->getExtensionChangelogs(); + asort($extensions); + foreach($extensions as $changelogFile) { + if (!preg_match('~extensions/([a-z]+)/CHANGELOG\\.md~', $changelogFile, $m)) { + throw new Exception("Illegal extension changelog file: " . $changelogFile); + } + list( , $extensionChangelog, ) = $this->splitChangelog($changelogFile, $version); + $name = $m[1]; + $ucname = ucfirst($name); + $changelog[] = "### $ucname Extension (yii2-$name)"; + $changelog = array_merge($changelog, $extensionChangelog); + } + + file_put_contents($file, implode("\n", array_merge($start, $changelog, $end))); + } + + /** + * Extract changelog content for a specific version + */ + protected function splitChangelog($file, $version) + { + $lines = explode("\n", file_get_contents($file)); + + // split the file into relevant parts + $start = []; + $changelog = []; + $end = []; + + $state = 'start'; + foreach($lines as $l => $line) { + // starting from the changelogs headline + if (isset($lines[$l-2]) && strpos($lines[$l-2], $version) !== false && + isset($lines[$l-1]) && strncmp($lines[$l-1], '---', 3) === 0) { + $state = 'changelog'; + } + if ($state === 'changelog' && isset($lines[$l+1]) && strncmp($lines[$l+1], '---', 3) === 0) { + $state = 'end'; + } + ${$state}[] = $line; + } + return [$start, $changelog, $end]; + } + + /** + * Ensure sorting of the changelog lines + */ + protected function resortChangelog($changelog) + { + // cleanup whitespace + foreach($changelog as $i => $line) { + $changelog[$i] = rtrim($line); + } + + // TODO sorting + return $changelog; + } + protected function getChangelogs() { - return array_merge([YII2_PATH . '/CHANGELOG.md'], glob(dirname(YII2_PATH) . '/extensions/*/CHANGELOG.md')); + return array_merge([$this->getFrameworkChangelog()], $this->getExtensionChangelogs()); + } + + protected function getFrameworkChangelog() + { + return YII2_PATH . '/CHANGELOG.md'; + } + + protected function getExtensionChangelogs() + { + return glob(dirname(YII2_PATH) . '/extensions/*/CHANGELOG.md'); } protected function composerSetStability($version) diff --git a/docs/guide-es/README.md b/docs/guide-es/README.md index 1242fe4..e93845c 100644 --- a/docs/guide-es/README.md +++ b/docs/guide-es/README.md @@ -62,7 +62,7 @@ Conceptos clave * [Componentes](concept-components.md) * [Propiedades](concept-properties.md) -* **TBD** [Eventos](concept-events.md) +* [Eventos](concept-events.md) * [Comportamientos (Behaviors)](concept-behaviors.md) * [Configuraciones](concept-configurations.md) * [Alias](concept-aliases.md) @@ -72,12 +72,16 @@ Conceptos clave Trabajar con bases de datos ------------------------------ - -* [Objeto de acceso a datos](db-dao.md) - Conexión a una base de datos, consultas básicas, transacciones y manipulación de esquemas -* **TBD** [Constructor de consultas](db-query-builder.md) - Consulta de la base de datos utilizando una simple capa de abstracción -* **TBD** [Active Record](db-active-record.md) - ORM Active Record, recuperación y manipulación de registros y definición de relaciones -* **TBD** [Migraciones](db-migrations.md) - Control de versiones de bases de datos en el entorno de desarrollo en equipo +--------------------------- + +* [Objeto de acceso a datos](db-dao.md) - Conexión a una base de datos, consultas básicas, transacciones y + manipulación de esquemas +* [Constructor de consultas](db-query-builder.md) - Consulta de la base de datos utilizando una simple capa de + abstracción +* **TBD** [Active Record](db-active-record.md) - ORM Active Record, recuperación y manipulación de registros y + definición de relaciones +* **TBD** [Migraciones](db-migrations.md) - Control de versiones de bases de datos en el entorno de desarrollo en + equipo * **TBD** [Sphinx](db-sphinx.md) * **TBD** [Redis](db-redis.md) * **TBD** [MongoDB](db-mongodb.md) @@ -102,7 +106,7 @@ Visualizar datos * **TBD** [Proveedores de datos](output-data-providers.md) * **TBD** [Widgets de datos](output-data-widgets.md) * **TBD** [Trabajar con scripts de cliente](output-client-scripts.md) -* **TBD** [Utilización de temas](output-theming.md) +* [Temas](output-theming.md) Seguridad @@ -193,7 +197,6 @@ Clases auxiliares * **TBD** [Información general](helper-overview.md) * **TBD** [ArrayHelper](helper-array.md) -* **TBD** [Html](helper-html.md) -* **TBD** [Url](helper-url.md) +* [Html](helper-html.md) +* [Url](helper-url.md) * **TBD** [Security](helper-security.md) - diff --git a/docs/guide-es/concept-events.md b/docs/guide-es/concept-events.md new file mode 100644 index 0000000..b02ec88 --- /dev/null +++ b/docs/guide-es/concept-events.md @@ -0,0 +1,291 @@ +Eventos +======= + +Los eventos permiten inyectar código dentro de otro código existente en ciertos puntos de ejecución. Se pueden adjuntar +código personalizado a un evento, cuando se lance (triggered), el código se ejecutará automáticamente. Por ejemplo, un +objeto `mailer` puede lanzar el evento `messageSent` cuando se envía un mensaje correctamente. Si se quiere rastrear +el correcto envío del mensaje, se puede, simplemente, añadir un código de seguimiento al evento `messageSent`. + +Yii introduce una clase base [[yii\base\Component]] para soportar eventos. Si una clase necesita lanzar un evento, +este debe extender a [[yii\base\Component]] o a una clase hija. + +Gestor de Eventos +----------------- + +Un gestor de eventos es una +[llamada de retorno PHP (PHP callback)](http://php.net/manual/es/language.types.callable.php) que se ejecuta cuando se +lanza el evento al que corresponde. Se puede usar cualquier llamada de retorno de las enumeradas a continuación: + +- una función de PHP global especificada como una cadena de texto (sin paréntesis), p. ej. `'trim'`; +- un método de objeto especificado como un array de un objeto y un nombre de método como una cadena de texto + (sin paréntesis), p. ej. `[$object, 'methodNAme']`; +- un método de clase estático especificado como un array de un nombre de clase y un método como una cadena de texto + (sin paréntesis), p. ej. `[$class, 'methodName']`; +- una función anónima, p. ej. `function ($event) { ... }`. + +La firma de un gestor de eventos es: + +```php +function ($event) { + // $event es un objeto de yii\base\Event o de una clase hija +} +``` + +Un gestor de eventos puede obtener la siguiente información acerca de un evento ya sucedido mediante el parámetro +`$event`: + +- [[yii\base\Event::name|event name]] +- [[yii\base\Event::sender|event sender]]: el objeto desde el que se ha ejecutado `trigger()` +- [[yii\base\Event::data|custom data]]: los datos que se proporcionan al adjuntar el gestor de eventos + (se explicará más adelante) + +Añadir Gestores de Eventos +-------------------------- + +Se puede añadir un gestor a un evento llamando al método [[yii\base\Component::on()]]. Por ejemplo: + +```php +$foo = new Foo; + +// este gestor es una función global +$foo->on(Foo::EVENT_HELLO, 'function_name'); + +// este gestor es un método de objeto +$foo->on(Foo::EVENT_HELLO, [$object, 'methodName']); + +// este gestor es un método de clase estática +$foo->on(Foo::EVENT_HELLO, ['app\components\Bar', 'methodName']); + +// este gestor es una función anónima +$foo->on(Foo::EVENT_HELLO, function ($event) { + // event handling logic +}); +``` + +También se pueden adjuntar gestores de eventos mediante [configuraciones](concept-configurations.md). Se pueden +encontrar más de talles en la sección [Configuraciones](concept-configurations.md#configuration-format). + +Cuando se adjunta un gestor de eventos, se pueden proporcionar datos adicionales como tercer parámetro de +[[yii\base\Component::on()]]. El gestor podrá acceder a los datos cuando se lance el evento y se ejecute el gestor. +Por ejemplo: + +```php +// El siguiente código muestra "abc" cuando se lanza el evento +// ya que $event->data contiene los datos enviados en el tercer parámetro de "on" +$foo->on(Foo::EVENT_HELLO, 'function_name', 'abc'); + +function function_name($event) { + echo $event->data; +} +``` + +Ordenación de Gestores de Eventos +--------------------------------- + +Se puede adjuntar uno o más gestores a un único evento. Cuando se lanza un evento, se ejecutarán los gestores adjuntos +en el orden que se hayan añadido al evento. Si un gestor necesita parar la invocación de los gestores que le siguen, +se puede establecer la propiedad [[yii\base\Event::handled]] del parámetro `$event` para que sea `true`: + +```php +$foo->on(Foo::EVENT_HELLO, function ($event) { + $event->handled = true; +}); +``` + +De forma predeterminada, cada nuevo gestor añadido se pone a la cola de la lista de gestores del evento. Por lo tanto, +el gestor se ejecutará en el último lugar cuando se lance el evento. Para insertar un nuevo gestor al principio de la +cola de gestores para que sea ejecutado primero, se debe llamar a [[yii\base\Component::on()]], pasando al cuarto +parámetro `$append` el valor `false`: + +```php +$foo->on(Foo::EVENT_HELLO, function ($event) { + // ... +}, $data, false); +``` + +Lanzamiento de Eventos +---------------------- + +Los eventos se lanzan llamando al método [[yii\base\Component::trigger()]]. El método requiere un *nombre de evento*, +y de forma opcional un objeto de evento que describa los parámetros que se enviarán a los gestores de eventos. Por +ejemplo: + +```php +namespace app\components; + +use yii\base\Component; +use yii\base\Event; + +class Foo extends Component +{ + const EVENT_HELLO = 'hello'; + + public function bar() + { + $this->trigger(self::EVENT_HELLO); + } +} +``` + +Con el código anterior, cada llamada a `bar()` lanzará un evento llamado `hello` + +> Consejo: Se recomienda usar las constantes de clase para representar nombres de eventos. En el anterior ejemplo, la + constante `EVENT_HELLO` representa el evento `hello`. Este enfoque proporciona tres beneficios. Primero, previene + errores tipográficos. Segundo, puede hacer que los IDEs reconozcan los eventos en las funciones de auto-completado. + Tercero, se puede ver que eventos soporta una clase simplemente revisando la declaración de constantes. + +A veces cuando se lanza un evento se puede querer pasar información adicional al gestor de eventos. Por ejemplo, un +`mailer` puede querer enviar la información del mensaje para que los gestores del evento `messageSent` para que los +gestores puedan saber las particularidades del mensaje enviado. Para hacerlo, se puede proporcionar un objeto de tipo +evento como segundo parámetro al método [[yii\base\Component::trigger()]]. El objeto de tipo evento debe ser una +instancia de la clase [[yii\base\Event]] o de sus hijas. Por ejemplo: + +```php +namespace app\components; + +use yii\base\Component; +use yii\base\Event; + +class MessageEvent extends Event +{ + public $message; +} + +class Mailer extends Component +{ + const EVENT_MESSAGE_SENT = 'messageSent'; + + public function send($message) + { + // ...enviando $message... + + $event = new MessageEvent; + $event->message = $message; + $this->trigger(self::EVENT_MESSAGE_SENT, $event); + } +} +``` + +Cuando se lanza el método [[yii\base\Component::trigger()]], se ejecutarán todos los gestores adjuntos al evento. + +Desadjuntar Gestores de Evento +------------------------------ + +Para desadjuntar un gestor de un evento, se puede ejecutar el método [[yii\base\Component::off()]]. Por ejemplo: + +```php +// el gestor es una función global +$foo->off(Foo::EVENT_HELLO, 'function_name'); + +// el gestor es un método de objeto +$foo->off(Foo::EVENT_HELLO, [$object, 'methodName']); + +// el gestor es un método estático de clase +$foo->off(Foo::EVENT_HELLO, ['app\components\Bar', 'methodName']); + +// el gestor es una función anónima +$foo->off(Foo::EVENT_HELLO, $anonymousFunction); +``` + +Tenga en cuenta que en general no se debe intentar desadjuntar las funciones anónimas a no ser que se almacene donde +se ha adjuntado al evento. En el anterior ejemplo, se asume que la función anónima se almacena como variable +`$anonymousFunction`. + +Para desadjuntar TODOS los gestores de un evento, se puede llamar [[yii\base\Component::off()]] sin el segundo +parámetro: + +```php +$foo->off(Foo::EVENT_HELLO); +``` + +Nivel de Clase (Class-Level) Gestores de Eventos +------------------------------------------------ + +En las subsecciones anteriores se ha descrito como adjuntar un gestor a un evento a *nivel de instancia*. A veces, se +puede querer que un gestor responda todos los eventos de *todos* las instancias de una clase en lugar de una instancia +especifica. En lugar de adjuntar un gestor de eventos a una instancia, se puede adjuntar un gestor a *nivel de clase* +llamando al método estático [[yii\base\Event::on()]]. + +Por ejemplo, un objeto de tipo [Active Record](db-active-record.md) lanzará un evento +[[yii\db\BaseActiveRecord::EVENT_AFTER_INSERT|EVENT_AFTER_INSERT]] cada vez que inserte un nuevo registro en la base +de datos. Para poder registrar las inserciones efectuadas por *todos* los objetos +[Active Record](db-active-record.md), se puede usar el siguiente código: + +```php +use Yii; +use yii\base\Event; +use yii\db\ActiveRecord; + +Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function ($event) { + Yii::trace(get_class($event->sender) . ' is inserted'); +}); +``` + +Se invocará al gestor de eventos cada vez que una instancia de [[yii\db\ActiveRecord|ActiveRecord]], o de uno de sus +clases hijas, lance un evento de tipo [[yii\db\BaseActiveRecord::EVENT_AFTER_INSERT|EVENT_AFTER_INSERT]]. Se puede +obtener el objeto que ha lanzado el evento mediante `$event->sender` en el gestor. + +Cuando un objeto lanza un evento, primero llamará los gestores a nivel de instancia, y a continuación los gestores a +nivel de clase. + +Se puede lanzar un evento de tipo *nivel de clase* llamando al método estático [[yii\base\Event::trigger()]]. Un +evento de nivel de clase no se asocia a un objeto en particular. Como resultado, esto provocará solamente la +invocación de los gestores de eventos a nivel de clase. + +```php +use yii\base\Event; + +Event::on(Foo::className(), Foo::EVENT_HELLO, function ($event) { + echo $event->sender; // displays "app\models\Foo" +}); + +Event::trigger(Foo::className(), Foo::EVENT_HELLO); +``` + +Tenga en cuenta que en este caso, el `$event->sender` hace referencia al nombre de la clase que lanza el evento en +lugar de a la instancia del objeto. + +> Nota: Debido a que los gestores a nivel de clase responderán a los eventos lanzados por cualquier instancia de la +clase, o cualquier clase hija, se debe usar con cuidado, especialmente en las clases de bajo nivel (low-level), tales +como [[yii\base\Object]]. + +Para desadjuntar un gestor de eventos a nivel de clase, se tiene que llamar a [[yii\base\Event::off()]]. Por ejemplo: + +```php +// desadjunta $handler +Event::off(Foo::className(), Foo::EVENT_HELLO, $handler); + +// desadjunta todos los gestores de Foo::EVENT_HELLO +Event::off(Foo::className(), Foo::EVENT_HELLO); +``` + +Eventos Globales +---------------- + +Yii soporta los llamados *eventos globales*, que en realidad es un truco basado en el gestor de eventos descrito +anteriormente. El evento global requiere un Singleton globalmente accesible, tal como la instancia de +[aplicación](structure-applications.md) en si misma. + +Para crear un evento global, un evento remitente (event sender) llama al método `trigger()` del Singleton para lanzar +el evento, en lugar de llamar al propio método `trigger()` del remitente. De forma similar, los gestores de eventos se +adjuntan al evento del Singleton. Por ejemplo: + +```php +use Yii; +use yii\base\Event; +use app\components\Foo; + +Yii::$app->on('bar', function ($event) { + echo get_class($event->sender); // muestra "app\components\Foo" +}); + +Yii::$app->trigger('bar', new Event(['sender' => new Foo])); +``` + +Un beneficio de usar eventos globales es que no se necesita un objeto cuando se adjuntan gestores a un evento para que +sean lanzados por el objeto. En su lugar, los gestores adjuntos y el lanzamiento de eventos se efectúan en el +Singleton (p. ej. la instancia de la aplicación). + +Sin embargo, debido a que los `namespaces` de los eventos globales son compartidos por todas partes, se les deben +asignar nombres bien pensados, como puede ser la introducción de algún `namespace` +(p. ej. "frontend.mail.sent", "backend.mail.sent"). diff --git a/docs/guide-es/db-query-builder.md b/docs/guide-es/db-query-builder.md new file mode 100644 index 0000000..5630f60 --- /dev/null +++ b/docs/guide-es/db-query-builder.md @@ -0,0 +1,470 @@ +Constructor de Consultas +======================== + +> Nota: Esta sección está en desarrollo. + +Yii proporciona una capa de acceso básico a bases de datos como se describe en la sección +[Objetos de Acceso a Bases de Datos](db-dao.md). La capa de acceso a bases de datos proporciona un método de bajo +nivel (low-level) para interaccionar con la base de datos. Aunque a veces puede ser útil la escritura de sentencias +SQLs puras, en otras situaciones puede ser pesado y propenso a errores. Otra manera de tratar con bases de datos puede +ser el uso de Constructores de Consultas (Query Builder). El Constructor de Consultas proporciona un medio orientado a +objetos para generar las consultas que se ejecutarán. + +Un uso típico de Constructor de Consultas puede ser el siguiente: + +```php +$rows = (new \yii\db\Query()) + ->select('id, name') + ->from('user') + ->limit(10) + ->all(); + +// que es equivalente al siguiente código: + +$query = (new \yii\db\Query()) + ->select('id, name') + ->from('user') + ->limit(10); + +// Crear un comando. Se puede obtener la consulta SQL actual utilizando $command->sql +$command = $query->createCommand(); + +// Ejecutar el comando: +$rows = $command->queryAll(); +``` + +Métodos de Consulta +------------------- + +Como se puede observar, primero se debe tratar con [[yii\db\Query]]. En realidad, `Query` sólo se encarga de +representar diversa información de la consulta. La lógica para generar la consulta se efectúa mediante +[[yii\db\QueryBuilder]] cuando se llama al método `createCommand()`, y la ejecución de la consulta la efectúa +[[yii\db\Command]]. + +Se ha establecido, por convenio, que [[yii\db\Query]] proporcione un conjunto de métodos de consulta comunes que +construirán la consulta, la ejecutarán, y devolverán el resultado. Por ejemplo: + +- [[yii\db\Query::all()|all()]]: construye la consulta, la ejecuta y devuelve todos los resultados en formato de array. +- [[yii\db\Query::one()|one()]]: devuelve la primera fila del resultado. +- [[yii\db\Query::column()|column()]]: devuelve la primera columna del resultado. +- [[yii\db\Query::scalar()|scalar()]]: devuelve la primera columna en la primera fila del resultado. +- [[yii\db\Query::exists()|exists()]]: devuelve un valor indicando si la el resultado devuelve algo. +- [[yii\db\Query::count()|count()]]: devuelve el resultado de la consulta `COUNT`. Otros métodos similares incluidos + son `sum($q)`, `average($q)`, `max($q)`, `min($q)`, que soportan las llamadas funciones de agregación. El parámetro + `$q` es obligatorio en estos métodos y puede ser el nombre de la columna o expresión. + +Construcción de Consultas +------------------------- + +A continuación se explicará como construir una sentencia SQL que incluya varias clausulas. Para simplificarlo, usamos +`$query` para representar el objeto [[yii\db\Query]]: + +### `SELECT` + +Para formar una consulta `SELECT` básica, se necesita especificar que columnas y de que tablas se seleccionarán: + +```php +$query->select('id, name') + ->from('user'); +``` + +Las opciones de select se pueden especificar como una cadena de texto (string) separada por comas o como un array. La +sintaxis del array es especialmente útil cuando se forma la selección dinámicamente. + +```php +$query->select(['id', 'name']) + ->from('user'); +``` + +> Información: Se debe usar siempre el formato array si la clausula `SELECT` contiene expresiones SQL. Esto se debe a + que una expresión SQL como `CONCAT(first_name, last_name) AS full_name` puede contener comas. Si se junta con otra + cadena de texto de otra columna, puede ser que la expresión se divida en varias partes por comas, esto puede + conllevar a errores. + +Cuando se especifican columnas, se pueden incluir los prefijos de las tablas o alias de columnas, p. ej. `user.id`, +`user.id AS user_id`. Si se usa un array para especificar las columnas, también se pueden usar las claves del array +para especificar los alias de columna, p. ej. `['user_id' => 'user.id', 'user_name' => 'user.name']`. + +A partir de la versión 2.0.1, también se pueden seleccionar subconsultas como columnas. Por ejemplo: + +```php +$subQuery = (new Query)->select('COUNT(*)')->from('user'); +$query = (new Query)->select(['id', 'count' => $subQuery])->from('post'); +// $query representa la siguiente sentencia SQL: +// SELECT `id`, (SELECT COUNT(*) FROM `user`) AS `count` FROM `post` +``` + +Para seleccionar filas distintas, se puede llamar a `distinct()`, como se muestra a continuación: + +```php +$query->select('user_id')->distinct()->from('post'); +``` + +### `FROM` + +Para especificar de que tabla(s) se quieren seleccionar los datos, se llama a `from()`: + +```php +$query->select('*')->from('user'); +``` + +Se pueden especificar múltiples tablas usando una cadena de texto separado por comas o un array. Los nombres de tablas +pueden contener prefijos de esquema (p. ej. `'public.user'`) y/o alias de tablas (p. ej. `'user u'). El método +entrecomillara automáticamente los nombres de tablas a menos que contengan algún paréntesis (que significa que se +proporciona la tabla como una subconsulta o una expresión de BD). Por ejemplo: + +```php +$query->select('u.*, p.*')->from(['user u', 'post p']); +``` + +Cuando se especifican las tablas como un array, también se pueden usar las claves de los arrays como alias de tablas +(si una tabla no necesita alias, no se usa una clave en formato texto). Por ejemplo: + +```php +$query->select('u.*, p.*')->from(['u' => 'user', 'p' => 'post']); +``` + +Se puede especificar una subconsulta usando un objeto `Query`. En este caso, la clave del array correspondiente se +usará como alias para la subconsulta. + +```php +$subQuery = (new Query())->select('id')->from('user')->where('status=1'); +$query->select('*')->from(['u' => $subQuery]); +``` + +### `WHERE` + +Habitualmente se seleccionan los datos basándose en ciertos criterios. El Constructor de Consultas tiene algunos +métodos útiles para especificarlos, el más poderoso de estos es `where`, y se puede usar de múltiples formas. + +La manera más simple para aplicar una condición es usar una cadena de texto: + +```php +$query->where('status=:status', [':status' => $status]); +``` + +Cuando se usan cadenas de texto, hay que asegurarse que se unen los parámetros de la consulta, no crear una consulta +mediante concatenación de cadenas de texto. El enfoque anterior es seguro, el que se muestra a continuación, no lo es: + +```php +$query->where("status=$status"); // Peligroso! +``` + +En lugar de enlazar los valores de estado inmediatamente, se puede hacer usando `params` o `addParams`: + +```php +$query->where('status=:status'); +$query->addParams([':status' => $status]); +``` + +Se pueden establecer múltiples condiciones en `where` usando el *formato hash*. + +```php +$query->where([ + 'status' => 10, + 'type' => 2, + 'id' => [4, 8, 15, 16, 23, 42], +]); +``` + +El código generará la el siguiente SQL: + +```sql +WHERE (`status` = 10) AND (`type` = 2) AND (`id` IN (4, 8, 15, 16, 23, 42)) +``` + +El valor NULO es un valor especial en las bases de datos, y el Constructor de Consultas lo gestiona inteligentemente. +Este código: + +```php +$query->where(['status' => null]); +``` + +da como resultado la siguiente cláusula WHERE: + +```sql +WHERE (`status` IS NULL) +``` + +También se pueden crear subconsultas con objetos de tipo `Query` como en el siguiente ejemplo: + +```php +$userQuery = (new Query)->select('id')->from('user'); +$query->where(['id' => $userQuery]); +``` + +que generará el siguiente código SQL: + +```sql +WHERE `id` IN (SELECT `id` FROM `user`) +``` + +Otra manera de usar el método es el formato de operando que es `[operator, operand1, operand2, ...]`. + +El operando puede ser uno de los siguientes (ver también [[yii\db\QueryInterface::where()]]): + +- `and`: los operandos deben concatenerase usando `AND`. por ejemplo, `['and', 'id=1', 'id=2']` generará + `id=1 AND id=2`. Si el operando es un array, se convertirá en una cadena de texto usando las reglas aquí descritas. + Por ejemplo, `['and', 'type=1', ['or', 'id=1', 'id=2']]` generará `type=1 AND (id=1 OR id=2)`. El método no + ejecutará ningún filtrado ni entrecomillado. + +- `or`: similar al operando `and` exceptuando que los operando son concatenados usando `OR`. + +- `between`: el operando 1 debe ser el nombre de columna, y los operandos 2 y 3 deben ser los valores iniciales y + finales del rango en el que se encuentra la columna. Por ejemplo, `['between', 'id', 1, 10]` generará + `id BETWEEN 1 AND 10`. + +- `not between`: similar a `between` exceptuando que `BETWEEN` se reemplaza por `NOT BETWEEN` en la condición + generada. + +- `in`: el operando 1 debe ser una columna o una expresión de BD. El operando 2 puede ser un array o un objeto de tipo + `Query`. Generará una condición `IN`. Si el operando 2 es un array, representará el rango de valores que puede + albergar la columna o la expresión de BD; Si el operando 2 es un objeto de tipo `Query`, se generará una subconsulta + y se usará como rango de la columna o de la expresión de BD. Por ejemplo, `['in', 'id', [1, 2, 3]]` generará + `id IN (1, 2, 3)`. El método entrecomillará adecuadamente el nombre de columna y filtrará los valores del rango. El + operando `in` también soporta columnas compuestas. En este caso, el operando 1 debe se un array de columnas, + mientras que el operando 2 debe ser un array de arrays o un objeto de tipo `Query` que represente el rango de las + columnas. + +- `not in`: similar que el operando `in` exceptuando que `IN` se reemplaza por `NOT IN` en la condición generada. + +- `like`: el operando 1 debe ser una columna o una expresión de BD, y el operando 2 debe ser una cadena de texto o un + array que represente los valores a los que tienen que asemejarse la columna o la expresión de BD.Por ejemplo, + `['like', 'name', 'tester']` generará `name LIKE '%tester%'`. Cuando se da el valor rango como un array, se + generarán múltiples predicados `LIKE` y se concatenaran usando `AND`. Por ejemplo, + `['like', 'name', ['test', 'sample']]` generará `name LIKE '%test%' AND name LIKE '%sample%'`. También se puede + proporcionar un tercer operando opcional para especificar como deben filtrarse los caracteres especiales en los + valores. El operando debe se un array que mapeen los caracteres especiales a sus caracteres filtrados asociados. Si + no se proporciona este operando, se aplicará el mapeo de filtrado predeterminado. Se puede usar `false` o un array + vacío para indicar que los valores ya están filtrados y no se necesita aplicar ningún filtro. Hay que tener en + cuenta que cuando se usa un el mapeo de filtrado (o no se especifica el tercer operando), los valores se encerraran + automáticamente entre un par de caracteres de porcentaje. + +> Nota: Cuando se usa PostgreSQL también se puede usar +[`ilike`](http://www.postgresql.org/docs/8.3/static/functions-matching.html#FUNCTIONS-LIKE) en lugar de `like` para +filtrar resultados insensibles a mayúsculas (case-insensitive). + +- `or like`: similar al operando `like` exceptuando que se usa `OR` para concatenar los predicados `LIKE` cuando haya + un segundo operando en un array. + +- `not like`: similar al operando `like` exceptuando que se usa `LIKE` en lugar de `NOT LIKE` en las condiciones + generadas. + +- `or not like`: similar al operando `not like` exceptuando que se usa `OR` para concatenar los predicados `NOT LIKE`. + +- `exists`: requiere un operando que debe ser una instancia de [[yii\db\Query]] que represente la subconsulta. Esto + generará una expresión `EXISTS (sub-query)`. + +- `not exists`: similar al operando `exists` y genera una expresión `NOT EXISTS (sub-query)`. + +Adicionalmente se puede especificar cualquier cosa como operando: + +```php +$query->select('id') + ->from('user') + ->where(['>=', 'id', 10]); +``` + +Cuyo resultado será: + +```sql +SELECT id FROM user WHERE id >= 10; +``` + +Si se construyen partes de una condición dinámicamente, es muy convenientes usar `andWhere()` y `orWhere()`: + +```php +$status = 10; +$search = 'yii'; + +$query->where(['status' => $status]); +if (!empty($search)) { + $query->andWhere(['like', 'title', $search]); +} +``` + +En el caso que `$search` no este vacío, se generará el siguiente código SQL: + +```sql +WHERE (`status` = 10) AND (`title` LIKE '%yii%') +``` + +#### Construcción de Condiciones de Filtro + +Cuando se generan condiciones de filtro basadas en datos recibidos de usuarios (inputs), a menudo se quieren gestionar +de forma especial las "datos vacíos" para ignorarlos en los filtros. Por ejemplo, teniendo un formulario HTML que +obtiene el nombre de usuario y la dirección de correo electrónico. Si el usuario solo rellena el campo de nombre de +usuario, se puede querer generar una consulta para saber si el nombre de usuario recibido es valido. Se puede usar +`filterWhere()` para conseguirlo: + +```php +// $username y $email son campos de formulario rellenados por usuarios +$query->filterWhere([ + 'username' => $username, + 'email' => $email, +]); +``` + +El método `filterWhere()` es muy similar al método `where()`. La principal diferencia es que el `filterWhere()` +eliminará los valores vacíos de las condiciones proporcionadas. Por lo tanto si `$email` es "vació", la consulta +resultante será `...WHERE username=:username`; y si tanto `$username` como `$email` son "vacías", la consulta no +tendrá `WHERE`. + +Decimos que un valor es *vacío* si es nulo, una cadena de texto vacía, una cadena de texto que consista en espacios en +blanco o un array vacío. + +También se pueden usar `andFilterWhere()` y `orFilterWhere()` para añadir más condiciones de filtro. + +### `ORDER BY` + +Se pueden usar `orderBy` y `addOrderBy` para ordenar resultados: + +```php +$query->orderBy([ + 'id' => SORT_ASC, + 'name' => SORT_DESC, +]); +``` + +Aquí estamos ordenando por `id` ascendente y después por `name` descendente. + +### `GROUP BY` and `HAVING` + +Para añadir `GROUP BY` al SQL generado se puede usar el siguiente código: + +```php +$query->groupBy('id, status'); +``` + +Si se quieren añadir otro campo después de usar `groupBy`: + +```php +$query->addGroupBy(['created_at', 'updated_at']); +``` + +Para añadir la condición `HAVING` se pueden usar los métodos `having` y `andHaving` y `orHaving`. Los parámetros para +ellos son similares a los del grupo de métodos `where`: + +```php +$query->having(['status' => $status]); +``` + +### `LIMIT` and `OFFSET` + +Para limitar el resultado a 10 filas se puede usar `limit`: + +```php +$query->limit(10); +``` + +Para saltarse las 100 primeras filas, se puede usar: + +```php +$query->offset(100); +``` + +### `JOIN` + +Las clausulas `JOIN` se generan en el Constructor de Consultas usando el método join aplicable: + +- `innerJoin()` +- `leftJoin()` +- `rightJoin()` + +Este left join selecciona los datos desde dos tablas relacionadas en una consulta: + +```php +$query->select(['user.name AS author', 'post.title as title']) + ->from('user') + ->leftJoin('post', 'post.user_id = user.id'); +``` + +En el código, el primer parámetro del método `leftjoin` especifica la tabla a la que aplicar el join. El segundo +parámetro, define la condición del join. + +Si la aplicación de bases de datos soporta otros tipos de joins, se pueden usar mediante el método `join` genérico: + +```php +$query->join('FULL OUTER JOIN', 'post', 'post.user_id = user.id'); +``` + +El primer argumento es el tipo de join a realizar. El segundo es la tabla a la que aplicar el join, y el tercero es la condición: + +Como en `FROM`, también se pueden efectuar joins con subconsultas. Para hacerlo, se debe especificar la subconsulta +como un array que tiene que contener un elemento. El valor del array tiene que ser un objeto de tipo `Query` que +represente la subconsulta, mientras que la clave del array es el alias de la subconsulta. Por ejemplo: + +```php +$query->leftJoin(['u' => $subQuery], 'u.id=author_id'); +``` + +### `UNION` + +En SQL `UNION` agrega resultados de una consulta a otra consulta. Las columnas devueltas por ambas consultas deben +coincidir. En Yii para construirla, primero se pueden formar dos objetos de tipo query y después usar el método +`union`: + +```php +$query = new Query(); +$query->select("id, category_id as type, name")->from('post')->limit(10); + +$anotherQuery = new Query(); +$anotherQuery->select('id, type, name')->from('user')->limit(10); + +$query->union($anotherQuery); +``` + +Consulta por Lotes +--------------- + +Cuando se trabaja con grandes cantidades de datos, los métodos como [[yii\db\Query::all()]] no son adecuados ya que +requieren la carga de todos los datos en memoria. Para mantener los requerimientos de memoria reducidos, Yii +proporciona soporte a las llamadas consultas por lotes (batch query). Una consulta por lotes usa un cursor de datos y +recupera los datos en bloques. + +Las consultas por lotes se pueden usar del siguiente modo: + +```php +use yii\db\Query; + +$query = (new Query()) + ->from('user') + ->orderBy('id'); + +foreach ($query->batch() as $users) { + // $users is an array of 100 or fewer rows from the user table +} + +// o si se quieren iterar las filas una a una +foreach ($query->each() as $user) { + // $user representa uno fila de datos de la tabla user +} +``` + +Los métodos [[yii\db\Query::batch()]] y [[yii\db\Query::each()]] devuelven un objeto [[yii\db\BatchQueryResult]] que +implementa una interfaz `Iterator` y así se puede usar en el constructor `foreach`. Durante la primera iteración, se +efectúa una consulta SQL a la base de datos. Desde entonces, los datos se recuperan por lotes en las iteraciones. El +tamaño predeterminado de los lotes es 100, que significa que se recuperan 100 filas de datos en cada lote. Se puede +modificar el tamaño de los lotes pasando pasando un primer parámetro a los métodos `batch()` o `each()`. + +En comparación con [[yii\db\Query::all()]], las consultas por lotes sólo cargan 100 filas de datos en memoria cada +vez. Si el procesan los datos y después se descartan inmediatamente, las consultas por lotes, pueden ayudar a mantener +el uso de memora bajo un limite. + +Si se especifica que el resultado de la consulta tiene que ser indexado por alguna columna mediante +[[yii\db\Query::indexBy()]], las consultas por lotes seguirán manteniendo el indice adecuado. Por ejemplo, + +```php +use yii\db\Query; + +$query = (new Query()) + ->from('user') + ->indexBy('username'); + +foreach ($query->batch() as $users) { + // $users esta indexado en la columna "username" +} + +foreach ($query->each() as $username => $user) { +} +``` \ No newline at end of file diff --git a/docs/guide-es/helper-html.md b/docs/guide-es/helper-html.md new file mode 100644 index 0000000..7dd05e9 --- /dev/null +++ b/docs/guide-es/helper-html.md @@ -0,0 +1,377 @@ +Clase auxiliar Html (Html helper) +================================= + +Todas las aplicaciones web generan grandes cantidades de marcado HTML (HTML markup). Si el marcado es estático, se +puede realizar de forma efectiva +[mezclando PHP y HTML en un mismo archivo](http://php.net/manual/es/language.basic-syntax.phpmode.php) pero cuando se +generan dinámicamente empieza a complicarse su gestion sin ayuda extra. Yii proporciona esta ayuda que proporciona un +conjunto de métodos estáticos para gestionar las etiquetas HTML usadas más comúnmente, sus opciones y contenidos. + +> Nota: Si el marcado es casi estático, es preferible usar HTML directamente. No es necesario encapsularlo todo con +llamadas a la clase auxiliar Html. + +Lo fundamental +-------------- + +Teniendo en cuenta que la construcción de HTML dinámico mediante la concatenación de cadenas de texto se complica +rápidamente, Yii proporciona un conjunto de métodos para manipular las opciones de etiquetas y la construcción de las +mismas basadas en estas opciones. + +### Generación de etiquetas + +El código de generación de etiquetas es similar al siguiente: + +```php +name), ['class' => 'username']) ?> +``` + +El primer argumento es el nombre de la etiqueta. El segundo es el contenido que se ubicara entre la etiqueta de +apertura y la de cierre. Hay que tener en cuenta que estamos usando `Html::encode`. Esto es debido a que el contenido +no se codifica automáticamente para permitir usar HTML cuando se necesite. La tercera opción es un array de opciones +HTML o, en otras palabras, los atributos de las etiquetas. En este array la clave representa el nombre del atributo +como podría ser `class`, `href` o `target` y el valor es su valor. + +El código anterior generara el siguiente HTML: + +```html +

samdark

+``` + +Si se necesita solo la apertura o el cierre de una etiqueta, se pueden usar los métodos `Html::beginTag()` y +`Html::endTag()`. + +Las opciones se usan en muchos métodos de la clase auxiliar Html y en varios widgets. En todos estos casos hay cierta +gestión adicional que se debe conocer: + +- Si un valor es `null`, el correspondiente atributo no se renderizará. +- Los atributos cuyos valores son de tipo booleano serán tratados como + [atributos booleanos](http://www.w3.org/TR/html5/infrastructure.html#boolean-attributes). +- Los valores de los atributos se codificaran en HTML usando [[yii\helpers\Html::encode()|Html::encode()]]. +- El atributo "data" puede recibir un array. En este caso, se "expandirá" y se renderizará una lista de atributos + `data` p. ej. `'data' => ['id' => 1, 'name' => 'yii']` se convierte en `data-id="1" data-name="yii"`. +- El atributo "data" puede recibir JSON. Se gestionará de la misma manera que un array p. ej. + `'data' => ['params' => ['id' => 1, 'name' => 'yii'], 'status' => 'ok']` se convierte en + `data-params='{"id":1,"name":"yii"}' data-status="ok"`. + +### Formación de clases y estilos dinámicamente + +Cuando se construyen opciones para etiquetas HTML, a menudo nos encontramos con valores predeterminados que hay que +modificar. Para añadir o eliminar clases CSS se puede usar el siguiente ejemplo: + +```php +$options = ['class' => 'btn btn-default']; + +if ($type === 'success') { + Html::removeCssClass($options, 'btn-default'); + Html::addCssClass($options, 'btn-success'); +} + +echo Html::tag('div', 'Pwede na', $options); + +// cuando $type sea 'success' se renderizará +//
Pwede na
+``` + +Para hacer lo mismo con los estilos para el atributo `style`: + +```php +$options = ['style' => ['width' => '100px', 'height' => '100px']]; + +// devuelve style="width: 100px; height: 200px; position: absolute;" +Html::addCssStyle($options, 'height: 200px; positon: absolute;'); + +// devuelve style="position: absolute;" +Html::removeCssStyle($options, ['width', 'height']); +``` + +Cuando se usa [[yii\helpers\Html::addCssStyle()|addCssStyle()]] se puede especificar si un array de pares clave-valor +corresponde a nombres y valores de la propiedad CSS correspondiente o a una cadena de texto como por ejemplo +`width: 100px; height: 200px;`. Estos formatos se pueden "hacer" y "deshacer" usando +[[yii\helpers\Html::cssStyleFromArray()|cssStyleFromArray()]] y +[[yii\helpers\Html::cssStyleToArray()|cssStyleToArray()]]. El método +[[yii\helpers\Html::removeCssStyle()|removeCssStyle()]] un array de propiedades que se eliminarán. Si sólo se +eliminará una propiedad, se puede especificar como una cadena de texto. + +Codificación y Descodificación de contenido +------------------------------------------- + +Para que el contenido se muestre correctamente y de forma segura en caracteres especiales HTML el contenido debe ser +codificado. En PHP esto se hace con [htmlspecialchars](http://www.php.net/manual/en/function.htmlspecialchars.php) y +[htmlspecialchars_decode](http://www.php.net/manual/en/function.htmlspecialchars-decode.php). El problema con el uso +de estos métodos directamente es que se tiene que especificar la codificación y opciones extra cada vez. Ya que las +opciones siempre son las mismas y la codificación debe coincidir con la de la aplicación para prevenir problemas de +seguridad, Yii proporciona dos métodos simples y compactos: + +```php +$userName = Html::encode($user->name); +echo $userName; + +$decodedUserName = Html::decode($userName); +``` + +Formularios +----------- + +El trato con el marcado de formularios es una tarea repetitiva y propensa a errores. Por esto hay un grupo de métodos +para ayudar a gestionarlos. + +> Nota: hay que considerar la opción de usar [[yii\widgets\ActiveForm|ActiveForm]] en caso que se gestionen +formularios que requieran validaciones. + +### Abrir y cerrar un formulario + +Se puede abrir un formulario con el metodo [[yii\helpers\Html::beginForm()|beginForm()]] como se muestra a +continuación: + +```php + $id], 'post', ['enctype' => 'multipart/form-data']) ?> +``` + +El primer argumento es la URL a la que se enviaran los datos del formulario. Se puede especificar en formato de ruta +de Yii con los parámetros aceptados por [[yii\helpers\Url::to()|Url::to()]]. El segundo es el método que se usara. +`post` es el método predeterminado. El tercero es un array de opciones para la etiqueta `form`. En este caso cambiamos +el método de codificación del formulario de `data` en una petición POST a `multipart/form-data`. Esto se requiere +cuando se quieren subir archivos. + +El cierre de la etiqueta `form` es simple: + +```php + +``` + +### Botones + +Para generar botones se puede usar el siguiente código: + +```php + 'teaser']) ?> + 'submit']) ?> + 'reset']) ?> +``` + +El primer argumento para los tres métodos es el titulo del botón y el segundo son las opciones. El titulo no esta +codificado pero si se usan datos recibidos por el usuario, deben codificarse mediante +[[yii\helpers\Html::encode()|Html::encode()]]. + +### Inputs + +Hay dos grupos en los métodos input. Unos empiezan con `active` y se llaman inputs activos y los otros no empiezan +así. Los inputs activos obtienen datos del modelo y del atributo especificado y los datos de los inputs normales se +especifica directamente. + +Los métodos más genéricos son: + +```php +type, input name, input value, options +name, ['class' => $username]) ?> + +type, model, model attribute name, options + $username]) ?> +``` + +Si se conoce el tipo de input de antemano, es conveniente usar los atajos de métodos: + +- [[yii\helpers\Html::buttonInput()]] +- [[yii\helpers\Html::submitInput()]] +- [[yii\helpers\Html::resetInput()]] +- [[yii\helpers\Html::textInput()]], [[yii\helpers\Html::activeTextInput()]] +- [[yii\helpers\Html::hiddenInput()]], [[yii\helpers\Html::activeHiddenInput()]] +- [[yii\helpers\Html::passwordInput()]] / [[yii\helpers\Html::activePasswordInput()]] +- [[yii\helpers\Html::fileInput()]], [[yii\helpers\Html::activeFileInput()]] +- [[yii\helpers\Html::textarea()]], [[yii\helpers\Html::activeTextarea()]] + +Los botones de opción (Radios) y las casillas de verificación (checkboxes) se especifican de forma un poco diferente: + +```php + 'I agree']); + 'agreement']) + + 'I agree']); + 'agreement']) +``` + +Las listas desplegables (dropdown list) se pueden renderizar como se muestra a continuación: + +```php + + + + + +``` + +El primer argumento es el nombre del input, el segundo es el valor seleccionado actualmente y el tercero es el array +de pares clave-valor donde la clave es al lista de valores y el valor del array es la lista a mostrar. + +Si se quiere habilitar la selección múltiple, se puede usar la lista seleccionable (checkbox list): + +```php + + +``` + +Si no, se puede usar la lista de opciones (radio list): + +```php + + +``` + +### Labels y errores + +De forma parecida que en los imputs hay dos métodos para generar labels. El activo que obtiene los datos del modelo y +el no-activo que acepta datos directamente: + +```php + 'label username']) ?> + 'label username']) +``` + +Para mostrar los errores de un modelo o más en forma de resumen: + +```php + 'errors']) ?> +``` + +Para mostrar un error individual: + +```php + 'error']) ?> +``` + +### Nombres y valores + +Existen métodos para obtener nombres, IDs y valores para los campos de entrada (inputs) basados en el modelo. Estos se +usan principalmente internamente pero a veces pueden resultar prácticos: + +```php +// Post[title] +echo Html::getInputName($post, 'title'); + +// post-title +echo Html::getInputId($post, 'title'); + +// mi primer post +echo Html::getAttributeValue($post, 'title'); + +// $post->authors[0] +echo Html::getAttributeValue($post, '[0]authors[0]'); +``` + +En el ejemplo anterior, el primer argumento es el modelo y el segundo es el atributo de expresión. En su forma más +simple es su nombre de atributo pero podría ser un nombre de atributo prefijado y/o añadido como sufijo con los +indices de un array, esto se usa principalmente para mostrar inputs en formatos de tablas: + +- `[0]content` se usa en campos de entrada de datos en formato de tablas para representar el atributo "content" para + el primer modelo del input en formato de tabla; +- `dates[0]` representa el primer elemento del array del atributo "dates"; +- `[0]dates[0]` representa el primer elemento del array del atributo "dates" para el primer modelo en formato de tabla. + +Para obtener el nombre de atributo sin sufijos o prefijos se puede usar el siguiente código: + +```php +// dates +echo Html::getAttributeName('dates[0]'); +``` + +Estilos y scripts +----------------- + +Existen dos métodos para generar etiquetas que envuelvan estilos y scripts incrustados (embebbed): + +```php + + +Genera + + + + true]); + +Genera + + +``` + +Si se quiere enlazar un estilo externo desde un archivo CSS: + +```php + 'IE 5']) ?> + +genera + + +``` + +El primer argumento es la URL. El segundo es un array de opciones. Adicionalmente, para regular las opciones se puede +especificar: + +- `condition` para envolver `` por lo que el sólo se + incluirá si el navegador no soporta JavaScript o si lo ha deshabilitado el usuario. + +Para enlazar un archivo JavaScript: + +```php + +``` + +Es igual que con las CSS, el primer argumento especifica el enlace al fichero que se quiere incluir. Las opciones se +pueden pasar como segundo argumento. En las opciones se puede especificar `condition` del mismo modo que se puede usar +para `cssFile`. + +Enlaces +------- + +Existe un método para generar hipervínculos a conveniencia: + +```php + $id], ['class' => 'profile-link']) ?> +``` + +El primer argumento es el titulo. No está codificado por lo que si se usan datos enviados por el usuario se tienen que +codificar usando `Html::encode()`. El segundo argumento es el que se introducirá en `href` de la etiqueta ` +``` + +Imagenes +-------- + +Para generar una etiqueta de tipo imagen se puede usar el siguiente ejemplo: + +```php + 'My logo']) ?> + +genera + +My logo +``` + +Aparte de los [aliases](concept-aliases.md) el primer argumento puede aceptar rutas, parámetros y URLs. Del mismo modo +que [Url::to()](helper-url.md). + +Listas +------ + +Las listas desordenadas se puede generar como se muestra a continuación: + +```php + function($item, $index) { + return Html::tag( + 'li', + $this->render('post', ['item' => $item]), + ['class' => 'post'] + ); +}]) ?> +``` + +Para generar listas ordenadas se puede usar `Html::ol()` en su lugar. diff --git a/docs/guide-es/helper-url.md b/docs/guide-es/helper-url.md new file mode 100644 index 0000000..b5e8b00 --- /dev/null +++ b/docs/guide-es/helper-url.md @@ -0,0 +1,156 @@ +Clase Auxiliar URL (URL Helper) +=============================== + +La clase auxiliar URL proporciona un conjunto de métodos estáticos para gestionar URLs. + +Obtener URLs Comunes +-------------------- + +Se pueden usar dos métodos para obtener URLs comunes: URL de inicio (home URL) y URL base (base URL) de la petición +(request) actual. Para obtener la URL de inicio se puede usar el siguiente código: + +```php +$relativeHomeUrl = Url::home(); +$absoluteHomeUrl = Url::home(true); +$httpsAbsoluteHomeUrl = Url::home('https'); +``` + +Si no se pasan parámetros, las URLs generadas son relativas. Se puede pasar `true`para obtener la URL absoluta del +esquema actual o especificar el esquema explícitamente (`https`, `http`). + +Para obtener la URL base de la petición actual, se puede usar el siguiente código: + +```php +$relativeBaseUrl = Url::base(); +$absoluteBaseUrl = Url::base(true); +$httpsAbsoluteBaseUrl = Url::base('https'); +``` + +El único parámetro del método funciona exactamente igual que para `Url::home()`. + +Creación de URLs +---------------- + +Para crear una URL para una ruta determinada se puede usar `Url::toRoute()`. El metodo utiliza [[\yii\web\UrlManager]] +para crear una URL: + +```php +$url = Url::toRoute(['product/view', 'id' => 42]); +``` + +Se puede especificar la ruta como una cadena de texto, p. ej. `site/index`. También se puede usar un array si se +quieren especificar parámetros para la URL que se esta generando. El formato del array debe ser: + +```php +// genera: /index.php?r=site/index¶m1=value1¶m2=value2 +['site/index', 'param1' => 'value1', 'param2' => 'value2'] +``` + +Si se quiere crear una URL con un enlace, se puede usar el formato de array con el parámetro `#`. Por ejemplo, + +```php +// genera: /index.php?r=site/index¶m1=value1#name +['site/index', 'param1' => 'value1', '#' => 'name'] +``` + +Una ruta puede ser absoluta o relativa. Una ruta absoluta tiene una barra al principio (p. ej. `/site/index`), +mientras que una ruta relativa no la tiene (p. ej. `site/index` o `index`). Una ruta relativa se convertirá en una +ruta absoluta siguiendo las siguientes normas: + +- Si la ruta es una cadena vacía, se usará la [[\yii\web\Controller::route|route]] actual; +- Si la ruta no contiene barras (p. ej. `index`), se considerará que es el ID de una acción del controlador actual y + se antepondrá con [[\yii\web\Controller::uniqueId]]; +- Si la ruta no tiene barra inicial (p. ej. `site/index`), se considerará que es una ruta relativa del modulo actual y + se le antepondrá el [[\yii\base\Module::uniqueId|uniqueId]] del modulo. + +A continuación se muestran varios ejemplos del uso de este método: + +```php +// /index?r=site/index +echo Url::toRoute('site/index'); + +// /index?r=site/index&src=ref1#name +echo Url::toRoute(['site/index', 'src' => 'ref1', '#' => 'name']); + +// http://www.example.com/index.php?r=site/index +echo Url::toRoute('site/index', true); + +// https://www.example.com/index.php?r=site/index +echo Url::toRoute('site/index', 'https'); +``` + +El otro método `Url::to()` es muy similar a [[toRoute()]]. La única diferencia es que este método requiere que la ruta +especificada sea un array. Si se pasa una cadena de texto, se tratara como una URL. + +El primer argumento puede ser: + +- un array: se llamará a [[toRoute()]] para generar la URL. Por ejemplo: `['site/index']`, + `['post/index', 'page' => 2]`. Se puede revisar [[toRoute()]] para obtener más detalles acerca de como especificar + una ruta. +- una cadena que empiece por `@`: se tratará como un alias, y se devolverá la cadena correspondiente asociada a este + alias. +- una cadena vacía: se devolverá la URL de la petición actual; +- una cadena de texto: se devolverá sin alteraciones. + +Cuando se especifique `$schema` (tanto una cadena de text como `true`), se devolverá una URL con información del host +(obtenida mediante [[\yii\web\UrlManager::hostInfo]]). Si `$url` ya es una URL absoluta, su esquema se reemplazará con +el especificado. + +A continuación se muestran algunos ejemplos de uso: + +```php +// /index?r=site/index +echo Url::to(['site/index']); + +// /index?r=site/index&src=ref1#name +echo Url::to(['site/index', 'src' => 'ref1', '#' => 'name']); + +// la URL solicitada actualmente +echo Url::to(); + +// /images/logo.gif +echo Url::to('@web/images/logo.gif'); + +// images/logo.gif +echo Url::to('images/logo.gif'); + +// http://www.example.com/images/logo.gif +echo Url::to('@web/images/logo.gif', true); + +// https://www.example.com/images/logo.gif +echo Url::to('@web/images/logo.gif', 'https'); +``` + +Recordar la URL para utilizarla más adelante +-------------------------------------------- + +Hay casos en que se necesita recordar la URL y después usarla durante el procesamiento de una de las peticiones +secuenciales. Se puede logar de la siguiente manera: + +```php +// Recuerda la URL actual +Url::remember(); + +// Recuerda la URL especificada. Revisar Url::to() para ver formatos de argumentos. +Url::remember(['product/view', 'id' => 42]); + +// Recuerda la URL especificada con un nombre asignado +Url::remember(['product/view', 'id' => 42], 'product'); +``` + +En la siguiente petición se puede obtener la URL memorizada de la siguiente manera: + +```php +$url = Url::previous(); +$productUrl = Url::previous('product'); +``` + +Reconocer la relatividad de URLs +-------------------------------- + +Para descubrir si una URL es relativa, es decir, que no contenga información del host, se puede utilizar el siguiente +código: + +```php +$isRelative = Url::isRelative('test/it'); +``` diff --git a/docs/guide-es/output-theming.md b/docs/guide-es/output-theming.md new file mode 100644 index 0000000..18a7d45 --- /dev/null +++ b/docs/guide-es/output-theming.md @@ -0,0 +1,99 @@ +Temas +===== + +> Nota: Esta sección está en desarrollo. + +Un tema (theme) es un directorio de archivos y de vistas (views) y layouts. Cada archivo de este directorio +sobrescribe el archivo correspondiente de una aplicación cuando se renderiza. Una única aplicación puede usar +múltiples temas para que pueden proporcionar experiencias totalmente diferentes. Solo se puede haber un único tema +activo. + +> Nota: Los temas no están destinados a ser redistribuidos ya que están demasiado ligados a la aplicación. Si se + quiere redistribuir una apariencia personalizada, se puede considerar la opción de + [asset bundles](structure-assets.md) de archivos CSS y Javascript. + +Configuración de un Tema +------------------------ + +La configuración de un tema se especifica a través del componente `view` de la aplicación. Para establecer que un tema +trabaje con vistas de aplicación básicas, la configuración de la aplicación debe contener lo siguiente: + +```php +'components' => [ + 'view' => [ + 'theme' => [ + 'pathMap' => ['@app/views' => '@app/themes/basic'], + 'baseUrl' => '@web/themes/basic', + ], + ], +], +``` + +En el ejemplo anterior, el `pathMap` define un mapa (map) de las rutas a las que se aplicará el tema mientras que +`baseUrl` define la URL base para los recursos a los que hacen referencia los archivos del tema. + +En nuestro caso `pathMap` es `['@app/views' => '@app/themes/basic']`. Esto significa que cada vista de `@app/views` +primero se buscará en `@app/themes/basic` y si existe, se usará la vista del directorio del tema en lugar de la vista +original. + +Por ejemplo, con la configuración anterior, la versión del tema para la vista `@app/views/site/index.php` será +`@app/themes/basic/site/index.php`. Básicamente se reemplaza `@app/views` en `@app/views/site/index.php` por +`@app/themes/basic`. + +### Temas para Módulos + +Para utilizar temas en los módulos, el `pathMap` debe ser similar al siguiente: + +```php +'components' => [ + 'view' => [ + 'theme' => [ + 'pathMap' => [ + '@app/views' => '@app/themes/basic', + '@app/modules' => '@app/themes/basic/modules', // <-- !!! + ], + ], + ], +], +``` + +Esto permite aplicar el tema a `@app/modules/blog/views/comment/index.php` con la vista +`@app/themes/basic/modules/blog/views/comment/index.php`. + +### Temas para Widgets + +Para utilizar un tema en una vista que se encuentre en `@app/widgets/currency/views/index.php`, se debe aplicar la +siguiente configuración para el componente vista, tema: + +```php +'components' => [ + 'view' => [ + 'theme' => [ + 'pathMap' => ['@app/widgets' => '@app/themes/basic/widgets'], + ], + ], +], +``` + +Con la configuración anterior, se puede crear una versión de la vista `@app/widgets/currency/index.php` para que se +aplique el tema en `@app/themes/basic/widgets/currency/index.php`. + +Uso de Multiples Rutas +---------------------- + +Es posible mapear una única ruta a múltiples rutas de temas. Por ejemplo: + +```php +'pathMap' => [ + '@app/views' => [ + '@app/themes/christmas', + '@app/themes/basic', + ], +] +``` + +En este caso, primero se buscara la vista en `@app/themes/christmas/site/index.php`, si no se encuentra, se intentará +en `@app/themes/basic/site/index.php`. Si la vista no se encuentra en ninguna de rutas especificadas, se usará la +vista de aplicación. + +Esta capacidad es especialmente útil si se quieren sobrescribir algunas rutas temporal o condicionalmente. \ No newline at end of file diff --git a/docs/guide-ja/README.md b/docs/guide-ja/README.md index c9157e8..b86d700 100644 --- a/docs/guide-ja/README.md +++ b/docs/guide-ja/README.md @@ -96,7 +96,7 @@ All Rights Reserved. データの表示 ------------ -* [データの書式設定](output-formatter.md) +* [データのフォーマット](output-formatter.md) * **TBD** [ページネーション](output-pagination.md) * **TBD** [並べ替え](output-sorting.md) * [データプロバイダ](output-data-providers.md) @@ -109,10 +109,10 @@ All Rights Reserved. ------------ * [認証](security-authentication.md) -* [権限](security-authorization.md) +* [権限付与](security-authorization.md) * [パスワードを扱う](security-passwords.md) * **TBD** [Auth クライアント](security-auth-clients.md) -* **TBD** [最善の慣行](security-best-practices.md) +* [ベストプラクティス](security-best-practices.md) キャッシュ @@ -128,49 +128,49 @@ All Rights Reserved. RESTful ウェブサービス ---------------------- -* [クイックスタート](rest-quick-start.md) -* [リソース](rest-resources.md) -* [コントローラ](rest-controllers.md) -* [ルーティング](rest-routing.md) -* [レスポンスの書式設定](rest-response-formatting.md) -* [認証](rest-authentication.md) -* [転送レート制限](rest-rate-limiting.md) -* [バージョン管理](rest-versioning.md) -* [エラー処理](rest-error-handling.md) +* **翻訳未着手** [クイックスタート](rest-quick-start.md) +* **翻訳未着手** [リソース](rest-resources.md) +* **翻訳未着手** [コントローラ](rest-controllers.md) +* **翻訳未着手** [ルーティング](rest-routing.md) +* **翻訳未着手** [レスポンスの書式設定](rest-response-formatting.md) +* **翻訳未着手** [認証](rest-authentication.md) +* **翻訳未着手** [転送レート制限](rest-rate-limiting.md) +* **翻訳未着手** [バージョン管理](rest-versioning.md) +* **翻訳未着手** [エラー処理](rest-error-handling.md) 開発ツール ---------- -* [デバッグツールバーとデバッガ](tool-debugger.md) -* [Gii を使ってコードを生成する](tool-gii.md) +* **翻訳未着手** [デバッグツールバーとデバッガ](tool-debugger.md) +* **翻訳未着手** [Gii を使ってコードを生成する](tool-gii.md) * **TBD** [API ドキュメントを生成する](tool-api-doc.md) テスト ------ -* [概要](test-overview.md) -* [テスト環境の構築](test-environment-setup.md) -* [ユニットテスト](test-unit.md) -* [機能テスト](test-functional.md) -* [承認テスト](test-acceptance.md) -* [フィクスチャ](test-fixtures.md) +* **翻訳未着手** [概要](test-overview.md) +* **翻訳未着手** [テスト環境の構築](test-environment-setup.md) +* **翻訳未着手** [ユニットテスト](test-unit.md) +* **翻訳未着手** [機能テスト](test-functional.md) +* **翻訳未着手** [承認テスト](test-acceptance.md) +* **翻訳未着手** [フィクスチャ](test-fixtures.md) スペシャルトピック ------------------ -* [アドバンストアプリケーションテンプレート](tutorial-advanced-app.md) -* [アプリケーションを一から構築する](tutorial-start-from-scratch.md) -* [コンソールコマンド](tutorial-console.md) -* [コアのバリデータ](tutorial-core-validators.md) -* [国際化](tutorial-i18n.md) -* [メール](tutorial-mailing.md) -* [パフォーマンスチューニング](tutorial-performance-tuning.md) -* [共有ホスト環境](tutorial-shared-hosting.md) -* [テンプレートエンジン](tutorial-template-engines.md) -* [サードパーティのコードを扱う](tutorial-yii-integration.md) +* **翻訳未着手** [アドバンストアプリケーションテンプレート](tutorial-advanced-app.md) +* **翻訳未着手** [アプリケーションを一から構築する](tutorial-start-from-scratch.md) +* **翻訳未着手** [コンソールコマンド](tutorial-console.md) +* **翻訳未着手** [コアのバリデータ](tutorial-core-validators.md) +* **翻訳未着手** [国際化](tutorial-i18n.md) +* **翻訳未着手** [メール](tutorial-mailing.md) +* **翻訳未着手** [パフォーマンスチューニング](tutorial-performance-tuning.md) +* **翻訳未着手** [共有ホスト環境](tutorial-shared-hosting.md) +* **翻訳未着手** [テンプレートエンジン](tutorial-template-engines.md) +* **翻訳未着手** [サードパーティのコードを扱う](tutorial-yii-integration.md) ウィジェット @@ -184,16 +184,16 @@ RESTful ウェブサービス * Menu: **TBD** link to demo page * LinkPager: **TBD** link to demo page * LinkSorter: **TBD** link to demo page -* [Bootstrap ウィジェット](widget-bootstrap.md) -* [Jquery UI ウィジェット](widget-jui.md) +* **翻訳未着手** [Bootstrap ウィジェット](widget-bootstrap.md) +* **翻訳未着手** [Jquery UI ウィジェット](widget-jui.md) ヘルパ ------ -* [概要](helper-overview.md) -* [ArrayHelper](helper-array.md) -* **TBD** [Html](helper-html.md) -* [Url](helper-url.md) +* **翻訳未着手** [概要](helper-overview.md) +* **翻訳未着手** [ArrayHelper](helper-array.md) +* **翻訳未着手** [Html](helper-html.md) +* **翻訳未着手** [Url](helper-url.md) * **TBD** [Security](helper-security.md) diff --git a/docs/guide-ja/concept-aliases.md b/docs/guide-ja/concept-aliases.md new file mode 100644 index 0000000..74a924c --- /dev/null +++ b/docs/guide-ja/concept-aliases.md @@ -0,0 +1,126 @@ +エイリアス +======= + +ファイルパスや URL を表すのにエイリアスを使用すると、あなたはプロジェクト内で絶対パスや URL をハードコードする必要がなくなります。エイリアスは、通常のファイルパスや URL と区別するために、 `@` 文字で始まる必要があります。Yii はすでに利用可能な多くの事前定義エイリアスを持っています。 +たとえば、 `@yii` というエイリアスは Yii フレームワークのインストールパスを表し、 `@web` は現在実行中の Web アプリケーションのベース URL を表します。 + + +エイリアスの定義 +---------------- + +[[Yii::setAlias()]] を呼び出すことにより、ファイルパスまたは URL のエイリアスを定義することができます。 + +```php +// ファイルパスのエイリアス +Yii::setAlias('@foo', '/path/to/foo'); + +// URL のエイリアス +Yii::setAlias('@bar', 'http://www.example.com'); +``` + +> 補足: エイリアスされているファイルパスやURLは、必ずしも実在するファイルまたはリソースを参照しない場合があります。 + +定義済みのエイリアスがあれば、スラッシュ `/` に続けて 1 つ以上のパスセグメントを追加することで([[Yii::setAlias()]] +の呼び出しを必要とせずに) 新しいエイリアスを導出することができます。 [[Yii::setAlias()]] を通じて定義されたエイリアスは +*ルートエイリアス* となり、それから派生したエイリアスは *派生エイリアス* になります。たとえば、 `@foo` がルートエイリアスなら、 +`@foo/bar/file.php` は派生エイリアスです。 + +エイリアスを、他のエイリアス (ルートまたは派生のいずれか) を使用して定義することができます: + +```php +Yii::setAlias('@foobar', '@foo/bar'); +``` + +ルートエイリアスは通常、 [ブートストラップ](runtime-bootstrapping.md) 段階で定義されます。 +たとえば、[エントリスクリプト](structure-entry-scripts.md) で [[Yii::setAlias()]] を呼び出すことができます。 +便宜上、 [アプリケーション](structure-applications.md) は、`aliases` という名前の書き込み可能なプロパティを提供しており、 +それをアプリケーションの [構成情報](concept-configurations.md) で設定することが可能です。 + +```php +return [ + // ... + 'aliases' => [ + '@foo' => '/path/to/foo', + '@bar' => 'http://www.example.com', + ], +]; +``` + + +エイリアスの解決 +----------------- + +[[Yii::getAlias()]] を呼び出して、ルートエイリアスが表すファイルパスまたはURLを解決することができます。 +同メソッドで、対応するファイルパスまたはURLに派生するエイリアスを解決することもできます。 + +```php +echo Yii::getAlias('@foo'); // /path/to/foo を表示 +echo Yii::getAlias('@bar'); // http://www.example.com を表示 +echo Yii::getAlias('@foo/bar/file.php'); // /path/to/foo/bar/file.php を表示 +``` + +派生エイリアスによって表されるパスやURLは、派生エイリアス内のルートエイリアス部分を、対応するパス/URL +で置換して決定されます。 + +> 補足: [[Yii::getAlias()]] メソッドは、 結果のパスやURLが実在するファイルやリソースを参照しているかをチェックしません。 + +ルートエイリアス名にはスラッシュ `/` 文字を含むことができます。 [[Yii::getAlias()]] メソッドは、 +エイリアスのどの部分がルートエイリアスであるかを賢く判別し、正確に対応するファイルパスやURLを決定します: + +```php +Yii::setAlias('@foo', '/path/to/foo'); +Yii::setAlias('@foo/bar', '/path2/bar'); +Yii::getAlias('@foo/test/file.php'); // /path/to/foo/test/file.php を表示 +Yii::getAlias('@foo/bar/file.php'); // /path2/bar/file.php を表示 +``` + +もし `@foo/bar` がルートエイリアスとして定義されていなければ、最後のステートメントは `/path/to/foo/bar/file.php` を表示します。 + + +エイリアスの使用 +------------- + +エイリアスは、それをパスやURLに変換するための [[Yii::getAlias()​]] の呼び出しがなくても、Yiiの多くの場所でみられます。 +たとえば、 [[yii\caching\FileCache::cachePath]] ファイルパスとファイルパスを表すエイリアスの両方を受け入れることができ、 +`@` プレフィックスによって、エイリアスとファイルパスを区別することができます。 + +```php +use yii\caching\FileCache; + +$cache = new FileCache([ + 'cachePath' => '@runtime/cache', +]); +``` + +プロパティやメソッドのパラメータがエイリアスをサポートしているかどうかは、API ドキュメントに注意を払ってください。 + + +事前定義されたエイリアス +------------------ + +Yii では、一般的に使用されるフ​​ァイルのパスと URL を簡単に参照できるよう、エイリアスのセットが事前に定義されています: + +- `@yii`, `BaseYii.php` ファイルがあるディレクトリ (フレームワークディレクトリとも呼ばれます) +- `@app`, 現在実行中のアプリケーションの [[yii\base\Application::basePath|ベースパス]] +- `@runtime`, 現在実行中のアプリケーションの [[yii\base\Application::runtimePath|ランタイムパス]] 。デフォルトは `@app/runtime` 。 +- `@webroot`, 現在実行中の Web アプリケーションの Web ルートディレクトリ。エントリスクリプトを含むディレクトリをもとに決定されます。 +- `@web`, 現在実行中の Web アプリケーションのベース URL。これは、 [[yii\web\Request::baseUrl]] と同じ値を持ちます。 +- `@vendor`, [[yii\base\Application::vendorPath|Composerのベンダーディレクトリ]] 。デフォルトは `@app/vendor` 。 +- `@bower`, [bower パッケージ](http://bower.io/) が含まれるルートディレクトリ。デフォルトは `@vendor/bower` 。 +- `@npm`, [npm パッケージ](https://www.npmjs.org/) が含まれるルートディレクトリ。デフォルトは `@vendor/npm` 。 + +`@yii` エイリアスは [エントリスクリプト](structure-entry-scripts.md) に `Yii.php` ファイルを読み込んだ時点で定義されます。 +エイリアスの残りの部分は、アプリケーションのコンストラクタ内で、アプリケーションの [構成情報](concept-configurations.md) を適用するときに定義されます。 + +エクステンションのエイリアス +----------------- + +Composer でインストールされる各 [エクステンション](structure-extensions.md) ごとに、エイリアスが自動的に定義されます。 +各エイリアスは、その `composer.json` ファイルで宣言された、エクステンションのルート名前空間にちなんで名付けられており、 +それらは、パ​​ッケージのルートディレクトリを表します。たとえば、あなたが `yiisoft/yii2-jui` エクステンションをインストールしたとすると、 +自動的に `@yii/jui` というエイリアスができ、 [ブートストラップ](runtime-bootstrapping.md) 段階で、次のと同等のものとして定義されます: + +```php +Yii::setAlias('@yii/jui', 'VendorPath/yiisoft/yii2-jui'); +``` + diff --git a/docs/guide-ja/concept-autoloading.md b/docs/guide-ja/concept-autoloading.md new file mode 100644 index 0000000..f4d74fe --- /dev/null +++ b/docs/guide-ja/concept-autoloading.md @@ -0,0 +1,88 @@ +クラスのオートローディング +================= + +Yiiは、必要となるすべてのクラスファイルを、特定してインクルードするにあたり、 [クラスのオートローディングメカニズム](http://www.php.net/manual/en/language.oop5.autoload.php) +を頼りにします。[PSR-4 標準](https://github.com/php-fig/fig-standards/blob/master/proposed/psr-4-autoloader/psr-4-autoloader.md) に準拠した、高性能なクラスのオートローダーを提供します。 +このオートローダーは、あなたが `Yii.php` ファイルをインクルードするときにインストールされます。 + +> 補足: 説明を簡単にするため、このセクションではクラスのオートローディングについてのみお話しします。しかし、 + ここに記述されている内容は、同様に、インタフェースとトレイトのオートロードにも適用されることに注意してください。 + + +Yii オートローダーの使用 +------------------------ + +Yii のクラスオートローダーを使用するには、自分のクラスを作成して名前を付けるとき、次の2つの単純なルールに従わなければなりません: + +* 各クラスは名前空間の下になければなりません (例 `foo\bar\MyClass`) +* 各クラスは次のアルゴリズムで決定される個別のファイルに保存されなければなりません: + +```php +// $className は先頭にバックスラッシュを持つ完全修飾名 +$classFile = Yii::getAlias('@' . str_replace('\\', '/', $className) . '.php'); +``` +たとえば、クラス名と名前空間が `foo\bar\MyClass` であれば、対応するクラスファイルのパスの [エイリアス](concept-aliases.md) は、 +`@foo/bar/MyClass.php` になります。このエイリアスがファイルパスになるようにするには、`@foo` または `@foo/bar` +のどちらかが、 [ルートエイリアス](concept-aliases.md#defining-aliases) でなければなりません。 + +[Basic Application Template](start-basic.md) を使用している場合、最上位の名前空間 `app` の下にクラスを置くことができ、 +そうすると、新しいエイリアスを定義しなくても、Yii によってそれらをオートロードできるようになります。これは `@app` +が [事前定義されたエイリアス](concept-aliases.md#predefined-aliases) であるためで、`app\components\MyClass` のようなクラス名を +今説明したアルゴリズムに従って、クラスファイル `AppBasePath/components/MyClass.php` だと解決できるのです。 + +[Advanced Application Template](tutorial-advanced-app.md) では、各階層にそれ自身のルートエイリアスを持っています。たとえば、 +フロントエンド層はルートエイリアス `@frontend` を持ち、バックエンド層は `@backend` です。その結果、名前空間 `frontend` の下に +フロントエンドクラスを置き、バックエンドクラスを `backend` の下に置けます。これで、これらのクラスは Yii のオートローダーによって +オートロードできるようになります。 + + +クラスマップ +--------- + +Yii のクラスオートローダーは、 *クラスマップ* 機能をサポートしており、クラス名を対応するクラスファイルのパスにマップできます。 +オートローダーがクラスをロードしているとき、クラスがマップに見つかるかどうかを最初にチェックします。もしあれば、対応する +ファイルのパスは、それ以上チェックされることなく、直接インクルードされます。これでクラスのオートローディングを非常に高速化できます。 +実際に、すべての Yii のコアクラスは、この方法でオートロードされています。 + +次の方法で、 `Yii::$classMap` に格納されるクラスマップにクラスを追加できます: + +```php +Yii::$classMap['foo\bar\MyClass'] = 'path/to/MyClass.php'; +``` + +クラスファイルのパスを指定するのに、 [エイリアス](concept-aliases.md) を使うことができます。クラスが使用される前にマップが準備できるように、 +[ブートストラップ](runtime-bootstrapping.md) プロセス内でクラスマップを設定する必要があります。 + + +他のオートローダーの使用 +----------------------- + +Yii はパッケージ依存関係マネージャとして Composer を包含しているので、Composer のオートローダーもインストールすることをお勧めします。 +あなたが独自のオートローダーを持つサードパーティライブラリを使用している場合、それらもインストールする必要があります。 + +Yii オートローダーを他のオートローダーと一緒に使うときは、他のすべてのオートローダーがインストールされた *後で* 、 `Yii.php` +ファイルをインクルードする必要があります。これで Yii のオートローダーが、任意クラスのオートローディング要求に応答する最初のものになります。 +たとえば、次のコードは [Basic Application Template](start-basic.md) の [エントリスクリプト](structure-entry-scripts.md) から抜粋したものです。 +最初の行は、Composer のオートローダーをインストールしており、二行目は Yii のオートローダーをインストールしています。 + +```php +require(__DIR__ . '/../vendor/autoload.php'); +require(__DIR__ . '/../vendor/yiisoft/yii2/Yii.php'); +``` + +あなたは Yii のオートローダーを使わず、Composer のオートローダーだけを単独で使用することもできます。しかし、そうすることによって、 +あなたのクラスのオートローディングのパフォーマンスは低下し、クラスをオートロード可能にするために Composer が設定したルールに従わなければならなくなります。 + +> Info: Yiiのオートローダーを使用したくない場合は、 `Yii.php` ファイルの独自のバージョンを作成し、 + それを [エントリスクリプト](structure-entry-scripts.md) でインクルードする必要があります。 + + +エクステンションクラスのオートロード +----------------------------- + +Yii のオートローダーは、 [エクステンション](structure-extensions.md) クラスのオートロードが可能です。唯一の要件は、 +エクステンションがその `composer.json` ファイルに正しく `autoload` セクションを指定していることです。 +`autoload` 指定方法の詳細については [Composer のドキュメント](https://getcomposer.org/doc/04-schema.md#autoload) 参照してください。 + +Yii のオートローダーを使用しない場合でも、まだ Composer のオートローダーはエクステンションクラスをオートロード可能です。 + diff --git a/docs/guide-ja/concept-behaviors.md b/docs/guide-ja/concept-behaviors.md new file mode 100644 index 0000000..fd75878 --- /dev/null +++ b/docs/guide-ja/concept-behaviors.md @@ -0,0 +1,332 @@ +ビヘイビア +========= + +ビヘイビアは [[yii\base\Behavior]] 、あるいはその子クラスのインスタンスです。ビヘイビアは +[ミックスイン](http://en.wikipedia.org/wiki/Mixin) としても知られ、既存の [[yii\base\Component|component]] クラスの +機能を、クラスの継承を変更せずに拡張することができます。コンポーネントにビヘイビアをアタッチすると、その +コンポーネントにはビヘイビアのメソッドとプロパティが "注入" され、それらのメソッドとプロパティは、 +コンポーネントクラス自体に定義されているかのようにアクセスできるようになります。また、ビヘイビアは、 +コンポーネントによってトリガされた [イベント](concept-events.md) に応答することができるので、 +ビヘイビアでコンポーネントの通常のコード実行をカスタマイズすることができます。 + + +ビヘイビアの定義 +------------------ + +ビヘイビアを定義するには、 [[yii\base\Behavior]] あるいは子クラスを継承するクラスを作成します。たとえば: + +```php +namespace app\components; + +use yii\base\Behavior; + +class MyBehavior extends Behavior +{ + public $prop1; + + private $_prop2; + + public function getProp2() + { + return $this->_prop2; + } + + public function setProp2($value) + { + $this->_prop2 = $value; + } + + public function foo() + { + // ... + } +} +``` + +上のコードは、 `app\components\MyBehavior` という、2つのプロパティ -- `prop1` と `prop2` -- と +`foo()` メソッドを持つビヘイビアクラスを定義します。`prop2` プロパティは、 `getProp2()` getter メソッドと `setProp2()` setter メソッドで定義されることに着目してください。 +[[yii\base\Behavior]] は [[yii\base\Object]] を継承しているので、getter と​​ setter による [プロパティ](concept-properties.md) 定義をサポートします。 + +このクラスはビヘイビアなので、コンポーネントにアタッチされると、そのコンポーネントは `prop1` と `prop2` プロパティ、それと `foo()` メソッドを持つようになります。 + +> Tip: ビヘイビア内から、[[yii\base\Behavior::owner]] プロパティを介して、ビヘイビアをアタッチしたコンポーネントにアクセスすることができます。 + +コンポーネントイベントのハンドリング +------------------ + +ビヘイビアが、アタッチされたコンポーネントがトリガするイベントに応答する必要がある場合は、 +[[yii\base\Behavior::events()]] メソッドをオーバーライドしましょう。たとえば: + +```php +namespace app\components; + +use yii\db\ActiveRecord; +use yii\base\Behavior; + +class MyBehavior extends Behavior +{ + // ... + + public function events() + { + return [ + ActiveRecord::EVENT_BEFORE_VALIDATE => 'beforeValidate', + ]; + } + + public function beforeValidate($event) + { + // ... + } +} +``` + +[[yii\base\Behavior::events()]] メソッドは、イベントとそれに対応するハンドラのリストを返します。 +上の例では [[yii\db\ActiveRecord::EVENT_BEFORE_VALIDATE|EVENT_BEFORE_VALIDATE]] イベントがあること、 +そのハンドラ定義である `beforeValidate()` を宣言しています。イベントハンドラを指定するときは、以下の表記方法が使えます: + +* ビヘイビアクラスのメソッド名を参照する文字列 (上の例など) +* オブジェクトまたはクラス名と文字列のメソッド名 (括弧なし) 例 `[$object, 'methodName']` +* 無名関数 + +イベントハンドラのシグネチャは次のようにしてください。`$event` はイベントのパラメータを参照します。イベントの詳細については +[イベント](concept-events.md) セクションを参照してください。 + +```php +function ($event) { +} +``` + +ビヘイビアのアタッチ +------------------- + +[[yii\base\Component|component]] へのビヘイビアのアタッチは、静的にも動的にも可能です。実際は、前者のほうがより一般的ですが。 + +ビヘイビアを静的にアタッチするには、ビヘイビアをアタッチしたいコンポーネントクラスの [[yii\base\Component::behaviors()|behaviors()]] メソッドをオーバーライドします。 +[[yii\base\Component::behaviors()|behaviors()]] メソッドは、ビヘイビアの [構成](concept-configurations.md) のリストを返さなければなりません。 +各ビヘイビアの構成内容は、ビヘイビアのクラス名でも、構成情報配列でもかまいません。 + +```php +namespace app\models; + +use yii\db\ActiveRecord; +use app\components\MyBehavior; + +class User extends ActiveRecord +{ + public function behaviors() + { + return [ + // 無名ビヘイビア ビヘイビアクラス名のみ + MyBehavior::className(), + + // 名前付きビヘイビア ビヘイビアクラス名のみ + 'myBehavior2' => MyBehavior::className(), + + // 無名ビヘイビア 構成情報配列 + [ + 'class' => MyBehavior::className(), + 'prop1' => 'value1', + 'prop2' => 'value2', + ], + + // 名前付きビヘイビア 構成情報配列 + 'myBehavior4' => [ + 'class' => MyBehavior::className(), + 'prop1' => 'value1', + 'prop2' => 'value2', + ] + ]; + } +} +``` + +ビヘイビア構成に対応する配列のキーを指定することによって、ビヘイビアに名前を関連付けることができます。この場合、ビヘイビアは *名前付きビヘイビア* と呼ばれます。上の例では、2つの名前付きビヘイビア​​ +`myBehavior2` と `myBehavior4` があります。ビヘイビアが名前と関連付けられていない場合は、 *無名ビヘイビア* と呼ばれます。 + + +ビヘイビアを動的にアタッチするには、ビヘイビアをアタッチしようとしているコンポーネントの [[yii\base\Component::attachBehavior()]] メソッドを呼びます: + +```php +use app\components\MyBehavior; + +// ビヘイビアオブジェクトをアタッチ +$component->attachBehavior('myBehavior1', new MyBehavior); + +// ビヘイビアクラスをアタッチ +$component->attachBehavior('myBehavior2', MyBehavior::className()); + +// 構成情報配列をアタッチ +$component->attachBehavior('myBehavior3', [ + 'class' => MyBehavior::className(), + 'prop1' => 'value1', + 'prop2' => 'value2', +]); +``` +[[yii\base\Component::attachBehaviors()]] メソッドを使うと、いちどに複数のビヘイビアをアタッチできます: + +```php +$component->attachBehaviors([ + 'myBehavior1' => new MyBehavior, // 名前付きビヘイビア + MyBehavior::className(), // 無名ビヘイビア +]); +``` + +次のように、 [構成情報](concept-configurations.md) を通じてビヘイビアをアタッチすることもできます: + +```php +[ + 'as myBehavior2' => MyBehavior::className(), + + 'as myBehavior3' => [ + 'class' => MyBehavior::className(), + 'prop1' => 'value1', + 'prop2' => 'value2', + ], +] +``` + +詳しくは [構成情報](concept-configurations.md#configuration-format) セクションを参照してください。 + +ビヘイビアの使用 +--------------- + +ビヘイビアを使用するには、まず上記の方法に従って [[yii\base\Component|コンポーネント]] にアタッチします。ビヘイビアがコンポーネントにアタッチされれば、その使用方法はシンプルです。 + +あなたは、アタッチされているコンポーネントを介して、ビヘイビアの *パブリック* メンバ変数、または getter や setter によって定義されたプロパティにアクセスすることができます: + +```php +// "prop1" はビヘイビアクラス内で定義されたプロパティ +echo $component->prop1; +$component->prop1 = $value; +``` + +また同様に、ビヘイビアの *パブリック* メソッドも呼ぶことができます: + +```php +// foo() はビヘイビアクラス内で定義されたパブリックメソッド +$component->foo(); +``` + +ご覧のように、 `$component` は `prop1` と `foo()` を定義していないにもかかわらず、 +アタッチされたビヘイビアによって、それらをコンポーネント定義の一部であるかのように使うことができるのです。 + +もし2つのビヘイビアが同じプロパティやメソッドを定義し、かつ両方とも同じコンポーネントにアタッチされている場合は、 +プロパティやメソッドのアクセス時に、*最初に* コンポーネントにアタッチされたビヘイビアが優先されます。 + +ビヘイビアはコンポーネントにアタッチされるとき、名前と関連付けられているかもしれません。その場合、 +その名前を使用してビヘイビアオブジェクトにアクセスすることができます: + +```php +$behavior = $component->getBehavior('myBehavior'); +``` + +また、コンポーネントにアタッチされた全てのビヘイビアを取得することもできます: + +```php +$behaviors = $component->getBehaviors(); +``` + + +ビヘイビアのデタッチ +------------------- + +ビヘイビアをデタッチするには、ビヘイビアに付けられた名前とともに [[yii\base\Component::detachBehavior()]] を呼び出します: + +```php +$component->detachBehavior('myBehavior1'); +``` + +*全ての* ビヘイビアをデタッチすることもできます: + +```php +$component->detachBehaviors(); +``` + + +`TimestampBehavior` の利用 +------------------------- + +しめくくりに、[[yii\behaviors\TimestampBehavior]] を見てみましょう。このビヘイビアは、 +保存時 (つまり挿入や更新) に、[[yii\db\ActiveRecord|アクティブレコード]] モデルの +タイムスタンプ属性の自動更新をサポートします。 + +まず、使用しようと考えている [[yii\db\ActiveRecord|アクティブレコード]] クラスに、このビヘイビアをアタッチします: + +```php +namespace app\models\User; + +use yii\db\ActiveRecord; +use yii\behaviors\TimestampBehavior; + +class User extends ActiveRecord +{ + // ... + + public function behaviors() + { + return [ + [ + 'class' => TimestampBehavior::className(), + 'attributes' => [ + ActiveRecord::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'], + ActiveRecord::EVENT_BEFORE_UPDATE => ['updated_at'], + ], + ], + ]; + } +} +``` + +上のビヘイビア構成は、レコードが: + +* 挿入されるとき、ビヘイビアは現在のタイムスタンプを `created_at` と `updated_at` 属性に割り当てます +* 更新されるとき、ビヘイビアは現在のタイムスタンプを `updated_at` 属性に割り当てます + +所定の位置にそのコードを使用すると、もし `User` オブジェクトを設け、それを保存しようとしたら、そこで、 +`created_at` と `updated_at` が自動的に現在のタイムスタンプで埋められます。 + +```php +$user = new User; +$user->email = 'test@example.com'; +$user->save(); +echo $user->created_at; // 現在のタイムスタンプが表示される +``` + +[[yii\behaviors\TimestampBehavior|TimestampBehavior]] は、指定された属性に現在のタイムスタンプを割り当てて +それをデータベースに保存する、便利なメソッド [[yii\behaviors\TimestampBehavior::touch()|touch()]] を提供します。 + +```php +$user->touch('login_time'); +``` + +ビヘイビアとトレイトの比較 +---------------------- + +ビヘイビアは、主となるクラスにそのプロパティやメソッドを「注入する」という点で [トレイト](http://www.php.net/traits) +に似ていますが、これらは多くの面で異なります。以下に説明するように、それらは互いに長所と短所を持っています。 +それらは代替手段というよりも、むしろ相互補完関係のようなものです。 + + +### ビヘイビアを使う理由 + +ビヘイビアは通常のクラスのように、継承をサポートしています。いっぽうトレイトは、 +言語サポートされたコピー&ペーストとみなすことができます。トレイトは継承をサポートしません。 + +ビヘイビアは、コンポーネントクラスの変更を必要とせずに、動的なコンポーネントへのアタッチとデタッチが可能です。トレイトを使用するには、クラスをトレイトを使って書き換える必要があります。 + +ビヘイビアは構成可能ですがトレイトは不可能です。 + +ビヘイビアは、イベントに応答することで、コンポーネントのコード実行をカスタマイズできます。 + +同じコンポーネントにアタッチされた異なるビヘイビア間で名前の競合がある場合、その競合は自動的に、 +先にコンポーネントにアタッチされたものを優先することで解消されます。 +別のトレイトが起こした名前競合の場合、影響を受けるプロパティやメソッドの名前変更による、手動での解決が必要です。 + + +### トレイトを使う理由 + +ビヘイビアは時間もメモリも食うオブジェクトなので、トレイトはビヘイビアよりはるかに効率的です。 + +トレイトは言語構造であるため、IDE との相性に優れています。 + diff --git a/docs/guide-ja/concept-components.md b/docs/guide-ja/concept-components.md new file mode 100644 index 0000000..466fae5 --- /dev/null +++ b/docs/guide-ja/concept-components.md @@ -0,0 +1,90 @@ +コンポーネント +========== + +コンポーネントは、Yiiアプリケーションの主要な構成ブロックです。コンポーネントは [[yii\base\Component]] 、 +またはその派生クラスのインスタンスです。コンポーネントが他のクラスに提供する主な機能は次の 3 つです: + +* [プロパティ](concept-properties.md) +* [イベント](concept-events.md) +* [ビヘイビア](concept-behaviors.md) + +個々にでも、組み合わせでも、これらの機能は Yii のクラスのカスタマイズ性と使いやすさをとても高めてくれます。たとえば、[[yii\jui\DatePicker|日付選択]] を行うユーザインターフェース·コンポーネントは、 +対話型の日付選択UIを生成するとき、ビューで次のように使用することができます: + +```php +use yii\jui\DatePicker; + +echo DatePicker::widget([ + 'language' => 'ja', + 'name' => 'country', + 'clientOptions' => [ + 'dateFormat' => 'yy-mm-dd', + ], +]); +``` + +クラスが [[yii\base\Component]] を継承しているおかげで、ウィジェットのプロパティは簡単に記述できます。 + +コンポーネントは非常に強力ですが、 [イベント](concept-events.md) と [ビヘイビア](concept-behaviors.md) をサポートするため、 +余分にメモリとCPU時間を要し、通常のオブジェクトよりも少し重くなります。 +あなたのコンポーネントがこれら2つの機能を必要としない場合、[[yii\base\Component]] の代わりに、 [[yii\base\Object]] からコンポーネントクラスを派生することを検討してもよいでしょう。 +そうすることで、あなたのコンポーネントは、 [プロパティ](concept-properties.md) のサポートが維持されたまま、通常のPHPオブジェクトのように効率的になります。 + +[[yii\base\Component]] または [[yii\base\Object]] からクラスを派生するときは、次の規約に従うことが推奨されます: + +- コンストラクタをオーバーライドする場合は、コンストラクタの *最後の* パラメータとして `$config` パラメータを指定し、親のコンストラクタにこのパラメータを渡すこと。 +- 自分がオーバーライドしたコンストラクタの *最後で* 、必ず親クラスのコンストラクタを呼び出すこと。 +- [[yii\base\Object::init()]] メソッドをオーバーライドする場合は、自分の `init` メソッドの *最初に* 、必ず `init` の親実装を呼び出すようにすること。 + +例: + +```php +namespace yii\components\MyClass; + +use yii\base\Object; + +class MyClass extends Object +{ + public $prop1; + public $prop2; + + public function __construct($param1, $param2, $config = []) + { + // ... 構成前の初期化 + + parent::__construct($config); + } + + public function init() + { + parent::init(); + + // ... 構成後の初期化 + } +} +``` +このガイドラインに従うことで、あなたのコンポーネントは生成時に [コンフィグ可能](concept-configurations.md) になります。例: + +```php +$component = new MyClass(1, 2, ['prop1' => 3, 'prop2' => 4]); +// とする代わりに +$component = \Yii::createObject([ + 'class' => MyClass::className(), + 'prop1' => 3, + 'prop2' => 4, +], [1, 2]); +``` + +> 補足: [[Yii::createObject()]] を呼び出すアプローチは複雑に見えますが、より強力です。というのも、それが [依存性注入コンテナ](concept-di-container.md) 上に実装されているからです。 + + +[[yii\base\Object]] クラスには、次のオブジェクトライフサイクルが適用されます: + +1. コンストラクタ内の事前初期化。ここでデフォルトのプロパティ値を設定することができます。 +2. `$config` によるオブジェクトの構成。構成情報は、コンストラクタ内で設定されたデフォルト値を上書きすることがあります。 +3. [[yii\base\Object::init()|init()]] 内の事後初期化。サニティ・チェックやプロパティの正規化を行いたいときは、このメソッドをオーバーライドします。 +4. オブジェクトのメソッド呼び出し。 + +最初の 3 つのステップは、すべてのオブジェクトのコンストラクタ内で発生します。これは、あなたがクラスインスタンス (つまり、オブジェクト) を得たときには、 +すでにそのオブジェクトが適切な、信頼性の高い状態に初期化されていることを意味します。 + diff --git a/docs/guide-ja/concept-configurations.md b/docs/guide-ja/concept-configurations.md new file mode 100644 index 0000000..9544734 --- /dev/null +++ b/docs/guide-ja/concept-configurations.md @@ -0,0 +1,260 @@ +構成情報 +============== + +新しいオブジェクトを作成したり、既存のオブジェクトを初期化するとき、Yiiでは構成情報が広く使用されています。構成情報は通常、作成されるオブジェクトのクラス名、およびオブジェクトの [プロパティ](concept-properties.md) +に割り当てられる初期値のリストを含みます。構成情報は、オブジェクトの [イベント](concept-events.md) にアタッチされるハンドラのリストや、オブジェクトにアタッチされる +[ビヘイビア](concept-behaviors.md) のリストを含むこともできます。 + +以下では、データベース接続を作成して初期化するために、構成情報が使用されています: + +```php +$config = [ + 'class' => 'yii\db\Connection', + 'dsn' => 'mysql:host=127.0.0.1;dbname=demo', + 'username' => 'root', + 'password' => '', + 'charset' => 'utf8', +]; + +$db = Yii::createObject($config); +``` +[[Yiiの::CreateObject()]] メソッドは引数に構成情報の配列を受け取り、構成情報で名前指定されたクラスをインスタンス化してオブジェクトを作成します。オブジェクトがインスタンス化されるとき、その他の設定は、 +オブジェクトのプロパティ、イベントハンドラ、およびビヘイビアを初期化するのに使われます。 + +すでにオブジェクトがある場合は、構成情報配列でオブジェクトのプロパティを初期化するのに [[Yii::configure()]] を使用することができます: + +```php +Yii::configure($object, $config); +``` + +なお、この場合には、構成情報配列に `class` 要素を含んではいけません。 + + +## 構成情報の形式 + +構成情報の形式は、フォーマルには次のように説明できます: + +```php +[ + 'class' => 'ClassName', + 'propertyName' => 'propertyValue', + 'on eventName' => $eventHandler, + 'as behaviorName' => $behaviorConfig, +] +``` + +ここで + +* `class` 要素は、作成されるオブジェクトの完全修飾クラス名を指定します。 +* `propertyName` 要素は、名前で指定されたプロパティの初期値を指定します。キーはプロパティ名で、値はそれに対応する初期値です。 + パブリックメンバ変数と getter/setter によって定義されている [プロパティ](concept-properties.md) のみを設定することができます。 +* `on eventName` 要素は、どのようなハンドラがオブジェクトの [イベント](concept-events.md) にアタッチされるかを指定します。 + 配列のキーが `on` に続けてイベント名という書式になることに注意してください。サポートされているイベントハンドラの形式については、 + [イベント](concept-events.md) のセクションを参照してください。 +* `as behaviorName` 要素は、どのような [ビヘイビア](concept-behaviors.md) がオブジェクトにアタッチされるかを指定します。 + 配列のキーが `as` に続けてビヘイビア名という書式になり、 `$behaviorConfig` で示される値が、ここで説明する一般的な構成情報のような、 + ビヘイビアを作成するための構成情報になることに注意してください。 + +下記は、初期プロパティ値、イベントハンドラ、およびビヘイビアでの構成を示した例です: + +```php +[ + 'class' => 'app\components\SearchEngine', + 'apiKey' => 'xxxxxxxx', + 'on search' => function ($event) { + Yii::info("Keyword searched: " . $event->keyword); + }, + 'as indexer' => [ + 'class' => 'app\components\IndexerBehavior', + // ... プロパティ初期値 ... + ], +] +``` + + +## 構成情報の使用 + +構成情報は Yii の多くの場所で使用されています。このセクションの冒頭では、 [[Yii::createObject()]] +を使って、構成情報に応じてオブジェクトを作成する方法を示しました。このサブセクションでは、 +アプリケーションの構成とウィジェットの構成という、2つの主要な構成情報の用途を説明します。 + + +### アプリケーションの構成 + +[アプリケーション](structure-applications.md) の構成は、おそらく Yii の中で最も複雑な配列のひとつです。 +それは [[yii\web\Application|application]] クラスが、設定可能なプロパティとイベントを数多く持つためです。 +さらに重要なことは、その [[yii\web\Application::components|components]] プロパティが、アプリケーションに登録されている +コンポーネント生成用の構成情報配列を受け取ることができることです。以下は、 [basic application template](start-basic.md) +のアプリケーション構成ファイルの概要です。 + +```php +$config = [ + 'id' => 'basic', + 'basePath' => dirname(__DIR__), + 'extensions' => require(__DIR__ . '/../vendor/yiisoft/extensions.php'), + 'components' => [ + 'cache' => [ + 'class' => 'yii\caching\FileCache', + ], + 'mailer' => [ + 'class' => 'yii\swiftmailer\Mailer', + ], + 'log' => [ + 'class' => 'yii\log\Dispatcher', + 'traceLevel' => YII_DEBUG ? 3 : 0, + 'targets' => [ + [ + 'class' => 'yii\log\FileTarget', + ], + ], + ], + 'db' => [ + 'class' => 'yii\db\Connection', + 'dsn' => 'mysql:host=localhost;dbname=stay2', + 'username' => 'root', + 'password' => '', + 'charset' => 'utf8', + ], + ], +]; +``` + +この構成情報には、 `class` キーがありません。それは、[エントリスクリプト](structure-entry-scripts.md) で以下のように、 +クラス名が既に与えられて使用されているためです。 + +```php +(new yii\web\Application($config))->run(); +``` + +アプリケーションの `components` プロパティ構成の詳細については、 [アプリケーション](structure-applications.md) セクションと [サービスロケータ](concept-service-locator.md) セクションにあります。 + + +### ウィジェットの構成 + +[ウィジェット](structure-widgets.md) を使用するときは、多くの場合、ウィジェットのプロパティをカスタマイズするために、構成情報を使用する必要があります。 +[[yii\base\Widget::widget()]] と [[yii\base\Widget::begin()]] の両メソッドを使って、ウィジェットを作成できます。それらは、以下のような構成情報配列を取ります。 + +```php +use yii\widgets\Menu; + +echo Menu::widget([ + 'activateItems' => false, + 'items' => [ + ['label' => 'ホーム', 'url' => ['site/index']], + ['label' => '製品', 'url' => ['product/index']], + ['label' => 'ログイン', 'url' => ['site/login'], 'visible' => Yii::$app->user->isGuest], + ], +]); +``` + +上記のコードは、 `Menu` ウィジェットを作成し、その `activateItems` プロパティが `false` になるよう初期化します。 +`items` プロパティも、表示されるメニュー項目で構成されます。 + +クラス名がすでに与えられているので、構成情報配列が `class` キーを持つべきではないことに注意してください。 + + +## 構成情報ファイル + +構成情報がとても複雑になる場合、一般的な方法は、 *構成情報ファイル* と呼ばれる、ひとつまたは複数の PHP ファイルにそれを格納することです。 +構成情報ファイルは、構成情報を表す PHP 配列を return します。 +たとえば、次のように、 `web.php` と名づけたファイルにアプリケーション構成を保持することができます。 + +```php +return [ + 'id' => 'basic', + 'basePath' => dirname(__DIR__), + 'extensions' => require(__DIR__ . '/../vendor/yiisoft/extensions.php'), + 'components' => require(__DIR__ . '/components.php'), +]; +``` +`components` の構成もまた複雑になるため、上記のように、 `components.php` と呼ぶ別のファイルにそれを格納し `web.php` でそのファイルを "require" しています。 +この `components.php` の内容は、次のようになっています。 + +```php +return [ + 'cache' => [ + 'class' => 'yii\caching\FileCache', + ], + 'mailer' => [ + 'class' => 'yii\swiftmailer\Mailer', + ], + 'log' => [ + 'class' => 'yii\log\Dispatcher', + 'traceLevel' => YII_DEBUG ? 3 : 0, + 'targets' => [ + [ + 'class' => 'yii\log\FileTarget', + ], + ], + ], + 'db' => [ + 'class' => 'yii\db\Connection', + 'dsn' => 'mysql:host=localhost;dbname=stay2', + 'username' => 'root', + 'password' => '', + 'charset' => 'utf8', + ], +]; +``` + +構成情報ファイルに格納されている構成情報を取得するには、以下のように、それを "require" するだけです: + +```php +$config = require('path/to/web.php'); +(new yii\web\Application($config))->run(); +``` + + +## デフォルト設定 + +[[Yii::createObject()]] メソッドは、 [依存性注入コンテナ](concept-di-container.md) をベースに実装されています。 +そのため、指定されたクラスが [[Yii::createObject()]] を使用して作成されるとき、そのすべてのインスタンスに適用される、 +いわゆる *デフォルト設定* のセットを指定することができます。デフォルト設定は、 +[ブートストラップ](runtime-bootstrapping.md) コード内の `Yii::$container->set()` を呼び出すことで指定することができます。 + +たとえばあなたが、すべてのリンクページャーが最大で5つのページボタン (デフォルト値は10) を伴って表示されるよう +[[yii\widgets\LinkPager]] をカスタマイズしたいとき、その目標を達成するには次のコードを使用することができます。 + +```php +\Yii::$container->set('yii\widgets\LinkPager', [ + 'maxButtonCount' => 5, +]); +``` + +デフォルト設定を使用しなければ、あなたは、リンクページャーを使うすべての箇所で `maxButtonCount` を設定しなければなりません。 + + +## 環境定数 + +構成情報は、多くの場合、アプリケーションが実行される環境に応じて変化します。たとえば、 +開発環境では `mydb_dev` という名前のデータベースを使用し、本番サーバー上では `mydb_prod` データベースを +使用したいかもしれません。環境の切り替えを容易にするために、Yii は、あなたのアプリケーションの +[エントリスクリプト](structure-entry-scripts.md) で定義可能な `YII_ENV` という名前の定数を提供します。 +たとえば: + +```php +defined('YII_ENV') or define('YII_ENV', 'dev'); +``` + +`YII_ENV` を次のいずれかの値と定義することができます: + +- `prod`: 本番環境。定数 `YII_ENV_PROD` は true と評価されます。 + とくに定義しない場合、これが `YII_ENV` のデフォルト値です。 +- `dev`: 開発環境。定数 `YII_ENV_DEV` は true と評価されます。 +- `test`: テスト環境。定数 `YII_ENV_TEST` は true と評価されます。 + +これらの環境定数を使用すると、現在の環境に基づいて条件付きで構成情報を指定することもできます。 +たとえば、アプリケーション構成情報には、開発環境での [デバッグツールバーとデバッガ](tool-debugger.md) +を有効にするために、次のコードを含むことができます。 + +```php +$config = [...]; + +if (YII_ENV_DEV) { + // 'dev' 環境用に構成情報を調整 + $config['bootstrap'][] = 'debug'; + $config['modules']['debug'] = 'yii\debug\Module'; +} + +return $config; +``` diff --git a/docs/guide-ja/concept-di-container.md b/docs/guide-ja/concept-di-container.md new file mode 100644 index 0000000..51508ff --- /dev/null +++ b/docs/guide-ja/concept-di-container.md @@ -0,0 +1,326 @@ +依存性注入コンテナ +============================== + +依存性注入 (DI) コンテナは、オブジェクトとそのすべての依存オブジェクトを、インスタンス化し、設定する方法を知っているオブジェクトです。 +なぜ DI コンテナが便利なのかは、[Martin の記事](http://martinfowler.com/articles/injection.html) の説明がわかりやすいでしょう。 +ここでは、主に Yii の提供する DI コンテナの使用方法を説明します。 + + +依存性注入 +-------------------- + +Yii は [[yii\di\Container]] クラスを通して DI コンテナの機能を提供します。これは、次の種類の依存性注入をサポートしています: + +* コンストラクタ·インジェクション +* セッター/プロパティ·インジェクション +* PHP コーラブル·インジェクション + + +### コンストラクタ·インジェクション + +DI コンテナは、コンストラクタパラメータの型ヒントの助けを借りた、コンストラクタ·インジェクションをサポートしています。 +型ヒントは、クラスやインタフェースが新しいオブジェクトの作成で使用されるさい、どれが依存であるのかということをコンテナに教えます。 +コンテナは、依存クラスやインタフェースのインスタンスを取得し、コンストラクタを通して、新しいオブジェクトにそれらの注入を試みます。 +たとえば + +```php +class Foo +{ + public function __construct(Bar $bar) + { + } +} + +$foo = $container->get('Foo'); +// これは下記と等価: +$bar = new Bar; +$foo = new Foo($bar); +``` + + +### セッター/プロパティ·インジェクション + +セッター/プロパティ·インジェクションは、[構成情報](concept-configurations.md) を通してサポートされます。 +依存関係を登録するときや、新しいオブジェクトを作成するとき、コンテナが使用する構成情報を提供することができ、 +それに対応するセッターまたはプロパティを通じて依存関係が注入されます。たとえば + +```php +use yii\base\Object; + +class Foo extends Object +{ + public $bar; + + private $_qux; + + public function getQux() + { + return $this->_qux; + } + + public function setQux(Qux $qux) + { + $this->_qux = $qux; + } +} + +$container->get('Foo', [], [ + 'bar' => $container->get('Bar'), + 'qux' => $container->get('Qux'), +]); +``` + + +### PHP コーラブル・インジェクション + +この場合、コンテナは、登録された PHP のコーラブルオブジェクトを使用し、クラスの新しいインスタンスを構築します。 +コーラブルは、依存関係を解決し、新しく作成されたオブジェクトに適切にそれらを注入する責任があります。たとえば + +```php +$container->set('Foo', function () { + return new Foo(new Bar); +}); + +$foo = $container->get('Foo'); +``` + + +依存関係の登録 +------------------------ + +あなたは、[[yii\di\Container::set()]] 使って依存関係を登録することができます。登録には依存関係の名前だけでなく、 +依存関係の定義が必要です。依存関係の名前は、クラス名、インタフェース名、エイリアス名を指定することができます。 +依存関係の定義には、クラス名、構成情報配列、PHPのコーラブルを指定できます。 + +```php +$container = new \yii\di\Container; + +// クラス名そのまま。これはなくてもかまいません。 +$container->set('yii\db\Connection'); + +// インターフェースの登録 +// クラスがインターフェースに依存する場合、対応するクラスが依存オブジェクトとしてインスタンス化されます +$container->set('yii\mail\MailInterface', 'yii\swiftmailer\Mailer'); + +// エイリアス名の登録。$container->get('foo') を使って Connection のインスタンスを作成できます +$container->set('foo', 'yii\db\Connection'); + +// 構成情報をともなうクラスの登録。クラスが get() でインスタンス化されるとき構成情報が適用されます +$container->set('yii\db\Connection', [ + 'dsn' => 'mysql:host=127.0.0.1;dbname=demo', + 'username' => 'root', + 'password' => '', + 'charset' => 'utf8', +]); + +// クラスの構成情報をともなうエイリアス名の登録 +// この場合、クラスを指定する "class" 要素が必要です +$container->set('db', [ + 'class' => 'yii\db\Connection', + 'dsn' => 'mysql:host=127.0.0.1;dbname=demo', + 'username' => 'root', + 'password' => '', + 'charset' => 'utf8', +]); + +// PHP コーラブルの登録 +// このコーラブルは $container->get('db') が呼ばれるたびに実行されます +$container->set('db', function ($container, $params, $config) { + return new \yii\db\Connection($config); +}); + +// コンポーネントインスタンスの登録 +// $container->get('pageCache') は呼ばれるたびに毎回同じインスタンスを返します +$container->set('pageCache', new FileCache); +``` + +> 補足: 依存関係名が、対応する依存関係の定義と同じである場合は、それを DI コンテナに登録する必要はありません。 + +`set()` を介して登録された依存性は、依存性が必要とされるたびにインスタンスを生成します。 +[[yii\di\Container::setSingleton()]] を使うと、単一のインスタンスをひとつだけ生成する依存関係を登録することができます: + +```php +$container->setSingleton('yii\db\Connection', [ + 'dsn' => 'mysql:host=127.0.0.1;dbname=demo', + 'username' => 'root', + 'password' => '', + 'charset' => 'utf8', +]); +``` + + +依存関係の解決 +---------------------- + +依存関係を登録すると、新しいオブジェクトを作成するのに DI コンテナを使用することができ、 +コンテナが自動的に、依存性をインスタンス化して新しく作成されたオブジェクトに注入することで、 +依存関係を解決します。依存関係の解決は再帰的、つまり、ある依存性が他の依存関係を持っている場合、 +それらの依存関係も自動的に解決されます。 + +[[yii\di\Container::get()]] を使って、新しいオブジェクトを作成することができます。 +このメソッドは、クラス名、インタフェース名、エイリアス名で指定できる依存関係の名前を受け取ります。 +依存関係名は、 `set()` や `setSingleton()` を介して登録されていたりされていなかったりする +可能性があります。オプションで、クラスのコンストラクタのパラメータのリストや、新しく作成された +オブジェクトを設定するための [設定情報](concept-configurations.md) を渡すことができます。 +たとえば + +```php +// "db" は事前に登録されたエイリアス名 +$db = $container->get('db'); + +// これと同じ意味: $engine = new \app\components\SearchEngine($apiKey, ['type' => 1]); +$engine = $container->get('app\components\SearchEngine', [$apiKey], ['type' => 1]); +``` + +見えないところで、DIコンテナは、単に新しいオブジェクトを作成するよりもはるかに多くの作業を行います。 +コンテナは、最初の依存クラスまたはインタフェースの名前を見つけるために、クラスのコンストラクタを検査し、 +自動的にそれらの依存関係を再帰で解決します。 + +次のコードでより洗練された例を示します。 `UserLister` クラスは `UserFinderInterface` +インタフェースを実装するオブジェクトに依存します。 `UserFinder` クラスはこのインターフェイスを実装していて、かつ、 +`Connection` オブジェクトに依存します。これらのすべての依存関係は、クラスのコンストラクタのパラメータのタイプヒンティングで宣言されています。 +プロパティ依存性の登録をすれば、DI コンテナは自動的にこれらの依存関係を解決し、単純に `get('userLister')` +を呼び出すだけで新しい `UserLister` インスタンスを作成できます。 + +```php +namespace app\models; + +use yii\base\Object; +use yii\db\Connection; +use yii\di\Container; + +interface UserFinderInterface +{ + function findUser(); +} + +class UserFinder extends Object implements UserFinderInterface +{ + public $db; + + public function __construct(Connection $db, $config = []) + { + $this->db = $db; + parent::__construct($config); + } + + public function findUser() + { + } +} + +class UserLister extends Object +{ + public $finder; + + public function __construct(UserFinderInterface $finder, $config = []) + { + $this->finder = $finder; + parent::__construct($config); + } +} + +$container = new Container; +$container->set('yii\db\Connection', [ + 'dsn' => '...', +]); +$container->set('app\models\UserFinderInterface', [ + 'class' => 'app\models\UserFinder', +]); +$container->set('userLister', 'app\models\UserLister'); + +$lister = $container->get('userLister'); + +// と、いうのはこれと同じ: + +$db = new \yii\db\Connection(['dsn' => '...']); +$finder = new UserFinder($db); +$lister = new UserLister($finder); +``` + + +実際の使いかた +--------------- + +あなたのアプリケーションの [エントリスクリプト](structure-entry-scripts.md) で `Yii.php` ファイルをインクルードするとき、 +Yii は DI コンテナを作成します。この DI コンテナは [[Yii::$container]] を介してアクセス可能です。 [[Yii::createObject()]] を呼び出したとき、 +このメソッドは実際には、新しいオブジェクトを作成ために、コンテナの [[yii\di\Container::get()|get()]] メソッドを呼び出しています。 +前述のとおり、DI コンテナは(もしあれば)自動的に依存関係を解決し、新しく作成されたオブジェクトにそれらを注入します。 +Yii は、新しいオブジェクトを作成するさいそのコアコードのほとんどで [[Yii::createObject()]] を使用しているため、これは、 +[[Yii::$container]] を扱えばグローバルにオブジェクトをカスタマイズすることができることを意味しています。 + +たとえば、 [[yii\widgets\LinkPager]] のページネーションボタンのデフォルト個数をグローバルにカスタマイズすることができます: + +```php +\Yii::$container->set('yii\widgets\LinkPager', ['maxButtonCount' => 5]); +``` + +次のコードでビューでウィジェットを使用すれば、 `maxButtonCount` プロパティは、 +クラスで定義されているデフォルト値 10 の代わりに 5 で初期化されます。 + +```php +echo \yii\widgets\LinkPager::widget(); +``` + +DIコンテナを経由して設定された値は、こうやって、まだまだ上書きすることができます: + +```php +echo \yii\widgets\LinkPager::widget(['maxButtonCount' => 20]); +``` + +DI コンテナの自動コンストラクタ・インジェクションの利点を活かす別の例です。 +あなたのコントローラクラスが、ホテル予約サービスのような、いくつかの他のオブジェクトに依存するとします。 +あなたは、コンストラクタパラメータを通して依存関係を宣言して、DI コンテナにあなたの課題を解決させることができます。 + +```php +namespace app\controllers; + +use yii\web\Controller; +use app\components\BookingInterface; + +class HotelController extends Controller +{ + protected $bookingService; + + public function __construct($id, $module, BookingInterface $bookingService, $config = []) + { + $this->bookingService = $bookingService; + parent::__construct($id, $module, $config); + } +} +``` + +あなたがブラウザからこのコントローラにアクセスすると、 `BookingInterface` をインスタンス化できませんという +不具合報告エラーが表示されるでしょう。これは、この依存関係に対処する方法を DI コンテナに教える必要があるからです: + +```php +\Yii::$container->set('app\components\BookingInterface', 'app\components\BookingService'); +``` + +これで、あなたが再びコントローラにアクセスするときは、 `app\components\BookingService` +のインスタンスが作成され、コントローラのコンストラクタに3番目のパラメータとして注入されるようになります。 + + +依存関係を登録するときに +----------------------------- + +依存関係は、新しいオブジェクトが作成されるとき必要とされるので、それらの登録は可能な限り早期に行われるべきです。 +推奨プラクティス以下のとおりです: + +* あなたがアプリケーションの開発者である場合、アプリケーションの [エントリスクリプト](structure-entry-scripts.md) 内、 + またはエントリスクリプトにインクルードされるスクリプト内で、依存関係を登録することができます。 +* あなたが再配布可能な [エクステンション](structure-extensions.md) の開発者である場合は、エクステンションのブートストラップクラス内で + 依存関係を登録することができます。 + + +まとめ +------- + +依存性注入と [サービスロケータ](concept-service-locator.md) はともに、疎結合でよりテストしやすい方法でのソフトウェア構築を可能にする、 +定番のデザインパターンです。依存性注入とサービスロケータへのより深い理解を得るために、 [Martin の記事](http://martinfowler.com/articles/injection.html) +を読むことを強くお勧めします。 + +Yiiはその [サービスロケータ](concept-service-locator.md) を、依存性注入(DI)コンテナの上に実装しています。 +サービスロケータは、新しいオブジェクトのインスタンスを作成しようとしたとき、DI コンテナに呼び出しを転送します。 +後者は、依存関係を、上で説明したように自動的に解決します。 + diff --git a/docs/guide-ja/concept-events.md b/docs/guide-ja/concept-events.md new file mode 100644 index 0000000..84b06e8 --- /dev/null +++ b/docs/guide-ja/concept-events.md @@ -0,0 +1,279 @@ +イベント +====== + +イベントを使うと、既存のコードの特定の実行ポイントに、カスタムコードを挿入することができます。イベントにカスタムコードを添付すると、 +イベントがトリガされたときにコードが自動的に実行されます。たとえば、メーラーオブジェクトがメッセージを正しく送信できたとき、 +`messageSent` イベントをトリガするとします。もしメッセージの送信がうまく行ったことを知りたければ、単に `messageSent` +イベントにトラッキングコードを付与すするだけで、それが可能になります。 + +Yiiはイベントをサポートするために、 [[yii\base\Component]] と呼ばれる基底クラスを導入してします。クラスがイベントをトリガする必要がある場合は、 +[[yii\base\Component]] もしくはその子クラスを継承する必要があります。 + + +イベントハンドラ +-------------- + +イベントハンドラとは、関連するイベントがトリガされたときに実行される、 [PHP コールバック](http://www.php.net/manual/en/language.types.callable.php) +です。次のコールバックのいずれも使用可能です: + +- 文字列で指定されたグローバル PHP 関数 (括弧を除く) `'trim'` など +- オブジェクトとメソッド名文字列の配列で指定された、オブジェクトのメソッド (括弧を除く) `[$object, 'methodName']` など +- クラス名文字列とメソッド名文字列の配列で指定された、静的なクラスメソッド `[$class, 'methodName']` など +- 無名関数 `function ($event) { ... }` など + +イベントハンドラのシグネチャはこのようになります: + +```php +function ($event) { + // $event は yii\base\Event またはその子クラスのオブジェクト +} +``` + +`$event` パラメータを介して、イベントハンドラは発生したイベントに関して次の情報を得ることができます: + +- [[yii\base\Event::name|イベント名]] +- [[yii\base\Event::sender|イベント送信元]]: `trigger()` メソッドを呼び出したオブジェクト +- [[yii\base\Event::data|カスタムデータ]]: イベントハンドラを接続するときに提供されたデータ (後述) + + +イベントハンドラのアタッチ +------------------------ + +イベントハンドラは [[yii\base\Component::on()]] を呼び出すことでアタッチできます。たとえば: + +```php +$foo = new Foo; + +// このハンドラはグローバル関数です +$foo->on(Foo::EVENT_HELLO, 'function_name'); + +// このハンドラはオブジェクトのメソッドです +$foo->on(Foo::EVENT_HELLO, [$object, 'methodName']); + +// このハンドラは静的なクラスメソッドです +$foo->on(Foo::EVENT_HELLO, ['app\components\Bar', 'methodName']); + +// このハンドラは無名関数です +$foo->on(Foo::EVENT_HELLO, function ($event) { + // イベント処理ロジック +}); +``` + +また、 [構成情報](concept-configurations.md) を通じてイベントハンドラをアタッチすることもできます。詳細については +[構成情報](concept-configurations.md) の章を参照してください。 + +イベントハンドラをアタッチするとき、 [[yii\base\Component::on()]] の3番目のパラメータとして、付加的なデータを提供することができます。 +そのデータは、イベントがトリガされてハンドラが呼び出されるときに、ハンドラ内で利用きます。たとえば: + +```php +// 次のコードはイベントがトリガされたとき "abc" を表示します +// "on" に3番目の引数として渡されたデータを $event->data が保持しているからです +$foo->on(Foo::EVENT_HELLO, 'function_name', 'abc'); + +function function_name($event) { + echo $event->data; +} +``` + +イベントハンドラの順序 +------------------- + +ひとつのイベントには、ひとつだけでなく複数のハンドラをアタッチすることができます。イベントがトリガされると、アタッチされたハンドラは、 +それらがイベントにアタッチされた順序どおりに呼び出されます。あるハンドラがその後に続くハンドラの呼び出しを停止する必要がある場合は、 +`$event` パラメータの [[yii\base\Event::handled]] プロパティを true に設定します: + +```php +$foo->on(Foo::EVENT_HELLO, function ($event) { + $event->handled = true; +}); +``` + +デフォルトでは、新たに接続されたハンドラは、イベントの既存のハンドラのキューに追加されます。その結果、 +イベントがトリガされたとき、そのハンドラは一番最後に呼び出されます。もし、そのハンドラが最初に呼び出されるよう、 +ハンドラのキューの先頭に新しいハンドラを挿入したい場合は、[[yii\base\Component::on()]] を呼び出とき、4番目のパラメータ `$append` に false を渡します: + +```php +$foo->on(Foo::EVENT_HELLO, function ($event) { + // ... +}, $data, false); +``` + +イベントのトリガー +----------------- + +イベントは、 [[yii\base\Component::trigger()]] メソッドを呼び出すことでトリガされます。このメソッドには **イベント名** が必須で、 +オプションで、イベントハンドラに渡されるパラメータを記述したイベントオブジェクトを渡すこともできます。たとえば: + +```php +namespace app\components; + +use yii\base\Component; +use yii\base\Event; + +class Foo extends Component +{ + const EVENT_HELLO = 'hello'; + + public function bar() + { + $this->trigger(self::EVENT_HELLO); + } +} +``` + +上記のコードでは、すべての `bar()` の呼び出しは、 `hello` という名前のイベントをトリガします。 + +> Tip: イベント名を表すときはクラス定数を使用することをお勧めします。上記の例では、定数 `EVENT_HELLO` は + `hello` イベントを表しています。このアプローチには 3 つの利点があります。まず、タイプミスを防ぐことができます。次に、IDE の自動補完サポートでイベントを + 認識できるようになります。第 3 に、クラスでどんなイベントがサポートされているかを表したいとき、定数の宣言をチェックするだけで済みます。 + +イベントをトリガするとき、イベントハンドラに追加情報を渡したいことがあります。たとえば、メーラーが `messageSent` イベントのハンドラに +メッセージ情報を渡して、ハンドラが送信されたメッセージの詳細を知ることができるようにしたいかもしれません。 +これを行うために、 [[yii\base\Component::trigger()]] メソッドの2番目のパラメータとして、イベントオブジェクトを与えることができます。 +イベントオブジェクトは [[yii\base\Event]] クラスあるいはその子クラスのインスタンスでなければなりません。たとえば: + +```php +namespace app\components; + +use yii\base\Component; +use yii\base\Event; + +class MessageEvent extends Event +{ + public $message; +} + +class Mailer extends Component +{ + const EVENT_MESSAGE_SENT = 'messageSent'; + + public function send($message) + { + // ... $message 送信 ... + + $event = new MessageEvent; + $event->message = $message; + $this->trigger(self::EVENT_MESSAGE_SENT, $event); + } +} +``` + +[[yii\base\Component::trigger()]] メソッドが呼び出されたとき、この名前を付けられたイベントに +アタッチされたハンドラがすべて呼び出されます。 + + +イベントハンドラのデタッチ +------------------------ + +イベントからハンドラを取り外すには、 [[yii\base\Component::off()]] メソッドを呼び出します。たとえば: + +```php +// このハンドラはグローバル関数です +$foo->off(Foo::EVENT_HELLO, 'function_name'); + +// このハンドラはオブジェクトのメソッドです +$foo->off(Foo::EVENT_HELLO, [$object, 'methodName']); + +// このハンドラは静的なクラスメソッドです +$foo->off(Foo::EVENT_HELLO, ['app\components\Bar', 'methodName']); + +// このハンドラは無名関数です +$foo->off(Foo::EVENT_HELLO, $anonymousFunction); +``` + +一般的には、イベントにアタッチされたときどこかに保存してある場合を除き、無名関数を取り外そうとはしないでください。 +上記の例は、無名関数は変数 `$anonymousFunction` として保存されていたものとしています。 + +イベントからすべてのハンドラを取り外すには、単純に、第 2 パラメータを指定せずに [[yii\base\Component::off()]] を呼び出します。 + +```php +$foo->off(Foo::EVENT_HELLO); +``` + + +クラスレベル・イベントハンドラ +-------------------------- + +ここまでの項では、 *インスタンスレベル* でのイベントにハンドラをアタッチする方法を説明してきました。 +場合によっては、特定のインスタンスだけではなく、クラスのすべてのインスタンスがトリガした +イベントに応答したいことがあります。すべてのインスタンスにイベントハンドラをアタッチする代わりに、静的メソッド +[[yii\base\Event::on()]] を呼び出すことで、 *クラスレベル* でハンドラをアタッチすることができます。 + +たとえば、[アクティブレコード](db-active-record.md) オブジェクトは、データベースに新しいレコードを挿入するたびに、 +[[yii\db\BaseActiveRecord::EVENT_AFTER_INSERT|EVENT_AFTER_INSERT]] イベントをトリガします。 *すべての* +[アクティブレコード](db-active-record.md) オブジェクトによって行われる挿入を追跡するには、次のコードが使えます: + +```php +use Yii; +use yii\base\Event; +use yii\db\ActiveRecord; + +Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function ($event) { + Yii::trace(get_class($event->sender) . ' が挿入されました'); +}); +``` + +[[yii\db\ActiveRecord|ActiveRecord]] またはその子クラスのいずれかが、 [[yii\db\BaseActiveRecord::EVENT_AFTER_INSERT|EVENT_AFTER_INSERT]] +をトリガーするといつでも、このイベントハンドラが呼び出されます。ハンドラの中では、 `$event->sender` を通して、 +イベントをトリガしたオブジェクトを取得することができます。 + +オブジェクトがイベントをトリガするときは、最初にインスタンスレベルのハンドラを呼び出し、続いてクラスレベルのハンドラとなります。 + +静的メソッド [[yii\base\Event::trigger()]] を呼び出すことによって、 *クラスレベル* でイベントをトリガすることができます。 +クラスレベルでのイベントは、特定のオブジェクトに関連付けられていません。そのため、これはクラスレベルのイベントハンドラだけを +呼び出します。たとえば: + +```php +use yii\base\Event; + +Event::on(Foo::className(), Foo::EVENT_HELLO, function ($event) { + echo $event->sender; // "app\models\Foo" を表示 +}); + +Event::trigger(Foo::className(), Foo::EVENT_HELLO); +``` + +この場合、`$event->sender` は、オブジェクトインスタンスではなく、イベントをトリガーするクラスの名前を指すことに注意してください。 + +> 注: クラスレベルのハンドラは、そのクラスのあらゆるインスタンス、またはあらゆる子クラスのインスタンスがトリガしたイベントに応答 + してしまうため、よく注意して使わなければなりません。 [[yii\base\Object]] のように、クラスが低レベルの基底クラスの場合は特にそうです。 + +クラスレベルのイベントハンドラを取り外すときは、 [[yii\base\Event::off()]] を呼び出します。たとえば: + +```php +// $handler をデタッチ +Event::off(Foo::className(), Foo::EVENT_HELLO, $handler); + +// Foo::EVENT_HELLO のすべてのハンドラをデタッチ +Event::off(Foo::className(), Foo::EVENT_HELLO); +``` + + +グローバル・イベント +------------- + +Yiiは、実際に上記のイベントメカニズムに基づいたトリックである、いわゆる *グローバル・イベント* をサポートしています。 +グローバル・イベントは、 [アプリケーション](structure-applications.md) インスタンス自身などの、グローバルにアクセス可能なシングルトンを必要とします。 + +グローバルイベントを作成するには、イベント送信者は、送信者の自前の `trigger()` メソッドを呼び出す代わりに、シングルトンの +`trigger()` メソッドを呼び出してイベントをトリガします。同じく、イベントハンドラも、シングルトンのイベントにアタッチされます。たとえば: + +```php +use Yii; +use yii\base\Event; +use app\components\Foo; + +Yii::$app->on('bar', function ($event) { + echo get_class($event->sender); // "app\components\Foo" を表示 +}); + +Yii::$app->trigger('bar', new Event(['sender' => new Foo])); +``` + +グローバルイベントを使用する利点は、オブジェクトによってトリガされるイベントハンドラを設けたいとき、オブジェクトがなくてもいい +ということです。その代わりに、ハンドラのアタッチとイベントのトリガはともに、(アプリケーションのインスタンスなど) シングルトンを +介して行われます。 + +しかし、グローバルイベントの名前空間はあらゆる部分から共有されているので、名前空間の整理 ("frontend.mail.sent"、"backend.mail.sent" など) +を導入するというような、賢いグローバルイベントの名前付けをする必要があります。 + diff --git a/docs/guide-ja/concept-properties.md b/docs/guide-ja/concept-properties.md new file mode 100644 index 0000000..d2a3dd1 --- /dev/null +++ b/docs/guide-ja/concept-properties.md @@ -0,0 +1,77 @@ +プロパティ +========== + +PHPでは、クラスのメンバ変数は *プロパティ* とも呼ばれます。これらの変数は、クラス定義の一部で、クラスのインスタンスの状態を表すために +(すなわち、クラスのあるインスタンスを別のものと区別するために) 使用されます。現実によく、特別な方法でこのプロパティの読み書きを扱いたい +場合があります。たとえば、`label` プロパティに割り当てられる文字列が常にトリミングされるようにしたい、など。その仕事を成し遂げるために、 +あなたは次のようなコードを使ってきたのではありませんか: + +```php +$object->label = trim($label); +``` + +上記のコードの欠点は、 `label` プロパティを設定するすべてのコードで、`trim()` を呼び出す必要があるということです。もし将来的に、 +`label` プロパティに、最初の文字を大文字にしなければならない、といった新たな要件が発生したら、 `label` に値を代入するすべてのコードを変更しなければなりません。コー​​ドの繰り返しはバグを誘発するので、できれば避けたいところです。 + +この問題を解決するために、Yii は *getter* メソッドと *setter* メソッドをベースにしたプロパティ定義をサポートする、 [[yii\base\Object]] 基底クラスを提供します。 +クラスがその機能を必要とするなら、 [[yii\base\Object]] またはその子クラスを継承しましょう。 + +> 補足: Yiiのフレームワークのほぼすべてのコアクラスは、 [[yii\base\Object]] またはその子クラスを継承しています。 + これは、コアクラスに getter または setter があれば、それをプロパティのように使用できることを意味します。 + +getter メソッドは、名前が `get` で始まるメソッドで、setter メソッドは、`set` で始まるメソッドです。 +`get` または `set` プレフィクスの後の名前で、プロパティ名を定義します。次のコードに示すように、たとえば、`getLabel()` という getter と `setLabel()` という setter は、 +`label` という名前のプロパティを定義します: + +```php +namespace app\components; + +use yii\base\Object; + +class Foo extends Object +{ + private $_label; + + public function getLabel() + { + return $this->_label; + } + + public function setLabel($value) + { + $this->_label = trim($value); + } +} +``` +(詳しく言うと、getter および setter メソッドは、この場合には、内部的に `_label` と名付けられた private 属性を参照する `label` プロパティを作っています。) + +getter と setter によって定義されたプロパティは、クラスのメンバ変数のように使用することができます。主な違いは、 +それらのプロパティが読み取りアクセスされるときは、対応する getter ソッドが呼び出されることであり、プロパティに値が割り当てられるときには、 +対応する setter メソッドが呼び出されるということです。例: + +```php +// $label = $object->getLabel(); と同じ +$label = $object->label; + +// $object->setLabel('abc'); と同じ +$object->label = 'abc'; +``` + +setter なしの getter で定義されたプロパティは、 *読み取り専用* です。そのようなプロパティに値を代入しようとすると、 +[[yii\base\InvalidCallException|InvalidCallException]] が発生します。同様に、getter なしの setter で定義されたプロパティは、 +*書き込み専用* で、そのようなプロパティを読み取りしようとしても、例外が発生します。書き込み専用のプロパティを持つのは一般的ではありませんが。 + +getter と ​​setter で定義されたプロパティには、いくつかの特別なルールと制限があります: + +* この種のプロパティでは、名前の *大文字と小文字を区別しません* 。たとえば、 `$object->label` と `$object->Label` は同じです。 + これは、PHPのメソッド名が大文字と小文字を区別しないためです。 +* この種のプロパティの名前と、クラスのメンバ変数の名前とが同じである場合、後者が優先されます。 + たとえば、上記の `Foo` クラスがもしメンバ変数 `label` を持っているとすると、`$object->label = 'abc'` + という代入は *メンバ変数の* `label` に作用することになり、その行で `setLabel()` setter メソッドは呼び出されなくなります。 +* これらのプロパティは可視性をサポートしていません。プロパティが public、protected、private であるかどうかで、 + getter または setter メソッドの定義に違いは生じません。 +* プロパティは、 *静的でない* getter および setter でしか定義できません。静的メソッドは同じようには扱われません。 + +このガイドの冒頭で説明した問題に戻ると、 `label` に値が代入されているあらゆる箇所で `trim()` を呼ぶのではなく、もう `setLabel()` という setter の内部だけで `trim()` を呼べば済むのです。 +さらに、新しい要求でラベルの先頭を大文字にする必要が発生しても、他のいっさいのコードに触れることなく、すぐに `setLabel()` メソッドを変更することができます。一箇所の変更は、すべての `label` への代入に普遍的に作用します。 + diff --git a/docs/guide-ja/concept-service-locator.md b/docs/guide-ja/concept-service-locator.md new file mode 100644 index 0000000..9730468 --- /dev/null +++ b/docs/guide-ja/concept-service-locator.md @@ -0,0 +1,83 @@ +サービスロケータ +=============== + +サービスロケータは、アプリケーションが必要とする可能性のある各種のサービス (またはコンポーネント) を提供する方法を知っているオブジェクトです。 +サービスロケータ内では、各コンポーネントは単一のインスタンスとして存在し、IDによって一意に識別されます。 +あなたは、このIDを使用してサービスロケータからコンポーネントを取得できます。 + +Yii では、サービスロケータは単純に [[yii\di\ServiceLocator]] のインスタンス、または子クラスのインスタンスです。 + +Yii の中で最も一般的に使用されるサービスロケータは、 *アプリケーション* オブジェクトで、 `\Yii::$app` +を通じてアクセスできます。それが提供するサービスは、 *アプリケーションコンポーネント* と呼ばれ、それは `request` 、 +`response`、 `urlManager` のようなコンポーネントです。あなたはサービスロケータによって提供される機能を通じて、 +簡単に、これらのコンポーネントを構成、あるいは独自の実装に置き換え、といったことができます。 + +アプリケーションオブジェクトの他に、各モジュールオブジェクトもまたサービスロケータです。 + +サービスロケータを使用する最初のステップは、コンポーネントを登録することです。コンポーネントは、 [[yii\di\ServiceLocator::set()]] +を通じて登録することができます。次のコードは、コンポーネントを登録するさまざまな方法を示しています。 + +```php +use yii\di\ServiceLocator; +use yii\caching\FileCache; + +$locator = new ServiceLocator; + +// コンポーネントの作成に使われるクラス名を使用して "cache" を登録 +$locator->set('cache', 'yii\caching\ApcCache'); + +// コンポーネントの作成に使われる構成情報配列を使用して "db" を登録 +$locator->set('db', [ + 'class' => 'yii\db\Connection', + 'dsn' => 'mysql:host=localhost;dbname=demo', + 'username' => 'root', + 'password' => '', +]); + +// コンポーネントを構築する匿名関数を使って "search" を登録 +$locator->set('search', function () { + return new app\components\SolrService; +}); + +// コンポーネントを使って "pageCache" を登録 +$locator->set('pageCache', new FileCache); +``` + +いったんコンポーネントが登録されたら、次の 2 つの方法のいずれかで、その ID を使ってそれにアクセスすることができます: + +```php +$cache = $locator->get('cache'); +// または代わりに +$cache = $locator->cache; +``` + +以上のように、 [[yii\di\ServiceLocator]] はコンポーネント ID を使用したプロパティのように、コンポーネントにアクセスすることができます。 +あなたが最初にコンポーネントにアクセスしたとき、 [[yii\di\ServiceLocator]] はコンポーネントの登録情報を使用してコンポーネントの新しいインスタンスを作成し、 +それを返します。後でそのコンポーネントが再度アクセスされた場合、サービスロケータは同じインスタンスを返します。 + +[[yii\di\ServiceLocator::has()]] を使って、コンポーネント ID がすでに登録されているかをチェックできます。 +無効なIDで [[yii\di\ServiceLocator::get()]] を呼び出した場合、例外がスローされます。 + +サービスロケータは多くの場合、 [構成情報](concept-configurations.md) で作成されるため、 +[[yii\di\ServiceLocator::setComponents()|components]] という名前の書き込み可能プロパティが提供されています。 +これで一度に複数のコンポーネントを設定して登録することができます。次のコードはアプリケーションを構成する構成情報配列を示しており、 +"db" と "cache" と "search" コンポーネントの登録もしています: + +```php +return [ + // ... + 'components' => [ + 'db' => [ + 'class' => 'yii\db\Connection', + 'dsn' => 'mysql:host=localhost;dbname=demo', + 'username' => 'root', + 'password' => '', + ], + 'cache' => 'yii\caching\ApcCache', + 'search' => function () { + return new app\components\SolrService; + }, + ], +]; +``` + diff --git a/docs/guide-ja/db-active-record.md b/docs/guide-ja/db-active-record.md new file mode 100644 index 0000000..122492c --- /dev/null +++ b/docs/guide-ja/db-active-record.md @@ -0,0 +1,1000 @@ +アクティブレコード +================== + +> Note|注意: この節はまだ執筆中です。 + +[アクティブレコード](http://ja.wikipedia.org/wiki/Active_Record) は、データベースに保存されているデータにアクセスするために、オブジェクト指向のインタフェイスを提供するものです。 +アクティブレコードクラスはデータベーステーブルと関連付けられて、アクティブレコードのインスタンスがそのテーブルの行に対応し、アクティブレコードのインスタンスの属性がその行のカラムの値を表現します。 +生の SQL 文を書く代りに、アクティブレコードを使って、オブジェクト指向の流儀でデータベーステーブルのデータを操作することが出来ます。 + +例えば、`Customer` が `customer` テーブルに関連付けられたアクティブレコードクラスであり、`name` が `customer` テーブルのカラムであると仮定しましょう。 +`customer` テーブルに新しい行を挿入するために次のコードを書くことが出来ます。 + +```php +$customer = new Customer(); +$customer->name = 'Qiang'; +$customer->save(); +``` + +上記のコードは、次のように生の SQL 文を使うのと等価なものですが、生の SQL 文の方は、直感的でなく、間違いも生じやすく、また、DBMS の違いによる互換性の問題も生じ得ます。 + +```php +$db->createCommand('INSERT INTO customer (name) VALUES (:name)', [ + ':name' => 'Qiang', +])->execute(); +``` + +下記が、現在 Yii のアクティブレコードによってサポートされているデータベースのリストです。 + +* MySQL 4.1 以降: [[yii\db\ActiveRecord]] による。 +* PostgreSQL 7.3 以降: [[yii\db\ActiveRecord]] による。 +* SQLite 2 および 3: [[yii\db\ActiveRecord]] による。 +* Microsoft SQL Server 2008 以降: [[yii\db\ActiveRecord]] による。 +* Oracle: [[yii\db\ActiveRecord]] による。 +* CUBRID 9.3 以降: [[yii\db\ActiveRecord]] による。(cubrid PDO 拡張の [バグ](http://jira.cubrid.org/browse/APIS-658) + のために、値を引用符で囲む機能が動作しません。そのため、サーバだけでなくクライアントも CUBRID 9.3 が必要になります) +* Sphnix: [[yii\sphinx\ActiveRecord]] による。`yii2-sphinx` エクステンションが必要。 +* ElasticSearch: [[yii\elasticsearch\ActiveRecord]] による。`yii2-elasticsearch` エクステンションが必要。 +* Redis 2.6.12 以降: [[yii\redis\ActiveRecord]] による。`yii2-redis` エクステンションが必要。 +* MongoDB 1.3.0 以降: [[yii\mongodb\ActiveRecord]] による。`yii2-mongodb` エクステンションが必要。 + +ご覧のように、Yii はリレーショナルデータベースだけでなく NoSQL データベースに対してもアクティブレコードのサポートを提供しています。 +このチュートリアルでは、主としてリレーショナルデータベースのためのアクティブレコードの使用方法を説明します。 +しかし、ここで説明するほとんどの内容は NoSQL データベースのためのアクティブレコードにも適用することが出来るものです。 + + +アクティブレコードクラスを宣言する +---------------------------------- + +アクティブレコードクラスを宣言するためには、[[yii\db\ActiveRecord]] を拡張して、クラスと関連付けられるデータベーステーブルの名前を返す `tableName` メソッドを実装する必要があります。 + +```php +namespace app\models; + +use yii\db\ActiveRecord; + +class Customer extends ActiveRecord +{ + const STATUS_ACTIVE = 'active'; + const STATUS_DELETED = 'deleted'; + + /** + * @return string アクティブレコードクラスと関連付けられるデータベーステーブルの名前 + */ + public static function tableName() + { + return 'customer'; + } +} +``` + + +カラムのデータにアクセスする +---------------------------- + +アクティブレコードは、対応するデータベーステーブルの行の各カラムをアクティブレコードオブジェクトの属性に割り付けます。 +属性は通常のオブジェクトのパブリックなプロパティと同様の振る舞いをします。 +属性の名前は対応するから無名と同じであり、大文字と小文字を区別します。 + +カラムの値を読み出すために、次の構文を使用することが出来ます。 + +```php +// "id" と "email" は、$customer アクティブレコードオブジェクトと関連付けられたテーブルのカラム名 +$id = $customer->id; +$email = $customer->email; +``` + +カラムの値を変更するためには、関連付けられたプロパティに新しい値を代入して、オブジェクトを保存します。 + +```php +$customer->email = 'jane@example.com'; +$customer->save(); +``` + + +データベースに接続する +---------------------- + +アクティブレコードは、データベースとの間でデータを交換するために [[yii\db\Connection|DB 接続]] を使用します。 +既定では、アクティブレコードは `db` [アプリケーションコンポーネント](structure-application-components.md) を接続として使用します。 +[データベースの基礎](db-dao.md) で説明したように、次のようにして、アプリケーションの構成情報ファイルの中で `db` コンポーネントを構成することが出来ます。 + +```php +return [ + 'components' => [ + 'db' => [ + 'class' => 'yii\db\Connection', + 'dsn' => 'mysql:host=localhost;dbname=testdb', + 'username' => 'demo', + 'password' => 'demo', + ], + ], +]; +``` + +アプリケーションの中で複数のデータベースを使っており、アクティブレコードクラスのために異なる DB 接続を使いたい場合は、[[yii\db\ActiveRecord::getDb()|getDb()]] メソッドをオーバーライドすることが出来ます。 + +```php +class Customer extends ActiveRecord +{ + // ... + + public static function getDb() + { + return \Yii::$app->db2; // "db2" アプリケーションコンポーネントを使用 + } +} +``` + + +データベースにデータを問い合わせる +---------------------------------- + +アクティブレコードは、DB クエリを構築してアクティブレコードインスタンスにデータを投入するために、二つの入力メソッドを提供しています。 + + - [[yii\db\ActiveRecord::find()]] + - [[yii\db\ActiveRecord::findBySql()]] + +この二つのメソッドは [[yii\db\ActiveQuery]] のインスタンスを返します。 + [[yii\db\ActiveQuery]] は [[yii\db\Query]] を拡張したものであり、従って、[[yii\db\Query]] と同じ一連の柔軟かつ強力な DB クエリ構築メソッド、例えば、`where()`、`join()`、`orderBy()` 等を提供します。 +下記の例は、いくつかの可能性を示すものです。 + +```php +// *アクティブ* な顧客を全て読み出して、その ID によって並べ替える +$customers = Customer::find() + ->where(['status' => Customer::STATUS_ACTIVE]) + ->orderBy('id') + ->all(); + +// ID が 1 である一人の顧客を返す +$customer = Customer::find() + ->where(['id' => 1]) + ->one(); + +// *アクティブ* な顧客の数を返す +$count = Customer::find() + ->where(['status' => Customer::STATUS_ACTIVE]) + ->count(); + +// 結果を顧客 ID によってインデックスする +$customers = Customer::find()->indexBy('id')->all(); +// $customers 配列は顧客 ID によってインデックスされる + +// 生の SQL 文を使って顧客を読み出す +$sql = 'SELECT * FROM customer'; +$customers = Customer::findBySql($sql)->all(); +``` + +> Tip|ヒント: 上記のコードでは、`Customer::STATUS_ACTIVE` は `Customer` で定義されている定数です。 + コードの中で、ハードコードされた文字列や数字ではなく、意味が分かる名前の定数を使用することは良いプラクティスです。 + + +プライマリキーの値または一連のカラムの値に合致するアクティブレコードのインスタンスを返すためのショートカットメソッドが二つ提供されています。 +すなわち、`findOne()` と `findAll()` です。 +前者は合致する最初のインスタンスを返し、後者は合致する全てのインスタンスを返します。 +例えば、 + +```php +// ID が 1 である顧客を一人返す +$customer = Customer::findOne(1); + +// ID が 1 である *アクティブ* な顧客を一人返す +$customer = Customer::findOne([ + 'id' => 1, + 'status' => Customer::STATUS_ACTIVE, +]); + +// ID が 1、2、または 3 である顧客を全て返す +$customers = Customer::findAll([1, 2, 3]); + +// 状態が「削除済み」である顧客を全て返す +$customer = Customer::findAll([ + 'status' => Customer::STATUS_DELETED, +]); +``` + +> Note: デフォルトでは、`findOne()` も `one()` も、クエリに `LIMIT 1` を追加しません。 + クエリが一つだけまたは少数の行のデータしか返さないことが分かっている場合 (例えば、プライマリキーか何かでクエリをする場合) は、これで十分であり、また、この方が望ましいでしょう。 + しかし、クエリが多数の行のデータを返す可能性がある場合は、パフォーマンスを向上させるために `limit(1)` を呼ぶべきです。 + 例えば、`Customer::find()->where(['status' => Customer::STATUS_ACTIVE])->limit(1)->one()` のように。 + + +### データを配列に読み出す + +大量のデータを処理する場合には、メモリ使用量を節約するために、データベースから取得したデータを配列に保持したいこともあるでしょう。 +これは、`asArray()` を呼ぶことによって実現できます。 + +```php +// 顧客を `Customer` オブジェクトでなく配列の形式で返す +$customers = Customer::find() + ->asArray() + ->all(); +// $customers の各要素は、「名前-値」のペアの配列 +``` + +このメソッドはメモリを節約してパフォーマンスを向上させますが、低い抽象レイヤに向って一歩を踏み出すものであり、アクティブレコードのレイヤが持ついくつかの機能を失うことになるという点に注意してください。 +`asArray` を使ってデータを読み出すことは、[クエリビルダ](db-dao.md) を使って普通のクエリを実行するのと、ほとんど同じことです。 +`asArray` を使うと、結果は、型変換の実行を伴わない単純な配列になります。 +その結果、アクティブレコードオブジェクトでアクセスする場合には整数になるフィールドが、文字列の値を含むことがあり得ます。 + +### データをバッチモードで読み出す + +[クエリビルダ](db-query-builder.md) において、大量のデータをデータベースから検索する場合に、メモリ使用量を最小化するために *バッチクエリ* を使うことが出来るということを説明しました。 +おなじテクニックをアクティブレコードでも使うことが出来ます。 +例えば、 + +```php +// 一度に 10 人の顧客を読み出す +foreach (Customer::find()->batch(10) as $customers) { + // $customers は 10 以下の Customer オブジェクトの配列 +} +// 一度に 10 人の顧客を読み出して、一人ずつ反復する +foreach (Customer::find()->each(10) as $customer) { + // $customer は Customer オブジェクト +} +// いーがーローディングをするバッチクエリ +foreach (Customer::find()->with('orders')->each() as $customer) { +} +``` + + +データベースのデータを操作する +------------------------------ + +アクティブレコードは、一つのアクティブレコードインスタンスに関連付けられたテーブルの一行を挿入、更新または削除するために、次のメソッドを提供しています。 + +- [[yii\db\ActiveRecord::save()|save()]] +- [[yii\db\ActiveRecord::insert()|insert()]] +- [[yii\db\ActiveRecord::update()|update()]] +- [[yii\db\ActiveRecord::delete()|delete()]] + +アクティブレコードは、アクティブレコードクラスと関連付けられたテーブル全体に適用する、次の静的なメソッドを提供しています。 +これらのメソッドはテーブル全体に影響を与えますので、使用するときはこの上なく注意深くしなければなりません。 +例えば、`deleteAll()` はテーブルの全ての行を削除します。 + +- [[yii\db\ActiveRecord::updateCounters()|updateCounters()]] +- [[yii\db\ActiveRecord::updateAll()|updateAll()]] +- [[yii\db\ActiveRecord::updateAllCounters()|updateAllCounters()]] +- [[yii\db\ActiveRecord::deleteAll()|deleteAll()]] + + +次の例は、これらのメソッドの使用方法を示すものです。 + +```php +// 新しい customer のレコードを挿入する +$customer = new Customer(); +$customer->name = 'James'; +$customer->email = 'james@example.com'; +$customer->save(); // $customer->insert() と等値 + +// 既存の customer のレコードを更新する +$customer = Customer::findOne($id); +$customer->email = 'james@example.com'; +$customer->save(); // $customer->update() と等値 + +// 既存の customer のレコードを削除する +$customer = Customer::findOne($id); +$customer->delete(); + +// いくつかの customer のレコードを削除する +Customer::deleteAll('age > :age AND gender = :gender', [':age' => 20, ':gender' => 'M']); + +// すべてのレコードの年齢に 1 を追加する +Customer::updateAllCounters(['age' => 1]); +``` + +> Info|情報: `save()` メソッドは、アクティブレコードインスタンスが新しいものであるか否かに従って、`insert()` または `update()` を呼びます + (内部的には、[[yii\db\ActiveRecord::isNewRecord]] の値をチェックして判断します)。 + アクティブレコードのインスタンスが `new` 演算子によって作成された場合は、`save()` を呼ぶと、テーブルに新しい行が挿入されます。 + データベースから読み出されたアクティブレコードに対して `save()` を呼ぶと、テーブルの中の対応する行が更新されます。 + + +### データの入力と検証 + +アクティブレコードは [[yii\base\Model]] を拡張したものですので、[モデル](structure-models.md) で説明したのと同じデータ入力と検証の機能をサポートしています。 +例えば、[[yii\base\Model::rules()|rules()]] メソッドをオーバーライドして検証規則を宣言することが出来ます。 +アクティブレコードインスタンスにユーザの入力データを一括代入することも出来ます。 +また、[[yii\base\Model::validate()|validate()]] を呼んで、データ検証を実行させることも出来ます。 + +`save()`、`insert()` または `update()` を呼ぶと、これらのメソッドが自動的に [[yii\base\Model::validate()|validate()]] を呼びます。 +検証が失敗すると、対応するデータ保存操作はキャンセルされます。 + +次の例は、アクティブレコードを使ってユーザ入力を収集/検証してデータベースに保存する方法を示すものです。 + +```php +// 新しいレコードを作成する +$model = new Customer; +if ($model->load(Yii::$app->request->post()) && $model->save()) { + // ユーザ入力が収集、検証されて、保存された +} + +// プライマリキーが $id であるレコードを更新する +$model = Customer::findOne($id); +if ($model === null) { + throw new NotFoundHttpException; +} +if ($model->load(Yii::$app->request->post()) && $model->save()) { + // ユーザ入力が収集、検証されて、保存された +} +``` + + +### デフォルト値を読み出す + +テーブルのカラムの定義は、デフォルト値を含むことが出来ます。 +アクティブレコードのためのウェブフォームに、このデフォルト値を事前に代入しておきたい場合があるでしょう。 +そうするためには、フォームを表示する前に、[[yii\db\ActiveRecord::loadDefaultValues()|loadDefaultValues()]] を呼びます。 + +```php +$customer = new Customer(); +$customer->loadDefaultValues(); +// ... $customer の HTML フォームを表示する ... +``` + +属性に対して何かの初期値を自分自身で設定したい場合は、アクティブレコードクラスの `init()` メソッドをオーバーライドして、そこで値を設定することが出来ます。 +例えば、`status` 属性のデフォルト値を設定したい場合は、 + +```php +public function init() +{ + parent::init(); + $this->status = self::STATUS_ACTIVE; +} +``` + +アクティブレコードのライフサイクル +---------------------------------- + +アクティブレコードがデータベースのデータの操作に使われるときのライフサイクルを理解しておくことは重要なことです。 +そのライフサイクルは、概して、対応するイベントと関連付けられており、それらのイベントに対して干渉したり反応したりするコードを注入できるようになっています。 +これらのイベントは特にアクティブレコードの [ビヘイビア](concept-behaviors.md) を開発するときに役に立ちます。 + +アクティブレコードの新しいインスタンスを作成する場合は、次のライフサイクルを経ます。 + +1. コンストラクタ +2. [[yii\db\ActiveRecord::init()|init()]]: [[yii\db\ActiveRecord::EVENT_INIT|EVENT_INIT]] イベントをトリガ + +[[yii\db\ActiveRecord::find()|find()]] メソッドによってデータを検索する場合は、新しくデータを投入されるアクティブレコードの全てが、それぞれ、次のライフサイクルを経ます。 + +1. コンストラクタ +2. [[yii\db\ActiveRecord::init()|init()]]: [[yii\db\ActiveRecord::EVENT_INIT|EVENT_INIT]] イベントをトリガ +3. [[yii\db\ActiveRecord::afterFind()|afterFind()]]: [[yii\db\ActiveRecord::EVENT_AFTER_FIND|EVENT_AFTER_FIND]] イベントをトリガ + +[[yii\db\ActiveRecord::save()|save()]] を呼んで、アクティブレコードを挿入または更新する場合は、次のライフサイクルを経ます。 + +1. [[yii\db\ActiveRecord::beforeValidate()|beforeValidate()]]: [[yii\db\ActiveRecord::EVENT_BEFORE_VALIDATE|EVENT_BEFORE_VALIDATE]] イベントをトリガ +2. [[yii\db\ActiveRecord::afterValidate()|afterValidate()]]: [[yii\db\ActiveRecord::EVENT_AFTER_VALIDATE|EVENT_AFTER_VALIDATE]] イベントをトリガ +3. [[yii\db\ActiveRecord::beforeSave()|beforeSave()]]: [[yii\db\ActiveRecord::EVENT_BEFORE_INSERT|EVENT_BEFORE_INSERT]] または [[yii\db\ActiveRecord::EVENT_BEFORE_UPDATE|EVENT_BEFORE_UPDATE]] イベントをトリガ +4. 実際のデータ挿入または更新を実行 +5. [[yii\db\ActiveRecord::afterSave()|afterSave()]]: [[yii\db\ActiveRecord::EVENT_AFTER_INSERT|EVENT_AFTER_INSERT]] または [[yii\db\ActiveRecord::EVENT_AFTER_UPDATE|EVENT_AFTER_UPDATE]] イベントをトリガ + +最後に、[[yii\db\ActiveRecord::delete()|delete()]] を呼んで、アクティブレコードを削除する場合は、次のライフサイクルを経ます。 + +1. [[yii\db\ActiveRecord::beforeDelete()|beforeDelete()]]: [[yii\db\ActiveRecord::EVENT_BEFORE_DELETE|EVENT_BEFORE_DELETE]] イベントをトリガ +2. 実際のデータ削除を実行 +3. [[yii\db\ActiveRecord::afterDelete()|afterDelete()]]: [[yii\db\ActiveRecord::EVENT_AFTER_DELETE|EVENT_AFTER_DELETE]] イベントをトリガ + + +リレーショナルデータを扱う +-------------------------- + +テーブルのリレーショナルデータもアクティブレコードを使ってクエリすることが出来ます +(すなわち、テーブル A のデータを選択すると、テーブル B の関連付けられたデータも一緒に取り込むことが出来ます)。 +アクティブレコードのおかげで、返されるリレーショナルデータは、プライマリテーブルと関連付けられたアクティブレコードオブジェクトのプロパティのようにアクセスすることが出来ます。 + +例えば、適切なリレーションが宣言されていれば、`$customer->orders` にアクセスすることによって、指定された顧客が発行した注文を表す `Order` オブジェクトの配列を取得することが出来ます。 + +リレーションを宣言するためには、[[yii\db\ActiveQuery]] オブジェクトを返すゲッターメソッドを定義します。そして、その [[yii\db\ActiveQuery]] オブジェクトは、リレーションのコンテキストに関する情報を持ち、従って関連するレコードだけをクエリするものとします。 +例えば、 + +```php +class Customer extends \yii\db\ActiveRecord +{ + public function getOrders() + { + // Customer は Order.customer_id -> id によって、複数の Order を持つ + return $this->hasMany(Order::className(), ['customer_id' => 'id']); + } +} + +class Order extends \yii\db\ActiveRecord +{ + public function getCustomer() + { + // Order は Customer.id -> customer_id によって、一つの Customer を持つ + return $this->hasOne(Customer::className(), ['id' => 'customer_id']); + } +} +``` + +上記の例で使用されている [[yii\db\ActiveRecord::hasMany()]] と [[yii\db\ActiveRecord::hasOne()]] のメソッドは、リレーショナルデータベースにおける多対一と一対一の関係を表現するために使われます。 +例えば、顧客 (customer) は複数の注文 (order) を持ち、注文 (order) は一つの顧客 (customer)を持つ、という関係です。 +これらのメソッドはともに二つのパラメータを取り、[[yii\db\ActiveQuery]] オブジェクトを返します。 + + - `$class`: 関連するモデルのクラス名。これは完全修飾のクラス名でなければなりません。 + - `$link`: 二つのテーブルに属するカラム間の関係。これは配列として与えられなければなりません。 + 配列のキーは、`$class` と関連付けられるテーブルにあるカラムの名前であり、配列の値はリレーションを宣言しているクラスのテーブルにあるカラムの名前です。 + リレーションをテーブルの外部キーに基づいて定義するのが望ましいプラクティスです。 + +リレーションを宣言した後は、リレーショナルデータを取得することは、対応するゲッターメソッドで定義されているコンポーネントのプロパティを取得するのと同じように、とても簡単なことになります。 + +```php +// 顧客の注文を取得する +$customer = Customer::findOne(1); +$orders = $customer->orders; // $orders は Order オブジェクトの配列 +``` + +舞台裏では、上記のコードは、各行について一つずつ、次の二つの SQL クエリを実行します。 + +```sql +SELECT * FROM customer WHERE id=1; +SELECT * FROM order WHERE customer_id=1; +``` + +> Tip|情報: `$customer->orders` という式に再びアクセスした場合は、第二の SQL クエリはもう実行されません。 + 第二の SQL クエリは、この式が最初にアクセスされた時だけ実行されます。 + 二度目以降のアクセスでは、内部的にキャッシュされている以前に読み出した結果が返されるだけです。 + リレーショナルデータを再クエリしたい場合は、単純に、まず既存の式を未設定状態に戻して (`unset($customer->orders);`) から、再度、`$customer->orders` にアクセスします。 + +場合によっては、リレーショナルクエリにパラメータを渡したいことがあります。 +例えば、顧客の注文を全て返す代りに、小計が指定した金額を超える大きな注文だけを返したいことがあるでしょう。 +そうするためには、次のようなゲッターメソッドで `bigOrders` リレーションを宣言します。 + +```php +class Customer extends \yii\db\ActiveRecord +{ + public function getBigOrders($threshold = 100) + { + return $this->hasMany(Order::className(), ['customer_id' => 'id']) + ->where('subtotal > :threshold', [':threshold' => $threshold]) + ->orderBy('id'); + } +} +``` + +`hasMany()` が 返す [[yii\db\ActiveQuery]] は、[[yii\db\ActiveQuery]] のメソッドを呼ぶことでクエリをカスタマイズ出来るものであることを覚えておいてください。 + +上記の宣言によって、`$customer->bigOrders` にアクセスした場合は、小計が 100 以上である注文だけが返されることになります。 +異なる閾値を指定するためには、次のコードを使用します。 + +```php +$orders = $customer->getBigOrders(200)->all(); +``` + +> Note|注意: リレーションメソッドは [[yii\db\ActiveQuery]] のインスタンスを返します。 +リレーションを属性 (すなわち、クラスのプロパティ) としてアクセスした場合は、返り値はリレーションのクエリ結果となります。 +クエリ結果は、リレーションが複数のレコードを返すものか否かに応じて、[[yii\db\ActiveRecord]] の一つのインスタンス、またはその配列、または null となります。 +例えば、`$customer->getOrders()` は `ActiveQuery` のインスタンスを返し、`$customer->orders` は `Order` オブジェクトの配列 (またはクエリ結果が無い場合は空の配列) を返します。 + + +中間テーブルを使うリレーション +------------------------------ + +場合によっては、二つのテーブルが [中間テーブル][] と呼ばれる中間的なテーブルによって関連付けられていることがあります。 +そのようなリレーションを宣言するために、[[yii\db\ActiveQuery::via()|via()]] または [[yii\db\ActiveQuery::viaTable()|viaTable()]] メソッドを呼んで、[[yii\db\ActiveQuery]] オブジェクトをカスタマイズすることが出来ます。 + +例えば、テーブル `order` とテーブル `item` が中間テーブル `order_item` によって関連付けられている場合、`Order` クラスにおいて `items` リレーションを次のように宣言することが出来ます。 + +```php +class Order extends \yii\db\ActiveRecord +{ + public function getItems() + { + return $this->hasMany(Item::className(), ['id' => 'item_id']) + ->viaTable('order_item', ['order_id' => 'id']); + } +} +``` + +[[yii\db\ActiveQuery::via()|via()]] メソッドは、最初のパラメータとして、結合テーブルの名前ではなく、アクティブレコードクラスで宣言されているリレーションの名前を取ること以外は、[[yii\db\ActiveQuery::viaTable()|viaTable()]] と同じです。 +例えば、上記の `items` リレーションは次のように宣言しても等値です。 + +```php +class Order extends \yii\db\ActiveRecord +{ + public function getOrderItems() + { + return $this->hasMany(OrderItem::className(), ['order_id' => 'id']); + } + + public function getItems() + { + return $this->hasMany(Item::className(), ['id' => 'item_id']) + ->via('orderItems'); + } +} +``` + +[中間テーブル]: https://en.wikipedia.org/wiki/Junction_table "Junction table on Wikipedia" + + +レイジーローディングとイーガーローディング +------------------------------------------ + +前に述べたように、関連オブジェクトに最初にアクセスしたときに、アクティブレコードは DB クエリを実行して関連データを読み出し、それを関連オブジェクトに投入します。 +同じ関連オブジェクトに再度アクセスしても、クエリは実行されません。 +これを *レイジーローディング* と呼びます。 +例えば、 + +```php +// 実行される SQL: SELECT * FROM customer WHERE id=1 +$customer = Customer::findOne(1); +// 実行される SQL: SELECT * FROM order WHERE customer_id=1 +$orders = $customer->orders; +// SQL は実行されない +$orders2 = $customer->orders; +``` + +レイジーローディングは非常に使い勝手が良いものです。しかし、次のシナリオでは、パフォーマンスの問題を生じ得ます。 + +```php +// 実行される SQL: SELECT * FROM customer WHERE id=1 +$customers = Customer::find()->limit(100)->all(); + +foreach ($customers as $customer) { + // 実行される SQL: SELECT * FROM order WHERE customer_id=... + $orders = $customer->orders; + // ... $orders を処理 ... +} +``` + +データベースに 100 人以上の顧客が登録されていると仮定した場合、上記のコードで何個の SQL クエリが実行されるでしようか? +101 です。最初の SQL クエリが 100 人の顧客を返します。 +次に、100 人の顧客全てについて、それぞれ、顧客の注文を返すための SQL クエリが実行されます。 + +上記のパフォーマンスの問題を解決するためには、[[yii\db\ActiveQuery::with()]] を呼んでいわゆる *イーガーローディング* を使うことが出来ます。 + +```php +// 実行される SQL: SELECT * FROM customer LIMIT 100; +// SELECT * FROM orders WHERE customer_id IN (1,2,...) +$customers = Customer::find()->limit(100) + ->with('orders')->all(); + +foreach ($customers as $customer) { + // SQL は実行されない + $orders = $customer->orders; + // ... $orders を処理 ... +} +``` + +ご覧のように、同じ仕事をするのに必要な SQL クエリがたった二つになります。 + +> Info|情報: 一般化して言うと、`N` 個のリレーションのうち `M` 個のリレーションが `via()` または `viaTable()` によって定義されている場合、この `N` 個のリレーションをイーガーロードしようとすると、合計で `1+M+N` 個の SQL クエリが実行されます。 +> 主たるテーブルの行を返すために一つ、`via()` または `viaTable()` の呼び出しに対応する `M` 個の中間テーブルのそれぞれに対して一つずつ、そして、`N` 個の関連テーブルのそれぞれに対して一つずつ、という訳です。 + +> Note|注意: イーガーローディングで `select()` をカスタマイズしようとする場合は、関連モデルにリンクするカラムを必ず含めてください。 +> そうしないと、関連モデルは読み出されません。例えば、 + +```php +$orders = Order::find()->select(['id', 'amount'])->with('customer')->all(); +// $orders[0]->customer は常に null になる。この問題を解決するためには、次のようにしなければならない。 +$orders = Order::find()->select(['id', 'amount', 'customer_id'])->with('customer')->all(); +``` + +場合によっては、リレーショナルクエリをその場でカスタマイズしたいことがあるでしょう。 +これは、レイジーローディングでもイーガーローディングでも、可能です。例えば、 + +```php +$customer = Customer::findOne(1); +// レイジーローディング: SELECT * FROM order WHERE customer_id=1 AND subtotal>100 +$orders = $customer->getOrders()->where('subtotal>100')->all(); + +// イーガーローディング: SELECT * FROM customer LIMIT 100 +// SELECT * FROM order WHERE customer_id IN (1,2,...) AND subtotal>100 +$customers = Customer::find()->limit(100)->with([ + 'orders' => function($query) { + $query->andWhere('subtotal>100'); + }, +])->all(); +``` + + +逆リレーション +-------------- + +リレーションは、たいていの場合、ペアで定義することが出来ます。 +例えば、`Customer` が `orders` という名前のリレーションを持ち、`Order` が `customer` という名前のリレーションを持つ、ということがあります。 + +```php +class Customer extends ActiveRecord +{ + .... + public function getOrders() + { + return $this->hasMany(Order::className(), ['customer_id' => 'id']); + } +} + +class Order extends ActiveRecord +{ + .... + public function getCustomer() + { + return $this->hasOne(Customer::className(), ['id' => 'customer_id']); + } +} +``` + +次に例示するクエリを実行すると、注文 (order) のリレーションとして取得した顧客 (customer) が、最初にその注文をリレーションとして取得した顧客とは別の Customer オブジェクトになってしまうことに気付くでしょう。 +また、`customer->orders` にアクセスすると一個の SQL が実行され、`order->customer` にアクセスするともう一つ別の SQL が実行されるということにも気付くでしょう。 + +```php +// SELECT * FROM customer WHERE id=1 +$customer = Customer::findOne(1); +// "等しくない" がエコーされる +// SELECT * FROM order WHERE customer_id=1 +// SELECT * FROM customer WHERE id=1 +if ($customer->orders[0]->customer === $customer) { + echo '等しい'; +} else { + echo '等しくない'; +} +``` + +冗長な最後の SQL 文の実行を避けるためには、次のように、[[yii\db\ActiveQuery::inverseOf()|inverseOf()]] メソッドを呼んで、`customer` と `oerders` のリレーションに対して逆リレーションを宣言することが出来ます。 + +```php +class Customer extends ActiveRecord +{ + .... + public function getOrders() + { + return $this->hasMany(Order::className(), ['customer_id' => 'id'])->inverseOf('customer'); + } +} +``` + +こうすると、上記と同じクエリを実行したときに、次の結果を得ることが出来ます。 + +```php +// SELECT * FROM customer WHERE id=1 +$customer = Customer::findOne(1); +// "等しい" がエコーされる +// SELECT * FROM order WHERE customer_id=1 +if ($customer->orders[0]->customer === $customer) { + echo '等しい'; +} else { + echo '等しくない'; +} +``` + +上記では、レイジーローディングにおいて逆リレーションを使う方法を示しました。 +逆リレーションはイーガーローディングにも適用されます。 + +```php +// SELECT * FROM customer +// SELECT * FROM order WHERE customer_id IN (1, 2, ...) +$customers = Customer::find()->with('orders')->all(); +// "等しい" がエコーされる +if ($customers[0]->orders[0]->customer === $customers[0]) { + echo '等しい'; +} else { + echo '等しくない'; +} +``` + +> Note|注意: 逆リレーションはピボットテーブルを含むリレーションに対しては定義することが出来ません。 +> つまり、リレーションが [[yii\db\ActiveQuery::via()|via()]] または [[yii\db\ActiveQuery::viaTable()|viaTable()]] によって定義されている場合は、[[yii\db\ActiveQuery::inverseOf()]] を追加で呼ぶことは出来ません。 + + +リレーションを使ってテーブルを結合する +-------------------------------------- + +リレーショナルデータベースを扱う場合、複数のテーブルを結合して、JOIN SQL 文にさまざまなクエリ条件とパラメータを指定することは、ごく当り前の仕事です。 +その目的を達するために、[[yii\db\ActiveQuery::join()]] を明示的に呼んで JOIN クエリを構築する代りに、既存のリレーション定義を再利用して [[yii\db\ActiveQuery::joinWith()]] を呼ぶことが出来ます。 +例えば、 + +```php +// 全ての注文を検索して、注文を顧客 ID と注文 ID でソートする。同時に "customer" をイーガーロードする。 +$orders = Order::find()->joinWith('customer')->orderBy('customer.id, order.id')->all(); +// 書籍を含む全ての注文を検索し、"books" をイーガーロードする。 +$orders = Order::find()->innerJoinWith('books')->all(); +``` + +上記において、[[yii\db\ActiveQuery::innerJoinWith()|innerJoinWith()]] メソッドは、結合タイプを `INNER JOIN` とする [[yii\db\ActiveQuery::joinWith()|joinWith()]] へのショートカットです。 + +一個または複数のリレーションを結合することが出来ます。リレーションにクエリ条件をその場で適用することも出来ます。 +また、サブリレーションを結合することも出来ます。例えば、 + +```php +// 複数のリレーションを結合 +// 書籍を含む注文で、過去 24 時間以内に登録した顧客によって発行された注文を検索する +$orders = Order::find()->innerJoinWith([ + 'books', + 'customer' => function ($query) { + $query->where('customer.created_at > ' . (time() - 24 * 3600)); + } +])->all(); +// サブリレーションとの結合: 書籍および書籍の著者を結合 +$orders = Order::find()->joinWith('books.author')->all(); +``` + +舞台裏では、Yii は最初に JOIN SQL 文を実行して、その JOIN SQL に適用された条件を満たす主たるモデルを取得します。 +そして、次にリレーションごとのクエリを実行して、対応する関連レコードを投入します。 + +[[yii\db\ActiveQuery::joinWith()|joinWith()]] と [[yii\db\ActiveQuery::with()|with()]] の違いは、前者が主たるモデルクラスのテーブルと関連モデルクラスのテーブルを結合して主たるモデルを読み出すのに対して、後者は主たるモデルクラスのテーブルに対してだけクエリを実行して主たるモデルを読み出す、という点にあります。 + +この違いによって、[[yii\db\ActiveQuery::joinWith()|joinWith()]] では、JOIN SQL 文だけに指定できるクエリ条件を適用することが出来ます。 +例えば、上記の例のように、関連モデルに対する条件によって主たるモデルをフィルタすることが出来ます。 +主たるモデルを関連テーブルのカラムを使って並び替えることも出来ます。 + +[[yii\db\ActiveQuery::joinWith()|joinWith()]] を使うときは、カラム名の曖昧さを解決することについて、あなたが責任を負わなければなりません。 +上記の例では、order テーブルと item テーブルがともに `id` という名前のカラムを持っているため、`item.id` と `order.id` を使って、`id` カラムの参照の曖昧さを解決しています。 + +既定では、リレーションを結合すると、リレーションがイーガーロードされることにもなります。 +この既定の動作は、指定されたリレーションをイーガーロードするかどうかを規定する `$eagerLoading` パラメータを渡して、変更することが出来ます。 + +また、既定では、[[yii\db\ActiveQuery::joinWith()|joinWith()]] は関連テーブルを結合するのに `LEFT JOIN` を使います。 +結合タイプをカスタマイズするために `$joinType` パラメータを渡すことが出来ます。 +`INNER JOIN` タイプのためのショートカットとして、[[yii\db\ActiveQuery::innerJoinWith()|innerJoinWith()]] を使うことが出来ます。 + +下記に、いくつかの例を追加します。 + +```php +// 書籍を含む注文を全て検索するが、"books" はイーガーロードしない。 +$orders = Order::find()->innerJoinWith('books', false)->all(); +// これも上と等値 +$orders = Order::find()->joinWith('books', false, 'INNER JOIN')->all(); +``` + +二つのテーブルを結合するとき、場合によっては、JOIN クエリの ON の部分で何らかの追加条件を指定する必要があります。 +これは、次のように、[[yii\db\ActiveQuery::onCondition()]] メソッドを呼んで実現することが出来ます。 + +```php +class User extends ActiveRecord +{ + public function getBooks() + { + return $this->hasMany(Item::className(), ['owner_id' => 'id'])->onCondition(['category_id' => 1]); + } +} +``` + +上記においては、[[yii\db\ActiveRecord::hasMany()|hasMany()]] メソッドが [[yii\db\ActiveQuery]] のインスタンスを返しています。 +そして、それに対して [[yii\db\ActiveQuery::onCondition()|onCondition()]] が呼ばれて、`category_id` が 1 である品目だけが返されるべきことを指定しています。 + +[[yii\db\ActiveQuery::joinWith()|joinWith()]] を使ってクエリを実行すると、指定された ON 条件が対応する JOIN クエリの ON の部分に挿入されます。 +例えば、 + +```php +// SELECT user.* FROM user LEFT JOIN item ON item.owner_id=user.id AND category_id=1 +// SELECT * FROM item WHERE owner_id IN (...) AND category_id=1 +$users = User::find()->joinWith('books')->all(); +``` + +[[yii\db\ActiveQuery::with()]] を使ってイーガーロードする場合や、レイジーロードする場合には、JOIN クエリは使われないため、ON 条件が対応する SQL 文の WHERE の部分に挿入されることに注意してください。 +例えば、 + +```php +// SELECT * FROM user WHERE id=10 +$user = User::findOne(10); +// SELECT * FROM item WHERE owner_id=10 AND category_id=1 +$books = $user->books; +``` + + +関連付けを扱う +-------------- + +アクティブレコードは、二つのアクティブレコードオブジェクト間の関連付けを確立および破棄するために、次の二つのメソッドを提供しています。 + +- [[yii\db\ActiveRecord::link()|link()]] +- [[yii\db\ActiveRecord::unlink()|unlink()]] + +例えば、顧客と新しい注文があると仮定したとき、次のコードを使って、その注文をその顧客のものとすることが出来ます。 + +```php +$customer = Customer::findOne(1); +$order = new Order(); +$order->subtotal = 100; +$customer->link('orders', $order); +``` + +上記の [[yii\db\ActiveRecord::link()|link()]] の呼び出しは、注文の `customer_id` に `$customer` のプライマリキーの値を設定し、[[yii\db\ActiveRecord::save()|save()]] を呼んで注文をデータベースに保存します。 + + +DBMS 間のリレーション +--------------------- + +アクティブレコードは、異なる DBMS に属するエンティティ間、例えば、リレーショナルデータベースのテーブルと MongoDB のコレクションの間に、リレーションを確立することを可能にしています。 +そのようなリレーションでも、何も特別なコードは必要ありません。 + +```php +// リレーショナルデータベースのアクティブレコード +class Customer extends \yii\db\ActiveRecord +{ + public static function tableName() + { + return 'customer'; + } + + public function getComments() + { + // リレーショナルデータベースに保存されている Customer は、MongoDB コレクションに保存されている複数の Comment を持つ + return $this->hasMany(Comment::className(), ['customer_id' => 'id']); + } +} + +// MongoDb のアクティブレコード +class Comment extends \yii\mongodb\ActiveRecord +{ + public static function collectionName() + { + return 'comment'; + } + + public function getCustomer() + { + // MongoDB コレクションに保存されている Comment は、リレーショナルデータベースに保存されている一つの Customer を持つ + return $this->hasOne(Customer::className(), ['id' => 'customer_id']); + } +} +``` + +アクティブレコードの全ての機能、例えば、イーガーローディングやレイジーローディング、関連付けの確立や破棄などが、DBMS 間のリレーションでも利用可能です。 + +> Note|注意: DBMS ごとのアクティブレコードの実装には、DBMS 固有のメソッドや機能が含まれる場合があり、そういうものは DBMS 間のリレーションには適用できないということを忘れないでください。 + 例えば、[[yii\db\ActiveQuery::joinWith()]] の使用が MongoDB コレクションに対するリレーションでは動作しないことは明白です。 + + +スコープ +-------- + +[[yii\db\ActiveRecord::find()|find()]] または [[yii\db\ActiveRecord::findBySql()|findBySql()]] を呼ぶと、[[yii\db\ActiveQuery|ActiveQuery]] のインスタンスが返されます。 +そして、追加のクエリメソッド、例えば、[[yii\db\ActiveQuery::where()|where()]] や [[yii\db\ActiveQuery::orderBy()|orderBy()]] を呼んで、クエリ条件をさらに指定することが出来ます。 + +別々の場所で同じ一連のクエリメソッドを呼びたいということがあり得ます。 +そのような場合には、いわゆる *スコープ* を定義することを検討すべきです。 +スコープは、本質的には、カスタムクエリクラスの中で定義されたメソッドであり、クエリオブジェクトを修正する一連のメソッドを呼ぶものです。 +スコープを定義しておくと、通常のクエリメソッドを呼ぶ代りに、スコープを使うことが出来るようになります。 + +スコープを定義するためには二つのステップが必要です。 +最初に、モデルのためのカスタムクエリクラスを作成して、このクラスの中に必要なスコープメソッドを定義します。 +例えば、`Comment` モデルのために `CommentQuery` クラスを作成して、次のように、`active()` というスコープメソッドを定義します。 + +```php +namespace app\models; + +use yii\db\ActiveQuery; + +class CommentQuery extends ActiveQuery +{ + public function active($state = true) + { + $this->andWhere(['active' => $state]); + return $this; + } +} +``` + +重要な点は、以下の通りです。 + +1. クラスは `yii\db\ActiveQuery` (または、`yii\mongodb\ActiveQuery` などの、その他の `ActiveQuery`) を拡張したものにしなければなりません。 +2. メソッドは `public` で、メソッドチェーンが出来るように `$this` を返さなければなりません。メソッドはパラメータを取ることが出来ます。 +3. クエリ条件を修正する方法については、[[yii\db\ActiveQuery]] のメソッド群を参照するのが非常に役に立ちます。 + +次に、[[yii\db\ActiveRecord::find()]] をオーバーライドして、通常の [[yii\db\ActiveQuery|ActiveQuery]] の代りに、カスタムクエリクラスを使うようにします。 +上記の例のためには、次のコードを書く必要があります。 + +```php +namespace app\models; + +use yii\db\ActiveRecord; + +class Comment extends ActiveRecord +{ + /** + * @inheritdoc + * @return CommentQuery + */ + public static function find() + { + return new CommentQuery(get_called_class()); + } +} +``` + +以上です。これで、カスタムスコープメソッドを使用することが出来ます。 + +```php +$comments = Comment::find()->active()->all(); +$inactiveComments = Comment::find()->active(false)->all(); +``` + +リレーションを定義するときにもスコープを使用することが出来ます。例えば、 + +```php +class Post extends \yii\db\ActiveRecord +{ + public function getActiveComments() + { + return $this->hasMany(Comment::className(), ['post_id' => 'id'])->active(); + + } +} +``` + +または、リレーショナルクエリを実行するときに、その場でスコープを使うことも出来ます。 + +```php +$posts = Post::find()->with([ + 'comments' => function($q) { + $q->active(); + } +])->all(); +``` + +### デフォルトスコープ + +あなたが Yii 1.1 を前に使ったことがあれば、*デフォルトスコープ* と呼ばれる概念を知っているかも知れません。 +デフォルトスコープは、全てのクエリに適用されるスコープです。 +デフォルトスコープは、[[yii\db\ActiveRecord::find()]] をオーバライドすることによって、簡単に定義することが出来ます。 +例えば、 + +```php +public static function find() +{ + return parent::find()->where(['deleted' => false]); +} +``` + +ただし、すべてのクエリにおいて、デフォルトの条件を上書きしないために、[[yii\db\ActiveQuery::where()|where()]] を使わず、[[yii\db\ActiveQuery::andWhere()|andWhere()]] または [[yii\db\ActiveQuery::orWhere()|orWhere()]] を使うべきであることに注意してください。 + + +トランザクション操作 +-------------------- + +アクティブレコードを扱う際には、二つの方法でトランザクション操作を処理することができます。 +最初の方法は、"[データベースの基礎](db-dao.md)" の「トランザクション」の項で説明したように、全てを手作業でやる方法です。 +もう一つの方法として、`transactions` メソッドを実装して、モデルのシナリオごとに、どの操作をトランザクションで囲むかを指定することが出来ます。 + +```php +class Post extends \yii\db\ActiveRecord +{ + public function transactions() + { + return [ + 'admin' => self::OP_INSERT, + 'api' => self::OP_INSERT | self::OP_UPDATE | self::OP_DELETE, + // 上は次と等値 + // 'api' => self::OP_ALL, + ]; + } +} +``` + +上記において、`admin` と `api` はモデルのシナリオであり、`OP_` で始まる定数は、これらのシナリオについてトランザクションで囲まれるべき操作を示しています。 +サポートされている操作は、`OP_INSERT`、`OP_UPDATE`、そして、`OP_DELETE` です。 +`OP_ALL` は三つ全てを示します。 + +このような自動的なトランザクションは、`beforeSave`、`afterSave`、`beforeDelete`、`afterDelete` によってデータベースに追加の変更を加えており、本体の変更と追加の変更の両方が成功した場合にだけデータベースにコミットしたい、というときに取り分けて有用です。 + +楽観的ロック +------------ + +楽観的ロックは、複数のユーザが編集のために同一のレコードにアクセスすることを許容しつつ、発生しうる衝突を回避するものです。 +例えば、ユーザが (別のユーザが先にデータを修正したために) 陳腐化したデータに対してレコードの保存を試みた場合は、[[\yii\db\StaleObjectException]] 例外が投げられて、更新または削除はスキップされます。 + +楽観的ロックは、`update()` と `delete()` メソッドだけでサポートされ、既定では使用されません。 + +楽観的ロックを使用するためには、 + +1. 各行のバージョン番号を保存するカラムを作成します。カラムのタイプは `BIGINT DEFAULT 0` でなければなりません。 + `optimisticLock()` メソッドをオーバーライドして、このカラムの名前を返すようにします。 +2. ユーザ入力を収集するウェブフォームに、更新されるレコードのロックバージョンを保持する隠しフィールドを追加します。 +3. データ更新を行うコントローラアクションにおいて、[[\yii\db\StaleObjectException]] 例外を捕捉して、衝突を解決するために必要なビジネスロジック (例えば、変更をマージしたり、データの陳腐化を知らせたり) を実装します。 + +ダーティな属性 +-------------- + +属性は、データベースからロードされた後、または最後のデータ保存の後に値が変更されると、ダーティであると見なされます。 +そして、`save()`、`update()`、`insert()` などを呼んでレコードデータを保存するときは、ダーティな属性だけがデータベースに保存されます。 +ダーティな属性が無い場合は、保存すべきものは無いことになり、クエリは何も発行されません。 + +参照 +---- + +以下も参照してください。 + +- [モデル](structure-models.md) +- [[yii\db\ActiveRecord]] diff --git a/docs/guide-ja/db-dao.md b/docs/guide-ja/db-dao.md index 5018edc..d78f39e 100644 --- a/docs/guide-ja/db-dao.md +++ b/docs/guide-ja/db-dao.md @@ -18,7 +18,7 @@ Yii はデフォルトで下記の DBMS をサポートしています。 - [CUBRID](http://www.cubrid.org/): バージョン 9.3 以上。(cubrid PDO 拡張の [バグ](http://jira.cubrid.org/browse/APIS-658) のために、値を引用符で囲む機能が動作しません。そのため、サーバだけでなくクライアントも CUBRID 9.3 が必要になります) - [Oracle](http://www.oracle.com/us/products/database/overview/index.html) -- [MSSQL](https://www.microsoft.com/en-us/sqlserver/default.aspx): バージョン 2005 以上。 +- [MSSQL](https://www.microsoft.com/en-us/sqlserver/default.aspx): バージョン 2008 以上。 構成 @@ -199,7 +199,7 @@ $connection->createCommand()->update('user', ['status' => 1], 'age > 30')->execu $connection->createCommand()->delete('user', 'status = 0')->execute(); ``` -テーブルとカラムの名前を引用符で囲む +テーブルとカラムの名前を引用符で囲む ------------------------------------ テーブルとカラムの名前をクエリの中で安全に使えるようにするために、Yii にそれらの名前を引用符で適切に囲ませることが出来ます。 diff --git a/docs/guide-ja/db-elasticsearch.md b/docs/guide-ja/db-elasticsearch.md new file mode 100644 index 0000000..411d83d --- /dev/null +++ b/docs/guide-ja/db-elasticsearch.md @@ -0,0 +1,6 @@ +Elasticsearch +============= + +> Note|注意: この節はまだ執筆中です。 +> +> まだ内容がありません。 diff --git a/docs/guide-ja/db-migrations.md b/docs/guide-ja/db-migrations.md new file mode 100644 index 0000000..5551156 --- /dev/null +++ b/docs/guide-ja/db-migrations.md @@ -0,0 +1,361 @@ +データベースマイグレーション +============================ + +> Note|注意: この節はまだ執筆中です。 + +ソースコードと同じように、データベースの構造も、データベース駆動型のアプリケーションが開発され保守されるにともなって徐々に発展していきます。 +例えば、開発中に新しいテーブルが追加されることもあるでしょうし、アプリケーションが実運用に移行した後になって追加のインデックスが必要であることが発見されることもあるでしょう。 +このようなデータベースの構造的な変更 (**マイグレーション** と呼ばれます) を追跡記録することが重要であるのは、ソースコードに対する変更がバージョン管理を使って追跡記録されるのと全く同じことです。 +ソースコードとデータベースの同期が失われると、バグが発生するか、アプリケーション全体が動かなくなるかします。 +こうした理由によって、データベースマイグレーションツールを提供して、データベースマイグレーションの履歴の追跡管理、新しいマイグレーションの適用、また、既存のマイグレーションの取消が出来るようにしています。 + +下記のステップは、開発中にチームによってデータベースマイグレーションが使用される例を示すものです。 + +1. Tim が新しいマイグレーション (例えば、新しいテーブルを作成したり、カラムの定義を変更したりなど) を作る。 +2. Tim が新しいマイグレーションをソースコントロールシステム (例えば Git や Mercurial) にコミットする。 +3. Doug がソースコントロールシステムから自分のレポジトリを更新して新しいマイグレーションを受け取る。 +4. Doug がマイグレーションを彼のローカルの開発用データベースに適用し、Tim が行った変更を反映して、自分のデータベースを同期する。 + +Yii はデータベースマイグレーションを `yii migrate` コマンドラインツールによってサポートします。 +このツールは、以下の機能をサポートしています。 + +* 新しいマイグレーションの作成 +* マイグレーションの適用、取消、再適用 +* マイグレーションの履歴と新規マイグレーションの閲覧 + + +マイグレーションを作成する +-------------------------- + +新しいマイグレーションを作成するためには、次のコマンドを実行します。 + +``` +yii migrate/create +``` + +要求される `name` パラメータには、マイグレーションの非常に短い説明を指定します。 +例えば、マイグレーションが *news* という名前のテーブルを作成するものである場合は、コマンドを次のようにして使います。 + +``` +yii migrate/create create_news_table +``` + +すぐ後で説明するように、マイグレーションでは、この `name` パラメータは PHP のクラス名の一部として使用されます。 +したがって、アルファベット、数字、および/または、アンダースコアだけを含まなければなりません。 + +上記のコマンドは、`m101129_185401_create_news_table.php` という名前の新しいファイルを作成します。 +このファイルは `@app/migrations` ディレクトリに作成されます。 +初期状態では、このマイグレーションファイルは以下のコードを含んでいます。 + +```php +class m101129_185401_create_news_table extends \yii\db\Migration +{ + public function up() + { + } + + public function down() + { + echo "m101129_185401_create_news_table cannot be reverted.\n"; + return false; + } +} +``` + +クラス名はファイル名と同じであり、`m_` というパターンに従います。ここで、 + +* `` は、マイグレーションが作成された (`yymmdd_hhmmss` という書式の) UTC タイムスタンプであり、 +* `` は、コマンドの `name` パラメータから取られた文字列です。 + +クラスの中では、`up()` メソッドが、実際のデータベースマイグレーションを実装するコードを含むべきメソッドです。 +言い換えると、`up()` メソッドが実際にデータベースを変更するコードを実行します。 +`down()` メソッドは、`up()` によって加えられた変更を取り消すコードを含むことが出来ます。 + +場合によっては、`down()` がデータベースマイグレーションを取り消すことが出来ないことがあります。 +例えば、マイグレーションがテーブルの行やテーブル全体を削除した場合は、そのデータを `down()` メソッドで復旧することは出来ません。 +そのような場合には、マイグレーションは不可逆であると呼ばれ、データベースを以前の状態にロールバックすることは出来ません。 +マイグレーションが不可逆である場合は、生成された上記のコードのように、`down()` メソッドは `false` を返して、マイグレーションが取り消せないものであることを示します。 + +例として、新しいテーブルを作成するマイグレーションを示しましょう。 + +```php + +use yii\db\Schema; + +class m101129_185401_create_news_table extends \yii\db\Migration +{ + public function up() + { + $this->createTable('news', [ + 'id' => 'pk', + 'title' => Schema::TYPE_STRING . ' NOT NULL', + 'content' => Schema::TYPE_TEXT, + ]); + } + + public function down() + { + $this->dropTable('news'); + } + +} +``` + +基底クラスである [[\yii\db\Migration]] が、データベース接続を `db` プロパティによって提供しています。 +これを使って、データベースのデータとスキーマを操作することが出来ます。 + +この例で使われているカラムのタイプは抽象的なタイプであり、Yii によって、あなたが使用するデータベース管理システムに応じて、対応するタイプに置き換えられます。 +抽象的なタイプを使うと、データベースに依存しないマイグレーションを書くことが出来ます。 +例えば、`pk` は、MySQL では `int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY` に置き換えられ、sqlite では `integer PRIMARY KEY AUTOINCREMENT NOT NULL` に置き換えられます。 +さらに詳細な説明と、利用可能なタイプについては、[[yii\db\QueryBuilder::getColumnType()]] のドキュメントを参照してください。 +また、カラムのタイプを定義するのに、[[yii\db\Schema]] で定義されている定数を使うことも出来ます。 + +> Note|注意: テーブル定義の最後に、単純な文字列として指定された、制約やその他の特別なテーブルオプションを追加することが出来ます。 +> 例えば、上記のマイグレーションでは、`content` 属性の定義の後に、`'CONSTRAINT ...'` やその他の特別なオプションを書くことが出来ます。 + + +トランザクションを使うマイグレーション +-------------------------------------- + +複雑な DB マイグレーションを実行するときは、通常、データベースの一貫性と整合性を保つために、個々のマイグレーションが全体として成功または失敗することを保証する必要があります。 +この目的を達成するために、DB トランザクションを利用することが出来ます。 +この目的のためには、`safeUp` と `safeDown` という特別なメソッドを使います。 + +```php + +use yii\db\Schema; + +class m101129_185401_create_news_table extends \yii\db\Migration +{ + public function safeUp() + { + $this->createTable('news', [ + 'id' => 'pk', + 'title' => Schema::TYPE_STRING . ' NOT NULL', + 'content' => Schema::TYPE_TEXT, + ]); + + $this->createTable('user', [ + 'id' => 'pk', + 'login' => Schema::TYPE_STRING . ' NOT NULL', + 'password' => Schema::TYPE_STRING . ' NOT NULL', + ]); + } + + public function safeDown() + { + $this->dropTable('news'); + $this->dropTable('user'); + } + +} +``` + +使用するクエリが一つだけでない場合は、`safeUp` と `safeDown` を使うことを推奨します。 + +> Note|注意: 全ての DBMS がトランザクションをサポートしている訳ではありません。 +> また、トランザクションに入れることが出来ない DB クエリもあります。 +> その場合には、代りに、`up()` と `down()` を実装しなければなりません。 +> また、MySQL の場合は、[暗黙のコミット](http://dev.mysql.com/doc/refman/5.1/en/implicit-commit.html) を引き起こす SQL 文があります。 + + +マイグレーションを適用する +-------------------------- + +利用できる全ての新しいマイグレーションを適用する (すなわち、ローカルのデータベースを最新の状態にする) ためには、次のコマンドを実行します。 + +``` +yii migrate +``` + +コマンドを実行すると、すべての新しいマイグレーションが一覧表示されます。 +マイグレーションを適用することを確認すると、クラス名のタイムスタンプの値の順に、一つずつ、すべての新しいマイグレーションクラスの `up()` メソッドが実行されます。 + +マイグレーションを適用した後に、マイグレーションツールは `migration` という名前のデータベーステーブルに記録を残します。 +これによって、ツールは、どのマイグレーションが適用済みで、どのマイグレーションが適用されていないかを特定することが出来ます。 +`migration` テーブルが存在しない場合は、ツールは `db` [アプリケーションコンポーネント](structure-application-components.md)によって指定されたデータベースの中にテーブルを自動的に作成します。 + +時として、新しいマイグレーションを一個また数個だけ適用したい場合があります。その場合は、次のコマンドを使うことが出来ます。 + + +``` +yii migrate/up 3 +``` + +このコマンドは、次の新しいマイグレーションを 3 個適用します。3 という値を変更して、適用されるマイグレーションの数を変更することが出来ます。 + +また、下記のコマンドを使って、特定のバージョンまでデータベースをマイグレートすることも可能です。 + +``` +yii migrate/to 101129_185401 +``` + +すなわち、マイグレーション名のタイムスタンプ部分を使って、データベースをマイグレートして到達したいバージョンを指定します。 +最後に適用されたマイグレーションと指定されたマイグレーションの間に複数のマイグレーションがある場合は、それらのマイグレーションがすべて適用されます。 +指定されたマイグレーションが適用済みである場合は、その後に適用されたすべてのマイグレーションが取り消されます (次の節で説明します)。 + + +マイグレーションを取り消す +-------------------------- + +適用された最後のマイグレーション (一個または複数個) を取り消したい場合は、下記のコマンドを使うことが出来ます。 + + +``` +yii migrate/down [step] +``` + +ここで、オプションの `step` パラメータは何個のマイグレーションを取り消すかを指定するものです。 +デフォルト値は 1 で、適用された最後のマイグレーションだけが取り消すされることを意味します。 + +前に説明したように、全てのマイグレーションが取り消せるとは限りません。 +取り消せないマイグレーションを取り消そうとすると例外が投げられて、取り消しのプロセス全体が終了させられます。 + + +マイグレーションを再適用する +---------------------------- + +マイグレーションの再適用とは、指定されたマイグレーションを最初に取り消してから、再度適用することを意味します。 +これは次のコマンドによって実行することが出来ます。 + +``` +yii migrate/redo [step] +``` + +ここで、オプションの `step` パラメータは何個のマイグレーションを再適用するかを指定するものです。 +デフォルト値は 1 で、最後のマイグレーションだけが再適用されることを意味します。 + + +マイグレーション情報を表示する +------------------------------ + +マイグレーションを適用したり取り消したりする他に、マイグレーションツールはマイグレーションの履歴、および、まだ適用されていない新しいマイグレーションを表示することも出来ます。 + + +``` +yii migrate/history [limit] +yii migrate/new [limit] +``` + +ここで、オプションの `limit` パラメータは、何個のマイグレーションを表示するかを指定するものです。 +`limit` が指定されない場合は、利用可能な全てのマイグレーションが表示されます。 + +最初のコマンドは適用済みのマイグレーションを表示し、第二のコマンドはまだ適用されていないマイグレーションを表示します。 + + +マイグレーション履歴を修正する +------------------------------ + +時として、実際には関係のあるマイグレーションを適用または取り消すことなく、マイグレーション履歴を特定のマイグレーションバージョンに修正したい場合があります。 +このことは新しいマイグレーションを開発するときにしばしば起ります。 +次のコマンドを使ってこの目的を達することが出来ます。 + +``` +yii migrate/mark 101129_185401 +``` + +このコマンドは `yii migrate/to` コマンドと非常によく似ていますが、マイグレーションを適用または取り消すことなく、マイグレーション履歴テーブルを指定されたバージョンに修正することだけを行うという点で違っています。 + + +マイグレーションコマンドをカスタマイズする +------------------------------------------ + +マイグレーションコマンドをカスタマイズする方法がいくつかあります。 + +### コマンドラインオプションを使う + +マイグレーションコマンドには、コマンドラインで指定できるいくつかのオプションがあります。 + +* `interactive`: 真偽値。マイグレーションを対話モードで実行するかどうかを指定します。 + デフォルト値は true で、指定されたマイグレーションを実行するときに、ユーザは何らかの入力を促されます。 + このオプションを false にセットして、マイグレーションがバックグラウンドプロセスとして実行されるようにすることが出来ます。 + +* `migrationPath`: 文字列。全てのマイグレーションクラスファイルを保存しているディレクトリを指定します。 + このパスは、パスエイリアスの形式で指定されなければならず、また、対応するディレクトリが存在する必要があります。 + このオプションが指定されない場合は、アプリケーションのベースパスの下の `migrations` サブディレクトリが使われます。 + +* `migrationTable`: 文字列。マイグレーション履歴の情報を保存するためのデータベーステーブル名を指定します。 + デフォルト値は `migration` です。このテーブルは、`version varchar(255) primary key, apply_time integer` という構造を持ちます。 + +* `db`: 文字列。データベース [アプリケーションコンポーネント](structure-application-components.md) の ID を指定します。 + デフォルト値は 'db' です。 + +* `templateFile`: 文字列。マイグレーションクラスを生成するためのコードテンプレートとして使われるファイルのパスを指定します。 + このパスは、パスエイリアスの形式で指定しなければなりません (例えば、`application.migrations.template`)。 + 指定されない場合は、内部テンプレートが使用されます。 + テンプレートの中の `{ClassName}` というトークンが実際のマイグレーションクラス名によって置き換えられます。 + +これらのオプションを指定するためには、下記の書式を使って migrate コマンドを実行します。 + +``` +yii migrate/up --option1=value1 --option2=value2 ... +``` + +例えば、`forum` モジュールのためのマイグレーションを実行するときに、マイグレーションファイルがモジュールの `migrations` ディレクトリに置かれている場合には、下記のコマンドを使うことが出来ます。 + + +``` +yii migrate/up --migrationPath=@app/modules/forum/migrations +``` + + +### コマンドをグローバルに構成する + +コマンドラインオプションを使ってマイグレーションコマンドをその場その場で構成することも出来ますが、場合によっては、一度にまとめてコマンドを構成しておきたいこともあります。 +例えば、マイグレーションの履歴を保存するのに別のテーブルを使用したいとか、カスタマイズしたマイグレーションテンプレートを使用したいとかです。 +そうするためには、コンソールアプリケーションの構成情報ファイルを以下のように修正します。 + + +```php +'controllerMap' => [ + 'migrate' => [ + 'class' => 'yii\console\controllers\MigrateController', + 'migrationTable' => 'my_custom_migrate_table', + ], +] +``` + +これで、`migrate` コマンドを実行すると、上記の構成情報が効果を発揮して、毎回コマンドラインオプションを入力する必要がなくなります。 +その他のコマンドラインオプションも、このようにして構成することが出来ます。 + + +### 複数のデータベースのマイグレーション + +既定では、マイグレーションは `db` [アプリケーションコンポーネント](structure-application-components.md) によって指定されるデータベースに対して適用されます。 +これは、`--db` オプションを指定することによって変更することが出来ます。例えば、 + +``` +yii migrate --db=db2 +``` + +上記のコマンドは、既定のマイグレーションパスに置かれている *全ての* マイグレーションを `db2` データベースに適用するものです。 + +アプリケーションが複数のデータベースを扱っている場合は、いくつかのマイグレーションはあるデータベースに適用されなければならず、他のマイグレーションは別のデータベースに適用されなければならない、ということがあり得ます。 +そのような場合には、異なるデータベースごとに基底マイグレーションクラスを作成して、下記のように [[yii\db\Migration::init()]] メソッドをオーバーライドすることを推奨します。 + +```php +public function init() +{ + $this->db = 'db2'; + parent::init(); +} +``` + +こうすると、特定のデータベースに適用されるべきマイグレーションを作成するためには、対応する基底マイグレーションクラスから拡張するだけで済みます。 +これで、`yii migrate` コマンドを実行すると、全てのマイグレーションはそれぞれ対応するデータベースに対して適用されるようになります。 + +> Info|情報: それぞれのマイグレーションはハードコードされた DB 接続を使用しますので、`migrate` コマンドの `--db` オプションは効果を持ちません。 + また、マイグレーション履歴はデフォルトの `db` データベースに保存されることに注意してください。 + +`--db` オプションによる DB 接続の変更をサポートしたい場合は、次のような別の方法で複数のデータベースを扱うことが出来ます。 + +それぞれのデータベースについて、マイグレーションパスを作成し、対応する全てのマイグレーションクラスをそこに保存します。 +マイグレーションを適用するためには、次のようなコマンドを実行します。 + +``` +yii migrate --migrationPath=@app/migrations/db1 --db=db1 +yii migrate --migrationPath=@app/migrations/db2 --db=db2 +... +``` + +> Info|情報: 上記の方法では、マイグレーション履歴は `--db` オプションによって指定された別々のデータベースに保存されます。 diff --git a/docs/guide-ja/db-mongodb.md b/docs/guide-ja/db-mongodb.md new file mode 100644 index 0000000..1430253 --- /dev/null +++ b/docs/guide-ja/db-mongodb.md @@ -0,0 +1,6 @@ +Mongo DB +======== + +> Note|注意: この節はまだ執筆中です。 +> +> まだ内容がありません。 diff --git a/docs/guide-ja/db-redis.md b/docs/guide-ja/db-redis.md new file mode 100644 index 0000000..ad8fbe9 --- /dev/null +++ b/docs/guide-ja/db-redis.md @@ -0,0 +1,6 @@ +Redis +===== + +> Note|注意: この節はまだ執筆中です。 +> +> まだ内容がありません。 diff --git a/docs/guide-ja/db-sphinx.md b/docs/guide-ja/db-sphinx.md new file mode 100644 index 0000000..9a8f486 --- /dev/null +++ b/docs/guide-ja/db-sphinx.md @@ -0,0 +1,6 @@ +Sphinx Search +============= + +> Note|注意: この節はまだ執筆中です。 +> +> まだ内容がありません。 diff --git a/docs/guide-ja/input-file-upload.md b/docs/guide-ja/input-file-upload.md new file mode 100644 index 0000000..67d2d03 --- /dev/null +++ b/docs/guide-ja/input-file-upload.md @@ -0,0 +1,254 @@ +ファイルをアップロードする +========================== + +Yii におけるファイルのアップロードは、フォームモデル、その検証規則、そして、いくらかのコントローラコードによって行われます。 +アップロードを適切に処理するために何が必要とされるのか、見ていきましよう。 + + +一つのファイルをアップロードする +-------------------------------- + +まず最初に、ファイルのアップロードを処理するモデルを作成する必要があります。 +次の内容を持つ `models/UploadForm.php` を作って作成してください。 + +```php +namespace app\models; + +use yii\base\Model; +use yii\web\UploadedFile; + +/** + * UploadForm がアップロードのフォームの背後にあるモデルである。 + */ +class UploadForm extends Model +{ + /** + * @var UploadedFile file 属性 + */ + public $file; + + /** + * @return array 検証規則 + */ + public function rules() + { + return [ + [['file'], 'file'], + ]; + } +} +``` + +上記のコードにおいて作成した `UploadForm` というモデルは、HTML フォームで `` となる `$file` という属性を持ちます。 +この属性は [[yii\validators\FileValidator|FileValidator]] を使用する `file` という検証規則を持ちます。 + +### フォームのビュー + +次に、フォームを表示するビューを作成します。 + +```php + + + ['enctype' => 'multipart/form-data']]) ?> + +field($model, 'file')->fileInput() ?> + + + + +``` + +ファイルのアップロードを可能にする `'enctype' => 'multipart/form-data'` は不可欠です。 +`fileInput()` がフォームの入力フィールドを表します。 + +### コントローラ + +そして、フォームとモデルを結び付けるコントローラを作成します。 + +```php +namespace app\controllers; + +use Yii; +use yii\web\Controller; +use app\models\UploadForm; +use yii\web\UploadedFile; + +class SiteController extends Controller +{ + public function actionUpload() + { + $model = new UploadForm(); + + if (Yii::$app->request->isPost) { + $model->file = UploadedFile::getInstance($model, 'file'); + + if ($model->file && $model->validate()) { + $model->file->saveAs('uploads/' . $model->file->baseName . '.' . $model->file->extension); + } + } + + return $this->render('upload', ['model' => $model]); + } +} +``` + +`model->load(...)` の代りに `UploadedFile::getInstance(...)` を使っています。 +[[\yii\web\UploadedFile|UploadedFile]] はモデルの検証を実行せず、アップロードされたファイルに関する情報を提供するだけです。 +そのため、`$model->validate()` を手作業で実行して、[[yii\validators\FileValidator|FileValidator]] を起動する必要があります。 +[[yii\validators\FileValidator|FileValidator]] は、下記のコアコードが示しているように、属性がファイルであることを要求します。 + +```php +if (!$file instanceof UploadedFile || $file->error == UPLOAD_ERR_NO_FILE) { + return [$this->uploadRequired, []]; // "ファイルをアップロードしてください。" というエラーメッセージ +} +``` + +検証が成功したら、ファイルを保存します。 + +```php +$model->file->saveAs('uploads/' . $model->file->baseName . '.' . $model->file->extension); +``` + +「ベーシック」アプリケーションテンプレートを使っている場合は、`uploads` フォルダを `web` の下に作成しなければなりません。 + +以上です。ページをロードして、アップロードを試して見てください。ファイルは `basic/web/uploads` にアップロードされます。 + +検証 +---- + +たいていの場合、検証規則を調整して、特定のファイルだけを受け取るようにしたり、アップロードを必須としたりする必要があります。 +下記で、よく使われる規則の構成を見てみましよう。 + +### Required + +ファイルのアップロードを必須とする必要がある場合は、次のように `skipOnEmpty` を `false` に設定します。 + +```php +public function rules() +{ + return [ + [['file'], 'file', 'skipOnEmpty' => false], + ]; +} +``` + +### MIME タイプ + +アップロードされるファイルのタイプを検証することは賢明なことです。 +`FileValidator` はこの目的のための `extensions` プロパティを持っています。 + +```php +public function rules() +{ + return [ + [['file'], 'file', 'extensions' => 'gif, jpg',], + ]; +} +``` + +ただし、ファイル拡張子が検証されるだけで、実際のファイルの中身は検証されないことを憶えておいてください。 +ファイルの中身も検証するためには、`FileValidator` の `mimeType` プロパティを使います。 + +```php +public function rules() +{ + return [ + [['file'], 'file', 'extensions' => 'jpg, png', 'mimeTypes' => 'image/jpeg, image/png',], + ]; +} +``` + +[一般的なメディアタイプの一覧表](http://en.wikipedia.org/wiki/Internet_media_type#List_of_common_media_types) + +### 画像のプロパティ + +画像をアップロードするときは、[[yii\validators\ImageValidator|ImageValidator]] が重宝するでしょう。 +このバリデータは、属性が有効な画像を受け取ったか否かを検証します。 +その画像は、[Imagine エクステンション](https://github.com/yiisoft/yii2/tree/master/extensions/imagine) によって、保存するか、または、処理することが出来ます。 + +複数のファイルをアップロードする +-------------------------------- + +複数のファイルを一度にアップロードする必要がある場合は、少し修正が必要になります。 + +モデル: + +```php +class UploadForm extends Model +{ + /** + * @var UploadedFile|Null ファイル属性 + */ + public $file; + + /** + * @return array 検証規則 + */ + public function rules() + { + return [ + [['file'], 'file', 'maxFiles' => 10], // <--- ここ ! + ]; + } +} +``` + +ビュー: + +```php + ['enctype' => 'multipart/form-data']]); +?> + +field($model, 'file[]')->fileInput(['multiple' => true]) ?> + + + + +``` + +違いがあるのは、次の行です。 + +```php +field($model, 'file[]')->fileInput(['multiple' => true]) ?> +``` + +コントローラ: + +```php +namespace app\controllers; + +use Yii; +use yii\web\Controller; +use app\models\UploadForm; +use yii\web\UploadedFile; + +class SiteController extends Controller +{ + public function actionUpload() + { + $model = new UploadForm(); + + if (Yii::$app->request->isPost) { + $model->file = UploadedFile::getInstances($model, 'file'); + + if ($model->file && $model->validate()) { + foreach ($model->file as $file) { + $file->saveAs('uploads/' . $file->baseName . '.' . $file->extension); + } + } + } + + return $this->render('upload', ['model' => $model]); + } +} +``` + +単一のファイルのアップロードとは、二つの点で異なります。 +最初の違いは、`UploadedFile::getInstance($model, 'file');` の代りに `UploadedFile::getInstances($model, 'file');` が使用されることです。 +前者が一つのインスタンスを返すだけなのに対して、後者はアップロードされた **全ての** ファイルのインスタンスを返します。 +第二の違いは、`foreach` によって、全てのファイルをそれぞれ保存している点です。 diff --git a/docs/guide-ja/input-forms.md b/docs/guide-ja/input-forms.md new file mode 100644 index 0000000..486fa37 --- /dev/null +++ b/docs/guide-ja/input-forms.md @@ -0,0 +1,191 @@ +フォームを扱う +============== + +> Note|注意: この節はまだ執筆中です。 + +Yii においてフォームを使用する主たる方法は [[yii\widgets\ActiveForm]] によるものです。 +フォームがモデルに基づくものである場合はこの方法を優先すべきです。 +これに加えて、[[yii\helpers\Html]] にはいくつかの有用なメソッドがあり、通常は、あらゆるフォームにボタンやヘルプテキストを追加するのに使うことが出来ます。 + +モデルに基づくフォームを作成する場合、最初のステップは、モデルそのものを定義することです。 +モデルは、アクティブレコードクラス、あるいは、もっと汎用的な Model クラスから派生させることが出来ます。 +このログインフォームの例では、汎用的なモデルを使用します。 + +```php +use yii\base\Model; + +class LoginForm extends Model +{ + public $username; + public $password; + + /** + * @return array 検証規則 + */ + public function rules() + { + return [ + // username と password はともに必須 + [['username', 'password'], 'required'], + // password は validatePassword() によって検証される + ['password', 'validatePassword'], + ]; + } + + /** + * パスワードを検証する + * このメソッドがパスワードのインライン検証に使用される + */ + public function validatePassword() + { + $user = User::findByUsername($this->username); + if (!$user || !$user->validatePassword($this->password)) { + $this->addError('password', 'Incorrect username or password.'); + } + } + + /** + * 提供された username と password でユーザをログインさせる。 + * @return boolean ユーザのログインが成功したかどうか + */ + public function login() + { + if ($this->validate()) { + $user = User::findByUsername($this->username); + return true; + } else { + return false; + } + } +} +``` + +コントローラはこのモデルのインスタンスをビューに渡し、ビューでは [[yii\widgets\ActiveForm|ActiveForm]] ウィジェットが使われます。 + +```php +use yii\helpers\Html; +use yii\widgets\ActiveForm; + + 'login-form', + 'options' => ['class' => 'form-horizontal'], +]) ?> + field($model, 'username') ?> + field($model, 'password')->passwordInput() ?> + +
+
+ 'btn btn-primary']) ?> +
+
+ +``` + +上記のコードでは、[[yii\widgets\ActiveForm::begin()|ActiveForm::begin()]] がフォームのインスタンスを作成するだけでなく、フォームの開始をマークしています。 +[[yii\widgets\ActiveForm::begin()|ActiveForm::begin()]] と [[yii\widgets\ActiveForm::end()|ActiveForm::end()]] の間に置かれた全てのコンテントが `
` タグによって囲まれます。 +その他のウィジェットと同じように、ウィジェットをどのように構成すべきかに関するオプションを指定するために、`begin` メソッドに配列を渡すことが出来ます。 +この例では、追加の CSS クラスと要素を特定するための ID が渡されて、開始 `` タグに適用されています。 + +フォームの中で、フォームの要素を作成するために、ActiveForm ウィジェットの [[yii\widgets\ActiveForm::field()|ActiveForm::field()]] メソッドが呼ばれています。このメソッドは、要素のラベルと、適用できる JavaScript の検証メソッドがあれば、それらも追加します。 +このメソッドの呼び出し結果を直接にエコーすると、結果は通常の (text の) インプットになります。 +出力結果をカスタマイズするためには、このメソッドの呼び出しに追加のメソッドをチェーンします。 + +```php +field($model, 'password')->passwordInput() ?> + +// または + +field($model, 'username')->textInput()->hint('お名前を入力してください')->label('お名前') ?> +``` + +これで、フォームのフィールドによって定義されたテンプレートに従って、`