+```

Inside report widgets you can use any [charts or indicators](../ui/form), lists or any other markup you wish. Remember that the report widgets extend the generic backend widgets and you can use any widget functionality in your report widgets. The next example shows a list report widget markup.
-
-
Top pages
-
-
-
-
-
- Page URL
- Pageviews
- % Pageviews
-
-
-
-
- /
- 90
-
-
-
-
-
- /docs
- 10
-
-
-
-
-
-
-
+```html
+
+```
### Report widget properties
@@ -360,48 +398,52 @@ Report widgets may have properties that users can manage with the Inspector:
The properties should be defined in the `defineProperties` method of the widget class. The properties are described in the [components article](../plugin/components#component-properties). Example:
- public function defineProperties()
- {
- return [
- 'title' => [
- 'title' => 'Widget title',
- 'default' => 'Top Pages',
- 'type' => 'string',
- 'validationPattern' => '^.+$',
- 'validationMessage' => 'The Widget Title is required.'
- ],
- 'days' => [
- 'title' => 'Number of days to display data for',
- 'default' => '7',
- 'type' => 'string',
- 'validationPattern' => '^[0-9]+$'
- ]
- ];
- }
+```php
+public function defineProperties()
+{
+ return [
+ 'title' => [
+ 'title' => 'Widget title',
+ 'default' => 'Top Pages',
+ 'type' => 'string',
+ 'validationPattern' => '^.+$',
+ 'validationMessage' => 'The Widget Title is required.'
+ ],
+ 'days' => [
+ 'title' => 'Number of days to display data for',
+ 'default' => '7',
+ 'type' => 'string',
+ 'validationPattern' => '^[0-9]+$'
+ ]
+ ];
+}
+```
### Report widget registration
Plugins can register report widgets by overriding the `registerReportWidgets` method inside the [Plugin registration class](../plugin/registration#registration-file). The method should return an array containing the widget classes in the keys and widget configuration (label, context, and required permissions) in the values. Example:
- public function registerReportWidgets()
- {
- return [
- 'Winter\GoogleAnalytics\ReportWidgets\TrafficOverview' => [
- 'label' => 'Google Analytics traffic overview',
- 'context' => 'dashboard',
- 'permissions' => [
- 'winter.googleanalytics.widgets.traffic_overview',
- ],
+```php
+public function registerReportWidgets()
+{
+ return [
+ 'Winter\GoogleAnalytics\ReportWidgets\TrafficOverview' => [
+ 'label' => 'Google Analytics traffic overview',
+ 'context' => 'dashboard',
+ 'permissions' => [
+ 'winter.googleanalytics.widgets.traffic_overview',
],
- 'Winter\GoogleAnalytics\ReportWidgets\TrafficSources' => [
- 'label' => 'Google Analytics traffic sources',
- 'context' => 'dashboard',
- 'permissions' => [
- 'winter.googleanaltyics.widgets.traffic_sources',
- ],
- ]
- ];
- }
+ ],
+ 'Winter\GoogleAnalytics\ReportWidgets\TrafficSources' => [
+ 'label' => 'Google Analytics traffic sources',
+ 'context' => 'dashboard',
+ 'permissions' => [
+ 'winter.googleanaltyics.widgets.traffic_sources',
+ ],
+ ]
+ ];
+}
+```
-The **label** element defines the widget name for the Add Widget popup window. The **context** element defines the context where the widget could be used. Winter's report widget system allows to host the report container on any page, and the container context name is unique. The widget container on the Dashboard page uses the **dashboard** context.
+The `label` element defines the widget name for the Add Widget popup window. The `context` element defines the context where the widget could be used. Winter's report widget system allows to host the report container on any page, and the container context name is unique. The widget container on the Dashboard page uses the `dashboard` context.
diff --git a/cms-components.md b/cms-components.md
index 0ed5e83c..ad58d263 100644
--- a/cms-components.md
+++ b/cms-components.md
@@ -23,19 +23,23 @@ This article describes the components basics and doesn't explain how to use [com
If you use the backend user interface you can add components to your pages, partials and layouts by clicking the component in the Components panel. If you use a text editor you can attach a component to a page or layout by adding its name to the [Configuration](themes#configuration-section) section of the template file. The next example demonstrates how to add a demo To-do component to a page:
- title = "Components demonstration"
- url = "/components"
+```ini
+title = "Components demonstration"
+url = "/components"
- [demoTodo]
- maxItems = 20
- ==
- ...
+[demoTodo]
+maxItems = 20
+==
+...
+```
This initializes the component with the properties that are defined in the component section. Many components have properties, but it is not a requirement. Some properties are required, and some properties have default values. If you are not sure what properties are supported by a component, refer to the documentation provided by the developer, or use the Inspector in the Winter backend. The Inspector opens when you click a component in the page or layout component panel.
When you refer a component, it automatically creates a page variable that matches the component name (`demoTodo` in the previous example). Components that provide HTML markup can be rendered on a page with the `{% component %}` tag, like this:
- {% component 'demoTodo' %}
+```twig
+{% component 'demoTodo' %}
+```
> **NOTE:** If two components with the same name are assigned to a page and layout together, the page component overrides any properties of the layout component.
@@ -44,59 +48,76 @@ When you refer a component, it automatically creates a page variable that matche
If there are two plugins that register components with the same name, you can attach a component by using its fully qualified class name and assigning it an *alias*:
- [Winter\Demo\Components\Todo demoTodoAlias]
- maxItems = 20
+```ini
+[Winter\Demo\Components\Todo demoTodoAlias]
+maxItems = 20
+```
The first parameter in the section is the class name, the second is the component alias name that will be used when attached to the page. If you specified a component alias you should use it everywhere in the page code when you refer to the component. Note that the next example refers to the component alias:
- {% component 'demoTodoAlias' %}
+```twig
+{% component 'demoTodoAlias' %}
+```
The aliases also allow you to define multiple components of the same class on a same page by using the short name first and an alias second. This lets you to use multiple instances of a same component on a page.
- [demoTodo todoA]
- maxItems = 10
- [demoTodo todoB]
- maxItems = 20
+```ini
+[demoTodo todoA]
+maxItems = 10
+[demoTodo todoB]
+maxItems = 20
+```
## Using external property values
By default property values are initialized in the Configuration section where the component is defined, and the property values are static, like this:
- [demoTodo]
- maxItems = 20
- ==
- ...
+```ini
+[demoTodo]
+maxItems = 20
+==
+...
+```
However there is a way to initialize properties with values loaded from external parameters - URL parameters or [partial](partials) parameters (for components defined in partials). Use the `{{ paramName }}` syntax for values that should be loaded from partial variables:
- [demoTodo]
- maxItems = {{ maxItems }}
- ==
- ...
+```ini
+[demoTodo]
+maxItems = {{ maxItems }}
+==
+...
+```
Assuming that in the example above the component **demoTodo** is defined in a partial, it will be initialized with a value loaded from the **maxItems** partial variable:
- {% partial 'my-todo-partial' maxItems='10' %}
+```twig
+{% partial 'my-todo-partial' maxItems='10' %}
+```
You may use dot notation to retrieve a deeply nested value from an external parameter:
- [demoTodo]
- maxItems = {{ data.maxItems }}
- ==
- ...
+```ini
+[demoTodo]
+maxItems = {{ data.maxItems }}
+==
+...
+```
To load a property value from the URL parameter, use the `{{ :paramName }}` syntax, where the name starts with a colon (`:`), for example:
- [demoTodo]
- maxItems = {{ :maxItems }}
- ==
- ...
+```ini
+[demoTodo]
+maxItems = {{ :maxItems }}
+==
+...
+```
The page, the component belongs to, should have a corresponding [URL parameter](pages#url-syntax) defined:
- url = "/todo/:maxItems"
-
+```ini
+url = "/todo/:maxItems"
+```
In the Winter backend you can use the Inspector tool for assigning external values to component properties. In the Inspector you don't need to use the curly brackets to enter the parameter name. Each field in the Inspector has an icon on the right side, which opens the external parameter name editor. Enter the parameter name as `paramName` for partial variables or `:paramName` for URL parameters.
@@ -106,7 +127,9 @@ Components can be designed to use variables at the time they are rendered, simil
In this example, the **maxItems** property of the component will be set to *7* at the time the component is rendered:
- {% component 'demoTodoAlias' maxItems='7' %}
+```twig
+{% component 'demoTodoAlias' maxItems='7' %}
+```
> **NOTE**: Not all components support passing variables when rendering.
@@ -120,25 +143,33 @@ The markup provided by components is generally intended as a usage example for t
Each component can have an entry point partial called **default.htm** that is rendered when the `{% component %}` tag is called, in the following example we will assume the component is called **blogPost**.
- url = "blog/post"
+```twig
+url = "blog/post"
- [blogPost]
- ==
- {% component "blogPost" %}
+[blogPost]
+==
+{% component "blogPost" %}
+```
The output will be rendered from the plugin directory **components/blogpost/default.htm**. You can copy all the markup from this file and paste it directly in the page or to a new partial, called **blog-post.htm** for example.
-
{{ __SELF__.post.title }}
-
{{ __SELF__.post.description }}
+```twig
+
{{ __SELF__.post.title }}
+
{{ __SELF__.post.description }}
+```
Inside the markup you may notice references to a variable called `__SELF__`, this refers to the component object and should be replaced with the component alias used on the page, in this example it is `blogPost`.
-
{{ blogPost.post.title }}
-
{{ blogPost.post.description }}
+```twig
+
{{ blogPost.post.title }}
+
{{ blogPost.post.description }}
+```
This is the only change needed to allow the default component markup to work anywhere inside the theme. Now the component markup can be customized and rendered using the theme partial.
- {% partial 'blog-post.htm' %}
+```twig
+{% partial 'blog-post.htm' %}
+```
This process can be repeated for all other partials found in the component partial directory.
@@ -147,11 +178,13 @@ This process can be repeated for all other partials found in the component parti
All component partials can be overridden using the theme partials. If a component called **channel** uses the **title.htm** partial.
- url = "mypage"
+```twig
+url = "mypage"
- [channel]
- ==
- {% component "channel" %}
+[channel]
+==
+{% component "channel" %}
+```
We can override the partial by creating a file in our theme called **partials/channel/title.htm**.
@@ -159,15 +192,17 @@ The file path segments are broken down like this:
Segment | Description
------------- | -------------
-**partials** | the theme partials directory
-**channel** | the component alias (a partial subdirectory)
-**title.htm** | the component partial to override
+`partials` | the theme partials directory
+`channel` | the component alias (a partial subdirectory)
+`title.htm` | the component partial to override
The partial subdirectory name can be customized to anything by simply assigning the component an alias of the same name. For example, by assigning the **channel** component with a different alias **foobar** the override directory is also changed:
- [channel foobar]
- ==
- {% component "foobar" %}
+```twig
+[channel foobar]
+==
+{% component "foobar" %}
+```
Now we can override the **title.htm** partial by creating a file in our theme called **partials/foobar/title.htm**.
@@ -176,27 +211,31 @@ Now we can override the **title.htm** partial by creating a file in our theme ca
There is a special component included in Winter called `viewBag` that can be used on any page or layout. It allows ad hoc properties to be defined and accessed inside the markup area easily as variables. A good usage example is defining an active menu item inside a page:
- title = "About"
- url = "/about.html"
- layout = "default"
+```ini
+title = "About"
+url = "/about.html"
+layout = "default"
- [viewBag]
- activeMenu = "about"
- ==
+[viewBag]
+activeMenu = "about"
+==
-
Page content...
+
Page content...
+```
Any property defined for the component is then made available inside the page, layout, or partial markup using the `viewBag` variable. For example, in this layout the **active** class is added to the list item if the `viewBag.activeMenu` value is set to **about**:
- description = "Default layout"
- ==
- [...]
+```twig
+description = "Default layout"
+==
+[...]
-
-
+
+
+```
> **NOTE**: The viewBag component is hidden on the backend and is only available for file-based editing. It can also be used by other plugins to store data.
@@ -209,30 +248,36 @@ When soft components are present on a page and the component is unavailable, no
You can define soft components by prefixing the component name with an `@` symbol.
- url = "mypage"
+```twig
+url = "mypage"
- [@channel]
- ==
- {% component "channel" %}
+[@channel]
+==
+{% component "channel" %}
+```
In this example, should the `channel` component not be available, the `{% component "channel" %}` tag will be ignored when the page is rendered.
Soft components also work with aliases as well:
- url = "mypage"
+```twig
+url = "mypage"
- [@channel channelSection]
- ==
- {% component "channelSection" %}
+[@channel channelSection]
+==
+{% component "channelSection" %}
+```
As soft components do not contain any of the data that the component may provide normally if the component is not available, you must take care to ensure that any custom markup will gracefully handle any missing component information. For example:
- url = "mypage"
-
- [@channel]
- ==
- {% if channel.name %}
-
- {% channel.name %}
-
- {% endif %}
\ No newline at end of file
+```twig
+url = "mypage"
+
+[@channel]
+==
+{% if channel.name %}
+
+ {% channel.name %}
+
+{% endif %}
+```
diff --git a/cms-content.md b/cms-content.md
index c8ad7c4b..9f767058 100644
--- a/cms-content.md
+++ b/cms-content.md
@@ -14,9 +14,9 @@ Content blocks files reside in the **/content** subdirectory of a theme director
Extension | Description
------------- | -------------
-**htm** | Used for HTML markup.
-**txt** | Used for plain text.
-**md** | Used for Markdown syntax.
+`htm` | Used for HTML markup.
+`txt` | Used for plain text.
+`md` | Used for Markdown syntax.
The extension affects the way content blocks are displayed in the backend user interface (with a WYSIWYG editor or with a plain text editor) and how the blocks are rendered on the website. Markdown blocks are converted to HTML before they are displayed.
@@ -25,22 +25,27 @@ The extension affects the way content blocks are displayed in the backend user i
Use the `{% content 'file.htm' %}` tag to render a content block in a [page](pages), [partial](partials) or [layout](layouts). Example of a page rendering a content block:
- url = "/contacts"
- ==
-
- {% content 'contacts.htm' %}
-
+```twig
+url = "/contacts"
+==
+
+ {% content 'contacts.htm' %}
+
+```
## Passing variables to content blocks
Sometimes you may need to pass variables to a content block from the external code. While content blocks do not support the use of Twig markup, they do support using variables with a basic syntax. You can pass variables to content blocks by specifying them after the content block name in the `{% content %}` tag:
- {% content 'welcome.htm' name='John' %}
-
+```twig
+{% content 'welcome.htm' name='John' %}
+```
Inside the content block, variables can be accessed using singular *curly brackets*:
-
This is a demo for {name}
+```twig
+
This is a demo for {name}
+```
More information can be found [in the Markup guide](../markup/tag-content).
@@ -49,6 +54,8 @@ More information can be found [in the Markup guide](../markup/tag-content).
You may register variables that are globally available to all content blocks with the `View::share` method.
- View::share('site_name', 'Winter CMS');
+```php
+View::share('site_name', 'Winter CMS');
+```
This code could be called inside the register or boot method of a [plugin registration file](../plugin/registration). Using the above example, the variable `{site_name}` will be available inside all content blocks.
diff --git a/cms-layouts.md b/cms-layouts.md
index 5e10e45b..8a749616 100644
--- a/cms-layouts.md
+++ b/cms-layouts.md
@@ -12,60 +12,72 @@ Layouts define the page scaffold, usually including everything that is present o
Layout templates reside in the **/layouts** subdirectory of a theme directory. Layout template files should have the **htm** extension. Inside the layout file you should use the `{% page %}` tag to output the page content. Simplest layout example:
-
-
- {% page %}
-
-
+```twig
+
+
+ {% page %}
+
+
+```
To use a layout for a [page](pages) the page should refer the layout file name (without extension) in the [Configuration](themes#configuration-section) section. Remember that if you refer a layout from a [subdirectory](themes#subdirectories) you should specify the subdirectory name. Example page template using the default.htm layout:
- url = "/"
- layout = "default"
- ==
-
Hello, world!
+```ini
+url = "/"
+layout = "default"
+==
+
Hello, world!
+```
When this page is requested its content is merged with the layout, or more precisely - the layout's `{% page %}` tag is replaced with the page content. The previous examples would generate the following markup:
-
-
-
Hello, world!
-
-
+```html
+
+
+
Hello, world!
+
+
+```
Note that you can render [partials](partials) in layouts. This lets you to share the common markup elements between different layouts. For example, you can have a partial that outputs the website CSS and JavaScript links. This approach simplifies the resource management - if you want to add a JavaScript reference you should modify a single partial instead of editing all the layouts.
The [Configuration](themes#configuration-section) section is optional for layouts. The supported configuration parameters are **name** and **description**. The parameters are optional and used in the backend user interface. Example layout template with a description:
- description = "Basic layout example"
- ==
-
-
- {% page %}
-
-
+```twig
+description = "Basic layout example"
+==
+
+
+ {% page %}
+
+
+```
## Placeholders
Placeholders allow pages to inject content to the layout. Placeholders are defined in the layout templates with the `{% placeholder %}` tag. The next example shows a layout template with a placeholder **head** defined in the HTML HEAD section.
-
-
- {% placeholder head %}
-
- ...
+```twig
+
+
+ {% placeholder head %}
+
+ ...
+```
Pages can inject content to placeholders with the `{% put %}` and `{% endput %}` tags. The following example demonstrates a simple page template which injects a CSS link to the placeholder **head** defined in the previous example.
- url = "/my-page"
- layout = "default"
- ==
- {% put head %}
-
- {% endput %}
+```twig
+url = "/my-page"
+layout = "default"
+==
+{% put head %}
+
+{% endput %}
-
The page content goes here.
+
The page content goes here.
+```
More information on placeholders can be found [in the Markup guide](../markup/tag-placeholder).
diff --git a/cms-mediamanager.md b/cms-mediamanager.md
index 22a66740..1f9fcd57 100644
--- a/cms-mediamanager.md
+++ b/cms-mediamanager.md
@@ -24,25 +24,29 @@ Create **media** folder in the bucket. The folder name doesn't matter. This fold
By default files in S3 buckets cannot be accessed directly. To make the bucket public, return to the bucket list and click the bucket. Click **Properties** button in the right sidebar. Expand **Permissions** tab. Click **Edit bucket policy** link. Paste the following code to the policy popup window. Replace the bucket name with your actual bucket name:
- {
- "Version": "2008-10-17",
- "Id": "Policy1397632521960",
- "Statement": [
- {
- "Sid": "Stmt1397633323327",
- "Effect": "Allow",
- "Principal": {
- "AWS": "*"
- },
- "Action": "s3:GetObject",
- "Resource": "arn:aws:s3:::BUCKETNAME/*"
- }
- ]
- }
+```json
+{
+ "Version": "2008-10-17",
+ "Id": "Policy1397632521960",
+ "Statement": [
+ {
+ "Sid": "Stmt1397633323327",
+ "Effect": "Allow",
+ "Principal": {
+ "AWS": "*"
+ },
+ "Action": "s3:GetObject",
+ "Resource": "arn:aws:s3:::BUCKETNAME/*"
+ }
+ ]
+}
+```
Click **Save** button to apply the policy. The policy gives public read-only access to all folders and directories in the bucket. If you're going to use the bucket for other needs, it's possible to setup a public access to a specific folder in the bucket, just specify the directory name in the **Resource** value:
- "arn:aws:s3:::BUCKETNAME/media/*"
+```
+"arn:aws:s3:::BUCKETNAME/media/*"
+```
You should also create an API user that Winter CMS will use for managing the bucket files. In AWS console go to IAM section. Go to Users tab and create a new user. The user name doesn't matter. Make sure that "Generate an access key for each user" checkbox is checked when you create a new user. After AWS creates a user, it allows you to see the security credentials - the user **Access Key ID** and **Secret Access Key**. Copy the keys and put them into a temporary text file.
@@ -52,10 +56,10 @@ Now you have all the information to update Winter CMS configuration. Open **conf
Parameter | Value
------------- | -------------
-**key** | the **Access Key ID** value of the user that you created before.
-**secret** | the **Secret Access Key** value of the user that you created fore.
-**bucket** | your bucket name.
-**region** | the bucket region code, see below.
+`key` | the **Access Key ID** value of the user that you created before.
+`secret` | the **Secret Access Key** value of the user that you created fore.
+`bucket` | your bucket name.
+`region` | the bucket region code, see below.
You can find the bucket region in S3 management console, in the bucket properties. The Properties tab displays the region name, for example Oregon. S3 driver configuration requires a bucket code. Use this table to find code for your bucket (you can also take a look at [AWS documentation](http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region)):
@@ -85,40 +89,44 @@ Region | Code
Example configuration after update:
- 'disks' => [
- ...
- 's3' => [
- 'driver' => 's3',
- 'key' => 'XXXXXXXXXXXXXXXXXXXX',
- 'secret' => 'xxxXxXX+XxxxxXXxXxxxxxxXxxXXXXXXXxxxX9Xx',
- 'region' => 'us-west-2',
- 'bucket' => 'my-bucket'
- ],
- ...
- ]
+```php
+'disks' => [
+ ...
+ 's3' => [
+ 'driver' => 's3',
+ 'key' => 'XXXXXXXXXXXXXXXXXXXX',
+ 'secret' => 'xxxXxXX+XxxxxXXxXxxxxxxXxxXXXXXXXxxxX9Xx',
+ 'region' => 'us-west-2',
+ 'bucket' => 'my-bucket'
+ ],
+ ...
+]
+```
Save **config/filesystem.php** script and open **config/cms.php** script. Find the section **storage**. In the **media** parameter update **disk**, **folder** and **path** parameters:
Parameter | Value
------------- | -------------
-**disk** | use **s3** value.
-**folder** | the name of the folder you created in S3 bucket.
-**path** | the public path of the folder in the bucket, see below.
+`disk` | use **s3** value.
+`folder` | the name of the folder you created in S3 bucket.
+`path` | the public path of the folder in the bucket, see below.
To obtain the path of the folder, open AWS console and go to S3 section. Navigate to the bucket and click the folder you created before. Upload any file to the folder and click the file. Click **Properties** button in the right sidebar. The file URL is in the **Link** parameter. Copy the URL and remove the file name and the trailing slash from it.
Example storage configuration:
- 'storage' => [
- ...
- 'media' => [
- 'disk' => 's3',
- 'folder' => 'media',
- 'path' => 'https://s3-us-west-2.amazonaws.com/your-bucket-name/media'
- ]
+```php
+'storage' => [
+ ...
+ 'media' => [
+ 'disk' => 's3',
+ 'folder' => 'media',
+ 'path' => 'https://s3-us-west-2.amazonaws.com/your-bucket-name/media'
]
+]
+```
-Congratulations! Now you're ready to use Amazon S3 with Winter CMS. Note that you can also configure Amazon CloudFront CDN to work with your bucket. This topic is not covered in this document, please refer to [CloudFront documentation](http://aws.amazon.com/cloudfront/). After you configure CloudFront, you will need to update the **path** parameter in the storage configuration.
+Congratulations! Now you're ready to use Amazon S3 with Winter CMS. Note that you can also configure Amazon CloudFront CDN to work with your bucket. This topic is not covered in this document, please refer to [CloudFront documentation](https://aws.amazon.com/cloudfront/). After you configure CloudFront, you will need to update the **path** parameter in the storage configuration.
## Configuring Rackspace CDN access
@@ -135,48 +143,52 @@ Now you have all the information to update Winter CMS configuration. Open **conf
Parameter | Value
------------- | -------------
-**username** | Rackspace user name (for example winter.cdn.api).
-**key** | the user's **API Key** that you can copy from Rackspace user profile page.
-**container** | the container name.
-**region** | the bucket region code, see below.
-**endpoint** | leave the value as is.
-**region** | you can find the region in the CDN container list in Rackspace control panel. The code is a 3-letter value, for example it's **ORD** for Chicago.
+`username` | Rackspace user name (for example winter.cdn.api).
+`key` | the user's **API Key** that you can copy from Rackspace user profile page.
+`container` | the container name.
+`region` | the bucket region code, see below.
+`endpoint` | leave the value as is.
+`region` | you can find the region in the CDN container list in Rackspace control panel. The code is a 3-letter value, for example it's **ORD** for Chicago.
Example configuration after update:
- 'disks' => [
- ...
- 'rackspace' => [
- 'driver' => 'rackspace',
- 'username' => 'winter.api.cdn',
- 'key' => 'xx00000000xxxxxx0x0x0x000xx0x0x0',
- 'container' => 'my-bucket',
- 'endpoint' => 'https://identity.api.rackspacecloud.com/v2.0/',
- 'region' => 'ORD'
- ],
- ...
- ]
+```php
+'disks' => [
+ ...
+ 'rackspace' => [
+ 'driver' => 'rackspace',
+ 'username' => 'winter.api.cdn',
+ 'key' => 'xx00000000xxxxxx0x0x0x000xx0x0x0',
+ 'container' => 'my-bucket',
+ 'endpoint' => 'https://identity.api.rackspacecloud.com/v2.0/',
+ 'region' => 'ORD'
+ ],
+ ...
+]
+```
Save **config/filesystem.php** script and open **config/cms.php** script. Find the section **storage**. In the **media** parameter update **disk**, **folder** and **path** parameters:
Parameter | Value
------------- | -------------
-**disk** | use **rackspace** value.
-**folder** | the name of the folder you created in CDN container.
-**path** | the public path of the folder in the container, see below.
+`disk` | use **rackspace** value.
+`folder` | the name of the folder you created in CDN container.
+`path` | the public path of the folder in the container, see below.
To obtain the path of the folder, go to the CDN container list in Rackspace console. Click the container and open the media folder. Upload any file. After the file is uploaded, click it. The file will open in a new browser tab. Copy the file URL and remove the file name and trailing slash from it.
Example storage configuration:
- 'storage' => [
- ...
- 'media' => [
- 'disk' => 'rackspace',
- 'folder' => 'media',
- 'path' => 'https://xxxxxxxxx-xxxxxxxxx.r00.cf0.rackcdn.com/media'
- ]
+```php
+'storage' => [
+ ...
+ 'media' => [
+ 'disk' => 'rackspace',
+ 'folder' => 'media',
+ 'path' => 'https://xxxxxxxxx-xxxxxxxxx.r00.cf0.rackcdn.com/media'
]
+]
+```
Congratulations! Now you're ready to use Rackspace CDN with Winter CMS.
@@ -185,51 +197,60 @@ Congratulations! Now you're ready to use Rackspace CDN with Winter CMS.
By default the system uses HTML5 audio and video tags to render audio and video files:
-
+```html
+
+```
or
-
+```html
+
+```
This behavior can be overridden. If there are **wn-audio-player.htm** and **wn-video-player.htm** CMS partials, they will be used for displaying audio and video contents. Inside the partials use the variable **src** to output a link to the source file. Example:
-
+```twig
+
+```
If you don't want to use HTML5 player you can provide any other markup in the partials. There's a [third-party script](https://html5media.info/) that enables support of HTML5 video and audio tags in older browsers.
As the partials are written with Twig, you can automate adding alternative video sources based on a naming convention. For example, if there's a convention that there's always a smaller resolution video for each full resolution video, and the smaller resolution file has extension "iphone.mp4", the generated markup could look like this:
-
-
-
-
+```twig
+
+
+
+
+```
## Other configuration options
There are several options that allow you to fine-tune the Media Manager. All of them could be defined in **config/cms.php** script, in the **storage/media** section, for example:
- 'storage' => [
- ...
-
- 'media' => [
- ...
- 'ignore' => ['.svn', '.git', '.DS_Store']
- ]
- ],
+```php
+'storage' => [
+ ...
+ 'media' => [
+ ...
+ 'ignore' => ['.svn', '.git', '.DS_Store']
+ ]
+],
+```
Parameter | Value
------------- | -------------
-**ignore** | a list of file and directory names to ignore. Defaults to ['.svn', '.git', '.DS_Store'].
-**ttl** | specifies the cache time-to-live, in minutes. The default value is 10. The cache invalidates automatically when Library items are added, updated or deleted.
-**imageExtensions** | file extensions corresponding to the Image document type. The default value is **['gif', 'png', 'jpg', 'jpeg', 'bmp']**.
-**videoExtensions** | file extensions corresponding to the Video document type. The default value is **['mp4', 'avi', 'mov', 'mpg']**.
-**audioExtensions** | file extensions corresponding to the Audio document type. The default value is **['mp3', 'wav', 'wma', 'm4a']**.
+`ignore` | a list of file and directory names to ignore. Defaults to ['.svn', '.git', '.DS_Store'].
+`ttl` | specifies the cache time-to-live, in minutes. The default value is 10. The cache invalidates automatically when Library items are added, updated or deleted.
+`imageExtensions` | file extensions corresponding to the Image document type. The default value is `['gif', 'png', 'jpg', 'jpeg', 'bmp']`.
+`videoExtensions` | file extensions corresponding to the Video document type. The default value is `['mp4', 'avi', 'mov', 'mpg']`.
+`audioExtensions` | file extensions corresponding to the Audio document type. The default value is `['mp3', 'wav', 'wma', 'm4a']`.
## Events
@@ -238,28 +259,32 @@ The Media Manager provides a few [events](../services/events) that you can liste
Event | Description | Parameters
------------- | ------------- | -------------
-**folder.delete** | Called when a folder is deleted | `(string) $path`
-**file.delete** | Called when a file is deleted | `(string) $path`
-**folder.rename** | Called when a folder is renamed | `(string) $originalPath`, `(string) $newPath`
-**file.rename** | Called when a file is renamed | `(string) $originalPath`, `(string) $newPath`
-**folder.create** | Called when a folder is created | `(string) $newFolderPath`
-**folder.move** | Called when a folder is moved | `(string) $path`, `(string) $dest`
-**file.move** | Called when a file is moved | `(string) $path`, `(string) $dest`
-**file.upload** | Called when a file is uploaded | `(string) $filePath`, `(\Symfony\Component\HttpFoundation\File\UploadedFile) $uploadedFile`
+`folder.delete` | Called when a folder is deleted | `(string) $path`
+`file.delete` | Called when a file is deleted | `(string) $path`
+`folder.rename` | Called when a folder is renamed | `(string) $originalPath`, `(string) $newPath`
+`file.rename` | Called when a file is renamed | `(string) $originalPath`, `(string) $newPath`
+`folder.create` | Called when a folder is created | `(string) $newFolderPath`
+`folder.move` | Called when a folder is moved | `(string) $path`, `(string) $dest`
+`file.move` | Called when a file is moved | `(string) $path`, `(string) $dest`
+`file.upload` | Called when a file is uploaded | `(string) $filePath`, `(\Symfony\Component\HttpFoundation\File\UploadedFile) $uploadedFile`
**To hook into these events, either extend the `Backend\Widgets\MediaManager` class directly:**
- Backend\Widgets\MediaManager::extend(function($widget) {
- $widget->bindEvent('file.rename', function ($originalPath, $newPath) {
- // Update custom references to path here
- });
+```php
+Backend\Widgets\MediaManager::extend(function($widget) {
+ $widget->bindEvent('file.rename', function ($originalPath, $newPath) {
+ // Update custom references to path here
});
-
+});
+```
+
**Or listen globally via the `Event` facade (each event is prefixed with `media.` and will be passed the instantiated `Backend\Widgets\MediaManager` object as the first parameter):**
- Event::listen('media.file.rename', function($widget, $originalPath, $newPath) {
- // Update custom references to path here
- });
+```php
+Event::listen('media.file.rename', function($widget, $originalPath, $newPath) {
+ // Update custom references to path here
+});
+```
## Troubleshooting
diff --git a/cms-pages.md b/cms-pages.md
index ee938a2f..d8f42088 100644
--- a/cms-pages.md
+++ b/cms-pages.md
@@ -17,9 +17,11 @@
All websites have pages. In Winter, frontend pages are rendered by page templates. Page template files reside in the **/pages** subdirectory of a theme directory. Page file names do not affect the routing, but it's a good idea to name your pages according to the page's function. The files should have the **htm** extension. The [Configuration](themes#configuration-section) and [Twig](themes#twig-section) template sections are required for pages, but the [PHP section](themes#php-section) is optional. Below, you can see the simplest home page example:
- url = "/"
- ==
-
Hello, world!
+```ini
+url = "/"
+==
+
Hello, world!
+```
## Page configuration
@@ -28,62 +30,80 @@ Page configuration is defined in the [Configuration Section](themes#configuratio
Parameter | Description
------------- | -------------
-**url** | the page URL, required. The URL syntax is described below.
-**title** | the page title, required.
-**layout** | the page [layout](layouts), optional. If specified, should contain the name of the layout file, without extension, for example: `default`.
-**description** | the page description for the backend interface, optional.
-**hidden** | hidden pages are accessible only by logged-in backend users, optional.
+`url` | the page URL, required. The URL syntax is described below.
+`title` | the page title, required.
+`layout` | the page [layout](layouts), optional. If specified, should contain the name of the layout file, without extension, for example: `default`.
+`description` | the page description for the backend interface, optional.
+`hidden` | hidden pages are accessible only by logged-in backend users, optional.
### URL syntax
The page URL is defined with the **url** configuration parameter. URLs should start with the forward slash character, and can contain parameters. URLs without parameters are fixed and strict. In the following example, the page URL is `/blog`.
- url = "/blog"
+```ini
+url = "/blog"
+```
> **NOTE:** The page URL is case-insensitive by default.
URLs with parameters are more flexible. A page with the URL pattern defined in the following example would be displayed for any address like `/blog/post/something`. URL parameters can be accessed by Winter components or from the page [PHP code](themes#php-section) section.
- url = "/blog/post/:post_id"
+```ini
+url = "/blog/post/:post_id"
+```
This is how you can access the URL parameter from the page's PHP section (see the [Dynamic pages](#dynamic-pages) section for more details):
- url = "/blog/post/:post_id"
- ==
- function onStart()
- {
- $post_id = $this->param('post_id');
- }
- ==
+```php
+url = "/blog/post/:post_id"
+==
+function onStart()
+{
+ $post_id = $this->param('post_id');
+}
+==
+```
Parameter names should be compatible with PHP variable names. To make a parameter optional, add a question mark after its name:
- url = "/blog/post/:post_id?"
+```ini
+url = "/blog/post/:post_id?"
+```
Parameters in the middle of the URL cannot be optional. In the next example, the `:post_id` parameter is marked as optional, but is processed as required.
- url = "/blog/:post_id?/comments"
+```ini
+url = "/blog/:post_id?/comments"
+```
Optional parameters can have default values which are used as fallback values in case the real parameter value is not presented in the URL. Default values cannot contain any asterisks, pipe symbols, or question marks. The default value is specified after the **question mark**. In the next example, the `category_id` parameter would be `10` for the URL `/blog/category`.
- url = "/blog/category/:category_id?10"
+```ini
+url = "/blog/category/:category_id?10"
+```
You can also use regular expressions to validate parameters. To add a validation expression, add a pipe symbol after the parameter name, or a question mark, and specify the expression. The forward slash symbol is not allowed in these expressions. Examples:
- url = "/blog/:post_id|^[0-9]+$/comments" - this will match /blog/10/comments
- ...
- url = "/blog/:post_id|^[0-9]+$" - this will match /blog/3
- ...
- url = "/blog/:post_name?|^[a-z0-9\-]+$" - this will match /blog/my-blog-post
+```ini
+url = "/blog/:post_id|^[0-9]+$/comments" - this will match /blog/10/comments
+...
+url = "/blog/:post_id|^[0-9]+$" - this will match /blog/3
+...
+url = "/blog/:post_name?|^[a-z0-9\-]+$" - this will match /blog/my-blog-post
+```
It is possible to use a special *wildcard* parameter by placing an **asterisk** after the parameter. Unlike regular parameters, wildcard parameters can match one or more URL segments. A URL can only ever contain a single wildcard parameter, cannot use regular expressions, or be followed by an optional parameter.
- url = "/blog/:category*/:slug"
+```ini
+url = "/blog/:category*/:slug"
+```
Wildcard parameters themselves can be made optional by preceding the asterisk with the `?` character however.
- url = "/blog/:slug?*"
+```ini
+url = "/blog/:slug?*"
+```
For example, a URL like `/color/:color/make/:make*/edit` will match `/color/brown/make/volkswagen/beetle/retro/edit` and extract the following parameter values:
@@ -104,33 +124,37 @@ Inside the [Twig section](themes#twig-section) of a page template, you can use a
There are special functions that can be defined in the PHP section of pages and layouts: `onInit`, `onStart`, and `onEnd`. The `onInit` function is executed when all components are initialized and before AJAX requests are handled. The `onStart` function is executed during the beginning of the page execution. The `onEnd` function is executed before the page is rendered and after the page [components](../cms/components) are executed. In the `onStart` and `onEnd` functions, you can inject variables into the Twig environment. Use `array notation` to pass variables to the page:
- url = "/"
- ==
- function onStart()
- {
- $this['hello'] = "Hello world!";
- }
- ==
-
{{ hello }}
+```php
+url = "/"
+==
+function onStart()
+{
+ $this['hello'] = "Hello world!";
+}
+==
+
{{ hello }}
+```
The next example is more complicated. It shows how to load a blog post collection from the database, and display on the page (the Acme\Blog plugin is imaginary):
- url = "/blog"
- ==
- use Acme\Blog\Classes\Post;
-
- function onStart()
- {
- $this['posts'] = Post::orderBy('created_at', 'desc')->get();
- }
- ==
-
Latest posts
-
- {% for post in posts %}
- {{ post.title }}
- {{ post.content }}
- {% endfor %}
-
+```twig
+url = "/blog"
+==
+use Acme\Blog\Classes\Post;
+
+function onStart()
+{
+ $this['posts'] = Post::orderBy('created_at', 'desc')->get();
+}
+==
+
Latest posts
+
+ {% for post in posts %}
+ {{ post.title }}
+ {{ post.content }}
+ {% endfor %}
+
+```
The default variables and Twig extensions provided by Winter are described in the [Markup Guide](../markup). The sequence that the handlers are executed in is described by the [Dynamic layouts](layouts#dynamic-layouts) article.
@@ -139,35 +163,43 @@ The default variables and Twig extensions provided by Winter are described in th
All methods defined in the execution life cycle have the ability to halt the process and return a response - simply return a response from the life cycle function. The example below will not load any page contents, and instead return the string *Hello world!* to the browser:
- function onStart()
- {
- return 'Hello world!';
- }
+```php
+function onStart()
+{
+ return 'Hello world!';
+}
+```
A more useful example might be triggering a redirect using the `Redirect` facade:
- public function onStart()
- {
- return Redirect::to('http://google.com');
- }
+```php
+public function onStart()
+{
+ return Redirect::to('http://google.com');
+}
+```
### Handling forms
You can handle standard forms with handler methods defined in the page or layout [PHP section](themes#php-section) (handling the AJAX requests is explained in the [AJAX Framework](../ajax/introduction) article). Use the [`form_open()`](../markup#standard-form) function to define a form that refers to an event handler. Example:
- {{ form_open({ request: 'onHandleForm' }) }}
- Please enter a string:
-
- {{ form_close() }}
-
Last submitted value: {{ lastValue }}
+```twig
+{{ form_open({ request: 'onHandleForm' }) }}
+ Please enter a string:
+
+{{ form_close() }}
+
Last submitted value: {{ lastValue }}
+```
The `onHandleForm` function can be defined in the page or layout [PHP section](themes#php-section), like so:
- function onHandleForm()
- {
- $this['lastValue'] = post('value');
- }
+```php
+function onHandleForm()
+{
+ $this['lastValue'] = post('value');
+}
+```
The handler loads the value with the `post` function and initializes the page's `lastValue` attribute variable which is displayed below the form in the first example.
@@ -175,7 +207,9 @@ The handler loads the value with the `post` function and initializes the page's
If you want to refer to a handler defined in a specific [component](../cms/components), use the component's name or alias in the handler reference:
- {{ form_open({ request: 'myComponent::onHandleForm' }) }}
+```twig
+{{ form_open({ request: 'myComponent::onHandleForm' }) }}
+```
## 404 page
@@ -192,14 +226,18 @@ By default, any errors will be shown with a detailed error page containing the f
The properties of a page can be accessed in the [PHP code section](../cms/themes#php-section), or [Components](../cms/components) by referencing `$this->page`.
- function onEnd()
- {
- $this->page->title = 'A different page title';
- }
+```php
+function onEnd()
+{
+ $this->page->title = 'A different page title';
+}
+```
They can also be accessed in the markup using the [`this.page` variable](../markup/this-page). For example, to return the title of a page:
-
The title of this page is: {{ this.page.title }}
+```twig
+
The title of this page is: {{ this.page.title }}
+```
More information can be found at [`this.page` in the Markup guide](../markup/this-page).
diff --git a/cms-partials.md b/cms-partials.md
index e645afc4..a5bff5bc 100644
--- a/cms-partials.md
+++ b/cms-partials.md
@@ -12,15 +12,19 @@
Partials contain reusable chunks of Twig markup that can be used anywhere throughout the website. Partials are extremely useful for page elements that repeat on different pages or layouts. A good partial example is a page footer which is used in different [page layouts](layouts). Also, partials are required for [updating the page content with AJAX](../ajax/update-partials).
-Partial templates files reside in the **/partials** subdirectory of a theme directory. Partial files should have the **htm** extension. Example of a simplest possible partial:
+Partial templates files reside in the `/partials` subdirectory of a theme directory. Partial files should have the `htm` extension. Example of a simplest possible partial:
-
This is a partial
+```html
+
This is a partial
+```
The [Configuration](themes#configuration-section) section is optional for partials and can contain the optional **description** parameter which is displayed in the backend user interface. The next example shows a partial with description:
- description = "Demo partial"
- ==
-
This is a partial
+```html
+description = "Demo partial"
+==
+
This is a partial
+```
The partial configuration section can also contain component definitions. [Components](components) are explained in another article.
@@ -29,29 +33,36 @@ The partial configuration section can also contain component definitions. [Compo
The `{% partial "partial-name" %}` Twig tag renders a partial. The tag has a single required parameter - the partial file name without the extension. Remember that if you refer a partial from a [subdirectory](themes#subdirectories) you should specify the subdirectory name. The `{% partial %}` tag can be used inside a page, layout or another partial. Example of a page referring to a partial:
-
+```twig
+
+```
## Passing variables to partials
You will find that you often need to pass variables to a partial from the external code. This makes partials even more useful. For example, you can have a partial that renders a list of blog posts. If you can pass the post collection to the partial, the same partial could be used on the blog archive page, on the blog category page and so on. You can pass variables to partials by specifying them after the partial name in the `{% partial %}` tag:
-
+```twig
+
+```
You can also assign new variables for use in the partial:
-
+```twig
+
+```
Inside the partial, variables can be accessed like any other markup variable:
-
Country: {{ country }}, city: {{ city }}.
-
+```twig
+
Country: {{ country }}, city: {{ city }}.
+```
## Dynamic partials
@@ -63,13 +74,15 @@ Partials, like pages, can use any Twig features. Please refer to the [Dynamic pa
There are special functions that can be defined in the PHP section of partials: `onStart` and `onEnd`. The `onStart` function is executed before the partial is rendered and before the partial [components](components) are executed. The `onEnd` function is executed before the partial is rendered and after the partial [components](components) are executed. In the onStart and onEnd functions you can inject variables to the Twig environment. Use the `array notation` to pass variables to the page:
- ==
- function onStart()
- {
- $this['hello'] = "Hello world!";
- }
- ==
-
{{ hello }}
+```php
+==
+function onStart()
+{
+ $this['hello'] = "Hello world!";
+}
+==
+
{{ hello }}
+```
The templating language provided by Winter is described in the [Markup Guide](../markup). The overall sequence the handlers are executed is described in the [Dynamic layouts](layouts#dynamic-layouts) article.
diff --git a/cms-themes.md b/cms-themes.md
index 6fc13d83..294e70e4 100644
--- a/cms-themes.md
+++ b/cms-themes.md
@@ -93,7 +93,9 @@ Winter supports single level subdirectories for **pages**, **partials**, **layou
To refer to a partial or a content file from a subdirectory, specify the subdirectory's name before the template's name. Example of rendering a partial from a subdirectory:
- {% partial "blog/category-list" %}
+```twig
+{% partial "blog/category-list" %}
+```
> **NOTE:** The template paths are always absolute. If, in a partial, you render another partial from the same subdirectory, you still need to specify the subdirectory's name.
@@ -104,77 +106,87 @@ Pages, partials and layout templates can include up to 3 sections: **configurati
Sections are separated with the `==` sequence.
For example:
- url = "/blog"
- layout = "default"
- ==
- function onStart()
- {
- $this['posts'] = ...;
- }
- ==
-
Blog archive
- {% for post in posts %}
-
{{ post.title }}
- {{ post.content }}
- {% endfor %}
+```twig
+url = "/blog"
+layout = "default"
+==
+function onStart()
+{
+ $this['posts'] = ...;
+}
+==
+
Blog archive
+{% for post in posts %}
+
{{ post.title }}
+ {{ post.content }}
+{% endfor %}
+```
### Configuration section
The configuration section sets the template parameters. Supported configuration parameters are specific for different CMS templates and described in their corresponding documentation articles. The configuration section uses the simple [INI format](http://en.wikipedia.org/wiki/INI_file), where string parameter values are enclosed within quotes. Example configuration section for a page template:
- url = "/blog"
- layout = "default"
+```ini
+url = "/blog"
+layout = "default"
- [component]
- parameter = "value"
+[component]
+parameter = "value"
+```
### PHP code section
The code in the PHP section executes every time before the template is rendered. The PHP section is optional for all CMS templates and its contents depend on the template type where it is defined. The PHP code section can contain optional open and close PHP tags to enable syntax highlighting in text editors. The open and close tags should always be specified on a different line to the section separator `==`.
- url = "/blog"
- layout = "default"
- ==
-
- function onStart()
- {
- $this['posts'] = ...;
- }
- ?>
- ==
-
Blog archive
- {% for post in posts %}
-
{{ post.title }}
- {{ post.content }}
- {% endfor %}
+```twig
+url = "/blog"
+layout = "default"
+==
+
+function onStart()
+{
+ $this['posts'] = ...;
+}
+?>
+==
+
Blog archive
+{% for post in posts %}
+
{{ post.title }}
+ {{ post.content }}
+{% endfor %}
+```
In the PHP section, you can only define functions and refer to namespaces with the PHP `use` keyword. No other PHP code is allowed in the PHP section. This is because the PHP section is converted to a PHP class when the page is parsed. Example of using a namespace reference:
- url = "/blog"
- layout = "default"
- ==
-
- use Acme\Blog\Classes\Post;
-
- function onStart()
- {
- $this['posts'] = Post::get();
- }
- ?>
- ==
+```php
+url = "/blog"
+layout = "default"
+==
+
+use Acme\Blog\Classes\Post;
+
+function onStart()
+{
+ $this['posts'] = Post::get();
+}
+?>
+==
+```
As a general way of setting variables, you should use the array access method on `$this`, although for simplicity you can use **object access as read-only**, for example:
- // Write via array
- $this['foo'] = 'bar';
+```php
+// Write via array
+$this['foo'] = 'bar';
- // Read via array
- echo $this['foo'];
+// Read via array
+echo $this['foo'];
- // Read-only via object
- echo $this->foo;
+// Read-only via object
+echo $this->foo;
+```
### Twig markup section
diff --git a/console-development.md b/console-development.md
index e36fe476..5fae68e6 100644
--- a/console-development.md
+++ b/console-development.md
@@ -19,53 +19,55 @@ In addition to the provided console commands, you may also build your own custom
If you wanted to create a console command called `acme:mycommand`, you might create the associated class for that command in a file called **plugins/acme/blog/console/MyCommand.php** and paste the following contents to get started:
- output->writeln('Hello world!');
+ }
- class MyCommand extends Command
+ /**
+ * Get the console command arguments.
+ * @return array
+ */
+ protected function getArguments()
{
- /**
- * @var string The console command name.
- */
- protected $name = 'acme:mycommand';
-
- /**
- * @var string The console command description.
- */
- protected $description = 'Does something cool.';
-
- /**
- * Execute the console command.
- * @return void
- */
- public function handle()
- {
- $this->output->writeln('Hello world!');
- }
-
- /**
- * Get the console command arguments.
- * @return array
- */
- protected function getArguments()
- {
- return [];
- }
-
- /**
- * Get the console command options.
- * @return array
- */
- protected function getOptions()
- {
- return [];
- }
+ return [];
+ }
+ /**
+ * Get the console command options.
+ * @return array
+ */
+ protected function getOptions()
+ {
+ return [];
}
+}
+```
+
Once your class is created you should fill out the `name` and `description` properties of the class, which will be used when displaying your command on the command `list` screen.
The `handle` method will be called when your command is executed. You may place any command logic in this method.
@@ -75,20 +77,24 @@ The `handle` method will be called when your command is executed. You may place
Arguments are defined by returning an array value from the `getArguments` method are where you may define any arguments your command receives. For example:
- /**
- * Get the console command arguments.
- * @return array
- */
- protected function getArguments()
- {
- return [
- ['example', InputArgument::REQUIRED, 'An example argument.'],
- ];
- }
+```php
+/**
+ * Get the console command arguments.
+ * @return array
+ */
+protected function getArguments()
+{
+ return [
+ ['example', InputArgument::REQUIRED, 'An example argument.'],
+ ];
+}
+```
When defining `arguments`, the array definition values represent the following:
- array($name, $mode, $description, $defaultValue)
+```php
+array($name, $mode, $description, $defaultValue)
+```
The argument `mode` may be any of the following: `InputArgument::REQUIRED` or `InputArgument::OPTIONAL`.
@@ -97,30 +103,38 @@ The argument `mode` may be any of the following: `InputArgument::REQUIRED` or `I
Options are defined by returning an array value from the `getOptions` method. Like arguments this method should return an array of commands, which are described by a list of array options. For example:
- /**
- * Get the console command options.
- * @return array
- */
- protected function getOptions()
- {
- return [
- ['example', null, InputOption::VALUE_OPTIONAL, 'An example option.', null],
- ];
- }
+```php
+/**
+ * Get the console command options.
+ * @return array
+ */
+protected function getOptions()
+{
+ return [
+ ['example', null, InputOption::VALUE_OPTIONAL, 'An example option.', null],
+ ];
+}
+```
When defining `options`, the array definition values represent the following:
- array($name, $shortcut, $mode, $description, $defaultValue)
+```php
+array($name, $shortcut, $mode, $description, $defaultValue)
+```
For options, the argument `mode` may be: `InputOption::VALUE_REQUIRED`, `InputOption::VALUE_OPTIONAL`, `InputOption::VALUE_IS_ARRAY`, `InputOption::VALUE_NONE`.
The `VALUE_IS_ARRAY` mode indicates that the switch may be used multiple times when calling the command:
- php artisan foo --option=bar --option=baz
+```bash
+php artisan foo --option=bar --option=baz
+```
The `VALUE_NONE` option indicates that the option is simply used as a "switch":
- php artisan foo --option
+```bash
+php artisan foo --option
+```
### Retrieving input
@@ -129,19 +143,27 @@ While your command is executing, you will obviously need to access the values fo
#### Retrieving the value of a command argument
- $value = $this->argument('name');
+```php
+$value = $this->argument('name');
+```
#### Retrieving all arguments
- $arguments = $this->argument();
+```php
+$arguments = $this->argument();
+```
#### Retrieving the value of a command option
- $value = $this->option('name');
+```php
+$value = $this->option('name');
+```
#### Retrieving all options
- $options = $this->option();
+```php
+$options = $this->option();
+```
### Writing output
@@ -150,48 +172,62 @@ To send output to the console, you may use the `info`, `comment`, `question` and
#### Sending information
- $this->info('Display this on the screen');
+```php
+$this->info('Display this on the screen');
+```
#### Sending an error message
- $this->error('Something went wrong!');
+```php
+$this->error('Something went wrong!');
+```
#### Asking the user for input
You may also use the `ask` and `confirm` methods to prompt the user for input:
- $name = $this->ask('What is your name?');
+```php
+$name = $this->ask('What is your name?');
+```
#### Asking the user for secret input
- $password = $this->secret('What is the password?');
+```php
+$password = $this->secret('What is the password?');
+```
#### Asking the user for confirmation
- if ($this->confirm('Do you wish to continue? [yes|no]'))
- {
- //
- }
+```php
+if ($this->confirm('Do you wish to continue? [yes|no]'))
+{
+ //
+}
+```
You may also specify a default value to the `confirm` method, which should be `true` or `false`:
- $this->confirm($question, true);
+```php
+$this->confirm($question, true);
+```
#### Progress Bars
For long running tasks, it could be helpful to show a progress indicator. Using the output object, we can start, advance and stop the Progress Bar. First, define the total number of steps the process will iterate through. Then, advance the Progress Bar after processing each item:
- $users = App\User::all();
+```php
+$users = App\User::all();
- $bar = $this->output->createProgressBar(count($users));
+$bar = $this->output->createProgressBar(count($users));
- foreach ($users as $user) {
- $this->performTask($user);
+foreach ($users as $user) {
+ $this->performTask($user);
- $bar->advance();
- }
+ $bar->advance();
+}
- $bar->finish();
+$bar->finish();
+```
For more advanced options, check out the [Symfony Progress Bar component documentation](https://symfony.com/doc/2.7/components/console/helpers/progressbar.html).
@@ -202,54 +238,67 @@ For more advanced options, check out the [Symfony Progress Bar component documen
Once your command class is finished, you need to register it so it will be available for use. This is typically done in the `register` method of a [Plugin registration file](../plugin/registration#registration-methods) using the `registerConsoleCommand` helper method.
- class Blog extends PluginBase
+```php
+class Blog extends PluginBase
+{
+ public function pluginDetails()
+ {
+ [...]
+ }
+
+ public function register()
{
- public function pluginDetails()
- {
- [...]
- }
-
- public function register()
- {
- $this->registerConsoleCommand('acme.mycommand', 'Acme\Blog\Console\MyConsoleCommand');
- }
+ $this->registerConsoleCommand('acme.mycommand', 'Acme\Blog\Console\MyConsoleCommand');
}
+}
+```
Alternatively, plugins can supply a file named **init.php** in the plugin directory that you can use to place command registration logic. Within this file, you may use the `Artisan::add` method to register the command:
- Artisan::add(new Acme\Blog\Console\MyCommand);
+```php
+Artisan::add(new Acme\Blog\Console\MyCommand);
+```
#### Registering a command in the application container
If your command is registered in the [application container](../services/application#app-container), you may use the `Artisan::resolve` method to make it available to Artisan:
- Artisan::resolve('binding.name');
+```php
+Artisan::resolve('binding.name');
+```
#### Registering commands in a service provider
If you need to register commands from within a [service provider](../services/application#service-providers), you should call the `commands` method from the provider's `boot` method, passing the [container](../services/application#app-container) binding for the command:
- public function boot()
- {
- $this->app->singleton('acme.mycommand', function() {
- return new \Acme\Blog\Console\MyConsoleCommand;
- });
+```php
+public function boot()
+{
+ $this->app->singleton('acme.mycommand', function() {
+ return new \Acme\Blog\Console\MyConsoleCommand;
+ });
- $this->commands('acme.mycommand');
- }
+ $this->commands('acme.mycommand');
+}
+```
## Calling other commands
Sometimes you may wish to call other commands from your command. You may do so using the `call` method:
- $this->call('winter:up');
+```php
+$this->call('winter:up');
+```
You can also pass arguments as an array:
- $this->call('plugin:refresh', ['name' => 'Winter.Demo']);
+```php
+$this->call('plugin:refresh', ['name' => 'Winter.Demo']);
+```
As well as options:
- $this->call('winter:update', ['--force' => true]);
-
+```php
+$this->call('winter:update', ['--force' => true]);
+```
diff --git a/console-scaffolding.md b/console-scaffolding.md
index bc269129..386b4814 100644
--- a/console-scaffolding.md
+++ b/console-scaffolding.md
@@ -21,60 +21,78 @@ Use the scaffolding commands to speed up the development process.
The `create:theme` command generates a theme folder and basic files for the theme. The parameter specifies the theme code.
- php artisan create:theme myauthor-mytheme
+```bash
+php artisan create:theme myauthor-mytheme
+```
### Create a Plugin
The `create:plugin` command generates a plugin folder and basic files for the plugin. The parameter specifies the author and plugin name.
- php artisan create:plugin Acme.Blog
+```bash
+php artisan create:plugin Acme.Blog
+```
### Create a Component
The `create:component` command creates a new component class and the default component view. The first parameter specifies the author and plugin name. The second parameter specifies the component class name.
- php artisan create:component Acme.Blog Post
+```bash
+php artisan create:component Acme.Blog Post
+```
### Create a Model
The `create:model` command generates the files needed for a new model. The first parameter specifies the author and plugin name. The second parameter specifies the model class name.
- php artisan create:model Acme.Blog Post
+```bash
+php artisan create:model Acme.Blog Post
+```
### Create a Settings Model
The `create:settings` command generates the files needed for a new [Settings model](../plugin/settings#database-settings). The first parameter specifies the author and plugin name. The second parameter is optional and specifies the Settings model class name (defaults to `Settings`).
- php artisan create:settings Acme.Blog CustomSettings
+```bash
+php artisan create:settings Acme.Blog CustomSettings
+```
### Create a backend Controller
The `create:controller` command generates a controller, configuration and view files. The first parameter specifies the author and plugin name. The second parameter specifies the controller class name.
- php artisan create:controller Acme.Blog Posts
+```bash
+php artisan create:controller Acme.Blog Posts
+```
### Create a FormWidget
The `create:formwidget` command generates a backend form widget, view and basic asset files. The first parameter specifies the author and plugin name. The second parameter specifies the form widget class name.
- php artisan create:formwidget Acme.Blog CategorySelector
+```bash
+php artisan create:formwidget Acme.Blog CategorySelector
+```
### Create a ReportWidget
The `create:reportwidget` command generates a backend report widget, view and basic asset files. The first parameter specifies the author and plugin name. The second parameter specifies the report widget class name.
- php artisan create:reportwidget Acme.Blog TopPosts
+```bash
+php artisan create:reportwidget Acme.Blog TopPosts
+```
### Create a console Command
The `create:command` command generates a [new console command](../console/development). The first parameter specifies the author and plugin name. The second parameter specifies the command name.
- php artisan create:command Winter.Blog MyCommand
+```bash
+php artisan create:command Winter.Blog MyCommand
+```
diff --git a/database-attachments.md b/database-attachments.md
index 036e8d5a..ddc6343f 100644
--- a/database-attachments.md
+++ b/database-attachments.md
@@ -15,84 +15,111 @@ In the examples below the model has a single Avatar attachment model and many Ph
A single file attachment:
- public $attachOne = [
- 'avatar' => 'System\Models\File'
- ];
+```php
+public $attachOne = [
+ 'avatar' => 'System\Models\File'
+];
+```
Multiple file attachments:
- public $attachMany = [
- 'photos' => 'System\Models\File'
- ];
+```php
+public $attachMany = [
+ 'photos' => 'System\Models\File'
+];
+```
> **NOTE:** If you have a column in your model's table with the same name as the attachment relationship it will not work. Attachments and the FileUpload FormWidget work using relationships, so if there is a column with the same name present in the table itself it will cause issues.
Protected attachments are uploaded to the application's **uploads/protected** directory which is not accessible for the direct access from the Web. A protected file attachment is defined by setting the *public* argument to `false`:
- public $attachOne = [
- 'avatar' => ['System\Models\File', 'public' => false]
- ];
+```php
+public $attachOne = [
+ 'avatar' => ['System\Models\File', 'public' => false]
+];
+```
### Creating new attachments
For singular attach relations (`$attachOne`), you may create an attachment directly via the model relationship, by setting its value using the `Input::file` method, which reads the file data from an input upload.
- $model->avatar = Input::file('file_input');
+```php
+$model->avatar = Input::file('file_input');
+```
You may also pass a string to the `data` attribute that contains an absolute path to a local file.
- $model->avatar = '/path/to/somefile.jpg';
+```php
+$model->avatar = '/path/to/somefile.jpg';
+```
Sometimes it may also be useful to create a `File` instance directly from (raw) data:
- $file = (new System\Models\File)->fromData('Some content', 'sometext.txt');
+```php
+$file = (new System\Models\File)->fromData('Some content', 'sometext.txt');
+```
For multiple attach relations (`$attachMany`), you may use the `create` method on the relationship instead, notice the file object is associated to the `data` attribute. This approach can be used for singular relations too, if you prefer.
- $model->avatar()->create(['data' => Input::file('file_input')]);
+```php
+$model->avatar()->create(['data' => Input::file('file_input')]);
+```
Alternatively, you can prepare a File model before hand, then manually associate the relationship later. Notice the `is_public` attribute must be set explicitly using this approach.
- $file = new System\Models\File;
- $file->data = Input::file('file_input');
- $file->is_public = true;
- $file->save();
+```php
+$file = new System\Models\File;
+$file->data = Input::file('file_input');
+$file->is_public = true;
+$file->save();
- $model->avatar()->add($file);
+$model->avatar()->add($file);
+```
You can also add a file from a URL. To work this method, you need install cURL PHP Extension.
- $file = new System\Models\File;
- $file->fromUrl('https://example.com/uploads/public/path/to/avatar.jpg');
+```php
+$file = new System\Models\File;
+$file->fromUrl('https://example.com/uploads/public/path/to/avatar.jpg');
- $user->avatar()->add($file);
+$user->avatar()->add($file);
+```
Occasionally you may need to change a file name. You may do so by using second method parameter.
+```php
$file->fromUrl('https://example.com/uploads/public/path/to/avatar.jpg', 'somefilename.jpg');
-
+```
### Viewing attachments
The `getPath` method returns the full URL of an uploaded public file. The following code would print something like **example.com/uploads/public/path/to/avatar.jpg**
- echo $model->avatar->getPath();
+```php
+echo $model->avatar->getPath();
+```
Returning multiple attachment file paths:
- foreach ($model->photos as $photo) {
- echo $photo->getPath();
- }
+```php
+foreach ($model->photos as $photo) {
+ echo $photo->getPath();
+}
+```
The `getLocalPath` method will return an absolute path of an uploaded file in the local filesystem.
- echo $model->avatar->getLocalPath();
+```php
+echo $model->avatar->getLocalPath();
+```
To output the file contents directly, use the `output` method, this will include the necessary headers for downloading the file:
- echo $model->avatar->output();
+```php
+echo $model->avatar->output();
+```
You can resize an image with the `getThumb` method. The method takes 3 parameters - image width, image height and the options parameter. Read more about these parameters on the [Image Resizing](../services/image-resizing#resize-parameters) page.
@@ -103,70 +130,84 @@ This section shows a full usage example of the model attachments feature - from
Inside your model define a relationship to the `System\Models\File` class, for example:
- class Post extends Model
- {
- public $attachOne = [
- 'featured_image' => 'System\Models\File'
- ];
- }
+```php
+class Post extends Model
+{
+ public $attachOne = [
+ 'featured_image' => 'System\Models\File'
+ ];
+}
+```
Build a form for uploading a file:
- = Form::open(['files' => true]) ?>
+```html
+= Form::open(['files' => true]) ?>
-
+
-
Upload File
+
Upload File
- = Form::close() ?>
+= Form::close() ?>
+```
Process the uploaded file on the server and attach it to a model:
- // Find the Blog Post model
- $post = Post::find(1);
+```php
+// Find the Blog Post model
+$post = Post::find(1);
- // Save the featured image of the Blog Post model
- if (Input::hasFile('example_file')) {
- $post->featured_image = Input::file('example_file');
- }
+// Save the featured image of the Blog Post model
+if (Input::hasFile('example_file')) {
+ $post->featured_image = Input::file('example_file');
+}
+```
Alternatively, you can use [deferred binding](../database/relations#deferred-binding) to defer the relationship:
- // Find the Blog Post model
- $post = Post::find(1);
+```php
+// Find the Blog Post model
+$post = Post::find(1);
- // Look for the postback data 'example_file' in the HTML form above
- $fileFromPost = Input::file('example_file');
+// Look for the postback data 'example_file' in the HTML form above
+$fileFromPost = Input::file('example_file');
- // If it exists, save it as the featured image with a deferred session key
- if ($fileFromPost) {
- $post->featured_image()->create(['data' => $fileFromPost], $sessionKey);
- }
+// If it exists, save it as the featured image with a deferred session key
+if ($fileFromPost) {
+ $post->featured_image()->create(['data' => $fileFromPost], $sessionKey);
+}
+```
Display the uploaded file on a page:
- // Find the Blog Post model again
- $post = Post::find(1);
+```php
+// Find the Blog Post model again
+$post = Post::find(1);
- // Look for the featured image address, otherwise use a default one
- if ($post->featured_image) {
- $featuredImage = $post->featured_image->getPath();
- }
- else {
- $featuredImage = 'http://placehold.it/220x300';
- }
+// Look for the featured image address, otherwise use a default one
+if ($post->featured_image) {
+ $featuredImage = $post->featured_image->getPath();
+}
+else {
+ $featuredImage = 'http://placehold.it/220x300';
+}
-
+
+```
If you need to access the owner of a file, you can use the `attachment` property of the `File` model:
- public $morphTo = [
- 'attachment' => []
- ];
+```php
+public $morphTo = [
+ 'attachment' => []
+];
+```
Example:
- $user = $file->attachment;
+```php
+$user = $file->attachment;
+```
For more information read the [polymorphic relationships](../database/relations#polymorphic-relations)
@@ -175,24 +216,26 @@ For more information read the [polymorphic relationships](../database/relations#
The example below uses [array validation](../services/validation#validating-arrays) to validate `$attachMany` relationships.
- use Winter\Storm\Database\Traits\Validation;
- use System\Models\File;
- use Model;
+```php
+use Winter\Storm\Database\Traits\Validation;
+use System\Models\File;
+use Model;
- class Gallery extends Model
- {
- use Validation;
+class Gallery extends Model
+{
+ use Validation;
- public $attachMany = [
- 'photos' => File::class
- ];
+ public $attachMany = [
+ 'photos' => File::class
+ ];
- public $rules = [
- 'photos' => 'required',
- 'photos.*' => 'image|max:1000|dimensions:min_width=100,min_height=100'
- ];
+ public $rules = [
+ 'photos' => 'required',
+ 'photos.*' => 'image|max:1000|dimensions:min_width=100,min_height=100'
+ ];
- /* some other code */
- }
+ /* some other code */
+}
+```
For more information on the `attribute.*` syntax used above, see [validating arrays](../services/validation#validating-arrays).
diff --git a/database-basics.md b/database-basics.md
index facd7774..bfc99111 100644
--- a/database-basics.md
+++ b/database-basics.md
@@ -29,21 +29,23 @@ Sometimes you may wish to use one database connection for SELECT statements, and
To see how read / write connections should be configured, let's look at this example:
- 'mysql' => [
- 'read' => [
- 'host' => '192.168.1.1',
- ],
- 'write' => [
- 'host' => '196.168.1.2'
- ],
- 'driver' => 'mysql',
- 'database' => 'database',
- 'username' => 'root',
- 'password' => '',
- 'charset' => 'utf8',
- 'collation' => 'utf8_unicode_ci',
- 'prefix' => '',
+```php
+'mysql' => [
+ 'read' => [
+ 'host' => '192.168.1.1',
],
+ 'write' => [
+ 'host' => '196.168.1.2'
+ ],
+ 'driver' => 'mysql',
+ 'database' => 'database',
+ 'username' => 'root',
+ 'password' => '',
+ 'charset' => 'utf8',
+ 'collation' => 'utf8_unicode_ci',
+ 'prefix' => '',
+],
+```
Note that two keys have been added to the configuration array: `read` and `write`. Both of these keys have array values containing a single key: `host`. The rest of the database options for the `read` and `write` connections will be merged from the main `mysql` array.
@@ -58,81 +60,107 @@ Once you have configured your database connection, you may run queries using the
To run a basic query, we can use the `select` method on the `Db` facade:
- $users = Db::select('select * from users where active = ?', [1]);
+```php
+$users = Db::select('select * from users where active = ?', [1]);
+```
The first argument passed to the `select` method is the raw SQL query, while the second argument is any parameter bindings that need to be bound to the query. Typically, these are the values of the `where` clause constraints. Parameter binding provides protection against SQL injection.
The `select` method will always return an `array` of results. Each result within the array will be a PHP `stdClass` object, allowing you to access the values of the results:
- foreach ($users as $user) {
- echo $user->name;
- }
+```php
+foreach ($users as $user) {
+ echo $user->name;
+}
+```
#### Using named bindings
Instead of using `?` to represent your parameter bindings, you may execute a query using named bindings:
- $results = Db::select('select * from users where id = :id', ['id' => 1]);
+```php
+$results = Db::select('select * from users where id = :id', ['id' => 1]);
+```
#### Running an insert statement
To execute an `insert` statement, you may use the `insert` method on the `Db` facade. Like `select`, this method takes the raw SQL query as its first argument and bindings as the second argument:
- Db::insert('insert into users (id, name) values (?, ?)', [1, 'Joe']);
+```php
+Db::insert('insert into users (id, name) values (?, ?)', [1, 'Joe']);
+```
#### Running an update statement
The `update` method should be used to update existing records in the database. The number of rows affected by the statement will be returned by the method:
- $affected = Db::update('update users set votes = 100 where name = ?', ['John']);
+```php
+$affected = Db::update('update users set votes = 100 where name = ?', ['John']);
+```
#### Running a delete statement
The `delete` method should be used to delete records from the database. Like `update`, the number of rows deleted will be returned:
- $deleted = Db::delete('delete from users');
+```php
+$deleted = Db::delete('delete from users');
+```
#### Running a general statement
Some database statements should not return any value. For these types of operations, you may use the `statement` method on the `Db` facade:
- Db::statement('drop table users');
+```php
+Db::statement('drop table users');
+```
## Multiple database connections
When using multiple connections, you may access each connection via the `connection` method on the `Db` facade. The `name` passed to the `connection` method should correspond to one of the connections listed in your `config/database.php` configuration file:
- $users = Db::connection('foo')->select(...);
+```php
+$users = Db::connection('foo')->select(...);
+```
You may also access the raw, underlying PDO instance using the `getPdo` method on a connection instance:
- $pdo = Db::connection()->getPdo();
+```php
+$pdo = Db::connection()->getPdo();
+```
## Database transactions
To run a set of operations within a database transaction, you may use the `transaction` method on the `Db` facade. If an exception is thrown within the transaction `Closure`, the transaction will automatically be rolled back. If the `Closure` executes successfully, the transaction will automatically be committed. You don't need to worry about manually rolling back or committing while using the `transaction` method:
- Db::transaction(function () {
- Db::table('users')->update(['votes' => 1]);
+```php
+Db::transaction(function () {
+ Db::table('users')->update(['votes' => 1]);
- Db::table('posts')->delete();
- });
+ Db::table('posts')->delete();
+});
+```
#### Manually using transactions
If you would like to begin a transaction manually and have complete control over rollbacks and commits, you may use the `beginTransaction` method on the `Db` facade:
- Db::beginTransaction();
+```php
+Db::beginTransaction();
+```
You can rollback the transaction via the `rollBack` method:
- Db::rollBack();
+```php
+Db::rollBack();
+```
Lastly, you can commit a transaction via the `commit` method:
- Db::commit();
+```php
+Db::commit();
+```
> **NOTE:** Using the `Db` facade's transaction methods also controls transactions for the [query builder](../database/query) and [model queries](../database/model).
@@ -141,9 +169,11 @@ Lastly, you can commit a transaction via the `commit` method:
If you would like to receive each SQL query executed by your application, you may use the `listen` method. This method is useful for logging queries or debugging.
- Db::listen(function($sql, $bindings, $time) {
- //
- });
+```php
+Db::listen(function($sql, $bindings, $time) {
+ //
+});
+```
Just like [event registration](../services/events#event-registration), you may register your query listener in the `boot` method of a [Plugin registration file](../plugin/registration#registration-methods). Alternatively, plugins can supply a file named **init.php** in the plugin directory that you can use to place this logic.
@@ -152,14 +182,20 @@ Just like [event registration](../services/events#event-registration), you may r
When query logging is enabled, a log is kept in memory of all queries that have been run for the current request. Call the `enableQueryLog` method to enable this feature.
- Db::connection()->enableQueryLog();
+```php
+Db::connection()->enableQueryLog();
+```
To get an array of the executed queries, you may use the `getQueryLog` method:
- $queries = Db::getQueryLog();
+```php
+$queries = Db::getQueryLog();
+```
However, in some cases, such as when inserting a large number of rows, this can cause the application to use excess memory. To disable the log, you may use the `disableQueryLog` method:
- Db::connection()->disableQueryLog();
+```php
+Db::connection()->disableQueryLog();
+```
> **NOTE**: For quicker debugging it may be more useful to call the `trace_sql` [helper function](../services/error-log#helpers) instead.
diff --git a/database-behaviors.md b/database-behaviors.md
index bceca3dd..5d48f0c5 100644
--- a/database-behaviors.md
+++ b/database-behaviors.md
@@ -12,70 +12,84 @@ Purged attributes will not be saved to the database when a model is created or u
attributes in your model, implement the `Winter.Storm.Database.Behaviors.Purgeable` behavior and declare
a `$purgeable` property with an array containing the attributes to purge.
- class User extends Model
- {
- public $implement = [
- 'Winter.Storm.Database.Behaviors.Purgeable'
- ];
-
- /**
- * @var array List of attributes to purge.
- */
- public $purgeable = [];
- }
-
-You can also dynamically implement this behavior in a class.
+```php
+class User extends Model
+{
+ public $implement = [
+ 'Winter.Storm.Database.Behaviors.Purgeable'
+ ];
/**
- * Extend the Winter.User user model to implement the purgeable behavior.
+ * @var array List of attributes to purge.
*/
- Winter\User\Models\User::extend(function($model) {
+ public $purgeable = [];
+}
+```
+
+You can also dynamically implement this behavior in a class.
- // Implement the purgeable behavior dynamically
- $model->implement[] = 'Winter.Storm.Database.Behaviors.Purgeable';
+```php
+/**
+ * Extend the Winter.User user model to implement the purgeable behavior.
+ */
+Winter\User\Models\User::extend(function($model) {
- // Declare the purgeable property dynamically for the purgeable behavior to use
- $model->addDynamicProperty('purgeable', []);
- });
+ // Implement the purgeable behavior dynamically
+ $model->implement[] = 'Winter.Storm.Database.Behaviors.Purgeable';
+
+ // Declare the purgeable property dynamically for the purgeable behavior to use
+ $model->addDynamicProperty('purgeable', []);
+});
+```
The defined attributes will be purged when the model is saved, before the [model events](#model-events)
are triggered, including validation. Use the `getOriginalPurgeValue` to find a value that was purged.
- return $user->getOriginalPurgeValue($propertyName);
+```php
+return $user->getOriginalPurgeValue($propertyName);
+```
## Sortable
Sorted models will store a number value in `sort_order` which maintains the sort order of each individual model in a collection. To store a sort order for your models, implement the `Winter\Storm\Database\Behaviors\Sortable` behavior and ensure that your schema has a column defined for it to use (example: `$table->integer('sort_order')->default(0);`).
- class User extends Model
- {
- public $implement = [
- 'Winter.Storm.Database.Behaviors.Sortable'
- ];
- }
+```php
+class User extends Model
+{
+ public $implement = [
+ 'Winter.Storm.Database.Behaviors.Sortable'
+ ];
+}
+```
You can also dynamically implement this behavior in a class.
- /**
- * Extend the Winter.User user model to implement the sortable behavior.
- */
- Winter\User\Models\User::extend(function($model) {
+```php
+/**
+ * Extend the Winter.User user model to implement the sortable behavior.
+ */
+Winter\User\Models\User::extend(function($model) {
- // Implement the sortable behavior dynamically
- $model->implement[] = 'Winter.Storm.Database.Behaviors.Sortable';
- });
+ // Implement the sortable behavior dynamically
+ $model->implement[] = 'Winter.Storm.Database.Behaviors.Sortable';
+});
+```
You may modify the key name used to identify the sort order by defining the `SORT_ORDER` constant:
- const SORT_ORDER = 'my_sort_order_column';
+```php
+const SORT_ORDER = 'my_sort_order_column';
+```
Use the `setSortableOrder` method to set the orders on a single record or multiple records.
- // Sets the order of the user to 1...
- $user->setSortableOrder($user->id, 1);
+```php
+// Sets the order of the user to 1...
+$user->setSortableOrder($user->id, 1);
- // Sets the order of records 1, 2, 3 to 3, 2, 1 respectively...
- $user->setSortableOrder([1, 2, 3], [3, 2, 1]);
+// Sets the order of records 1, 2, 3 to 3, 2, 1 respectively...
+$user->setSortableOrder([1, 2, 3], [3, 2, 1]);
+```
> **NOTE:** If implementing this behavior in a model where data (rows) already existed previously, the data set may need to be initialized before this behavior will work correctly. To do so, either manually update each row's `sort_order` column or run a query against the data to copy the record's `id` column to the `sort_order` column (ex. `UPDATE myvendor_myplugin_mymodelrecords SET sort_order = id`).
diff --git a/database-collection.md b/database-collection.md
index 02536f78..e1ab2e33 100644
--- a/database-collection.md
+++ b/database-collection.md
@@ -15,22 +15,26 @@ All multi-result sets returned by a model are an instance of the `Illuminate\Dat
All collections also serve as iterators, allowing you to loop over them as if they were simple PHP arrays:
- $users = User::where('is_active', true)->get();
+```php
+$users = User::where('is_active', true)->get();
- foreach ($users as $user) {
- echo $user->name;
- }
+foreach ($users as $user) {
+ echo $user->name;
+}
+```
However, collections are much more powerful than arrays and expose a variety of map / reduce operations using an intuitive interface. For example, let's filter all active models and gather the name for each filtered user:
- $users = User::get();
+```php
+$users = User::get();
- $names = $users->filter(function ($user) {
- return $user->is_active === true;
- })
- ->map(function ($user) {
- return $user->name;
- });
+$names = $users->filter(function ($user) {
+ return $user->is_active === true;
+ })
+ ->map(function ($user) {
+ return $user->name;
+ });
+```
> **NOTE:** While most model collection methods return a new instance of an `Eloquent` collection, the `pluck`, `keys`, `zip`, `collapse`, `flatten` and `flip` methods return a base collection instance. Likewise, if a `map` operation returns a collection that does not contain any models, it will be automatically cast to a base collection.
@@ -46,119 +50,149 @@ In addition, the `Illuminate\Database\Eloquent\Collection` class provides a supe
The `contains` method may be used to determine if a given model instance is contained by the collection. This method accepts a primary key or a model instance:
- $users->contains(1);
+```php
+$users->contains(1);
- $users->contains(User::find(1));
+$users->contains(User::find(1));
+```
**diff($items)**
The `diff` method returns all of the models that are not present in the given collection:
- use App\User;
+```php
+use App\User;
- $users = $users->diff(User::whereIn('id', [1, 2, 3])->get());
+$users = $users->diff(User::whereIn('id', [1, 2, 3])->get());
+```
**except($keys)**
The `except` method returns all of the models that do not have the given primary keys:
- $users = $users->except([1, 2, 3]);
+```php
+$users = $users->except([1, 2, 3]);
+```
**find($key)**
The `find` method finds a model that has a given primary key. If `$key` is a model instance, `find` will attempt to return a model matching the primary key. If `$key` is an array of keys, find will return all models which match the `$keys` using `whereIn()`:
- $users = User::all();
+```php
+$users = User::all();
- $user = $users->find(1);
+$user = $users->find(1);
+```
**fresh($with = [])**
The `fresh` method retrieves a fresh instance of each model in the collection from the database. In addition, any specified relationships will be eager loaded:
- $users = $users->fresh();
+```php
+$users = $users->fresh();
- $users = $users->fresh('comments');
+$users = $users->fresh('comments');
+```
**intersect($items)**
The `intersect` method returns all of the models that are also present in the given collection:
- use App\User;
+```php
+use App\User;
- $users = $users->intersect(User::whereIn('id', [1, 2, 3])->get());
+$users = $users->intersect(User::whereIn('id', [1, 2, 3])->get());
+```
**load($relations)**
The `load` method eager loads the given relationships for all models in the collection:
- $users->load('comments', 'posts');
+```php
+$users->load('comments', 'posts');
- $users->load('comments.author');
+$users->load('comments.author');
+```
**loadMissing($relations)**
The `loadMissing` method eager loads the given relationships for all models in the collection if the relationships are not already loaded:
- $users->loadMissing('comments', 'posts');
+```php
+$users->loadMissing('comments', 'posts');
- $users->loadMissing('comments.author');
+$users->loadMissing('comments.author');
+```
**modelKeys()**
The `modelKeys` method returns the primary keys for all models in the collection:
- $users->modelKeys();
+```php
+$users->modelKeys();
- // [1, 2, 3, 4, 5]
+// [1, 2, 3, 4, 5]
+```
**makeVisible($attributes)**
The `makeVisible` method makes attributes visible that are typically "hidden" on each model in the collection:
- $users = $users->makeVisible(['address', 'phone_number']);
+```php
+$users = $users->makeVisible(['address', 'phone_number']);
+```
**makeHidden($attributes)**
The `makeHidden` method hides attributes that are typically "visible" on each model in the collection:
- $users = $users->makeHidden(['address', 'phone_number']);
+```php
+$users = $users->makeHidden(['address', 'phone_number']);
+```
**only($keys)**
The `only` method returns all of the models that have the given primary keys:
- $users = $users->only([1, 2, 3]);
+```php
+$users = $users->only([1, 2, 3]);
+```
**unique($key = null, $strict = false)**
The `unique` method returns all of the unique models in the collection. Any models of the same type with the same primary key as another model in the collection are removed.
- $users = $users->unique();
+```php
+$users = $users->unique();
+```
## Custom collections
If you need to use a custom `Collection` object with your own extension methods, you may override the `newCollection` method on your model:
- class User extends Model
+```php
+class User extends Model
+{
+ /**
+ * Create a new Collection instance.
+ */
+ public function newCollection(array $models = [])
{
- /**
- * Create a new Collection instance.
- */
- public function newCollection(array $models = [])
- {
- return new CustomCollection($models);
- }
+ return new CustomCollection($models);
}
+}
+```
Once you have defined a `newCollection` method, you will receive an instance of your custom collection anytime the model returns a `Collection` instance. If you would like to use a custom collection for every model in your plugin or application, you should override the `newCollection` method on a model base class that is extended by all of your models.
- use Winter\Storm\Database\Collection as CollectionBase;
+```php
+use Winter\Storm\Database\Collection as CollectionBase;
- class CustomCollection extends CollectionBase
- {
- }
+class CustomCollection extends CollectionBase
+{
+}
+```
## Data feed
@@ -172,48 +206,54 @@ The `DataFeed` class mimics a regular model and supports `limit` and `paginate`
The next example will combine the User, Post and Comment models in to a single collection and returns the first 10 records.
- $feed = new Winter\Storm\Database\DataFeed;
- $feed->add('user', new User);
- $feed->add('post', Post::where('category_id', 7));
+```php
+$feed = new Winter\Storm\Database\DataFeed;
+$feed->add('user', new User);
+$feed->add('post', Post::where('category_id', 7));
- $feed->add('comment', function() {
- $comment = new Comment;
- return $comment->where('approved', true);
- });
+$feed->add('comment', function() {
+ $comment = new Comment;
+ return $comment->where('approved', true);
+});
- $results = $feed->limit(10)->get();
+$results = $feed->limit(10)->get();
+```
### Processing results
The `get` method will return a `Collection` object that contains the results. Records can be differentiated by using the `tag_name` attribute which was set as the first parameter when the model was added.
- foreach ($results as $result) {
+```php
+foreach ($results as $result) {
- if ($result->tag_name == 'post')
- echo "New Blog Post: " . $record->title;
+ if ($result->tag_name == 'post')
+ echo "New Blog Post: " . $record->title;
- elseif ($result->tag_name == 'comment')
- echo "New Comment: " . $record->content;
+ elseif ($result->tag_name == 'comment')
+ echo "New Comment: " . $record->content;
- elseif ($result->tag_name == 'user')
- echo "New User: " . $record->name;
+ elseif ($result->tag_name == 'user')
+ echo "New User: " . $record->name;
- }
+}
+```
### Ordering results
Results can be ordered by a single database column, either shared default used by all datasets or individually specified with the `add` method. The direction of results must also be shared.
- // Ordered by updated_at if it exists, otherwise created_at
- $feed->add('user', new User, 'ifnull(updated_at, created_at)');
+```php
+// Ordered by updated_at if it exists, otherwise created_at
+$feed->add('user', new User, 'ifnull(updated_at, created_at)');
- // Ordered by id
- $feed->add('comments', new Comment, 'id');
+// Ordered by id
+$feed->add('comments', new Comment, 'id');
- // Ordered by name (specified default below)
- $feed->add('posts', new Post);
+// Ordered by name (specified default below)
+$feed->add('posts', new Post);
- // Specifies the default column and the direction
- $feed->orderBy('name', 'asc')->get();
+// Specifies the default column and the direction
+$feed->orderBy('name', 'asc')->get();
+```
diff --git a/database-model.md b/database-model.md
index a16c8778..e600ef32 100644
--- a/database-model.md
+++ b/database-model.md
@@ -42,19 +42,21 @@ The model configuration directory could contain the model's [list column](../bac
In most cases, you should create one model class for each database table. All model classes must extend the `Model` class. The most basic representation of a model used inside a Plugin looks like this:
- namespace Acme\Blog\Models;
+```php
+namespace Acme\Blog\Models;
- use Model;
+use Model;
- class Post extends Model
- {
- /**
- * The table associated with the model.
- *
- * @var string
- */
- protected $table = 'acme_blog_posts';
- }
+class Post extends Model
+{
+ /**
+ * The table associated with the model.
+ *
+ * @var string
+ */
+ protected $table = 'acme_blog_posts';
+}
+```
The `$table` protected field specifies the database table corresponding the model. The table name is a snake case name of the author, plugin and pluralized record type name.
@@ -63,20 +65,22 @@ The `$table` protected field specifies the database table corresponding the mode
There are some standard properties that can be found on models, in addition to those provided by [model traits](traits). For example:
- class User extends Model
- {
- protected $primaryKey = 'id';
+```php
+class User extends Model
+{
+ protected $primaryKey = 'id';
- public $exists = false;
+ public $exists = false;
- protected $dates = ['last_seen_at'];
+ protected $dates = ['last_seen_at'];
- public $timestamps = true;
+ public $timestamps = true;
- protected $jsonable = ['permissions'];
+ protected $jsonable = ['permissions'];
- protected $guarded = ['*'];
- }
+ protected $guarded = ['*'];
+}
+```
Property | Description
------------- | -------------
@@ -97,70 +101,80 @@ Property | Description
Models will assume that each table has a primary key column named `id`. You may define a `$primaryKey` property to override this convention.
- class Post extends Model
- {
- /**
- * The primary key for the model.
- *
- * @var string
- */
- protected $primaryKey = 'id';
- }
+```php
+class Post extends Model
+{
+ /**
+ * The primary key for the model.
+ *
+ * @var string
+ */
+ protected $primaryKey = 'id';
+}
+```
#### Incrementing
Models will assume that the primary key is an incrementing integer value, which means that by default the primary key will be cast to an integer automatically. If you wish to use a non-incrementing or a non-numeric primary key you must set the public `$incrementing` property to false.
- class Message extends Model
- {
- /**
- * The primary key for the model is not an integer.
- *
- * @var bool
- */
- public $incrementing = false;
- }
+```php
+class Message extends Model
+{
+ /**
+ * The primary key for the model is not an integer.
+ *
+ * @var bool
+ */
+ public $incrementing = false;
+}
+```
#### Timestamps
By default, a model will expect `created_at` and `updated_at` columns to exist on your tables. If you do not wish to have these columns managed automatically, set the `$timestamps` property on your model to `false`:
- class Post extends Model
- {
- /**
- * Indicates if the model should be timestamped.
- *
- * @var bool
- */
- public $timestamps = false;
- }
+```php
+class Post extends Model
+{
+ /**
+ * Indicates if the model should be timestamped.
+ *
+ * @var bool
+ */
+ public $timestamps = false;
+}
+```
If you need to customize the format of your timestamps, set the `$dateFormat` property on your model. This property determines how date attributes are stored in the database, as well as their format when the model is serialized to an array or JSON:
- class Post extends Model
- {
- /**
- * The storage format of the model's date columns.
- *
- * @var string
- */
- protected $dateFormat = 'U';
- }
+```php
+class Post extends Model
+{
+ /**
+ * The storage format of the model's date columns.
+ *
+ * @var string
+ */
+ protected $dateFormat = 'U';
+}
+```
#### Values stored as JSON
When attributes names are passed to the `$jsonable` property, the values will be serialized and deserialized from the database as JSON:
- class Post extends Model
- {
- /**
- * @var array Attribute names to encode and decode using JSON.
- */
- protected $jsonable = ['data'];
- }
+```php
+class Post extends Model
+{
+ /**
+ * @var array Attribute names to encode and decode using JSON.
+ */
+ protected $jsonable = ['data'];
+}
+```
## Retrieving models
@@ -174,26 +188,32 @@ When requesting data from the database the model will retrieve values primarily
Once you have created a model and [its associated database table](../database/structure#migration-structure), you are ready to start retrieving data from your database. Think of each model as a powerful [query builder](../database/query) allowing you to query the database table associated with the model. For example:
- $flights = Flight::all();
+```php
+$flights = Flight::all();
+```
#### Accessing column values
If you have a model instance, you may access the column values of the model by accessing the corresponding property. For example, let's loop through each `Flight` instance returned by our query and echo the value of the `name` column:
- foreach ($flights as $flight) {
- echo $flight->name;
- }
+```php
+foreach ($flights as $flight) {
+ echo $flight->name;
+}
+```
#### Adding additional constraints
The `all` method will return all of the results in the model's table. Since each model serves as a [query builder](../database/query), you may also add constraints to queries, and then use the `get` method to retrieve the results:
- $flights = Flight::where('active', 1)
- ->orderBy('name', 'desc')
- ->take(10)
- ->get();
+```php
+$flights = Flight::where('active', 1)
+ ->orderBy('name', 'desc')
+ ->take(10)
+ ->get();
+```
> **NOTE:** Since models are query builders, you should familiarize yourself with all of the methods available on the [query builder](../database/query). You may use any of these methods in your model queries.
@@ -202,20 +222,24 @@ The `all` method will return all of the results in the model's table. Since each
For methods like `all` and `get` which retrieve multiple results, an instance of a `Collection` will be returned. This class provides [a variety of helpful methods](../database/collection) for working with your results. Of course, you can simply loop over this collection like an array:
- foreach ($flights as $flight) {
- echo $flight->name;
- }
+```php
+foreach ($flights as $flight) {
+ echo $flight->name;
+}
+```
#### Chunking results
If you need to process thousands of records, use the `chunk` command. The `chunk` method will retrieve a "chunk" of models, feeding them to a given `Closure` for processing. Using the `chunk` method will conserve memory when working with large result sets:
- Flight::chunk(200, function ($flights) {
- foreach ($flights as $flight) {
- //
- }
- });
+```php
+Flight::chunk(200, function ($flights) {
+ foreach ($flights as $flight) {
+ //
+ }
+});
+```
The first argument passed to the method is the number of records you wish to receive per "chunk". The Closure passed as the second argument will be called for each chunk that is retrieved from the database.
@@ -224,35 +248,43 @@ The first argument passed to the method is the number of records you wish to rec
In addition to retrieving all of the records for a given table, you may also retrieve single records using `find` and `first`. Instead of returning a collection of models, these methods return a single model instance:
- // Retrieve a model by its primary key
- $flight = Flight::find(1);
+```php
+// Retrieve a model by its primary key
+$flight = Flight::find(1);
- // Retrieve the first model matching the query constraints
- $flight = Flight::where('active', 1)->first();
+// Retrieve the first model matching the query constraints
+$flight = Flight::where('active', 1)->first();
+```
#### Not found exceptions
Sometimes you may wish to throw an exception if a model is not found. This is particularly useful in routes or controllers. The `findOrFail` and `firstOrFail` methods will retrieve the first result of the query. However, if no result is found, a `Illuminate\Database\Eloquent\ModelNotFoundException` will be thrown:
- $model = Flight::findOrFail(1);
+```php
+$model = Flight::findOrFail(1);
- $model = Flight::where('legs', '>', 100)->firstOrFail();
+$model = Flight::where('legs', '>', 100)->firstOrFail();
+```
When [developing an API](../services/router), if the exception is not caught, a `404` HTTP response is automatically sent back to the user, so it is not necessary to write explicit checks to return `404` responses when using these methods:
- Route::get('/api/flights/{id}', function ($id) {
- return Flight::findOrFail($id);
- });
+```php
+Route::get('/api/flights/{id}', function ($id) {
+ return Flight::findOrFail($id);
+});
+```
### Retrieving aggregates
You may also use `count`, `sum`, `max`, and other [aggregate functions](../database/query#aggregates) provided by the query builder. These methods return the appropriate scalar value instead of a full model instance:
- $count = Flight::where('active', 1)->count();
+```php
+$count = Flight::where('active', 1)->count();
- $max = Flight::where('active', 1)->max('price');
+$max = Flight::where('active', 1)->max('price');
+```
## Inserting & updating models
@@ -264,9 +296,11 @@ Inserting and updating data are the cornerstone feature of models, it makes the
To create a new record in the database, simply create a new model instance, set attributes on the model, then call the `save` method:
- $flight = new Flight;
- $flight->name = 'Sydney to Canberra';
- $flight->save();
+```php
+$flight = new Flight;
+$flight->name = 'Sydney to Canberra';
+$flight->save();
+```
In this example, we simply create a new instance of the `Flight` model and assign the `name` attribute. When we call the `save` method, a record will be inserted into the database. The `created_at` and `updated_at` timestamps will automatically be set too, so there is no need to set them manually.
@@ -275,15 +309,19 @@ In this example, we simply create a new instance of the `Flight` model and assig
The `save` method may also be used to update models that already exist in the database. To update a model, you should retrieve it, set any attributes you wish to update, and then call the `save` method. Again, the `updated_at` timestamp will automatically be updated, so there is no need to manually set its value:
- $flight = Flight::find(1);
- $flight->name = 'Darwin to Adelaide';
- $flight->save();
+```php
+$flight = Flight::find(1);
+$flight->name = 'Darwin to Adelaide';
+$flight->save();
+```
Updates can also be performed against any number of models that match a given query. In this example, all flights that are `active` and have a `destination` of `San Diego` will be marked as delayed:
- Flight::where('is_active', true)
- ->where('destination', 'Perth')
- ->update(['delayed' => true]);
+```php
+Flight::where('is_active', true)
+ ->where('destination', 'Perth')
+ ->update(['delayed' => true]);
+```
The `update` method expects an array of column and value pairs representing the columns that should be updated.
@@ -291,10 +329,12 @@ The `update` method expects an array of column and value pairs representing the
If you would like to perform multiple "upserts" in a single query, then you should use the `upsert` method instead. The method's first argument consists of the values to insert or update, while the second argument lists the column(s) that uniquely identify records within the associated table. The method's third and final argument is an array of the columns that should be updated if a matching record already exists in the database. The `upsert` method will automatically set the `created_at` and `updated_at` timestamps if timestamps are enabled on the model:
- MyVendor\MyPlugin\Models\Flight::upsert([
- ['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99],
- ['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150]
- ], ['departure', 'destination'], ['price']);
+```php
+MyVendor\MyPlugin\Models\Flight::upsert([
+ ['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99],
+ ['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150]
+], ['departure', 'destination'], ['price']);
+```
> **NOTE::** All databases except SQL Server require the columns in the second argument of the `upsert` method to have a "primary" or "unique" index.
@@ -307,31 +347,37 @@ A mass-assignment vulnerability occurs when a user passes an unexpected HTTP par
To get started, you should define which model attributes you want to make mass assignable. You may do this using the `$fillable` property on the model. For example, let's make the `name` attribute of our `Flight` model mass assignable:
- class Flight extends Model
- {
- /**
- * The attributes that are mass assignable.
- *
- * @var array
- */
- protected $fillable = ['name'];
- }
+```php
+class Flight extends Model
+{
+ /**
+ * The attributes that are mass assignable.
+ *
+ * @var array
+ */
+ protected $fillable = ['name'];
+}
+```
Once we have made the attributes mass assignable, we can use the `create` method to insert a new record in the database. The `create` method returns the saved model instance:
- $flight = Flight::create(['name' => 'Flight 10']);
+```php
+$flight = Flight::create(['name' => 'Flight 10']);
+```
While `$fillable` serves as a "white list" of attributes that should be mass assignable, you may also choose to use `$guarded`. The `$guarded` property should contain an array of attributes that you do not want to be mass assignable. All other attributes not in the array will be mass assignable. So, `$guarded` functions like a "black list". Of course, you should use either `$fillable` or `$guarded` - not both:
- class Flight extends Model
- {
- /**
- * The attributes that aren't mass assignable.
- *
- * @var array
- */
- protected $guarded = ['price'];
- }
+```php
+class Flight extends Model
+{
+ /**
+ * The attributes that aren't mass assignable.
+ *
+ * @var array
+ */
+ protected $guarded = ['price'];
+}
+```
In the example above, all attributes **except for `price`** will be mass assignable.
@@ -339,46 +385,56 @@ In the example above, all attributes **except for `price`** will be mass assigna
Sometimes you may wish to only instantiate a new instance of a model. You can do this using the `make` method. The `make` method will simply return a new instance without saving or creating anything.
- $flight = Flight::make(['name' => 'Flight 10']);
+```php
+$flight = Flight::make(['name' => 'Flight 10']);
- // Functionally the same as...
- $flight = new Flight;
- $flight->fill(['name' => 'Flight 10']);
+// Functionally the same as...
+$flight = new Flight;
+$flight->fill(['name' => 'Flight 10']);
+```
There are two other methods you may use to create models by mass assigning attributes: `firstOrCreate` and `firstOrNew`. The `firstOrCreate` method will attempt to locate a database record using the given column / value pairs. If the model can not be found in the database, a record will be inserted with the given attributes.
The `firstOrNew` method, like `firstOrCreate` will attempt to locate a record in the database matching the given attributes. However, if a model is not found, a new model instance will be returned. Note that the model returned by `firstOrNew` has not yet been persisted to the database. You will need to call `save` manually to persist it:
- // Retrieve the flight by the attributes, otherwise create it
- $flight = Flight::firstOrCreate(['name' => 'Flight 10']);
+```php
+// Retrieve the flight by the attributes, otherwise create it
+$flight = Flight::firstOrCreate(['name' => 'Flight 10']);
- // Retrieve the flight by the attributes, or instantiate a new instance
- $flight = Flight::firstOrNew(['name' => 'Flight 10']);
+// Retrieve the flight by the attributes, or instantiate a new instance
+$flight = Flight::firstOrNew(['name' => 'Flight 10']);
+```
## Deleting models
To delete a model, call the `delete` method on a model instance:
- $flight = Flight::find(1);
+```php
+$flight = Flight::find(1);
- $flight->delete();
+$flight->delete();
+```
#### Deleting an existing model by key
In the example above, we are retrieving the model from the database before calling the `delete` method. However, if you know the primary key of the model, you may delete the model without retrieving it. To do so, call the `destroy` method:
- Flight::destroy(1);
+```php
+Flight::destroy(1);
- Flight::destroy([1, 2, 3]);
+Flight::destroy([1, 2, 3]);
- Flight::destroy(1, 2, 3);
+Flight::destroy(1, 2, 3);
+```
#### Deleting models by query
You may also run a delete query on a set of models. In this example, we will delete all flights that are marked as inactive:
- $deletedRows = Flight::where('active', 0)->delete();
+```php
+$deletedRows = Flight::where('active', 0)->delete();
+```
> **NOTE**: It is important to mention that [model events](#model-events) will not fire when deleting records directly from a query.
@@ -390,50 +446,57 @@ You may also run a delete query on a set of models. In this example, we will del
Scopes allow you to define common sets of constraints that you may easily re-use throughout your application. For example, you may need to frequently retrieve all users that are considered "popular". To define a scope, simply prefix a model method with `scope`:
- class User extends Model
+```php
+class User extends Model
+{
+ /**
+ * Scope a query to only include popular users.
+ */
+ public function scopePopular($query)
+ {
+ return $query->where('votes', '>', 100);
+ }
+
+ /**
+ * Scope a query to only include active users.
+ */
+ public function scopeActive($query)
{
- /**
- * Scope a query to only include popular users.
- */
- public function scopePopular($query)
- {
- return $query->where('votes', '>', 100);
- }
-
- /**
- * Scope a query to only include active users.
- */
- public function scopeActive($query)
- {
- return $query->where('is_active', 1);
- }
+ return $query->where('is_active', 1);
}
+}
+```
#### Utilizing a query scope
Once the scope has been defined, you may call the scope methods when querying the model. However, you do not need to include the `scope` prefix when calling the method. You can even chain calls to various scopes, for example:
- $users = User::popular()->active()->orderBy('created_at')->get();
+```php
+$users = User::popular()->active()->orderBy('created_at')->get();
+```
#### Dynamic scopes
Sometimes you may wish to define a scope that accepts parameters. To get started, just add your additional parameters to your scope. Scope parameters should be defined after the `$query` argument:
- class User extends Model
+```php
+class User extends Model
+{
+ /**
+ * Scope a query to only include users of a given type.
+ */
+ public function scopeApplyType($query, $type)
{
- /**
- * Scope a query to only include users of a given type.
- */
- public function scopeApplyType($query, $type)
- {
- return $query->where('type', $type);
- }
+ return $query->where('type', $type);
}
+}
+```
Now you may pass the parameters when calling the scope:
- $users = User::applyType('admin')->get();
-
+```php
+$users = User::applyType('admin')->get();
+```
#### Global scopes
@@ -446,6 +509,7 @@ Global scopes allow you to add constraints to all queries for a given model. Win
Writing a global scope is simple. First, define a class that implements the `Illuminate\Database\Eloquent\Scope` interface. Winter does not have a conventional location that you should place scope classes, so you are free to place this class in any directory that you wish.
The `Scope` interface requires you to implement one method: `apply`. The `apply` method may add `where` constraints or other types of clauses to the query as needed:
+
```php
slug = Str::slug($this->name);
- }
+```php
+/**
+ * Generate a URL slug for this model
+ */
+public function beforeCreate()
+{
+ $this->slug = Str::slug($this->name);
+}
+```
> **NOTE:** Relationships created with [deferred-binding](relations#deferred-binding) (i.e: file attachments) will not be available in the `afterSave` model event if they have not been committed yet. To access uncommitted bindings, use the `withDeferred($sessionKey)` method on the relation. Example: `$this->images()->withDeferred(post('_session_key'))->get();`
@@ -600,38 +666,46 @@ Whenever a new model is saved for the first time, the `beforeCreate` and `afterC
For example, let's define an event listener that populates the slug attribute when a model is first created:
- /**
- * Generate a URL slug for this model
- */
- public function beforeCreate()
- {
- $this->slug = Str::slug($this->name);
- }
+```php
+/**
+ * Generate a URL slug for this model
+ */
+public function beforeCreate()
+{
+ $this->slug = Str::slug($this->name);
+}
+```
Returning `false` from an event will cancel the `save` / `update` operation:
- public function beforeCreate()
- {
- if (!$user->isValid()) {
- return false;
- }
+```php
+public function beforeCreate()
+{
+ if (!$user->isValid()) {
+ return false;
}
+}
+```
It's possible to access old values using the `original` attribute. For example:
- public function afterUpdate()
- {
- if ($this->title != $this->original['title']) {
- // title changed
- }
+```php
+public function afterUpdate()
+{
+ if ($this->title != $this->original['title']) {
+ // title changed
}
+}
+```
You can externally bind to [local events](../services/events) for a single instance of a model using the `bindEvent` method. The event name should be the same as the method override name, prefixed with `model.`.
- $flight = new Flight;
- $flight->bindEvent('model.beforeCreate', function() use ($model) {
- $model->slug = Str::slug($model->name);
- })
+```php
+$flight = new Flight;
+$flight->bindEvent('model.beforeCreate', function() use ($model) {
+ $model->slug = Str::slug($model->name);
+})
+```
## Extending models
@@ -640,33 +714,39 @@ Since models are [equipped to use behaviors](../services/behaviors), they can be
Inside the closure you can add relations to the model. Here we extend the `Backend\Models\User` model to include a profile (has one) relationship referencing the `Acme\Demo\Models\Profile` model.
- \Backend\Models\User::extend(function($model) {
- $model->hasOne['profile'] = ['Acme\Demo\Models\Profile', 'key' => 'user_id'];
- });
+```php
+\Backend\Models\User::extend(function($model) {
+ $model->hasOne['profile'] = ['Acme\Demo\Models\Profile', 'key' => 'user_id'];
+});
+```
This approach can also be used to bind to [local events](#events), the following code listens for the `model.beforeSave` event.
- \Backend\Models\User::extend(function($model) {
- $model->bindEvent('model.beforeSave', function() use ($model) {
- // ...
- });
+```php
+\Backend\Models\User::extend(function($model) {
+ $model->bindEvent('model.beforeSave', function() use ($model) {
+ // ...
});
+});
+```
> **NOTE:** Typically the best place to place code is within your plugin registration class `boot` method as this will be run on every request ensuring that the extensions you make to the model are available everywhere.
Additionally, a few methods exist to extend protected model properties.
- \Backend\Models\User::extend(function($model) {
- // add cast attributes
- $model->addCasts([
- 'some_extended_field' => 'int',
- ]);
-
- // add a date attribute
- $model->addDateAttribute('updated_at');
-
- // add fillable or jsonable fields
- // these methods accept one or more strings, or an array of strings
- $model->addFillable('first_name');
- $model->addJsonable('some_data');
- });
+```php
+\Backend\Models\User::extend(function($model) {
+ // add cast attributes
+ $model->addCasts([
+ 'some_extended_field' => 'int',
+ ]);
+
+ // add a date attribute
+ $model->addDateAttribute('updated_at');
+
+ // add fillable or jsonable fields
+ // these methods accept one or more strings, or an array of strings
+ $model->addFillable('first_name');
+ $model->addJsonable('some_data');
+});
+```
diff --git a/database-mutators.md b/database-mutators.md
index 0bba8146..d7aa52fd 100644
--- a/database-mutators.md
+++ b/database-mutators.md
@@ -19,57 +19,65 @@ In addition to custom accessors and mutators, you can also automatically cast da
To define an accessor, create a `getFooAttribute` method on your model where `Foo` is the "camel" cased name of the column you wish to access. In this example, we'll define an accessor for the `first_name` attribute. The accessor will automatically be called when attempting to retrieve the value of `first_name`:
- first_name;
+$firstName = $user->first_name;
+```
#### Defining a mutator
To define a mutator, define a `setFooAttribute` method on your model where `Foo` is the "camel" cased name of the column you wish to access. In this example, let's define a mutator for the `first_name` attribute. This mutator will be automatically called when we attempt to set the value of the `first_name` attribute on the model:
- attributes['first_name'] = strtolower($value);
- }
+ $this->attributes['first_name'] = strtolower($value);
}
+}
+```
The mutator will receive the value that is being set on the attribute, allowing you to manipulate the value and set the manipulated value on the model's internal `$attributes` property. For example, if we attempt to set the `first_name` attribute to `Sally`:
- $user = User::find(1);
+```php
+$user = User::find(1);
- $user->first_name = 'Sally';
+$user->first_name = 'Sally';
+```
Here the `setFirstNameAttribute` function will be called with the value `Sally`. The mutator will then apply the `strtolower` function to the name and set its value in the internal `$attributes` array.
@@ -80,41 +88,49 @@ By default, Models in Winter will convert the `created_at` and `updated_at` colu
You may customize which fields are automatically mutated, and even completely disable this mutation, by overriding the `$dates` property of your model:
- class User extends Model
- {
- /**
- * The attributes that should be mutated to dates.
- *
- * @var array
- */
- protected $dates = ['created_at', 'updated_at', 'disabled_at'];
- }
+```php
+class User extends Model
+{
+ /**
+ * The attributes that should be mutated to dates.
+ *
+ * @var array
+ */
+ protected $dates = ['created_at', 'updated_at', 'disabled_at'];
+}
+```
When a column is considered a date, you may set its value to a UNIX timestamp, date string (`Y-m-d`), date-time string, and of course a `DateTime` / `Carbon` instance, and the date's value will automatically be correctly stored in your database:
- $user = User::find(1);
+```php
+$user = User::find(1);
- $user->disabled_at = Carbon::now();
+$user->disabled_at = Carbon::now();
- $user->save();
+$user->save();
+```
As noted above, when retrieving attributes that are listed in your `$dates` property, they will automatically be cast to [Carbon](https://github.com/briannesbitt/Carbon) instances, allowing you to use any of Carbon's methods on your attributes:
- $user = User::find(1);
+```php
+$user = User::find(1);
- return $user->disabled_at->getTimestamp();
+return $user->disabled_at->getTimestamp();
+```
By default, timestamps are formatted as `'Y-m-d H:i:s'`. If you need to customize the timestamp format, set the `$dateFormat` property on your model. This property determines how date attributes are stored in the database, as well as their format when the model is serialized to an array or JSON:
- class Flight extends Model
- {
- /**
- * The storage format of the model's date columns.
- *
- * @var string
- */
- protected $dateFormat = 'U';
- }
+```php
+class Flight extends Model
+{
+ /**
+ * The storage format of the model's date columns.
+ *
+ * @var string
+ */
+ protected $dateFormat = 'U';
+}
+```
## Attribute casting
@@ -123,50 +139,58 @@ The `$casts` property on your model provides a convenient method of converting a
For example, let's cast the `is_admin` attribute, which is stored in our database as an integer (`0` or `1`) to a boolean value:
- class User extends Model
- {
- /**
- * The attributes that should be casted to native types.
- *
- * @var array
- */
- protected $casts = [
- 'is_admin' => 'boolean',
- ];
- }
+```php
+class User extends Model
+{
+ /**
+ * The attributes that should be casted to native types.
+ *
+ * @var array
+ */
+ protected $casts = [
+ 'is_admin' => 'boolean',
+ ];
+}
+```
Now the `is_admin` attribute will always be cast to a boolean when you access it, even if the underlying value is stored in the database as an integer:
- $user = User::find(1);
+```php
+$user = User::find(1);
- if ($user->is_admin) {
- //
- }
+if ($user->is_admin) {
+ //
+}
+```
#### Array casting
The `array` cast type is particularly useful when working with columns that are stored as serialized JSON. For example, if your database has a `TEXT` field type that contains serialized JSON, adding the `array` cast to that attribute will automatically deserialize the attribute to a PHP array when you access it on your Eloquent model:
- class User extends Model
- {
- /**
- * The attributes that should be casted to native types.
- *
- * @var array
- */
- protected $casts = [
- 'options' => 'array',
- ];
- }
+```php
+class User extends Model
+{
+ /**
+ * The attributes that should be casted to native types.
+ *
+ * @var array
+ */
+ protected $casts = [
+ 'options' => 'array',
+ ];
+}
+```
Once the cast is defined, you may access the `options` attribute and it will automatically be deserialized from JSON into a PHP array. When you set the value of the `options` attribute, the given array will automatically be serialized back into JSON for storage:
- $user = User::find(1);
+```php
+$user = User::find(1);
- $options = $user->options;
+$options = $user->options;
- $options['key'] = 'value';
+$options['key'] = 'value';
- $user->options = $options;
+$user->options = $options;
- $user->save();
+$user->save();
+```
diff --git a/database-query.md b/database-query.md
index 2421368e..a832551e 100644
--- a/database-query.md
+++ b/database-query.md
@@ -34,74 +34,91 @@ The database query builder provides a convenient, fluent interface to creating a
To begin a fluent query, use the `table` method on the `Db` facade. The `table` method returns a fluent query builder instance for the given table, allowing you to chain more constraints onto the query and then finally get the results. In this example, let's just `get` all records from a table:
- $users = Db::table('users')->get();
+```php
+$users = Db::table('users')->get();
+```
Like [raw queries](../database/basics#running-queries), the `get` method returns an `array` of results where each result is an instance of the PHP `stdClass` object. You may access each column's value by accessing the column as a property of the object:
- foreach ($users as $user) {
- echo $user->name;
- }
+```php
+foreach ($users as $user) {
+ echo $user->name;
+}
+```
#### Retrieving a single row / column from a table
If you just need to retrieve a single row from the database table, you may use the `first` method. This method will return a single `stdClass` object:
- $user = Db::table('users')->where('name', 'John')->first();
+```php
+$user = Db::table('users')->where('name', 'John')->first();
- echo $user->name;
+echo $user->name;
+```
If you don't even need an entire row, you may extract a single value from a record using the `value` method. This method will return the value of the column directly:
- $email = Db::table('users')->where('name', 'John')->value('email');
-
+```php
+$email = Db::table('users')->where('name', 'John')->value('email');
+```
#### Retrieving a list of column values
If you would like to retrieve an array containing the values of a single column, you may use the `lists` method. In this example, we'll retrieve an array of role titles:
- $titles = Db::table('roles')->lists('title');
+```php
+$titles = Db::table('roles')->lists('title');
- foreach ($titles as $title) {
- echo $title;
- }
+foreach ($titles as $title) {
+ echo $title;
+}
+```
You may also specify a custom key column for the returned array:
- $roles = Db::table('roles')->lists('title', 'name');
+```php
+$roles = Db::table('roles')->lists('title', 'name');
- foreach ($roles as $name => $title) {
- echo $title;
- }
+foreach ($roles as $name => $title) {
+ echo $title;
+}
+```
### Chunking results
If you need to work with thousands of database records, consider using the `chunk` method. This method retrieves a small "chunk" of the results at a time, and feeds each chunk into a `Closure` for processing. This method is very useful for writing [console commands](../console/development) that process thousands of records. For example, let's work with the entire `users` table in chunks of 100 records at a time:
- Db::table('users')->chunk(100, function($users) {
- foreach ($users as $user) {
- //
- }
- });
+```php
+Db::table('users')->chunk(100, function($users) {
+ foreach ($users as $user) {
+ //
+ }
+});
+```
You may stop further chunks from being processed by returning `false` from the `Closure`:
- Db::table('users')->chunk(100, function($users) {
- // Process the records...
+```php
+Db::table('users')->chunk(100, function($users) {
+ // Process the records...
- return false;
- });
+ return false;
+});
+```
If you are updating database records while chunking results, your chunk results could change in unexpected ways. So, when updating records while chunking, it is always best to use the `chunkById` method instead. This method will automatically paginate the results based on the record's primary key:
- Db::table('users')->where('active', false)
- ->chunkById(100, function ($users) {
- foreach ($users as $user) {
- Db::table('users')
- ->where('id', $user->id)
- ->update(['active' => true]);
- }
- });
+```php
+Db::table('users')->where('active', false)
+ ->chunkById(100, function ($users) {
+ foreach ($users as $user) {
+ Db::table('users')
+ ->where('id', $user->id)
+ ->update(['active' => true]);
+ }
+ });
+```
> **NOTE:** When updating or deleting records inside the chunk callback, any changes to the primary key or foreign keys could affect the chunk query. This could potentially result in records not being included in the chunked results.
@@ -110,23 +127,29 @@ If you are updating database records while chunking results, your chunk results
The query builder also provides a variety of aggregate methods, such as `count`, `max`, `min`, `avg`, and `sum`. You may call any of these methods after constructing your query:
- $users = Db::table('users')->count();
+```php
+$users = Db::table('users')->count();
- $price = Db::table('orders')->max('price');
+$price = Db::table('orders')->max('price');
+```
Of course, you may combine these methods with other clauses to build your query:
- $price = Db::table('orders')
- ->where('is_finalized', 1)
- ->avg('price');
+```php
+$price = Db::table('orders')
+ ->where('is_finalized', 1)
+ ->avg('price');
+```
#### Determining if records exist
Instead of using the `count` method to determine if any records exist that match your query's constraints, you may use the `exists` and `doesntExist` methods:
- return Db::table('orders')->where('finalized', 1)->exists();
+```php
+return Db::table('orders')->where('finalized', 1)->exists();
- return Db::table('orders')->where('finalized', 1)->doesntExist();
+return Db::table('orders')->where('finalized', 1)->doesntExist();
+```
## Selects
@@ -135,33 +158,43 @@ Instead of using the `count` method to determine if any records exist that match
Of course, you may not always want to select all columns from a database table. Using the `select` method, you can specify a custom `select` clause for the query:
- $users = Db::table('users')->select('name', 'email as user_email')->get();
+```php
+$users = Db::table('users')->select('name', 'email as user_email')->get();
+```
The `distinct` method allows you to force the query to return distinct results:
- $users = Db::table('users')->distinct()->get();
+```php
+$users = Db::table('users')->distinct()->get();
+```
If you already have a query builder instance and you wish to add a column to its existing select clause, you may use the `addSelect` method:
- $query = Db::table('users')->select('name');
+```php
+$query = Db::table('users')->select('name');
- $users = $query->addSelect('age')->get();
+$users = $query->addSelect('age')->get();
+```
If you wish to concatenate columns and/or strings together, you may use the `selectConcat` method to specify a list of concatenated values and the resulting alias. If you wish to use strings in the concatenation, you must provide a quoted string:
- $query = Db::table('users')->selectConcat(['"Name: "', 'first_name', 'last_name'], 'name_string');
+```php
+$query = Db::table('users')->selectConcat(['"Name: "', 'first_name', 'last_name'], 'name_string');
- $nameString = $query->first()->name_string; // Name: John Smith
+$nameString = $query->first()->name_string; // Name: John Smith
+```
#### Raw expressions
Sometimes you may need to use a raw expression in a query. To create a raw expression, you may use the `Db::raw` method:
- $users = Db::table('users')
- ->select(Db::raw('count(*) as user_count, status'))
- ->where('status', '<>', 1)
- ->groupBy('status')
- ->get();
+```php
+$users = Db::table('users')
+ ->select(Db::raw('count(*) as user_count, status'))
+ ->where('status', '<>', 1)
+ ->groupBy('status')
+ ->get();
+```
> **NOTE:** Raw statements will be injected into the query as strings, so you should be extremely careful to not create SQL injection vulnerabilities.
@@ -173,44 +206,54 @@ Instead of using `Db::raw`, you may also use the following methods to insert a r
The `selectRaw` method can be used in place of `addSelect(Db::raw(...)).` This method accepts an optional array of bindings as its second argument:
- $orders = Db::table('orders')
- ->selectRaw('price * ? as price_with_tax', [1.0825])
- ->get();
+```php
+$orders = Db::table('orders')
+ ->selectRaw('price * ? as price_with_tax', [1.0825])
+ ->get();
+```
**whereRaw / orWhereRaw**
The `whereRaw` and `orWhereRaw` methods can be used to inject a raw `where` clause into your query. These methods accept an optional array of bindings as their second argument:
- $orders = Db::table('orders')
- ->whereRaw('price > IF(state = "TX", ?, 100)', [200])
- ->get();
+```php
+$orders = Db::table('orders')
+ ->whereRaw('price > IF(state = "TX", ?, 100)', [200])
+ ->get();
+```
**havingRaw / orHavingRaw**
The `havingRaw` and `orHavingRaw` methods may be used to set a raw string as the value of the `having` clause. These methods accept an optional array of bindings as their second argument:
- $orders = Db::table('orders')
- ->select('department', Db::raw('SUM(price) as total_sales'))
- ->groupBy('department')
- ->havingRaw('SUM(price) > ?', [2500])
- ->get();
+```php
+$orders = Db::table('orders')
+ ->select('department', Db::raw('SUM(price) as total_sales'))
+ ->groupBy('department')
+ ->havingRaw('SUM(price) > ?', [2500])
+ ->get();
+```
**orderByRaw**
The `orderByRaw` method may be used to set a raw string as the value of the order by clause:
- $orders = Db::table('orders')
- ->orderByRaw('updated_at - created_at DESC')
- ->get();
+```php
+$orders = Db::table('orders')
+ ->orderByRaw('updated_at - created_at DESC')
+ ->get();
+```
**groupByRaw**
The `groupByRaw` method may be used to set a raw string as the value of the group by clause:
- $orders = Db::table('orders')
- ->select('city', 'state')
- ->groupByRaw('city, state')
- ->get();
+```php
+$orders = Db::table('orders')
+ ->select('city', 'state')
+ ->groupByRaw('city, state')
+ ->get();
+```
## Joins
@@ -219,77 +262,91 @@ The `groupByRaw` method may be used to set a raw string as the value of the grou
The query builder may also be used to write join statements. To perform a basic SQL "inner join", you may use the `join` method on a query builder instance. The first argument passed to the `join` method is the name of the table you need to join to, while the remaining arguments specify the column constraints for the join. Of course, as you can see, you can join to multiple tables in a single query:
- $users = Db::table('users')
- ->join('contacts', 'users.id', '=', 'contacts.user_id')
- ->join('orders', 'users.id', '=', 'orders.user_id')
- ->select('users.*', 'contacts.phone', 'orders.price')
- ->get();
+```php
+$users = Db::table('users')
+ ->join('contacts', 'users.id', '=', 'contacts.user_id')
+ ->join('orders', 'users.id', '=', 'orders.user_id')
+ ->select('users.*', 'contacts.phone', 'orders.price')
+ ->get();
+```
#### Left join / right join statement
If you would like to perform a "left join" or "right join" instead of an "inner join", use the `leftJoin` or `rightJoin` method. The `leftJoin` and `rightJoin` methods have the same signature as the `join` method:
- $users = Db::table('users')
- ->leftJoin('posts', 'users.id', '=', 'posts.user_id')
- ->get();
+```php
+$users = Db::table('users')
+ ->leftJoin('posts', 'users.id', '=', 'posts.user_id')
+ ->get();
- $users = Db::table('users')
- ->rightJoin('posts', 'users.id', '=', 'posts.user_id')
- ->get();
+$users = Db::table('users')
+ ->rightJoin('posts', 'users.id', '=', 'posts.user_id')
+ ->get();
+```
#### Cross join statement
To perform a "cross join" use the `crossJoin` method with the name of the table you wish to cross join to. Cross joins generate a cartesian product between the first table and the joined table:
- $users = Db::table('sizes')
- ->crossJoin('colors')
- ->get();
+```php
+$users = Db::table('sizes')
+ ->crossJoin('colors')
+ ->get();
+```
#### Advanced join statements
You may also specify more advanced join clauses. To get started, pass a `Closure` as the second argument into the `join` method. The `Closure` will receive a `JoinClause` object which allows you to specify constraints on the `join` clause:
- Db::table('users')
- ->join('contacts', function ($join) {
- $join->on('users.id', '=', 'contacts.user_id')->orOn(...);
- })
- ->get();
+```php
+Db::table('users')
+ ->join('contacts', function ($join) {
+ $join->on('users.id', '=', 'contacts.user_id')->orOn(...);
+ })
+ ->get();
+```
If you would like to use a "where" style clause on your joins, you may use the `where` and `orWhere` methods on a join. Instead of comparing two columns, these methods will compare the column against a value:
- Db::table('users')
- ->join('contacts', function ($join) {
- $join->on('users.id', '=', 'contacts.user_id')
- ->where('contacts.user_id', '>', 5);
- })
- ->get();
+```php
+Db::table('users')
+ ->join('contacts', function ($join) {
+ $join->on('users.id', '=', 'contacts.user_id')
+ ->where('contacts.user_id', '>', 5);
+ })
+ ->get();
+```
#### Subquery joins
You may use the `joinSub`, `leftJoinSub`, and `rightJoinSub` methods to join a query to a subquery. Each of these methods receive three arguments: the subquery, its table alias, and a Closure that defines the related columns:
- $latestPosts = Db::table('posts')
- ->select('user_id', Db::raw('MAX(created_at) as last_post_created_at'))
- ->where('is_published', true)
- ->groupBy('user_id');
+```php
+$latestPosts = Db::table('posts')
+ ->select('user_id', Db::raw('MAX(created_at) as last_post_created_at'))
+ ->where('is_published', true)
+ ->groupBy('user_id');
- $users = Db::table('users')
- ->joinSub($latestPosts, 'latest_posts', function ($join) {
- $join->on('users.id', '=', 'latest_posts.user_id');
- })->get();
+$users = Db::table('users')
+ ->joinSub($latestPosts, 'latest_posts', function ($join) {
+ $join->on('users.id', '=', 'latest_posts.user_id');
+ })->get();
+```
## Unions
The query builder also provides a quick way to "union" two queries together. For example, you may create an initial query, and then use the `union` method to union it with a second query:
- $first = Db::table('users')
- ->whereNull('first_name');
+```php
+$first = Db::table('users')
+ ->whereNull('first_name');
- $users = Db::table('users')
- ->whereNull('last_name')
- ->union($first)
- ->get();
+$users = Db::table('users')
+ ->whereNull('last_name')
+ ->union($first)
+ ->get();
+```
The `unionAll` method is also available and has the same method signature as `union`.
@@ -302,34 +359,42 @@ To add `where` clauses to the query, use the `where` method on a query builder i
For example, here is a query that verifies the value of the "votes" column is equal to 100:
- $users = Db::table('users')->where('votes', '=', 100)->get();
+```php
+$users = Db::table('users')->where('votes', '=', 100)->get();
+```
For convenience, if you simply want to verify that a column is equal to a given value, you may pass the value directly as the second argument to the `where` method:
- $users = Db::table('users')->where('votes', 100)->get();
+```php
+$users = Db::table('users')->where('votes', 100)->get();
+```
Of course, you may use a variety of other operators when writing a `where` clause:
- $users = Db::table('users')
- ->where('votes', '>=', 100)
- ->get();
+```php
+$users = Db::table('users')
+ ->where('votes', '>=', 100)
+ ->get();
- $users = Db::table('users')
- ->where('votes', '<>', 100)
- ->get();
+$users = Db::table('users')
+ ->where('votes', '<>', 100)
+ ->get();
- $users = Db::table('users')
- ->where('name', 'like', 'T%')
- ->get();
+$users = Db::table('users')
+ ->where('name', 'like', 'T%')
+ ->get();
+```
#### "Or" statements
You may chain where constraints together, as well as add `or` clauses to the query. The `orWhere` method accepts the same arguments as the `where` method:
- $users = Db::table('users')
- ->where('votes', '>', 100)
- ->orWhere('name', 'John')
- ->get();
+```php
+$users = Db::table('users')
+ ->where('votes', '>', 100)
+ ->orWhere('name', 'John')
+ ->get();
+```
> **Tip:** You can also prefix `or` to any of the where statements methods below, to make the condition an "OR" condition - for example, `orWhereBetween`, `orWhereIn`, etc.
@@ -337,42 +402,54 @@ You may chain where constraints together, as well as add `or` clauses to the que
The `whereBetween` method verifies that a column's value is between two values:
- $users = Db::table('users')
- ->whereBetween('votes', [1, 100])->get();
+```php
+$users = Db::table('users')
+ ->whereBetween('votes', [1, 100])->get();
+```
The `whereNotBetween` method verifies that a column's value lies outside of two values:
- $users = Db::table('users')
- ->whereNotBetween('votes', [1, 100])
- ->get();
+```php
+$users = Db::table('users')
+ ->whereNotBetween('votes', [1, 100])
+ ->get();
+```
#### "Where in" statements
The `whereIn` method verifies that a given column's value is contained within the given array:
- $users = Db::table('users')
- ->whereIn('id', [1, 2, 3])
- ->get();
+```php
+$users = Db::table('users')
+ ->whereIn('id', [1, 2, 3])
+ ->get();
+```
The `whereNotIn` method verifies that the given column's value is **not** contained in the given array:
- $users = Db::table('users')
- ->whereNotIn('id', [1, 2, 3])
- ->get();
+```php
+$users = Db::table('users')
+ ->whereNotIn('id', [1, 2, 3])
+ ->get();
+```
#### "Where null" statements
The `whereNull` method verifies that the value of the given column is `NULL`:
- $users = Db::table('users')
- ->whereNull('updated_at')
- ->get();
+```php
+$users = Db::table('users')
+ ->whereNull('updated_at')
+ ->get();
+```
The `whereNotNull` method verifies that the column's value is **not** `NULL`:
- $users = Db::table('users')
- ->whereNotNull('updated_at')
- ->get();
+```php
+$users = Db::table('users')
+ ->whereNotNull('updated_at')
+ ->get();
+```
### Advanced where clauses
@@ -381,96 +458,116 @@ The `whereNotNull` method verifies that the column's value is **not** `NULL`:
Sometimes you may need to create more advanced where clauses such as "where exists" or nested parameter groupings. The Laravel query builder can handle these as well. To get started, let's look at an example of grouping constraints within parenthesis:
- Db::table('users')
- ->where('name', '=', 'John')
- ->orWhere(function ($query) {
- $query->where('votes', '>', 100)
- ->where('title', '<>', 'Admin');
- })
- ->get();
+```php
+Db::table('users')
+ ->where('name', '=', 'John')
+ ->orWhere(function ($query) {
+ $query->where('votes', '>', 100)
+ ->where('title', '<>', 'Admin');
+ })
+ ->get();
+```
As you can see, passing `Closure` into the `orWhere` method instructs the query builder to begin a constraint group. The `Closure` will receive a query builder instance which you can use to set the constraints that should be contained within the parenthesis group. The example above will produce the following SQL:
- select * from users where name = 'John' or (votes > 100 and title <> 'Admin')
+```sql
+select * from users where name = 'John' or (votes > 100 and title <> 'Admin')
+```
#### Exists statements
The `whereExists` method allows you to write `where exist` SQL clauses. The `whereExists` method accepts a `Closure` argument, which will receive a query builder instance allowing you to define the query that should be placed inside of the "exists" clause:
- Db::table('users')
- ->whereExists(function ($query) {
- $query->select(Db::raw(1))
- ->from('orders')
- ->whereRaw('orders.user_id = users.id');
- })
- ->get();
+```php
+Db::table('users')
+ ->whereExists(function ($query) {
+ $query->select(Db::raw(1))
+ ->from('orders')
+ ->whereRaw('orders.user_id = users.id');
+ })
+ ->get();
+```
The query above will produce the following SQL:
- select * from users where exists (
- select 1 from orders where orders.user_id = users.id
- )
+```php
+select * from users where exists (
+ select 1 from orders where orders.user_id = users.id
+)
+```
#### JSON "where" statements
Winter CMS also supports querying JSON column types on databases that provide support for JSON column types. To query a JSON column, use the `->` operator:
- $users = Db::table('users')
- ->where('options->language', 'en')
- ->get();
+```php
+$users = Db::table('users')
+ ->where('options->language', 'en')
+ ->get();
- $users = Db::table('users')
- ->where('preferences->dining->meal', 'salad')
- ->get();
+$users = Db::table('users')
+ ->where('preferences->dining->meal', 'salad')
+ ->get();
+```
You may use `whereJsonContains` to query JSON arrays (not supported on SQLite):
- $users = Db::table('users')
- ->whereJsonContains('options->languages', 'en')
- ->get();
+```php
+$users = Db::table('users')
+ ->whereJsonContains('options->languages', 'en')
+ ->get();
+```
MySQL and PostgreSQL support `whereJsonContains` with multiple values:
- $users = Db::table('users')
- ->whereJsonContains('options->languages', ['en', 'de'])
- ->get();
+```php
+$users = Db::table('users')
+ ->whereJsonContains('options->languages', ['en', 'de'])
+ ->get();
+```
You may use `whereJsonLength` to query JSON arrays by their length:
- $users = Db::table('users')
- ->whereJsonLength('options->languages', 0)
- ->get();
+```php
+$users = Db::table('users')
+ ->whereJsonLength('options->languages', 0)
+ ->get();
- $users = Db::table('users')
- ->whereJsonLength('options->languages', '>', 1)
- ->get();
+$users = Db::table('users')
+ ->whereJsonLength('options->languages', '>', 1)
+ ->get();
+```
### Conditional clauses
Sometimes you may want clauses to apply to a query only when something else is true. For instance you may only want to apply a `where` statement if a given input value is present on the incoming request. You may accomplish this using the `when` method:
- $role = $request->input('role');
+```php
+$role = $request->input('role');
- $users = Db::table('users')
- ->when($role, function ($query, $role) {
- return $query->where('role_id', $role);
- })
- ->get();
+$users = Db::table('users')
+ ->when($role, function ($query, $role) {
+ return $query->where('role_id', $role);
+ })
+ ->get();
+```
The `when` method only executes the given Closure when the first parameter is `true`. If the first parameter is `false`, the Closure will not be executed.
You may pass another Closure as the third parameter to the `when` method. This Closure will execute if the first parameter evaluates as false. To illustrate how this feature may be used, we will use it to configure the default sorting of a query:
- $sortBy = null;
+```php
+$sortBy = null;
- $users = Db::table('users')
- ->when($sortBy, function ($query, $sortBy) {
- return $query->orderBy($sortBy);
- }, function ($query) {
- return $query->orderBy('name');
- })
- ->get();
+$users = Db::table('users')
+ ->when($sortBy, function ($query, $sortBy) {
+ return $query->orderBy($sortBy);
+ }, function ($query) {
+ return $query->orderBy('name');
+ })
+ ->get();
+```
## Ordering, grouping, limit, & offset
@@ -479,41 +576,51 @@ You may pass another Closure as the third parameter to the `when` method. This C
The `orderBy` method allows you to sort the result of the query by a given column. The first argument to the `orderBy` method should be the column you wish to sort by, while the second argument controls the direction of the sort and may be either `asc` or `desc`:
- $users = Db::table('users')
- ->orderBy('name', 'desc')
- ->get();
+```php
+$users = Db::table('users')
+ ->orderBy('name', 'desc')
+ ->get();
+```
#### Latest / oldest
The `latest` and `oldest` methods allow you to easily order results by date. By default, result will be ordered by the `created_at` column. Or, you may pass the column name that you wish to sort by:
- $user = Db::table('users')
- ->latest()
- ->first();
+```php
+$user = Db::table('users')
+ ->latest()
+ ->first();
+```
#### Random order
The `inRandomOrder` method may be used to sort the query results randomly. For example, you may use this method to fetch a random user:
- $randomUser = Db::table('users')
- ->inRandomOrder()
- ->first();
+```php
+$randomUser = Db::table('users')
+ ->inRandomOrder()
+ ->first();
+```
#### Grouping
The `groupBy` and `having` methods may be used to group the query results. The `having` method's signature is similar to that of the `where` method:
- $users = Db::table('users')
- ->groupBy('account_id')
- ->having('account_id', '>', 100)
- ->get();
+```php
+$users = Db::table('users')
+ ->groupBy('account_id')
+ ->having('account_id', '>', 100)
+ ->get();
+```
You may pass multiple arguments to the `groupBy` method to group by multiple columns:
- $users = Db::table('users')
- ->groupBy('first_name', 'status')
- ->having('account_id', '>', 100)
- ->get();
+```php
+$users = Db::table('users')
+ ->groupBy('first_name', 'status')
+ ->having('account_id', '>', 100)
+ ->get();
+```
For more advanced `having` statements, you may wish to use the [`havingRaw`](#aggregates) method.
@@ -521,31 +628,39 @@ For more advanced `having` statements, you may wish to use the [`havingRaw`](#ag
To limit the number of results returned from the query, or to skip a given number of results in the query (`OFFSET`), you may use the `skip` and `take` methods:
- $users = Db::table('users')->skip(10)->take(5)->get();
+```php
+$users = Db::table('users')->skip(10)->take(5)->get();
+```
## Inserts
The query builder also provides an `insert` method for inserting records into the database table. The `insert` method accepts an array of column names and values to insert:
- Db::table('users')->insert(
- ['email' => 'john@example.com', 'votes' => 0]
- );
+```php
+Db::table('users')->insert(
+ ['email' => 'john@example.com', 'votes' => 0]
+);
+```
You may even insert several records into the table with a single call to `insert` by passing an array of arrays. Each array represents a row to be inserted into the table:
- Db::table('users')->insert([
- ['email' => 'taylor@example.com', 'votes' => 0],
- ['email' => 'dayle@example.com', 'votes' => 0]
- ]);
+```php
+Db::table('users')->insert([
+ ['email' => 'taylor@example.com', 'votes' => 0],
+ ['email' => 'dayle@example.com', 'votes' => 0]
+]);
+```
#### Auto-incrementing IDs
If the table has an auto-incrementing id, use the `insertGetId` method to insert a record and then retrieve the ID:
- $id = Db::table('users')->insertGetId(
- ['email' => 'john@example.com', 'votes' => 0]
- );
+```php
+$id = Db::table('users')->insertGetId(
+ ['email' => 'john@example.com', 'votes' => 0]
+);
+```
> **NOTE:** When using the PostgreSQL database driver, the insertGetId method expects the auto-incrementing column to be named `id`. If you would like to retrieve the ID from a different "sequence", you may pass the sequence name as the second parameter to the `insertGetId` method.
@@ -554,9 +669,11 @@ If the table has an auto-incrementing id, use the `insertGetId` method to insert
In addition to inserting records into the database, the query builder can also update existing records using the `update` method. The `update` method, like the `insert` method, accepts an array of column and value pairs containing the columns to be updated. You may constrain the `update` query using `where` clauses:
- Db::table('users')
- ->where('id', 1)
- ->update(['votes' => 1]);
+```php
+Db::table('users')
+ ->where('id', 1)
+ ->update(['votes' => 1]);
+```
#### Update or Insert (One query per row)
@@ -564,20 +681,24 @@ Sometimes you may want to update an existing record in the database or create it
The `updateOrInsert` method will first attempt to locate a matching database record using the first argument's column and value pairs. If the record exists, it will be updated with the values in the second argument. If the record can not be found, a new record will be inserted with the merged attributes of both arguments:
- Db::table('users')
- ->updateOrInsert(
- ['email' => 'john@example.com', 'name' => 'John'],
- ['votes' => '2']
- );
+```php
+Db::table('users')
+ ->updateOrInsert(
+ ['email' => 'john@example.com', 'name' => 'John'],
+ ['votes' => '2']
+ );
+```
#### Update or Insert / `upsert()` (Batch query to process multiple rows in one DB call)
The `upsert` method will insert rows that do not exist and update the rows that already exist with the new values. The method's first argument consists of the values to insert or update, while the second argument lists the column(s) that uniquely identify records within the associated table. The method's third and final argument is an array of columns that should be updated if a matching record already exists in the database:
- DB::table('flights')->upsert([
- ['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99],
- ['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150]
- ], ['departure', 'destination'], ['price']);
+```php
+DB::table('flights')->upsert([
+ ['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99],
+ ['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150]
+], ['departure', 'destination'], ['price']);
+```
> **NOTE:** All databases except SQL Server require the columns in the second argument of the `upsert` method to have a "primary" or "unique" index.
@@ -585,9 +706,11 @@ The `upsert` method will insert rows that do not exist and update the rows that
When updating a JSON column, you should use `->` syntax to access the appropriate key in the JSON object. This operation is supported on MySQL 5.7+ and PostgreSQL 9.5+:
- $affected = Db::table('users')
- ->where('id', 1)
- ->update(['options->enabled' => true]);
+```php
+$affected = Db::table('users')
+ ->where('id', 1)
+ ->update(['options->enabled' => true]);
+```
#### Increment / decrement
@@ -595,43 +718,57 @@ The query builder also provides convenient methods for incrementing or decrement
Both of these methods accept at least one argument: the column to modify. A second argument may optionally be passed to control the amount by which the column should be incremented / decremented.
- Db::table('users')->increment('votes');
+```php
+Db::table('users')->increment('votes');
- Db::table('users')->increment('votes', 5);
+Db::table('users')->increment('votes', 5);
- Db::table('users')->decrement('votes');
+Db::table('users')->decrement('votes');
- Db::table('users')->decrement('votes', 5);
+Db::table('users')->decrement('votes', 5);
+```
You may also specify additional columns to update during the operation:
- Db::table('users')->increment('votes', 1, ['name' => 'John']);
+```php
+Db::table('users')->increment('votes', 1, ['name' => 'John']);
+```
## Deletes
The query builder may also be used to delete records from the table via the `delete` method:
- Db::table('users')->delete();
+```php
+Db::table('users')->delete();
+```
You may constrain `delete` statements by adding `where` clauses before calling the `delete` method:
- Db::table('users')->where('votes', '<', 100)->delete();
+```php
+Db::table('users')->where('votes', '<', 100)->delete();
+```
If you wish to truncate the entire table, which will remove all rows and reset the auto-incrementing ID to zero, you may use the `truncate` method:
- Db::table('users')->truncate();
+```php
+Db::table('users')->truncate();
+```
## Pessimistic locking
The query builder also includes a few functions to help you do "pessimistic locking" on your `select` statements. To run the statement with a "shared lock", you may use the `sharedLock` method on a query. A shared lock prevents the selected rows from being modified until your transaction commits:
- Db::table('users')->where('votes', '>', 100)->sharedLock()->get();
+```php
+Db::table('users')->where('votes', '>', 100)->sharedLock()->get();
+```
Alternatively, you may use the `lockForUpdate` method. A "for update" lock prevents the rows from being modified or from being selected with another shared lock:
- Db::table('users')->where('votes', '>', 100)->lockForUpdate()->get();
+```php
+Db::table('users')->where('votes', '>', 100)->lockForUpdate()->get();
+```
## Caching queries
@@ -641,7 +778,9 @@ Alternatively, you may use the `lockForUpdate` method. A "for update" lock preve
You may easily cache the results of a query using the [Cache service](../services/cache). Simply chain the `remember` or `rememberForever` method when preparing the query.
- $users = Db::table('users')->remember(10)->get();
+```php
+$users = Db::table('users')->remember(10)->get();
+```
In this example, the results of the query will be cached for ten minutes. While the results are cached, the query will not be run against the database, and the results will be loaded from the default cache driver specified for your application.
@@ -650,19 +789,25 @@ In this example, the results of the query will be cached for ten minutes. While
Duplicate queries across the same request can be prevented by using in-memory caching. This feature is enabled by default for [queries prepared by a model](../database/model#retrieving-models) but not those generated directly using the `Db` facade.
- Db::table('users')->get(); // Result from database
- Db::table('users')->get(); // Result from database
+```php
+Db::table('users')->get(); // Result from database
+Db::table('users')->get(); // Result from database
- Model::all(); // Result from database
- Model::all(); // Result from in-memory cache
+Model::all(); // Result from database
+Model::all(); // Result from in-memory cache
+```
You may enable or disable the duplicate cache with either the `enableDuplicateCache` or `disableDuplicateCache` method.
- Db::table('users')->enableDuplicateCache()->get();
+```php
+Db::table('users')->enableDuplicateCache()->get();
+```
If a query is stored in the cache, it will automatically be cleared when an insert, update, delete, or truncate statement is used. However you may clear the cache manually using the `flushDuplicateCache` method.
- Db::flushDuplicateCache();
+```php
+Db::flushDuplicateCache();
+```
> **NOTE**: In-memory caching is disabled entirely when running via the command-line interface (CLI).
@@ -671,6 +816,8 @@ If a query is stored in the cache, it will automatically be cleared when an inse
You may use the `dd` or `dump` methods while building a query to dump the query bindings and SQL. The `dd` method will display the debug information and then stop executing the request. The `dump` method will display the debug information but allow the request to keep executing:
- Db::table('users')->where('votes', '>', 100)->dd();
+```php
+Db::table('users')->where('votes', '>', 100)->dd();
- Db::table('users')->where('votes', '>', 100)->dump();
+Db::table('users')->where('votes', '>', 100)->dump();
+```
diff --git a/database-relations.md b/database-relations.md
index 24aad8c5..57d398c9 100644
--- a/database-relations.md
+++ b/database-relations.md
@@ -40,20 +40,26 @@ Database tables are often related to one another. For example, a blog post may h
Model relationships are defined as properties on your model classes. An example of defining relationships:
- class User extends Model
- {
- public $hasMany = [
- 'posts' => 'Acme\Blog\Models\Post'
- ]
- }
+```php
+class User extends Model
+{
+ public $hasMany = [
+ 'posts' => 'Acme\Blog\Models\Post'
+ ]
+}
+```
Relationships like models themselves, also serve as powerful [query builders](query), accessing relationships as functions provides powerful method chaining and querying capabilities. For example:
- $user->posts()->where('is_active', true)->get();
+```php
+$user->posts()->where('is_active', true)->get();
+```
Accessing a relationship as a property is also possible:
- $user->posts;
+```php
+$user->posts;
+```
> **NOTE**: All relationship queries have [in-memory caching enabled](../database/query#in-memory-caching) by default. The `load($relation)` method won't force cache to flush. To reload the memory cache use the `reloadRelations()` or the `reload()` methods on the model object.
@@ -62,9 +68,11 @@ Accessing a relationship as a property is also possible:
Each definition can be an array where the key is the relation name and the value is a detail array. The detail array's first value is always the related model class name and all other values are parameters that must have a key name.
- public $hasMany = [
- 'posts' => ['Acme\Blog\Models\Post', 'delete' => true]
- ];
+```php
+public $hasMany = [
+ 'posts' => ['Acme\Blog\Models\Post', 'delete' => true]
+];
+```
The following are parameters that can be used with all relations:
@@ -80,40 +88,46 @@ Argument | Description
Example filter using **order** and **conditions**:
+```php
+public $belongsToMany = [
+ 'categories' => [
+ 'Acme\Blog\Models\Category',
+ 'order' => 'name desc',
+ 'conditions' => 'is_active = 1'
+ ]
+];
+```
+
+Example filter using **scope**:
+
+```php
+class Post extends Model
+{
public $belongsToMany = [
'categories' => [
'Acme\Blog\Models\Category',
- 'order' => 'name desc',
- 'conditions' => 'is_active = 1'
+ 'scope' => 'isActive'
]
];
+}
-Example filter using **scope**:
-
- class Post extends Model
+class Category extends Model
+{
+ public function scopeIsActive($query)
{
- public $belongsToMany = [
- 'categories' => [
- 'Acme\Blog\Models\Category',
- 'scope' => 'isActive'
- ]
- ];
- }
-
- class Category extends Model
- {
- public function scopeIsActive($query)
- {
- return $query->where('is_active', true)->orderBy('name', 'desc');
- }
+ return $query->where('is_active', true)->orderBy('name', 'desc');
}
+}
+```
Example filter using **count**:
- public $belongsToMany = [
- 'users' => ['Backend\Models\User'],
- 'users_count' => ['Backend\Models\User', 'count' => true]
- ];
+```php
+public $belongsToMany = [
+ 'users' => ['Backend\Models\User'],
+ 'users_count' => ['Backend\Models\User', 'count' => true]
+];
+```
## Relationship types
@@ -132,133 +146,167 @@ The following relations types are available:
A one-to-one relationship is a very basic relation. For example, a `User` model might be associated with one `Phone`. To define this relationship, we add a `phone` entry to the `$hasOne` property on the `User` model.
- 'Acme\Blog\Models\Phone'
- ];
- }
+class User extends Model
+{
+ public $hasOne = [
+ 'phone' => 'Acme\Blog\Models\Phone'
+ ];
+}
+```
Once the relationship is defined, we may retrieve the related record using the model property of the same name. These properties are dynamic and allow you to access them as if they were regular attributes on the model:
- $phone = User::find(1)->phone;
+```php
+$phone = User::find(1)->phone;
+```
The model assumes the foreign key of the relationship based on the model name. In this case, the `Phone` model is automatically assumed to have a `user_id` foreign key. If you wish to override this convention, you may pass the `key` parameter to the definition:
- public $hasOne = [
- 'phone' => ['Acme\Blog\Models\Phone', 'key' => 'my_user_id']
- ];
+```php
+public $hasOne = [
+ 'phone' => ['Acme\Blog\Models\Phone', 'key' => 'my_user_id']
+];
+```
Additionally, the model assumes that the foreign key should have a value matching the `id` column of the parent. In other words, it will look for the value of the user's `id` column in the `user_id` column of the `Phone` record. If you would like the relationship to use a value other than `id`, you may pass the `otherKey` parameter to the definition:
- public $hasOne = [
- 'phone' => ['Acme\Blog\Models\Phone', 'key' => 'my_user_id', 'otherKey' => 'my_id']
- ];
+```php
+public $hasOne = [
+ 'phone' => ['Acme\Blog\Models\Phone', 'key' => 'my_user_id', 'otherKey' => 'my_id']
+];
+```
#### Defining the inverse of the relation
Now that we can access the `Phone` model from our `User`. Let's do the opposite and define a relationship on the `Phone` model that will let us access the `User` that owns the phone. We can define the inverse of a `hasOne` relationship using the `$belongsTo` property:
- class Phone extends Model
- {
- public $belongsTo = [
- 'user' => 'Acme\Blog\Models\User'
- ];
- }
+```php
+class Phone extends Model
+{
+ public $belongsTo = [
+ 'user' => 'Acme\Blog\Models\User'
+ ];
+}
+```
In the example above, the model will try to match the `user_id` from the `Phone` model to an `id` on the `User` model. It determines the default foreign key name by examining the name of the relationship definition and suffixing the name with `_id`. However, if the foreign key on the `Phone` model is not `user_id`, you may pass a custom key name using the `key` parameter on the definition:
- public $belongsTo = [
- 'user' => ['Acme\Blog\Models\User', 'key' => 'my_user_id']
- ];
+```php
+public $belongsTo = [
+ 'user' => ['Acme\Blog\Models\User', 'key' => 'my_user_id']
+];
+```
If your parent model does not use `id` as its primary key, or you wish to join the child model to a different column, you may pass the `otherKey` parameter to the definition specifying your parent table's custom key:
- public $belongsTo = [
- 'user' => ['Acme\Blog\Models\User', 'key' => 'my_user_id', 'otherKey' => 'my_id']
- ];
+```php
+public $belongsTo = [
+ 'user' => ['Acme\Blog\Models\User', 'key' => 'my_user_id', 'otherKey' => 'my_id']
+];
+```
#### Default models
The `belongsTo` relationship lets you define a default model that will be returned if the given relationship is `null`. This pattern is often referred to as the [Null Object pattern](https://en.wikipedia.org/wiki/Null_Object_pattern) and can help remove conditional checks in your code. In the following example, the `user` relation will return an empty `Acme\Blog\Models\User` model if no `user` is attached to the post:
- public $belongsTo = [
- 'user' => ['Acme\Blog\Models\User', 'default' => true]
- ];
+```php
+public $belongsTo = [
+ 'user' => ['Acme\Blog\Models\User', 'default' => true]
+];
+```
To populate the default model with attributes, you may pass an array to the `default` parameter:
- public $belongsTo = [
- 'user' => [
- 'Acme\Blog\Models\User',
- 'default' => ['name' => 'Guest']
- ]
- ];
+```php
+public $belongsTo = [
+ 'user' => [
+ 'Acme\Blog\Models\User',
+ 'default' => ['name' => 'Guest']
+ ]
+];
+```
### One To Many
A one-to-many relationship is used to define relationships where a single model owns any amount of other models. For example, a blog post may have an infinite number of comments. Like all other relationships, one-to-many relationships are defined adding an entry to the `$hasMany` property on your model:
- class Post extends Model
- {
- public $hasMany = [
- 'comments' => 'Acme\Blog\Models\Comment'
- ];
- }
+```php
+class Post extends Model
+{
+ public $hasMany = [
+ 'comments' => 'Acme\Blog\Models\Comment'
+ ];
+}
+```
Remember, the model will automatically determine the proper foreign key column on the `Comment` model. By convention, it will take the "snake case" name of the owning model and suffix it with `_id`. So for this example, we can assume the foreign key on the `Comment` model is `post_id`.
Once the relationship has been defined, we can access the collection of comments by accessing the `comments` property. Remember, since the model provides "dynamic properties", we can access relationships as if they were defined as properties on the model:
- $comments = Post::find(1)->comments;
+```php
+$comments = Post::find(1)->comments;
- foreach ($comments as $comment) {
- //
- }
+foreach ($comments as $comment) {
+ //
+}
+```
Of course, since all relationships also serve as query builders, you can add further constraints to which comments are retrieved by calling the `comments` method and continuing to chain conditions onto the query:
- $comments = Post::find(1)->comments()->where('title', 'foo')->first();
+```php
+$comments = Post::find(1)->comments()->where('title', 'foo')->first();
+```
Like the `hasOne` relation, you may also override the foreign and local keys by passing the `key` and `otherKey` parameters on the definition respectively:
- public $hasMany = [
- 'comments' => ['Acme\Blog\Models\Comment', 'key' => 'my_post_id', 'otherKey' => 'my_id']
- ];
+```php
+public $hasMany = [
+ 'comments' => ['Acme\Blog\Models\Comment', 'key' => 'my_post_id', 'otherKey' => 'my_id']
+];
+```
#### Defining the inverse of the relation
Now that we can access all of a post's comments, let's define a relationship to allow a comment to access its parent post. To define the inverse of a `hasMany` relationship, define the `$belongsTo` property on the child model:
- class Comment extends Model
- {
- public $belongsTo = [
- 'post' => 'Acme\Blog\Models\Post'
- ];
- }
+```php
+class Comment extends Model
+{
+ public $belongsTo = [
+ 'post' => 'Acme\Blog\Models\Post'
+ ];
+}
+```
Once the relationship has been defined, we can retrieve the `Post` model for a `Comment` by accessing the `post` "dynamic property":
- $comment = Comment::find(1);
+```php
+$comment = Comment::find(1);
- echo $comment->post->title;
+echo $comment->post->title;
+```
In the example above, the model will try to match the `post_id` from the `Comment` model to an `id` on the `Post` model. It determines the default foreign key name by examining the name of the relationship and suffixing it with `_id`. However, if the foreign key on the `Comment` model is not `post_id`, you may pass a custom key name using the `key` parameter:
- public $belongsTo = [
- 'post' => ['Acme\Blog\Models\Post', 'key' => 'my_post_id']
- ];
+```php
+public $belongsTo = [
+ 'post' => ['Acme\Blog\Models\Post', 'key' => 'my_post_id']
+];
+```
If your parent model does not use `id` as its primary key, or you wish to join the child model to a different column, you may pass the `otherKey` parameter to the definition specifying your parent table's custom key:
- public $belongsTo = [
- 'post' => ['Acme\Blog\Models\Post', 'key' => 'my_post_id', 'otherKey' => 'my_id']
- ];
+```php
+public $belongsTo = [
+ 'post' => ['Acme\Blog\Models\Post', 'key' => 'my_post_id', 'otherKey' => 'my_id']
+];
+```
### Many To Many
@@ -267,61 +315,75 @@ Many-to-many relations are slightly more complicated than `hasOne` and `hasMany`
Below is an example that shows the [database table structure](../plugin/updates#migration-files) used to create the join table.
- Schema::create('role_user', function($table)
- {
- $table->integer('user_id')->unsigned();
- $table->integer('role_id')->unsigned();
- $table->primary(['user_id', 'role_id']);
- });
+```php
+Schema::create('role_user', function($table)
+{
+ $table->integer('user_id')->unsigned();
+ $table->integer('role_id')->unsigned();
+ $table->primary(['user_id', 'role_id']);
+});
+```
Many-to-many relationships are defined adding an entry to the `$belongsToMany` property on your model class. For example, let's define the `roles` method on our `User` model:
- class User extends Model
- {
- public $belongsToMany = [
- 'roles' => 'Acme\Blog\Models\Role'
- ];
- }
+```php
+class User extends Model
+{
+ public $belongsToMany = [
+ 'roles' => 'Acme\Blog\Models\Role'
+ ];
+}
+```
Once the relationship is defined, you may access the user's roles using the `roles` dynamic property:
- $user = User::find(1);
+```php
+$user = User::find(1);
- foreach ($user->roles as $role) {
- //
- }
+foreach ($user->roles as $role) {
+ //
+}
+```
Of course, like all other relationship types, you may call the `roles` method to continue chaining query constraints onto the relationship:
- $roles = User::find(1)->roles()->orderBy('name')->get();
+```php
+$roles = User::find(1)->roles()->orderBy('name')->get();
+```
As mentioned previously, to determine the table name of the relationship's joining table, the model will join the two related model names in alphabetical order. However, you are free to override this convention. You may do so by passing the `table` parameter to the `belongsToMany` definition:
- public $belongsToMany = [
- 'roles' => ['Acme\Blog\Models\Role', 'table' => 'acme_blog_role_user']
- ];
+```php
+public $belongsToMany = [
+ 'roles' => ['Acme\Blog\Models\Role', 'table' => 'acme_blog_role_user']
+];
+```
In addition to customizing the name of the joining table, you may also customize the column names of the keys on the table by passing additional parameters to the `belongsToMany` definition. The `key` parameter is the foreign key name of the model on which you are defining the relationship, while the `otherKey` parameter is the foreign key name of the model that you are joining to:
- public $belongsToMany = [
- 'roles' => [
- 'Acme\Blog\Models\Role',
- 'table' => 'acme_blog_role_user',
- 'key' => 'my_user_id',
- 'otherKey' => 'my_role_id'
- ]
- ];
+```php
+public $belongsToMany = [
+ 'roles' => [
+ 'Acme\Blog\Models\Role',
+ 'table' => 'acme_blog_role_user',
+ 'key' => 'my_user_id',
+ 'otherKey' => 'my_role_id'
+ ]
+];
+```
#### Defining the inverse of the relationship
To define the inverse of a many-to-many relationship, you simply place another `$belongsToMany` property on your related model. To continue our user roles example, let's define the `users` relationship on the `Role` model:
- class Role extends Model
- {
- public $belongsToMany = [
- 'users' => 'Acme\Blog\Models\User'
- ];
- }
+```php
+class Role extends Model
+{
+ public $belongsToMany = [
+ 'users' => 'Acme\Blog\Models\User'
+ ];
+}
+```
As you can see, the relationship is defined exactly the same as its `User` counterpart, with the exception of simply referencing the `Acme\Blog\Models\User` model. Since we're reusing the `$belongsToMany` property, all of the usual table and key customization options are available when defining the inverse of many-to-many relationships.
@@ -329,28 +391,34 @@ As you can see, the relationship is defined exactly the same as its `User` count
As you have already learned, working with many-to-many relations requires the presence of an intermediate join table. Models provide some very helpful ways of interacting with this table. For example, let's assume our `User` object has many `Role` objects that it is related to. After accessing this relationship, we may access the intermediate table using the `pivot` attribute on the models:
- $user = User::find(1);
+```php
+$user = User::find(1);
- foreach ($user->roles as $role) {
- echo $role->pivot->created_at;
- }
+foreach ($user->roles as $role) {
+ echo $role->pivot->created_at;
+}
+```
Notice that each `Role` model we retrieve is automatically assigned a `pivot` attribute. This attribute contains a model representing the intermediate table, and may be used like any other model.
By default, only the model keys will be present on the `pivot` object. If your pivot table contains extra attributes, you must specify them when defining the relationship:
- public $belongsToMany = [
- 'roles' => [
- 'Acme\Blog\Models\Role',
- 'pivot' => ['column1', 'column2']
- ]
- ];
+```php
+public $belongsToMany = [
+ 'roles' => [
+ 'Acme\Blog\Models\Role',
+ 'pivot' => ['column1', 'column2']
+ ]
+];
+```
If you want your pivot table to have automatically maintained `created_at` and `updated_at` timestamps, use the `timestamps` parameter on the relationship definition:
- public $belongsToMany = [
- 'roles' => ['Acme\Blog\Models\Role', 'timestamps' => true]
- ];
+```php
+public $belongsToMany = [
+ 'roles' => ['Acme\Blog\Models\Role', 'timestamps' => true]
+];
+```
These are the parameters supported for `belongsToMany` relations:
@@ -388,29 +456,33 @@ Though `posts` does not contain a `country_id` column, the `hasManyThrough` rela
Now that we have examined the table structure for the relationship, let's define it on the `Country` model:
- class Country extends Model
- {
- public $hasManyThrough = [
- 'posts' => [
- 'Acme\Blog\Models\Post',
- 'through' => 'Acme\Blog\Models\User'
- ],
- ];
- }
-
-The first argument passed to the `$hasManyThrough` relation is the name of the final model we wish to access, while the `through` parameter is the name of the intermediate model.
-
-Typical foreign key conventions will be used when performing the relationship's queries. If you would like to customize the keys of the relationship, you may pass them as the `key`, `otherKey` and `throughKey` parameters to the `$hasManyThrough` definition. The `key` parameter is the name of the foreign key on the intermediate model, the `throughKey` parameter is the name of the foreign key on the final model, while the `otherKey` is the local key.
-
+```php
+class Country extends Model
+{
public $hasManyThrough = [
'posts' => [
'Acme\Blog\Models\Post',
- 'key' => 'my_country_id',
- 'through' => 'Acme\Blog\Models\User',
- 'throughKey' => 'my_user_id',
- 'otherKey' => 'my_id'
+ 'through' => 'Acme\Blog\Models\User'
],
];
+}
+```
+
+The first argument passed to the `$hasManyThrough` relation is the name of the final model we wish to access, while the `through` parameter is the name of the intermediate model.
+
+Typical foreign key conventions will be used when performing the relationship's queries. If you would like to customize the keys of the relationship, you may pass them as the `key`, `otherKey` and `throughKey` parameters to the `$hasManyThrough` definition. The `key` parameter is the name of the foreign key on the intermediate model, the `throughKey` parameter is the name of the foreign key on the final model, while the `otherKey` is the local key.
+
+```php
+public $hasManyThrough = [
+ 'posts' => [
+ 'Acme\Blog\Models\Post',
+ 'key' => 'my_country_id',
+ 'through' => 'Acme\Blog\Models\User',
+ 'throughKey' => 'my_user_id',
+ 'otherKey' => 'my_id'
+ ],
+];
+```
### Has One Through
@@ -430,30 +502,33 @@ The has-one-through relationship links models through a single intermediate rela
Though the `history` table does not contain a `supplier_id` column, the `hasOneThrough` relation can provide access to the user's history to the supplier model. Now that we have examined the table structure for the relationship, let's define it on the `Supplier` model:
- class Supplier extends Model
- {
- public $hasOneThrough = [
- 'userHistory' => [
- 'Acme\Supplies\Model\History',
- 'through' => 'Acme\Supplies\Model\User'
- ],
- ];
- }
-
-The first array parameter passed to the `$hasOneThrough` property is the name of the final model we wish to access, while the `through` key is the name of the intermediate model.
-
-Typical foreign key conventions will be used when performing the relationship's queries. If you would like to customize the keys of the relationship, you may pass them as the `key`, `otherKey` and `throughKey` parameters to the `$hasManyThrough` definition. The `key` parameter is the name of the foreign key on the intermediate model, the `throughKey` parameter is the name of the foreign key on the final model, while the `otherKey` is the local key.
-
+```php
+class Supplier extends Model
+{
public $hasOneThrough = [
'userHistory' => [
'Acme\Supplies\Model\History',
- 'key' => 'supplier_id',
'through' => 'Acme\Supplies\Model\User'
- 'throughKey' => 'user_id',
- 'otherKey' => 'id'
],
];
+}
+```
+
+The first array parameter passed to the `$hasOneThrough` property is the name of the final model we wish to access, while the `through` key is the name of the intermediate model.
+
+Typical foreign key conventions will be used when performing the relationship's queries. If you would like to customize the keys of the relationship, you may pass them as the `key`, `otherKey` and `throughKey` parameters to the `$hasManyThrough` definition. The `key` parameter is the name of the foreign key on the intermediate model, the `throughKey` parameter is the name of the foreign key on the final model, while the `otherKey` is the local key.
+```php
+public $hasOneThrough = [
+ 'userHistory' => [
+ 'Acme\Supplies\Model\History',
+ 'key' => 'supplier_id',
+ 'through' => 'Acme\Supplies\Model\User'
+ 'throughKey' => 'user_id',
+ 'otherKey' => 'id'
+ ],
+];
+```
### Polymorphic relations
@@ -487,40 +562,46 @@ Two important columns to note are the `imageable_id` and `imageable_type` column
Next, let's examine the model definitions needed to build this relationship:
- class Photo extends Model
- {
- public $morphTo = [
- 'imageable' => []
- ];
- }
+```php
+class Photo extends Model
+{
+ public $morphTo = [
+ 'imageable' => []
+ ];
+}
- class Staff extends Model
- {
- public $morphOne = [
- 'photo' => ['Acme\Blog\Models\Photo', 'name' => 'imageable']
- ];
- }
+class Staff extends Model
+{
+ public $morphOne = [
+ 'photo' => ['Acme\Blog\Models\Photo', 'name' => 'imageable']
+ ];
+}
- class Product extends Model
- {
- public $morphOne = [
- 'photo' => ['Acme\Blog\Models\Photo', 'name' => 'imageable']
- ];
- }
+class Product extends Model
+{
+ public $morphOne = [
+ 'photo' => ['Acme\Blog\Models\Photo', 'name' => 'imageable']
+ ];
+}
+```
#### Retrieving Polymorphic relations
Once your database table and models are defined, you may access the relationships via your models. For example, to access the photo for a staff member, we can simply use the `photo` dynamic property:
- $staff = Staff::find(1);
+```php
+$staff = Staff::find(1);
- $photo = $staff->photo
+$photo = $staff->photo;
+```
You may also retrieve the owner of a polymorphic relation from the polymorphic model by accessing the name of the `morphTo` relationship. In our case, that is the `imageable` definition on the `Photo` model. So, we will access it as a dynamic property:
- $photo = Photo::find(1);
+```php
+$photo = Photo::find(1);
- $imageable = $photo->imageable;
+$imageable = $photo->imageable;
+```
The `imageable` relation on the `Photo` model will return either a `Staff` or `Product` instance, depending on which type of model owns the photo.
@@ -551,52 +632,60 @@ A one-to-many polymorphic relation is similar to a simple one-to-many relation;
Next, let's examine the model definitions needed to build this relationship:
- class Comment extends Model
- {
- public $morphTo = [
- 'commentable' => []
- ];
- }
+```php
+class Comment extends Model
+{
+ public $morphTo = [
+ 'commentable' => []
+ ];
+}
- class Post extends Model
- {
- public $morphMany = [
- 'comments' => ['Acme\Blog\Models\Comment', 'name' => 'commentable']
- ];
- }
+class Post extends Model
+{
+ public $morphMany = [
+ 'comments' => ['Acme\Blog\Models\Comment', 'name' => 'commentable']
+ ];
+}
- class Product extends Model
- {
- public $morphMany = [
- 'comments' => ['Acme\Blog\Models\Comment', 'name' => 'commentable']
- ];
- }
+class Product extends Model
+{
+ public $morphMany = [
+ 'comments' => ['Acme\Blog\Models\Comment', 'name' => 'commentable']
+ ];
+}
+```
#### Retrieving The Relationship
Once your database table and models are defined, you may access the relationships via your models. For example, to access all of the comments for a post, we can use the `comments` dynamic property:
- $post = Author\Plugin\Models\Post::find(1);
+```php
+$post = Author\Plugin\Models\Post::find(1);
- foreach ($post->comments as $comment) {
- //
- }
+foreach ($post->comments as $comment) {
+ //
+}
+```
You may also retrieve the owner of a polymorphic relation from the polymorphic model by accessing the name of the `morphTo` relationship. In our case, that is the `commentable` definition on the `Comment` model. So, we will access it as a dynamic property:
- $comment = Author\Plugin\Models\Comment::find(1);
+```php
+$comment = Author\Plugin\Models\Comment::find(1);
- $commentable = $comment->commentable;
+$commentable = $comment->commentable;
+```
The `commentable` relation on the `Comment` model will return either a `Post` or `Video` instance, depending on which type of model owns the comment.
You are also able to update the owner of the related model by setting the attribute with the name of the `morphTo` relationship, in this case `commentable`.
- $comment = Author\Plugin\Models\Comment::find(1);
- $video = Author\Plugin\Models\Video::find(1);
+```php
+$comment = Author\Plugin\Models\Comment::find(1);
+$video = Author\Plugin\Models\Video::find(1);
- $comment->commentable = $video;
- $comment->save()
+$comment->commentable = $video;
+$comment->save();
+```
### Many To Many
@@ -626,42 +715,50 @@ In addition to "one-to-one" and "one-to-many" relations, you may also define "ma
Next, we're ready to define the relationships on the model. The `Post` and `Video` models will both have a `tags` relation defined in the `$morphToMany` property on the base model class:
- class Post extends Model
- {
- public $morphToMany = [
- 'tags' => ['Acme\Blog\Models\Tag', 'name' => 'taggable']
- ];
- }
+```php
+class Post extends Model
+{
+ public $morphToMany = [
+ 'tags' => ['Acme\Blog\Models\Tag', 'name' => 'taggable']
+ ];
+}
+```
#### Defining the inverse of the relationship
Next, on the `Tag` model, you should define a relation for each of its related models. So, for this example, we will define a `posts` relation and a `videos` relation:
- class Tag extends Model
- {
- public $morphedByMany = [
- 'posts' => ['Acme\Blog\Models\Post', 'name' => 'taggable'],
- 'videos' => ['Acme\Blog\Models\Video', 'name' => 'taggable']
- ];
- }
+```php
+class Tag extends Model
+{
+ public $morphedByMany = [
+ 'posts' => ['Acme\Blog\Models\Post', 'name' => 'taggable'],
+ 'videos' => ['Acme\Blog\Models\Video', 'name' => 'taggable']
+ ];
+}
+```
#### Retrieving the relationship
Once your database table and models are defined, you may access the relationships via your models. For example, to access all of the tags for a post, you can simply use the `tags` dynamic property:
- $post = Post::find(1);
+```php
+$post = Post::find(1);
- foreach ($post->tags as $tag) {
- //
- }
+foreach ($post->tags as $tag) {
+ //
+}
+```
You may also retrieve the owner of a polymorphic relation from the polymorphic model by accessing the name of the relationship defined in the `$morphedByMany` property. In our case, that is the `posts` or `videos` methods on the `Tag` model. So, you will access those relations as dynamic properties:
- $tag = Tag::find(1);
+```php
+$tag = Tag::find(1);
- foreach ($tag->videos as $video) {
- //
- }
+foreach ($tag->videos as $video) {
+ //
+}
+```
#### Custom Polymorphic types
@@ -670,12 +767,14 @@ By default, the fully qualified class name is used to store the related model ty
Using a custom polymorphic type lets you decouple your database from your application's internal structure. You may define a relationship "morph map" to provide a custom name for each model instead of the class name:
- use Winter\Storm\Database\Relations\Relation;
+```php
+use Winter\Storm\Database\Relations\Relation;
- Relation::morphMap([
- 'staff' => 'Acme\Blog\Models\Staff',
- 'product' => 'Acme\Blog\Models\Product',
- ]);
+Relation::morphMap([
+ 'staff' => 'Acme\Blog\Models\Staff',
+ 'product' => 'Acme\Blog\Models\Product',
+]);
+```
The most common place to register the `morphMap` in the `boot` method of a [Plugin registration file](../plugin/registration#registration-methods).
@@ -686,34 +785,40 @@ Since all types of Model relationships can be called via functions, you may call
For example, imagine a blog system in which a `User` model has many associated `Post` models:
- class User extends Model
- {
- public $hasMany = [
- 'posts' => ['Acme\Blog\Models\Post']
- ];
- }
+```php
+class User extends Model
+{
+ public $hasMany = [
+ 'posts' => ['Acme\Blog\Models\Post']
+ ];
+}
+```
### Access via relationship method
You may query the **posts** relationship and add additional constraints to the relationship using the `posts` method. This gives you the ability to chain any of the [query builder](query) methods on the relationship.
- $user = User::find(1);
+```php
+$user = User::find(1);
- $posts = $user->posts()->where('is_active', 1)->get();
+$posts = $user->posts()->where('is_active', 1)->get();
- $post = $user->posts()->first();
+$post = $user->posts()->first();
+```
### Access via dynamic property
If you do not need to add additional constraints to a relationship query, you may simply access the relationship as if it were a property. For example, continuing to use our `User` and `Post` example models, we may access all of a user's posts using the `$user->posts` property instead.
- $user = User::find(1);
+```php
+$user = User::find(1);
- foreach ($user->posts as $post) {
- // ...
- }
+foreach ($user->posts as $post) {
+ // ...
+}
+```
Dynamic properties are "lazy loading", meaning they will only load their relationship data when you actually access them. Because of this, developers often use [eager loading](#eager-loading) to pre-load relationships they know will be accessed after loading the model. Eager loading provides a significant reduction in SQL queries that must be executed to load a model's relations.
@@ -722,111 +827,139 @@ Dynamic properties are "lazy loading", meaning they will only load their relatio
When accessing the records for a model, you may wish to limit your results based on the existence of a relationship. For example, imagine you want to retrieve all blog posts that have at least one comment. To do so, you may pass the name of the relationship to the `has` method:
- // Retrieve all posts that have at least one comment...
- $posts = Post::has('comments')->get();
+```php
+// Retrieve all posts that have at least one comment...
+$posts = Post::has('comments')->get();
+```
You may also specify an operator and count to further customize the query:
- // Retrieve all posts that have three or more comments...
- $posts = Post::has('comments', '>=', 3)->get();
+```php
+// Retrieve all posts that have three or more comments...
+$posts = Post::has('comments', '>=', 3)->get();
+```
Nested `has` statements may also be constructed using "dot" notation. For example, you may retrieve all posts that have at least one comment and vote:
- // Retrieve all posts that have at least one comment with votes...
- $posts = Post::has('comments.votes')->get();
+```php
+// Retrieve all posts that have at least one comment with votes...
+$posts = Post::has('comments.votes')->get();
+```
If you need even more power, you may use the `whereHas` and `orWhereHas` methods to put "where" conditions on your `has` queries. These methods allow you to add customized constraints to a relationship constraint, such as checking the content of a comment:
- // Retrieve all posts with at least one comment containing words like foo%
- $posts = Post::whereHas('comments', function ($query) {
- $query->where('content', 'like', 'foo%');
- })->get();
+```php
+// Retrieve all posts with at least one comment containing words like foo%
+$posts = Post::whereHas('comments', function ($query) {
+ $query->where('content', 'like', 'foo%');
+})->get();
+```
## Eager loading
When accessing relationships as properties, the relationship data is "lazy loaded". This means the relationship data is not actually loaded until you first access the property. However, models can "eager load" relationships at the time you query the parent model. Eager loading alleviates the N + 1 query problem. To illustrate the N + 1 query problem, consider a `Book` model that is related to `Author`:
- class Book extends Model
- {
- public $belongsTo = [
- 'author' => ['Acme\Blog\Models\Author']
- ];
- }
+```php
+class Book extends Model
+{
+ public $belongsTo = [
+ 'author' => ['Acme\Blog\Models\Author']
+ ];
+}
+```
Now let's retrieve all books and their authors:
- $books = Book::all();
+```php
+$books = Book::all();
- foreach ($books as $book) {
- echo $book->author->name;
- }
+foreach ($books as $book) {
+ echo $book->author->name;
+}
+```
This loop will execute 1 query to retrieve all of the books on the table, then another query for each book to retrieve the author. So, if we have 25 books, this loop would run 26 queries: 1 for the original book, and 25 additional queries to retrieve the author of each book.
Thankfully we can use eager loading to reduce this operation to just 2 queries. When querying, you may specify which relationships should be eager loaded using the `with` method:
- $books = Book::with('author')->get();
+```php
+$books = Book::with('author')->get();
- foreach ($books as $book) {
- echo $book->author->name;
- }
+foreach ($books as $book) {
+ echo $book->author->name;
+}
+```
For this operation only two queries will be executed:
- select * from books
+```sql
+select * from books
- select * from authors where id in (1, 2, 3, 4, 5, ...)
+select * from authors where id in (1, 2, 3, 4, 5, ...)
+```
#### Eager loading multiple relationships
Sometimes you may need to eager load several different relationships in a single operation. To do so, just pass additional arguments to the `with` method:
- $books = Book::with('author', 'publisher')->get();
+```php
+$books = Book::with('author', 'publisher')->get();
+```
#### Nested eager loading
To eager load nested relationships, you may use "dot" syntax. For example, let's eager load all of the book's authors and all of the author's personal contacts in one statement:
- $books = Book::with('author.contacts')->get();
+```php
+$books = Book::with('author.contacts')->get();
+```
### Constraining eager loads
Sometimes you may wish to eager load a relationship, but also specify additional query constraints for the eager loading query. Here's an example:
- $users = User::with([
- 'posts' => function ($query) {
- $query->where('title', 'like', '%first%');
- }
- ])->get();
+```php
+$users = User::with([
+ 'posts' => function ($query) {
+ $query->where('title', 'like', '%first%');
+ }
+])->get();
+```
In this example, the model will only eager load posts if the post's `title` column contains the word `first`. Of course, you may call other [query builder](query) methods to further customize the eager loading operation:
- $users = User::with([
- 'posts' => function ($query) {
- $query->orderBy('created_at', 'desc');
- }
- ])->get();
+```php
+$users = User::with([
+ 'posts' => function ($query) {
+ $query->orderBy('created_at', 'desc');
+ }
+])->get();
+```
### Lazy eager loading
Sometimes you may need to eager load a relationship after the parent model has already been retrieved. For example, this may be useful if you need to dynamically decide whether to load related models:
- $books = Book::all();
+```php
+$books = Book::all();
- if ($someCondition) {
- $books->load('author', 'publisher');
- }
+if ($someCondition) {
+ $books->load('author', 'publisher');
+}
+```
If you need to set additional query constraints on the eager loading query, you may pass a `Closure` to the `load` method:
- $books->load([
- 'author' => function ($query) {
- $query->orderBy('published_date', 'asc');
- }
- ]);
+```php
+$books->load([
+ 'author' => function ($query) {
+ $query->orderBy('published_date', 'asc');
+ }
+]);
+```
## Inserting related models
@@ -842,28 +975,34 @@ Winter provides convenient methods for adding new models to relationships. Prima
Use the `add` method to associate a new relationship.
- $comment = new Comment(['message' => 'A new comment.']);
+```php
+$comment = new Comment(['message' => 'A new comment.']);
- $post = Post::find(1);
+$post = Post::find(1);
- $comment = $post->comments()->add($comment);
+$comment = $post->comments()->add($comment);
+```
Notice that we did not access the `comments` relationship as a dynamic property. Instead, we called the `comments` method to obtain an instance of the relationship. The `add` method will automatically add the appropriate `post_id` value to the new `Comment` model.
If you need to save multiple related models, you may use the `addMany` method:
- $post = Post::find(1);
+```php
+$post = Post::find(1);
- $post->comments()->addMany([
- new Comment(['message' => 'A new comment.']),
- new Comment(['message' => 'Another comment.']),
- ]);
+$post->comments()->addMany([
+ new Comment(['message' => 'A new comment.']),
+ new Comment(['message' => 'Another comment.']),
+]);
+```
#### Remove method
Comparatively, the `remove` method can be used to disassociate a relationship, making it an orphaned record.
- $post->comments()->remove($comment);
+```php
+$post->comments()->remove($comment);
+```
In the case of many-to-many relations, the record is removed from the relationship's collection instead.
@@ -871,31 +1010,39 @@ In the case of many-to-many relations, the record is removed from the relationsh
In the case of a "belongs to" relationship, you may use the `dissociate` method, which doesn't require the related model passed to it.
- $post->author()->dissociate();
+```php
+$post->author()->dissociate();
+```
#### Adding with pivot data
When working with a many-to-many relationship, the `add` method accepts an array of additional intermediate "pivot" table attributes as its second argument as an array.
- $user = User::find(1);
+```php
+$user = User::find(1);
- $pivotData = ['expires' => $expires];
+$pivotData = ['expires' => $expires];
- $user->roles()->add($role, $pivotData);
+$user->roles()->add($role, $pivotData);
+```
The second argument of the `add` method can also specify the session key used by [deferred binding](#deferred-binding) when passed as a string. In these cases the pivot data can be provided as the third argument instead.
- $user->roles()->add($role, $sessionKey, $pivotData);
+```php
+$user->roles()->add($role, $sessionKey, $pivotData);
+```
#### Create method
While `add` and `addMany` accept a full model instance, you may also use the `create` method, that accepts a PHP array of attributes, creates a model, and inserts it into the database.
- $post = Post::find(1);
+```php
+$post = Post::find(1);
- $comment = $post->comments()->create([
- 'message' => 'A new comment.',
- ]);
+$comment = $post->comments()->create([
+ 'message' => 'A new comment.',
+]);
+```
Before using the `create` method, be sure to review the documentation on attribute [mass assignment](model#mass-assignment) as the attributes in the PHP array are restricted by the model's "fillable" definition.
@@ -904,39 +1051,47 @@ Before using the `create` method, be sure to review the documentation on attribu
Relationships can be set directly via their properties in the same way you would access them. Setting a relationship using this approach will overwrite any relationship that existed previously. The model should be saved afterwards like you would with any attribute.
- $post->author = $author;
+```php
+$post->author = $author;
- $post->comments = [$comment1, $comment2];
+$post->comments = [$comment1, $comment2];
- $post->save();
+$post->save();
+```
Alternatively you may set the relationship using the primary key, this is useful when working with HTML forms.
- // Assign to author with ID of 3
- $post->author = 3;
+```php
+// Assign to author with ID of 3
+$post->author = 3;
- // Assign comments with IDs of 1, 2 and 3
- $post->comments = [1, 2, 3];
+// Assign comments with IDs of 1, 2 and 3
+$post->comments = [1, 2, 3];
- $post->save();
+$post->save();
+```
Relationships can be disassociated by assigning the NULL value to the property.
- $post->author = null;
+```php
+$post->author = null;
- $post->comments = null;
+$post->comments = null;
- $post->save();
+$post->save();
+```
Similar to [deferred binding](#deferred-binding), relationships defined on non-existent models are deferred in memory until they are saved. In this example the post does not exist yet, so the `post_id` attribute cannot be set on the comment via `$post->comments`. Therefore the association is deferred until the post is created by calling the `save` method.
- $comment = Comment::find(1);
+```php
+$comment = Comment::find(1);
- $post = new Post;
+$post = new Post;
- $post->comments = [$comment];
+$post->comments = [$comment];
- $post->save();
+$post->save();
+```
### Many To Many relations
@@ -945,67 +1100,83 @@ Similar to [deferred binding](#deferred-binding), relationships defined on non-e
When working with many-to-many relationships, Models provide a few additional helper methods to make working with related models more convenient. For example, let's imagine a user can have many roles and a role can have many users. To attach a role to a user by inserting a record in the intermediate table that joins the models, use the `attach` method:
- $user = User::find(1);
+```php
+$user = User::find(1);
- $user->roles()->attach($roleId);
+$user->roles()->attach($roleId);
+```
When attaching a relationship to a model, you may also pass an array of additional data to be inserted into the intermediate table:
- $user->roles()->attach($roleId, ['expires' => $expires]);
+```php
+$user->roles()->attach($roleId, ['expires' => $expires]);
+```
Of course, sometimes it may be necessary to remove a role from a user. To remove a many-to-many relationship record, use the `detach` method. The `detach` method will remove the appropriate record out of the intermediate table; however, both models will remain in the database:
- // Detach a single role from the user...
- $user->roles()->detach($roleId);
+```php
+// Detach a single role from the user...
+$user->roles()->detach($roleId);
- // Detach all roles from the user...
- $user->roles()->detach();
+// Detach all roles from the user...
+$user->roles()->detach();
+```
For convenience, `attach` and `detach` also accept arrays of IDs as input:
- $user = User::find(1);
+```php
+$user = User::find(1);
- $user->roles()->detach([1, 2, 3]);
+$user->roles()->detach([1, 2, 3]);
- $user->roles()->attach([1 => ['expires' => $expires], 2, 3]);
+$user->roles()->attach([1 => ['expires' => $expires], 2, 3]);
+```
#### Syncing For convenience
You may also use the `sync` method to construct many-to-many associations. The `sync` method accepts an array of IDs to place on the intermediate table. Any IDs that are not in the given array will be removed from the intermediate table. So, after this operation is complete, only the IDs in the array will exist in the intermediate table:
- $user->roles()->sync([1, 2, 3]);
+```php
+$user->roles()->sync([1, 2, 3]);
+```
You may also pass additional intermediate table values with the IDs:
- $user->roles()->sync([1 => ['expires' => true], 2, 3]);
+```php
+$user->roles()->sync([1 => ['expires' => true], 2, 3]);
+```
### Touching parent timestamps
When a model `belongsTo` or `belongsToMany` another model, such as a `Comment` which belongs to a `Post`, it is sometimes helpful to update the parent's timestamp when the child model is updated. For example, when a `Comment` model is updated, you may want to automatically "touch" the `updated_at` timestamp of the owning `Post`. Just add a `touches` property containing the names of the relationships to the child model:
- class Comment extends Model
- {
- /**
- * All of the relationships to be touched.
- */
- protected $touches = ['post'];
-
- /**
- * Relations
- */
- public $belongsTo = [
- 'post' => ['Acme\Blog\Models\Post']
- ];
- }
+```php
+class Comment extends Model
+{
+ /**
+ * All of the relationships to be touched.
+ */
+ protected $touches = ['post'];
+
+ /**
+ * Relations
+ */
+ public $belongsTo = [
+ 'post' => ['Acme\Blog\Models\Post']
+ ];
+}
+```
Now, when you update a `Comment`, the owning `Post` will have its `updated_at` column updated as well:
- $comment = Comment::find(1);
+```php
+$comment = Comment::find(1);
- $comment->text = 'Edit to this comment!';
+$comment->text = 'Edit to this comment!';
- $comment->save();
+$comment->save();
+```
## Deferred binding
@@ -1019,19 +1190,23 @@ You can defer any number of **slave** models against a **master** model using a
The session key is required for deferred bindings. You can think of a session key as of a transaction identifier. The same session key should be used for binding/unbinding relationships and saving the master model. You can generate the session key with PHP `uniqid()` function. Note that both [backend forms](../backend/forms) and the [frontend `form()` function](../markup/function-form) generates a hidden field containing the session key automatically.
- $sessionKey = uniqid('session_key', true);
+```php
+$sessionKey = uniqid('session_key', true);
+```
### Defer a relation binding
The comment in the next example will not be added to the post unless the post is saved.
- $comment = new Comment;
- $comment->content = "Hello world!";
- $comment->save();
+```php
+$comment = new Comment;
+$comment->content = "Hello world!";
+$comment->save();
- $post = new Post;
- $post->comments()->add($comment, $sessionKey);
+$post = new Post;
+$post->comments()->add($comment, $sessionKey);
+```
> **NOTE**: the `$post` object has not been saved but the relationship will be created if the saving happens.
@@ -1040,50 +1215,64 @@ The comment in the next example will not be added to the post unless the post is
The comment in the next example will not be deleted unless the post is saved.
- $comment = Comment::find(1);
- $post = Post::find(1);
- $post->comments()->remove($comment, $sessionKey);
+```php
+$comment = Comment::find(1);
+$post = Post::find(1);
+$post->comments()->remove($comment, $sessionKey);
+```
### List all bindings
Use the `withDeferred` method of a relation to load all records, including deferred. The results will include existing relations as well.
- $post->comments()->withDeferred($sessionKey)->get();
+```php
+$post->comments()->withDeferred($sessionKey)->get();
+```
### Cancel all bindings
It's a good idea to cancel deferred binding and delete the slave objects rather than leaving them as orphans.
- $post->cancelDeferred($sessionKey);
+```php
+$post->cancelDeferred($sessionKey);
+```
### Commit all bindings
You can commit (bind or unbind) all deferred bindings when you save the master model by providing the session key with the second argument of the `save` method.
- $post = new Post;
- $post->title = "First blog post";
- $post->save(null, $sessionKey);
+```php
+$post = new Post;
+$post->title = "First blog post";
+$post->save(null, $sessionKey);
+```
The same approach works with the model's `create` method:
- $post = Post::create(['title' => 'First blog post'], $sessionKey);
+```php
+$post = Post::create(['title' => 'First blog post'], $sessionKey);
+```
### Lazily commit bindings
If you are unable to supply the `$sessionKey` when saving, you can commit the bindings at any time using the the next code:
- $post->commitDeferred($sessionKey);
+```php
+$post->commitDeferred($sessionKey);
+```
### Clean up orphaned bindings
Destroys all bindings that have not been committed and are older than 1 day:
- Winter\Storm\Database\Models\DeferredBinding::cleanUp(1);
+```php
+Winter\Storm\Database\Models\DeferredBinding::cleanUp(1);
+```
> **NOTE:** Winter automatically destroys deferred bindings that are older than 5 days. It happens when a backend user logs into the system.
@@ -1092,13 +1281,15 @@ Destroys all bindings that have not been committed and are older than 1 day:
Sometimes you might need to disable deferred binding entirely for a given model, for instance if you are loading it from a separate database connection. In order to do that, you need to make sure that the model's `sessionKey` property is `null` before the pre and post deferred binding hooks in the internal save method are run. To do that, you can bind to the model's `model.saveInternal` event:
- public function __construct()
- {
- $result = parent::__construct(...func_get_args());
- $this->bindEvent('model.saveInternal', function () {
- $this->sessionKey = null;
- });
- return $result;
- }
+```php
+public function __construct()
+{
+ $result = parent::__construct(...func_get_args());
+ $this->bindEvent('model.saveInternal', function () {
+ $this->sessionKey = null;
+ });
+ return $result;
+}
+```
> **NOTE:** This will disable deferred binding entirely for any model's you apply this override to.
diff --git a/database-serialization.md b/database-serialization.md
index afaeaa34..b20e87aa 100644
--- a/database-serialization.md
+++ b/database-serialization.md
@@ -17,95 +17,113 @@ When building JSON APIs, you will often need to convert your models and relation
To convert a model and its loaded [relationships](relations) to an array, you may use the `toArray` method. This method is recursive, so all attributes and all relations (including the relations of relations) will be converted to arrays:
- $user = User::with('roles')->first();
+```php
+$user = User::with('roles')->first();
- return $user->toArray();
+return $user->toArray();
+```
You may also convert [collections](collection) to arrays:
- $users = User::all();
+```php
+$users = User::all();
- return $users->toArray();
+return $users->toArray();
+```
#### Converting a model to JSON
To convert a model to JSON, you may use the `toJson` method. Like `toArray`, the `toJson` method is recursive, so all attributes and relations will be converted to JSON:
- $user = User::find(1);
+```php
+$user = User::find(1);
- return $user->toJson();
+return $user->toJson();
+```
Alternatively, you may cast a model or collection to a string, which will automatically call the `toJson` method:
- $user = User::find(1);
+```php
+$user = User::find(1);
- return (string) $user;
+return (string) $user;
+```
Since models and collections are converted to JSON when cast to a string, you can return Model objects directly from your application's routes, AJAX handlers or controllers:
- Route::get('users', function () {
- return User::all();
- });
+```php
+Route::get('users', function () {
+ return User::all();
+});
+```
## Hiding attributes from JSON
Sometimes you may wish to limit the attributes, such as passwords, that are included in your model's array or JSON representation. To do so, add a `$hidden` property definition to your model:
-
## Appending values to JSON
Occasionally, you may need to add array attributes that do not have a corresponding column in your database. To do so, first define an [accessor](../database/mutators) for the value:
- class User extends Model
+```php
+class User extends Model
+{
+ /**
+ * Get the administrator flag for the user.
+ *
+ * @return bool
+ */
+ public function getIsAdminAttribute()
{
- /**
- * Get the administrator flag for the user.
- *
- * @return bool
- */
- public function getIsAdminAttribute()
- {
- return $this->attributes['admin'] == 'yes';
- }
+ return $this->attributes['admin'] == 'yes';
}
+}
+```
Once you have created the accessor, add the attribute name to the `appends` property on the model:
- class User extends Model
- {
- /**
- * The accessors to append to the model's array form.
- *
- * @var array
- */
- protected $appends = ['is_admin'];
- }
+```php
+class User extends Model
+{
+ /**
+ * The accessors to append to the model's array form.
+ *
+ * @var array
+ */
+ protected $appends = ['is_admin'];
+}
+```
Once the attribute has been added to the `appends` list, it will be included in both the model's array and JSON forms. Attributes in the `appends` array will also respect the `visible` and `hidden` settings configured on the model.
diff --git a/database-structure.md b/database-structure.md
index 9bb2f31d..bc4c5770 100644
--- a/database-structure.md
+++ b/database-structure.md
@@ -23,43 +23,47 @@ Migrations and seed files allow you to build, modify and populate database table
A migration file should define a class that extends the `Winter\Storm\Database\Updates\Migration` class and contains two methods: `up` and `down`. The `up` method is used to add new tables, columns, or indexes to your database, while the `down` method should simply reverse the operations performed by the `up` method. Within both of these methods you may use the [schema builder](#creating-tables) to expressively create and modify tables. For example, let's look at a sample migration that creates a `winter_blog_posts` table:
- engine = 'InnoDB';
- $table->increments('id');
- $table->string('title');
- $table->string('slug')->index();
- $table->text('excerpt')->nullable();
- $table->text('content');
- $table->timestamp('published_at')->nullable();
- $table->boolean('is_published')->default(false);
- $table->timestamps();
- });
- }
-
- public function down()
- {
- Schema::drop('winter_blog_posts');
- }
+ $table->engine = 'InnoDB';
+ $table->increments('id');
+ $table->string('title');
+ $table->string('slug')->index();
+ $table->text('excerpt')->nullable();
+ $table->text('content');
+ $table->timestamp('published_at')->nullable();
+ $table->boolean('is_published')->default(false);
+ $table->timestamps();
+ });
}
+ public function down()
+ {
+ Schema::drop('winter_blog_posts');
+ }
+}
+```
+
### Creating tables
To create a new database table, use the `create` method on the `Schema` facade. The `create` method accepts two arguments. The first is the name of the table, while the second is a `Closure` which receives an object used to define the new table:
- Schema::create('users', function ($table) {
- $table->increments('id');
- });
+```php
+Schema::create('users', function ($table) {
+ $table->increments('id');
+});
+```
Of course, when creating the table, you may use any of the schema builder's [column methods](#creating-columns) to define the table's columns.
@@ -67,51 +71,63 @@ Of course, when creating the table, you may use any of the schema builder's [col
You may easily check for the existence of a table or column using the `hasTable` and `hasColumn` methods:
- if (Schema::hasTable('users')) {
- //
- }
+```php
+if (Schema::hasTable('users')) {
+ //
+}
- if (Schema::hasColumn('users', 'email')) {
- //
- }
+if (Schema::hasColumn('users', 'email')) {
+ //
+}
+```
#### Connection & storage engine
If you want to perform a schema operation on a database connection that is not your default connection, use the `connection` method:
- Schema::connection('foo')->create('users', function ($table) {
- $table->increments('id');
- });
+```php
+Schema::connection('foo')->create('users', function ($table) {
+ $table->increments('id');
+});
+```
To set the storage engine for a table, set the `engine` property on the schema builder:
- Schema::create('users', function ($table) {
- $table->engine = 'InnoDB';
+```php
+Schema::create('users', function ($table) {
+ $table->engine = 'InnoDB';
- $table->increments('id');
- });
+ $table->increments('id');
+});
+```
### Renaming / dropping tables
To rename an existing database table, use the `rename` method:
- Schema::rename($from, $to);
+```php
+Schema::rename($from, $to);
+```
To drop an existing table, you may use the `drop` or `dropIfExists` methods:
- Schema::drop('users');
+```php
+Schema::drop('users');
- Schema::dropIfExists('users');
+Schema::dropIfExists('users');
+```
### Creating columns
To update an existing table, we will use the `table` method on the `Schema` facade. Like the `create` method, the `table` method accepts two arguments, the name of the table and a `Closure` that receives an object we can use to add columns to the table:
- Schema::table('users', function ($table) {
- $table->string('email');
- });
+```php
+Schema::table('users', function ($table) {
+ $table->string('email');
+});
+```
#### Available Column Types
@@ -154,9 +170,11 @@ Command | Description
In addition to the column types listed above, there are several other column "modifiers" which you may use while adding the column. For example, to make the column "nullable", you may use the `nullable` method:
- Schema::table('users', function ($table) {
- $table->string('email')->nullable();
- });
+```php
+Schema::table('users', function ($table) {
+ $table->string('email')->nullable();
+});
+```
Below is a list of all the available column modifiers. This list does not include the [index modifiers](#creating-indexes):
@@ -174,24 +192,30 @@ Modifier | Description
The `change` method allows you to modify an existing column to a new type, or modify the column's attributes. For example, you may wish to increase the size of a string column. To see the `change` method in action, let's increase the size of the `name` column from 25 to 50:
- Schema::table('users', function ($table) {
- $table->string('name', 50)->change();
- });
+```php
+Schema::table('users', function ($table) {
+ $table->string('name', 50)->change();
+});
+```
We could also modify a column to be nullable:
- Schema::table('users', function ($table) {
- $table->string('name', 50)->nullable()->change();
- });
+```php
+Schema::table('users', function ($table) {
+ $table->string('name', 50)->nullable()->change();
+});
+```
#### Renaming columns
To rename a column, you may use the `renameColumn` method on the Schema builder:
- Schema::table('users', function ($table) {
- $table->renameColumn('from', 'to');
- });
+```php
+Schema::table('users', function ($table) {
+ $table->renameColumn('from', 'to');
+});
+```
> **NOTE:** Renaming columns in a table with a `enum` column is not currently supported.
@@ -200,34 +224,45 @@ To rename a column, you may use the `renameColumn` method on the Schema builder:
To drop a column, use the `dropColumn` method on the Schema builder:
- Schema::table('users', function ($table) {
- $table->dropColumn('votes');
- });
+```php
+Schema::table('users', function ($table) {
+ $table->dropColumn('votes');
+});
+```
You may drop multiple columns from a table by passing an array of column names to the `dropColumn` method:
- Schema::table('users', function ($table) {
- $table->dropColumn(['votes', 'avatar', 'location']);
- });
+```php
+Schema::table('users', function ($table) {
+ $table->dropColumn(['votes', 'avatar', 'location']);
+});
+```
### Creating indexes
The schema builder supports several types of indexes. First, let's look at an example that specifies a column's values should be unique. To create the index, we can simply chain the `unique` method onto the column definition:
- $table->string('email')->unique();
+```php
+$table->string('email')->unique();
+```
Alternatively, you may create the index after defining the column. For example:
- $table->unique('email');
+```php
+$table->unique('email');
+```
You may even pass an array of columns to an index method to create a compound index:
- $table->index(['account_id', 'created_at']);
-
+```php
+$table->index(['account_id', 'created_at']);
+```
In most cases you should specify a name for the index manually as the second argument, to avoid the system automatically generating one that is too long:
- $table->index(['account_id', 'created_at'], 'account_created');
+```php
+$table->index(['account_id', 'created_at'], 'account_created');
+```
#### Available index types
@@ -254,81 +289,95 @@ Command | Description
There is also support for creating foreign key constraints, which are used to force referential integrity at the database level. For example, let's define a `user_id` column on the `posts` table that references the `id` column on a `users` table:
- Schema::table('posts', function ($table) {
- $table->integer('user_id')->unsigned();
+```php
+Schema::table('posts', function ($table) {
+ $table->integer('user_id')->unsigned();
- $table->foreign('user_id')->references('id')->on('users');
- });
+ $table->foreign('user_id')->references('id')->on('users');
+});
+```
As before, you may specify a name for the constraint manually by passing a second argument to the `foreign` method:
- $table->foreign('user_id', 'user_foreign')
- ->references('id')
- ->on('users');
+```php
+$table->foreign('user_id', 'user_foreign')
+ ->references('id')
+ ->on('users');
+```
You may also specify the desired action for the "on delete" and "on update" properties of the constraint:
- $table->foreign('user_id')
- ->references('id')
- ->on('users')
- ->onDelete('cascade');
+```php
+$table->foreign('user_id')
+ ->references('id')
+ ->on('users')
+ ->onDelete('cascade');
+```
To drop a foreign key, you may use the `dropForeign` method. Foreign key constraints use the same naming convention as indexes. So, if one is not specified manually, we will concatenate the table name and the columns in the constraint then suffix the name with "_foreign":
- $table->dropForeign('posts_user_id_foreign');
+```php
+$table->dropForeign('posts_user_id_foreign');
+```
## Seeder structure
Like migration files, a seeder class only contains one method by default: `run`and should extend the `Seeder` class. The `run` method is called when the update process is executed. Within this method, you may insert data into your database however you wish. You may use the [query builder](../database/query) to manually insert data or you may use your [model classes](../database/model). In the example below, we'll create a new user using the `User` model inside the `run` method:
- 'user@example.com',
- 'login' => 'user',
- 'password' => 'password123',
- 'password_confirmation' => 'password123',
- 'first_name' => 'Actual',
- 'last_name' => 'Person',
- 'is_activated' => true
- ]);
- }
- }
+```php
+insert([
+ $user = User::create([
'email' => 'user@example.com',
'login' => 'user',
- [...]
+ 'password' => 'password123',
+ 'password_confirmation' => 'password123',
+ 'first_name' => 'Actual',
+ 'last_name' => 'Person',
+ 'is_activated' => true
]);
}
+}
+```
+
+Alternatively, the same can be achieved using the `Db::table` [query builder](../database/query) method:
+
+```php
+public function run()
+{
+ $user = Db::table('users')->insert([
+ 'email' => 'user@example.com',
+ 'login' => 'user',
+ [...]
+ ]);
+}
+```
### Calling additional seeders
Within the `DatabaseSeeder` class, you may use the `call` method to execute additional seed classes. Using the `call` method allows you to break up your database seeding into multiple files so that no single seeder class becomes overwhelmingly large. Simply pass the name of the seeder class you wish to run:
- /**
- * Run the database seeds.
- *
- * @return void
- */
- public function run()
- {
- Model::unguard();
-
- $this->call('Acme\Users\Updates\UserTableSeeder');
- $this->call('Acme\Users\Updates\PostsTableSeeder');
- $this->call('Acme\Users\Updates\CommentsTableSeeder');
- }
+```php
+/**
+ * Run the database seeds.
+ *
+ * @return void
+ */
+public function run()
+{
+ Model::unguard();
+
+ $this->call('Acme\Users\Updates\UserTableSeeder');
+ $this->call('Acme\Users\Updates\PostsTableSeeder');
+ $this->call('Acme\Users\Updates\CommentsTableSeeder');
+}
+```
diff --git a/database-traits.md b/database-traits.md
index b99e33fd..d5ae084d 100644
--- a/database-traits.md
+++ b/database-traits.md
@@ -19,49 +19,57 @@ Model traits are used to implement common functionality.
Hashed attributes are hashed immediately when the attribute is first set on the model. To hash attributes in your model, apply the `Winter\Storm\Database\Traits\Hashable` trait and declare a `$hashable` property with an array containing the attributes to hash.
- class User extends Model
- {
- use \Winter\Storm\Database\Traits\Hashable;
-
- /**
- * @var array List of attributes to hash.
- */
- protected $hashable = ['password'];
- }
+```php
+class User extends Model
+{
+ use \Winter\Storm\Database\Traits\Hashable;
+
+ /**
+ * @var array List of attributes to hash.
+ */
+ protected $hashable = ['password'];
+}
+```
## Purgeable
Purged attributes will not be saved to the database when a model is created or updated. To purge attributes in your model, apply the `Winter\Storm\Database\Traits\Purgeable` trait and declare a `$purgeable` property with an array containing the attributes to purge.
- class User extends Model
- {
- use \Winter\Storm\Database\Traits\Purgeable;
+```php
+class User extends Model
+{
+ use \Winter\Storm\Database\Traits\Purgeable;
- /**
- * @var array List of attributes to purge.
- */
- protected $purgeable = ['password_confirmation'];
- }
+ /**
+ * @var array List of attributes to purge.
+ */
+ protected $purgeable = ['password_confirmation'];
+}
+```
The defined attributes will be purged when the model is saved, before the [model events](#model-events) are triggered, including validation. Use the `getOriginalPurgeValue` to find a value that was purged.
- return $user->getOriginalPurgeValue('password_confirmation');
+```php
+return $user->getOriginalPurgeValue('password_confirmation');
+```
## Encryptable
Similar to the [hashable trait](#hashable), encrypted attributes are encrypted when set but also decrypted when an attribute is retrieved. To encrypt attributes in your model, apply the `Winter\Storm\Database\Traits\Encryptable` trait and declare a `$encryptable` property with an array containing the attributes to encrypt.
- class User extends Model
- {
- use \Winter\Storm\Database\Traits\Encryptable;
+```php
+class User extends Model
+{
+ use \Winter\Storm\Database\Traits\Encryptable;
- /**
- * @var array List of attributes to encrypt.
- */
- protected $encryptable = ['api_key', 'api_secret'];
- }
+ /**
+ * @var array List of attributes to encrypt.
+ */
+ protected $encryptable = ['api_key', 'api_secret'];
+}
+```
> **NOTE:** Encrypted attributes will be serialized and unserialized as a part of the encryption / decryption process. Do not make an attribute that is `encryptable` also [`jsonable`](model#standard-properties) at the same time as the `jsonable` process will attempt to decode a value that has already been unserialized by the encryptor.
@@ -70,37 +78,45 @@ Similar to the [hashable trait](#hashable), encrypted attributes are encrypted w
Slugs are meaningful codes that are commonly used in page URLs. To automatically generate a unique slug for your model, apply the `Winter\Storm\Database\Traits\Sluggable` trait and declare a `$slugs` property.
- class User extends Model
- {
- use \Winter\Storm\Database\Traits\Sluggable;
+```php
+class User extends Model
+{
+ use \Winter\Storm\Database\Traits\Sluggable;
- /**
- * @var array Generate slugs for these attributes.
- */
- protected $slugs = ['slug' => 'name'];
- }
+ /**
+ * @var array Generate slugs for these attributes.
+ */
+ protected $slugs = ['slug' => 'name'];
+}
+```
The `$slugs` property should be an array where the key is the destination column for the slug and the value is the source string used to generate the slug. In the above example, if the `name` column was set to **Cheyenne**, as a result the `slug` column would be set to **cheyenne**, **cheyenne-2**, or **cheyenne-3**, etc before the model is created.
To generate a slug from multiple sources, pass another array as the source value:
- protected $slugs = [
- 'slug' => ['first_name', 'last_name']
- ];
+```php
+protected $slugs = [
+ 'slug' => ['first_name', 'last_name']
+];
+```
Slugs are only generated when a model first created. To override or disable this functionality, simply set the slug attribute manually:
- $user = new User;
- $user->name = 'Remy';
- $user->slug = 'custom-slug';
- $user->save(); // Slug will not be generated
+```php
+$user = new User;
+$user->name = 'Remy';
+$user->slug = 'custom-slug';
+$user->save(); // Slug will not be generated
+```
Use the `slugAttributes` method to regenerate slugs when updating a model:
- $user = User::find(1);
- $user->slug = null;
- $user->slugAttributes();
- $user->save();
+```php
+$user = User::find(1);
+$user->slug = null;
+$user->slugAttributes();
+$user->save();
+```
### Sluggable with SoftDelete trait
@@ -110,76 +126,91 @@ You might want to prevent slug duplication when recovering soft deleted models.
Set the `$allowTrashedSlugs` attribute to `true` in order to take into account soft deleted records when generating new slugs.
- protected $allowTrashedSlugs = true;
+```php
+protected $allowTrashedSlugs = true;
+```
## Revisionable
Winter models can record the history of changes in values by storing revisions. To store revisions for your model, apply the `Winter\Storm\Database\Traits\Revisionable` trait and declare a `$revisionable` property with an array containing the attributes to monitor for changes. You also need to define a `$morphMany` [model relation](relations) called `revision_history` that refers to the `System\Models\Revision` class with the name `revisionable`, this is where the revision history data is stored.
- class User extends Model
- {
- use \Winter\Storm\Database\Traits\Revisionable;
-
- /**
- * @var array Monitor these attributes for changes.
- */
- protected $revisionable = ['name', 'email'];
-
- /**
- * @var array Relations
- */
- public $morphMany = [
- 'revision_history' => ['System\Models\Revision', 'name' => 'revisionable']
- ];
- }
+```php
+class User extends Model
+{
+ use \Winter\Storm\Database\Traits\Revisionable;
-By default 500 records will be kept, however this can be modified by declaring a `$revisionableLimit` property on the model with a new limit value.
+ /**
+ * @var array Monitor these attributes for changes.
+ */
+ protected $revisionable = ['name', 'email'];
/**
- * @var int Maximum number of revision records to keep.
+ * @var array Relations
*/
- public $revisionableLimit = 8;
+ public $morphMany = [
+ 'revision_history' => ['System\Models\Revision', 'name' => 'revisionable']
+ ];
+}
+```
+
+By default 500 records will be kept, however this can be modified by declaring a `$revisionableLimit` property on the model with a new limit value.
+
+```php
+/**
+ * @var int Maximum number of revision records to keep.
+ */
+public $revisionableLimit = 8;
+```
The revision history can be accessed like any other relation:
- $history = User::find(1)->revision_history;
+```php
+$history = User::find(1)->revision_history;
- foreach ($history as $record) {
- echo $record->field . ' updated ';
- echo 'from ' . $record->old_value;
- echo 'to ' . $record->new_value;
- }
+foreach ($history as $record) {
+ echo $record->field . ' updated ';
+ echo 'from ' . $record->old_value;
+ echo 'to ' . $record->new_value;
+}
+```
The revision record optionally supports a user relationship using the `user_id` attribute. You may include a `getRevisionableUser` method in your model to keep track of the user that made the modification.
- public function getRevisionableUser()
- {
- return BackendAuth::getUser()->id;
- }
+```php
+public function getRevisionableUser()
+{
+ return BackendAuth::getUser()->id;
+}
+```
## Sortable
Sorted models will store a number value in `sort_order` which maintains the sort order of each individual model in a collection. To store a sort order for your models, apply the `Winter\Storm\Database\Traits\Sortable` trait and ensure that your schema has a column defined for it to use (example: `$table->integer('sort_order')->default(0);`).
- class User extends Model
- {
- use \Winter\Storm\Database\Traits\Sortable;
- }
-
+```php
+class User extends Model
+{
+ use \Winter\Storm\Database\Traits\Sortable;
+}
+```
You may modify the key name used to identify the sort order by defining the `SORT_ORDER` constant:
- const SORT_ORDER = 'my_sort_order_column';
+```php
+const SORT_ORDER = 'my_sort_order_column';
+```
Use the `setSortableOrder` method to set the orders on a single record or multiple records.
- // Sets the order of the user to 1...
- $user->setSortableOrder($user->id, 1);
+```php
+// Sets the order of the user to 1...
+$user->setSortableOrder($user->id, 1);
- // Sets the order of records 1, 2, 3 to 3, 2, 1 respectively...
- $user->setSortableOrder([1, 2, 3], [3, 2, 1]);
+// Sets the order of records 1, 2, 3 to 3, 2, 1 respectively...
+$user->setSortableOrder([1, 2, 3], [3, 2, 1]);
+```
> **NOTE:** If adding this trait to a model where data (rows) already existed previously, the data set may need to be initialized before this trait will work correctly. To do so, either manually update each row's `sort_order` column or run a query against the data to copy the record's `id` column to the `sort_order` column (ex. `UPDATE myvendor_myplugin_mymodelrecords SET sort_order = id`).
@@ -188,95 +219,121 @@ Use the `setSortableOrder` method to set the orders on a single record or multip
A simple tree model will use the `parent_id` column maintain a parent and child relationship between models. To use the simple tree, apply the `Winter\Storm\Database\Traits\SimpleTree` trait.
- class Category extends Model
- {
- use \Winter\Storm\Database\Traits\SimpleTree;
- }
+```php
+class Category extends Model
+{
+ use \Winter\Storm\Database\Traits\SimpleTree;
+}
+```
This trait will automatically inject two [model relations](../database/relations) called `parent` and `children`, it is the equivalent of the following definitions:
- public $belongsTo = [
- 'parent' => ['User', 'key' => 'parent_id'],
- ];
+```php
+public $belongsTo = [
+ 'parent' => ['User', 'key' => 'parent_id'],
+];
- public $hasMany = [
- 'children' => ['User', 'key' => 'parent_id'],
- ];
+public $hasMany = [
+ 'children' => ['User', 'key' => 'parent_id'],
+];
+```
You may modify the key name used to identify the parent by defining the `PARENT_ID` constant:
- const PARENT_ID = 'my_parent_column';
+```php
+const PARENT_ID = 'my_parent_column';
+```
Collections of models that use this trait will return the type of `Winter\Storm\Database\TreeCollection` which adds the `toNested` method. To build an eager loaded tree structure, return the records with the relations eager loaded.
- Category::all()->toNested();
+```php
+Category::all()->toNested();
+```
### Rendering
In order to render all levels of items and their children, you can use recursive processing
- {% macro renderChildren(item) %}
- {% import _self as SELF %}
- {% if item.children is not empty %}
-
- {% for child in item.children %}
- {{ child.name }}{{ SELF.renderChildren(child) | raw }}
- {% endfor %}
-
- {% endif %}
- {% endmacro %}
-
+```twig
+{% macro renderChildren(item) %}
{% import _self as SELF %}
- {{ SELF.renderChildren(category) | raw }}
+ {% if item.children is not empty %}
+
+ {% for child in item.children %}
+ {{ child.name }}{{ SELF.renderChildren(child) | raw }}
+ {% endfor %}
+
+ {% endif %}
+{% endmacro %}
+
+{% import _self as SELF %}
+{{ SELF.renderChildren(category) | raw }}
+```
## Nested Tree
The [nested set model](https://en.wikipedia.org/wiki/Nested_set_model) is an advanced technique for maintaining hierachies among models using `parent_id`, `nest_left`, `nest_right`, and `nest_depth` columns. To use a nested set model, apply the `Winter\Storm\Database\Traits\NestedTree` trait. All of the features of the `SimpleTree` trait are inherently available in this model.
- class Category extends Model
- {
- use \Winter\Storm\Database\Traits\NestedTree;
- }
+```php
+class Category extends Model
+{
+ use \Winter\Storm\Database\Traits\NestedTree;
+}
+```
### Creating a root node
By default, all nodes are created as roots:
- $root = Category::create(['name' => 'Root category']);
+```php
+$root = Category::create(['name' => 'Root category']);
+```
Alternatively, you may find yourself in the need of converting an existing node into a root node:
- $node->makeRoot();
+```php
+$node->makeRoot();
+```
You may also nullify it's `parent_id` column which works the same as `makeRoot'.
- $node->parent_id = null;
- $node->save();
+```php
+$node->parent_id = null;
+$node->save();
+```
### Inserting nodes
You can insert new nodes directly by the relation:
- $child1 = $root->children()->create(['name' => 'Child 1']);
+```php
+$child1 = $root->children()->create(['name' => 'Child 1']);
+```
Or use the `makeChildOf` method for existing nodes:
- $child2 = Category::create(['name' => 'Child 2']);
- $child2->makeChildOf($root);
+```php
+$child2 = Category::create(['name' => 'Child 2']);
+$child2->makeChildOf($root);
+```
### Deleting nodes
When a node is deleted with the `delete` method, all descendants of the node will also be deleted. Note that the delete [model events](../database/model#model-events) will not be fired for the child models.
- $child1->delete();
+```php
+$child1->delete();
+```
### Getting the nesting level of a node
The `getLevel` method will return current nesting level, or depth, of a node.
- // 0 when root
- $node->getLevel()
+```php
+// 0 when root
+$node->getLevel()
+```
### Moving nodes around
@@ -294,39 +351,45 @@ There are several methods for moving nodes around:
Winter models uses the built-in [Validator class](../services/validation). The validation rules are defined in the model class as a property named `$rules` and the class must use the trait `Winter\Storm\Database\Traits\Validation`:
- class User extends Model
- {
- use \Winter\Storm\Database\Traits\Validation;
+```php
+class User extends Model
+{
+ use \Winter\Storm\Database\Traits\Validation;
- public $rules = [
- 'name' => 'required|between:4,16',
- 'email' => 'required|email',
- 'password' => 'required|alpha_num|between:4,8|confirmed',
- 'password_confirmation' => 'required|alpha_num|between:4,8'
- ];
- }
+ public $rules = [
+ 'name' => 'required|between:4,16',
+ 'email' => 'required|email',
+ 'password' => 'required|alpha_num|between:4,8|confirmed',
+ 'password_confirmation' => 'required|alpha_num|between:4,8'
+ ];
+}
+```
> **NOTE**: You're free to use the [array syntax](../services/validation#validating-arrays) for validation rules as well.
- class User extends Model
- {
- use \Winter\Storm\Database\Traits\Validation;
+```php
+class User extends Model
+{
+ use \Winter\Storm\Database\Traits\Validation;
- public $rules = [
- 'links.*.url' => 'required|url',
- 'links.*.anchor' => 'required'
- ];
- }
+ public $rules = [
+ 'links.*.url' => 'required|url',
+ 'links.*.anchor' => 'required'
+ ];
+}
+```
Models validate themselves automatically when the `save` method is called.
- $user = new User;
- $user->name = 'Actual Person';
- $user->email = 'a.person@example.com';
- $user->password = 'passw0rd';
+```php
+$user = new User;
+$user->name = 'Actual Person';
+$user->email = 'a.person@example.com';
+$user->password = 'passw0rd';
- // Returns false if model is invalid
- $success = $user->save();
+// Returns false if model is invalid
+$success = $user->save();
+```
> **NOTE:** You can also validate a model at any time using the `validate` method.
@@ -342,41 +405,47 @@ When a model fails to validate, a `Illuminate\Support\MessageBag` object is atta
The `forceSave` method validates the model and saves regardless of whether or not there are validation errors.
- $user = new User;
+```php
+$user = new User;
- // Creates a user without validation
- $user->forceSave();
+// Creates a user without validation
+$user->forceSave();
+```
### Custom error messages
Just like the Validator class, you can set custom error messages using the [same syntax](../services/validation#custom-error-messages).
- class User extends Model
- {
- public $customMessages = [
- 'required' => 'The :attribute field is required.',
- ...
- ];
- }
+```php
+class User extends Model
+{
+ public $customMessages = [
+ 'required' => 'The :attribute field is required.',
+ ...
+ ];
+}
+```
You can also add custom error messages to the array syntax for validation rules as well.
- class User extends Model
- {
- use \Winter\Storm\Database\Traits\Validation;
+```php
+class User extends Model
+{
+ use \Winter\Storm\Database\Traits\Validation;
- public $rules = [
- 'links.*.url' => 'required|url',
- 'links.*.anchor' => 'required'
- ];
+ public $rules = [
+ 'links.*.url' => 'required|url',
+ 'links.*.anchor' => 'required'
+ ];
- public $customMessages = [
- 'links.*.url.required' => 'The url is required',
- 'links.*.url.*' => 'The url needs to be a valid url'
- 'links.*.anchor.required' => 'The anchor text is required',
- ];
- }
+ public $customMessages = [
+ 'links.*.url.required' => 'The url is required',
+ 'links.*.url.*' => 'The url needs to be a valid url'
+ 'links.*.anchor.required' => 'The anchor text is required',
+ ];
+}
+```
In the above example you can write custom error messages to specific validation rules (here we used: `required`). Or you can use a `*` to select everything else (here we added a custom message to the `url` validation rule using the `*`).
@@ -385,26 +454,30 @@ In the above example you can write custom error messages to specific validation
You may also set custom attribute names with the `$attributeNames` array.
- class User extends Model
- {
- public $attributeNames = [
- 'email' => 'Email Address',
- ...
- ];
- }
+```php
+class User extends Model
+{
+ public $attributeNames = [
+ 'email' => 'Email Address',
+ ...
+ ];
+}
+```
### Dynamic validation rules
You can apply rules dynamically by overriding the `beforeValidate` [model event](../database/model#events) method. Here we check if the `is_remote` attribute is `false` and then dynamically set the `latitude` and `longitude` attributes to be required fields.
- public function beforeValidate()
- {
- if (!$this->is_remote) {
- $this->rules['latitude'] = 'required';
- $this->rules['longitude'] = 'required';
- }
+```php
+public function beforeValidate()
+{
+ if (!$this->is_remote) {
+ $this->rules['latitude'] = 'required';
+ $this->rules['longitude'] = 'required';
}
+}
+```
### Custom validation rules
@@ -416,26 +489,32 @@ You can also create custom validation rules the [same way](../services/validatio
When soft deleting a model, it is not actually removed from your database. Instead, a `deleted_at` timestamp is set on the record. To enable soft deletes for a model, apply the `Winter\Storm\Database\Traits\SoftDelete` trait to the model and add the deleted_at column to your `$dates` property:
- class User extends Model
- {
- use \Winter\Storm\Database\Traits\SoftDelete;
+```php
+class User extends Model
+{
+ use \Winter\Storm\Database\Traits\SoftDelete;
- protected $dates = ['deleted_at'];
- }
+ protected $dates = ['deleted_at'];
+}
+```
To add a `deleted_at` column to your table, you may use the `softDeletes` method from a migration:
- Schema::table('posts', function ($table) {
- $table->softDeletes();
- });
+```php
+Schema::table('posts', function ($table) {
+ $table->softDeletes();
+});
+```
Now, when you call the `delete` method on the model, the `deleted_at` column will be set to the current timestamp. When querying a model that uses soft deletes, the "deleted" models will not be included in query results.
To determine if a given model instance has been soft deleted, use the `trashed` method:
- if ($user->trashed()) {
- //
- }
+```php
+if ($user->trashed()) {
+ //
+}
+```
### Querying soft deleted models
@@ -444,62 +523,78 @@ To determine if a given model instance has been soft deleted, use the `trashed`
As noted above, soft deleted models will automatically be excluded from query results. However, you may force soft deleted models to appear in a result set using the `withTrashed` method on the query:
- $users = User::withTrashed()->where('account_id', 1)->get();
+```php
+$users = User::withTrashed()->where('account_id', 1)->get();
+```
The `withTrashed` method may also be used on a [relationship](relations) query:
- $flight->history()->withTrashed()->get();
+```php
+$flight->history()->withTrashed()->get();
+```
#### Retrieving only soft deleted models
The `onlyTrashed` method will retrieve **only** soft deleted models:
- $users = User::onlyTrashed()->where('account_id', 1)->get();
+```php
+$users = User::onlyTrashed()->where('account_id', 1)->get();
+```
#### Restoring soft deleted models
Sometimes you may wish to "un-delete" a soft deleted model. To restore a soft deleted model into an active state, use the `restore` method on a model instance:
- $user->restore();
+```php
+$user->restore();
+```
You may also use the `restore` method in a query to quickly restore multiple models:
- // Restore a single model instance...
- User::withTrashed()->where('account_id', 1)->restore();
+```php
+// Restore a single model instance...
+User::withTrashed()->where('account_id', 1)->restore();
- // Restore all related models...
- $user->posts()->restore();
+// Restore all related models...
+$user->posts()->restore();
+```
#### Permanently deleting models
Sometimes you may need to truly remove a model from your database. To permanently remove a soft deleted model from the database, use the `forceDelete` method:
- // Force deleting a single model instance...
- $user->forceDelete();
+```php
+// Force deleting a single model instance...
+$user->forceDelete();
- // Force deleting all related models...
- $user->posts()->forceDelete();
+// Force deleting all related models...
+$user->posts()->forceDelete();
+```
### Soft deleting relations
When two related models have soft deletes enabled, you can cascade the delete event by defining the `softDelete` option in the [relation definition](../database/relations#detailed-relationships). In this example, if the user model is soft deleted, the comments belonging to that user will also be soft deleted.
- class User extends Model
- {
- use \Winter\Storm\Database\Traits\SoftDelete;
+```php
+class User extends Model
+{
+ use \Winter\Storm\Database\Traits\SoftDelete;
- public $hasMany = [
- 'comments' => ['Acme\Blog\Models\Comment', 'softDelete' => true]
- ];
- }
+ public $hasMany = [
+ 'comments' => ['Acme\Blog\Models\Comment', 'softDelete' => true]
+ ];
+}
+```
> **NOTE:** If the related model does not use the soft delete trait, it will be treated the same as the `delete` option and deleted permanently.
Under these same conditions, when the primary model is restored, all the related models that use the `softDelete` option will also be restored.
- // Restore the user and comments
- $user->restore();
+```php
+// Restore the user and comments
+$user->restore();
+```
### Soft Delete with Sluggable trait
@@ -512,12 +607,14 @@ In order to make the model restoration less painful [checkout the Sluggable sect
Nullable attributes are set to `NULL` when left empty. To nullify attributes in your model, apply the `Winter\Storm\Database\Traits\Nullable` trait and declare a `$nullable` property with an array containing the attributes to nullify.
- class Product extends Model
- {
- use \Winter\Storm\Database\Traits\Nullable;
+```php
+class Product extends Model
+{
+ use \Winter\Storm\Database\Traits\Nullable;
- /**
- * @var array Nullable attributes.
- */
- protected $nullable = ['sku'];
- }
+ /**
+ * @var array Nullable attributes.
+ */
+ protected $nullable = ['sku'];
+}
+```
diff --git a/events-introduction.md b/events-introduction.md
index df369c5f..5f6b8dc9 100644
--- a/events-introduction.md
+++ b/events-introduction.md
@@ -19,31 +19,41 @@
The `Event` class provides a simple observer implementation, allowing you to subscribe and listen for events in your application. For example, you may listen for when a user signs in and update their last login date.
- Event::listen('auth.login', function($user) {
- $user->last_login = new DateTime;
- $user->save();
- });
+```php
+Event::listen('auth.login', function($user) {
+ $user->last_login = new DateTime;
+ $user->save();
+});
+```
This is event made available with the `Event::fire` method which is called as part of the user sign in logic, thereby making the logic extensible.
- Event::fire('auth.login', [$user]);
+```php
+Event::fire('auth.login', [$user]);
+```
## Subscribing to events
The `Event::listen` method is primarily used to subscribe to events and can be done from anywhere within your application code. The first argument is the event name.
- Event::listen('acme.blog.myevent', ...);
+```php
+Event::listen('acme.blog.myevent', ...);
+```
The second argument can be a closure that specifies what should happen when the event is fired. The closure can accept optional some arguments, provided by [the firing event](#events-firing).
- Event::listen('acme.blog.myevent', function($arg1, $arg2) {
- // Do something
- });
+```php
+Event::listen('acme.blog.myevent', function($arg1, $arg2) {
+ // Do something
+});
+```
You may also pass a reference to any callable object or a [dedicated event class](#using-classes-as-listeners) and this will be used instead.
- Event::listen('auth.login', [$this, 'LoginHandler']);
+```php
+Event::listen('auth.login', [$this, 'LoginHandler']);
+```
> **NOTE**: The callable method can choose to specify all, some or none of the arguments. Either way the event will not throw any errors unless it specifies too many.
@@ -52,21 +62,25 @@ You may also pass a reference to any callable object or a [dedicated event class
The most common place is the `boot` method of a [Plugin registration file](../plugin/registration#registration-methods).
- class Plugin extends PluginBase
- {
- [...]
+```php
+class Plugin extends PluginBase
+{
+ [...]
- public function boot()
- {
- Event::listen(...);
- }
+ public function boot()
+ {
+ Event::listen(...);
}
+}
+```
Alternatively, plugins can supply a file named **init.php** in the plugin directory that you can use to place event registration logic. For example:
-
### Halting events
Sometimes you may wish to stop the propagation of an event to other listeners. You may do so using by returning `false` from your listener:
- Event::listen('auth.login', function($event) {
- // Handle the event
+```php
+Event::listen('auth.login', function($event) {
+ // Handle the event
- return false;
- });
+ return false;
+});
+```
### Wildcard listeners
@@ -99,74 +117,98 @@ When registering an event listener, you may use asterisks to specify wildcard li
The following listener will handle all events that begin with `foo.`.
- Event::listen('foo.*', function($event, $params) {
- // Handle the event...
- });
+```php
+Event::listen('foo.*', function($event, $params) {
+ // Handle the event...
+});
+```
You may use the `Event::firing` method to determine exactly which event was fired:
- Event::listen('foo.*', function($event, $params) {
- if (Event::firing() === 'foo.bar') {
- // ...
- }
- });
+```php
+Event::listen('foo.*', function($event, $params) {
+ if (Event::firing() === 'foo.bar') {
+ // ...
+ }
+});
+```
## Firing events
You may use the `Event::fire` method anywhere in your code to make the logic extensible. This means other developers, or even your own internal code, can "hook" to this point of code and inject specific logic. The first argument of should be the event name.
- Event::fire('myevent')
+```php
+Event::fire('myevent')
+```
It is always a good idea to prefix event names with your plugin namespace code, this will prevent collisions with other plugins.
- Event::fire('acme.blog.myevent');
+```php
+Event::fire('acme.blog.myevent');
+```
The second argument is an array of values that will be passed as arguments to [the event listener](#events-subscribing) subscribing to it.
- Event::fire('acme.blog.myevent', [$arg1, $arg2]);
+```php
+Event::fire('acme.blog.myevent', [$arg1, $arg2]);
+```
The third argument specifies whether the event should be a [halting event](#subscribing-halting), meaning it should halt if a "non null" value is returned. This argument is set to false by default.
- Event::fire('acme.blog.myevent', [...], true);
+```php
+Event::fire('acme.blog.myevent', [...], true);
+```
If the event is halting, the first value returned with be captured.
- // Single result, event halted
- $result = Event::fire('acme.blog.myevent', [...], true);
+```php
+// Single result, event halted
+$result = Event::fire('acme.blog.myevent', [...], true);
+```
Otherwise it returns a collection of all the responses from all the events in the form of an array.
- // Multiple results, all events fired
- $results = Event::fire('acme.blog.myevent', [...]);
+```php
+// Multiple results, all events fired
+$results = Event::fire('acme.blog.myevent', [...]);
+```
## Passing arguments by reference
When processing or filtering over a value passed to an event, you may prefix the variable with `&` to pass it by reference. This allows multiple listeners to manipulate the result and pass it to the next one.
- Event::fire('cms.processContent', [&$content]);
+```php
+Event::fire('cms.processContent', [&$content]);
+```
When listening for the event, the argument also needs to be declared with the `&` symbol in the closure definition. In the example below, the `$content` variable will have "AB" appended to the result.
- Event::listen('cms.processContent', function (&$content) {
- $content = $content . 'A';
- });
+```php
+Event::listen('cms.processContent', function (&$content) {
+ $content = $content . 'A';
+});
- Event::listen('cms.processContent', function (&$content) {
- $content = $content . 'B';
- });
+Event::listen('cms.processContent', function (&$content) {
+ $content = $content . 'B';
+});
+```
### Queued events
Firing events can be deferred in [conjunction with queues](../services/queues). Use the `Event::queue` method to "queue" the event for firing but not fire it immediately.
- Event::queue('foo', [$user]);
+```php
+Event::queue('foo', [$user]);
+```
You may use the `Event::flush` method to flush all queued events.
- Event::flush('foo');
+```php
+Event::flush('foo');
+```
## Using classes as listeners
@@ -178,87 +220,105 @@ In some cases, you may wish to use a class to handle an event rather than a Clos
The event class can be registered with the `Event::listen` method like any other, passing the class name as a string.
- Event::listen('auth.login', 'LoginHandler');
+```php
+Event::listen('auth.login', 'LoginHandler');
+```
By default, the `handle` method on the `LoginHandler` class will be called:
- class LoginHandler
+```php
+class LoginHandler
+{
+ public function handle($data)
{
- public function handle($data)
- {
- // ...
- }
+ // ...
}
+}
+```
If you do not wish to use the default `handle` method, you may specify the method name that should be subscribed.
- Event::listen('auth.login', 'LoginHandler@onLogin');
+```php
+Event::listen('auth.login', 'LoginHandler@onLogin');
+```
### Subscribe to entire class
Event subscribers are classes that may subscribe to multiple events from within the class itself. Subscribers should define a `subscribe` method, which will be passed an event dispatcher instance.
- class UserEventHandler
+```php
+class UserEventHandler
+{
+ /**
+ * Handle user login events.
+ */
+ public function userLogin($event)
+ {
+ // ...
+ }
+
+ /**
+ * Handle user logout events.
+ */
+ public function userLogout($event)
{
- /**
- * Handle user login events.
- */
- public function userLogin($event)
- {
- // ...
- }
-
- /**
- * Handle user logout events.
- */
- public function userLogout($event)
- {
- // ...
- }
-
- /**
- * Register the listeners for the subscriber.
- *
- * @param Illuminate\Events\Dispatcher $events
- * @return array
- */
- public function subscribe($events)
- {
- $events->listen('auth.login', 'UserEventHandler@userLogin');
-
- $events->listen('auth.logout', 'UserEventHandler@userLogout');
- }
+ // ...
}
+ /**
+ * Register the listeners for the subscriber.
+ *
+ * @param Illuminate\Events\Dispatcher $events
+ * @return array
+ */
+ public function subscribe($events)
+ {
+ $events->listen('auth.login', 'UserEventHandler@userLogin');
+
+ $events->listen('auth.logout', 'UserEventHandler@userLogout');
+ }
+}
+```
+
Once the subscriber has been defined, it may be registered with the `Event::subscribe` method.
- Event::subscribe(new UserEventHandler);
+```php
+Event::subscribe(new UserEventHandler);
+```
You may also use the [Application IoC container](../services/application) to resolve your subscriber. To do so, simply pass the name of your subscriber to the `subscribe` method.
- Event::subscribe('UserEventHandler');
+```php
+Event::subscribe('UserEventHandler');
+```
## Event emitter trait
Sometimes you want to bind events to a single instance of an object. You may use an alternative event system by implementing the `Winter\Storm\Support\Traits\Emitter` trait inside your class.
- class UserManager
- {
- use \Winter\Storm\Support\Traits\Emitter;
- }
+```php
+class UserManager
+{
+ use \Winter\Storm\Support\Traits\Emitter;
+}
+```
This trait provides a method to listen for events with `bindEvent`.
- $manager = new UserManager;
- $manager->bindEvent('user.beforeRegister', function($user) {
- // Check if the $user is a spammer
- });
+```php
+$manager = new UserManager;
+$manager->bindEvent('user.beforeRegister', function($user) {
+ // Check if the $user is a spammer
+});
+```
The `fireEvent` method is used to fire events.
- $manager = new UserManager;
- $manager->fireEvent('user.beforeRegister', [$user]);
+```php
+$manager = new UserManager;
+$manager->fireEvent('user.beforeRegister', [$user]);
+```
These events will only occur on the local object as opposed to globally.