From afbb5da3b8ecb56574f2c477e123991d8216f447 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Mar 2023 05:27:51 +0000 Subject: [PATCH 1/3] Bump nokogiri from 1.8.4 to 1.8.5 Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.8.4 to 1.8.5. - [Release notes](https://github.com/sparklemotion/nokogiri/releases) - [Changelog](https://github.com/sparklemotion/nokogiri/blob/main/CHANGELOG.md) - [Commits](https://github.com/sparklemotion/nokogiri/compare/v1.8.4...v1.8.5) --- updated-dependencies: - dependency-name: nokogiri dependency-type: indirect ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index e2337cd348..6690698e29 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -246,7 +246,7 @@ GEM jekyll-seo-tag (~> 2.1) minitest (5.11.3) multipart-post (2.0.0) - nokogiri (1.8.4) + nokogiri (1.8.5) mini_portile2 (~> 2.3.0) octokit (4.12.0) sawyer (~> 0.8.0, >= 0.5.3) From bdca09419451e2a1d2ef9c9eda746b06e1d04e4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20Bre=C3=9Fler?= Date: Mon, 29 Jan 2024 11:48:32 +0100 Subject: [PATCH 2/3] B3-IT: Update/change php developer guide and frontend developer guide, update links --- Gemfile | 1 + Gemfile.lock | 429 +++--- _config.yml | 21 +- common/css/stylesheet.css | 885 +++++++---- guides/m1x/magefordev/mage-for-dev-1.html | 1295 ++++++++++------- guides/m1x/magefordev/mage-for-dev-2.html | 718 ++++++--- guides/m1x/magefordev/mage-for-dev-3.html | 903 ++++++++---- guides/m1x/magefordev/mage-for-dev-4.html | 1220 ++++++++++------ guides/m1x/magefordev/mage-for-dev-5.html | 1168 +++++++++------ guides/m1x/magefordev/mage-for-dev-6.html | 1089 +++++++++----- guides/m1x/magefordev/mage-for-dev-7.html | 686 ++++++--- guides/m1x/magefordev/mage-for-dev-8.html | 618 +++++--- .../magefordev/mage-for-frontend-dev-1.html | 421 ++++++ .../magefordev/mage-for-frontend-dev-10.html | 335 +++++ .../magefordev/mage-for-frontend-dev-2.html | 350 +++++ .../magefordev/mage-for-frontend-dev-3.html | 380 +++++ .../magefordev/mage-for-frontend-dev-4.html | 348 +++++ .../magefordev/mage-for-frontend-dev-5.html | 351 +++++ .../magefordev/mage-for-frontend-dev-6.html | 569 ++++++++ .../magefordev/mage-for-frontend-dev-7.html | 646 ++++++++ .../magefordev/mage-for-frontend-dev-8.html | 378 +++++ .../magefordev/mage-for-frontend-dev-9.html | 440 ++++++ index.html | 37 +- 23 files changed, 10015 insertions(+), 3273 deletions(-) create mode 100644 guides/m1x/magefordev/mage-for-frontend-dev-1.html create mode 100644 guides/m1x/magefordev/mage-for-frontend-dev-10.html create mode 100644 guides/m1x/magefordev/mage-for-frontend-dev-2.html create mode 100644 guides/m1x/magefordev/mage-for-frontend-dev-3.html create mode 100644 guides/m1x/magefordev/mage-for-frontend-dev-4.html create mode 100644 guides/m1x/magefordev/mage-for-frontend-dev-5.html create mode 100644 guides/m1x/magefordev/mage-for-frontend-dev-6.html create mode 100644 guides/m1x/magefordev/mage-for-frontend-dev-7.html create mode 100644 guides/m1x/magefordev/mage-for-frontend-dev-8.html create mode 100644 guides/m1x/magefordev/mage-for-frontend-dev-9.html diff --git a/Gemfile b/Gemfile index 1cb3ba0f00..46c0e1bf71 100644 --- a/Gemfile +++ b/Gemfile @@ -3,6 +3,7 @@ source 'https://rubygems.org' gem 'github-pages' gem 'jekyll-last-modified-at' gem 'devdocs', :git => 'https://github.com/magento-devdocs/devdocs-theme.git', :branch => 'devdocs-stable' +gem "webrick", "~> 1.8" gem 'wdm' if Gem.win_platform? diff --git a/Gemfile.lock b/Gemfile.lock index 6690698e29..41e3c20189 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/magento-devdocs/devdocs-theme.git - revision: f864973ba8a063a7ad3da4ecfe47fa978a230f17 + revision: edabd815ee3ead3efffe983be5cb6961c3543aae branch: devdocs-stable specs: devdocs (0.0.1) @@ -9,283 +9,332 @@ GIT GEM remote: https://rubygems.org/ specs: - activesupport (4.2.10) - i18n (~> 0.7) - minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) - tzinfo (~> 1.1) - addressable (2.5.2) - public_suffix (>= 2.0.2, < 4.0) - algolia_html_extractor (2.6.1) + Ascii85 (1.1.0) + activesupport (7.1.2) + base64 + bigdecimal + concurrent-ruby (~> 1.0, >= 1.0.2) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + minitest (>= 5.1) + mutex_m + tzinfo (~> 2.0) + addressable (2.8.6) + public_suffix (>= 2.0.2, < 6.0) + afm (0.2.2) + algolia_html_extractor (2.6.4) json (~> 2.0) - nokogiri (~> 1.8.2) - algoliasearch (1.23.2) + nokogiri (~> 1.10) + algoliasearch (1.27.5) httpclient (~> 2.8, >= 2.8.3) json (>= 1.5.1) + async (2.8.0) + console (~> 1.10) + fiber-annotation + io-event (~> 1.1) + timers (~> 4.1) + base64 (0.2.0) + bigdecimal (3.1.5) coffee-script (2.4.1) coffee-script-source execjs coffee-script-source (1.11.1) colorator (1.1.0) - colorize (0.8.1) - commonmarker (0.17.13) - ruby-enum (~> 0.5) - concurrent-ruby (1.0.5) - dnsruby (1.61.2) - addressable (~> 2.5) - em-websocket (0.5.1) + commonmarker (0.23.10) + concurrent-ruby (1.2.2) + connection_pool (2.4.1) + console (1.23.3) + fiber-annotation + fiber-local + dnsruby (1.70.0) + simpleidn (~> 0.2.1) + drb (2.2.0) + ruby2_keywords + em-websocket (0.5.3) eventmachine (>= 0.12.9) - http_parser.rb (~> 0.6.0) - ethon (0.11.0) - ffi (>= 1.3.0) + http_parser.rb (~> 0) + ethon (0.16.0) + ffi (>= 1.15.0) eventmachine (1.2.7) - execjs (2.7.0) - faraday (0.15.3) - multipart-post (>= 1.2, < 3) - ffi (1.9.25) + execjs (2.9.1) + faraday (2.9.0) + faraday-net_http (>= 2.0, < 3.2) + faraday-net_http (3.1.0) + net-http + ffi (1.16.3-x64-mingw-ucrt) + fiber-annotation (0.2.0) + fiber-local (1.0.0) filesize (0.2.0) forwardable-extended (2.6.0) - gemoji (3.0.0) - github-pages (192) - activesupport (= 4.2.10) - github-pages-health-check (= 1.8.1) - jekyll (= 3.7.4) - jekyll-avatar (= 0.6.0) + gemoji (3.0.1) + github-pages (228) + github-pages-health-check (= 1.17.9) + jekyll (= 3.9.3) + jekyll-avatar (= 0.7.0) jekyll-coffeescript (= 1.1.1) - jekyll-commonmark-ghpages (= 0.1.5) + jekyll-commonmark-ghpages (= 0.4.0) jekyll-default-layout (= 0.1.4) - jekyll-feed (= 0.10.0) + jekyll-feed (= 0.15.1) jekyll-gist (= 1.5.0) - jekyll-github-metadata (= 2.9.4) - jekyll-mentions (= 1.4.1) - jekyll-optional-front-matter (= 0.3.0) + jekyll-github-metadata (= 2.13.0) + jekyll-include-cache (= 0.2.1) + jekyll-mentions (= 1.6.0) + jekyll-optional-front-matter (= 0.3.2) jekyll-paginate (= 1.1.0) - jekyll-readme-index (= 0.2.0) - jekyll-redirect-from (= 0.14.0) - jekyll-relative-links (= 0.5.3) - jekyll-remote-theme (= 0.3.1) + jekyll-readme-index (= 0.3.0) + jekyll-redirect-from (= 0.16.0) + jekyll-relative-links (= 0.6.1) + jekyll-remote-theme (= 0.4.3) jekyll-sass-converter (= 1.5.2) - jekyll-seo-tag (= 2.5.0) - jekyll-sitemap (= 1.2.0) - jekyll-swiss (= 0.4.0) - jekyll-theme-architect (= 0.1.1) - jekyll-theme-cayman (= 0.1.1) - jekyll-theme-dinky (= 0.1.1) - jekyll-theme-hacker (= 0.1.1) - jekyll-theme-leap-day (= 0.1.1) - jekyll-theme-merlot (= 0.1.1) - jekyll-theme-midnight (= 0.1.1) - jekyll-theme-minimal (= 0.1.1) - jekyll-theme-modernist (= 0.1.1) - jekyll-theme-primer (= 0.5.3) - jekyll-theme-slate (= 0.1.1) - jekyll-theme-tactile (= 0.1.1) - jekyll-theme-time-machine (= 0.1.1) - jekyll-titles-from-headings (= 0.5.1) - jemoji (= 0.10.1) - kramdown (= 1.17.0) - liquid (= 4.0.0) - listen (= 3.1.5) + jekyll-seo-tag (= 2.8.0) + jekyll-sitemap (= 1.4.0) + jekyll-swiss (= 1.0.0) + jekyll-theme-architect (= 0.2.0) + jekyll-theme-cayman (= 0.2.0) + jekyll-theme-dinky (= 0.2.0) + jekyll-theme-hacker (= 0.2.0) + jekyll-theme-leap-day (= 0.2.0) + jekyll-theme-merlot (= 0.2.0) + jekyll-theme-midnight (= 0.2.0) + jekyll-theme-minimal (= 0.2.0) + jekyll-theme-modernist (= 0.2.0) + jekyll-theme-primer (= 0.6.0) + jekyll-theme-slate (= 0.2.0) + jekyll-theme-tactile (= 0.2.0) + jekyll-theme-time-machine (= 0.2.0) + jekyll-titles-from-headings (= 0.5.3) + jemoji (= 0.12.0) + kramdown (= 2.3.2) + kramdown-parser-gfm (= 1.1.0) + liquid (= 4.0.4) mercenary (~> 0.3) - minima (= 2.5.0) - nokogiri (>= 1.8.2, < 2.0) - rouge (= 2.2.1) + minima (= 2.5.1) + nokogiri (>= 1.13.6, < 2.0) + rouge (= 3.26.0) terminal-table (~> 1.4) - github-pages-health-check (1.8.1) + github-pages-health-check (1.17.9) addressable (~> 2.3) dnsruby (~> 1.60) octokit (~> 4.0) - public_suffix (~> 2.0) + public_suffix (>= 3.0, < 5.0) typhoeus (~> 1.3) - html-pipeline (2.8.4) + hashery (2.1.2) + html-pipeline (2.14.3) activesupport (>= 2) nokogiri (>= 1.4) - html-proofer (3.9.2) - activesupport (>= 4.2, < 6.0) + html-proofer (5.0.8) addressable (~> 2.3) - colorize (~> 0.8) - mercenary (~> 0.3.2) - nokogiri (~> 1.8.1) - parallel (~> 1.3) + async (~> 2.1) + nokogiri (~> 1.13) + pdf-reader (~> 2.11) + rainbow (~> 3.0) typhoeus (~> 1.3) yell (~> 2.0) - http_parser.rb (0.6.0) + zeitwerk (~> 2.5) + http_parser.rb (0.8.0) httpclient (2.8.3) - i18n (0.9.5) + i18n (1.14.1) concurrent-ruby (~> 1.0) - jekyll (3.7.4) + io-event (1.4.1) + jekyll (3.9.3) addressable (~> 2.4) colorator (~> 1.0) em-websocket (~> 0.5) - i18n (~> 0.7) + i18n (>= 0.7, < 2) jekyll-sass-converter (~> 1.0) jekyll-watch (~> 2.0) - kramdown (~> 1.14) + kramdown (>= 1.17, < 3) liquid (~> 4.0) mercenary (~> 0.3.3) pathutil (~> 0.9) rouge (>= 1.7, < 4) safe_yaml (~> 1.0) - jekyll-algolia (1.4.7) + jekyll-algolia (1.7.1) algolia_html_extractor (~> 2.6) - algoliasearch (~> 1.18) + algoliasearch (~> 1.26) filesize (~> 0.1) - jekyll (~> 3.0) + jekyll (>= 3.6, < 5.0) json (~> 2.0) nokogiri (~> 1.6) progressbar (~> 1.9) verbal_expressions (~> 0.1.5) - jekyll-avatar (0.6.0) - jekyll (~> 3.0) + jekyll-avatar (0.7.0) + jekyll (>= 3.0, < 5.0) jekyll-coffeescript (1.1.1) coffee-script (~> 2.2) coffee-script-source (~> 1.11.1) - jekyll-commonmark (1.2.0) - commonmarker (~> 0.14) - jekyll (>= 3.0, < 4.0) - jekyll-commonmark-ghpages (0.1.5) - commonmarker (~> 0.17.6) - jekyll-commonmark (~> 1) - rouge (~> 2) + jekyll-commonmark (1.4.0) + commonmarker (~> 0.22) + jekyll-commonmark-ghpages (0.4.0) + commonmarker (~> 0.23.7) + jekyll (~> 3.9.0) + jekyll-commonmark (~> 1.4.0) + rouge (>= 2.0, < 5.0) jekyll-default-layout (0.1.4) jekyll (~> 3.0) - jekyll-feed (0.10.0) - jekyll (~> 3.3) + jekyll-feed (0.15.1) + jekyll (>= 3.7, < 5.0) jekyll-gist (1.5.0) octokit (~> 4.2) - jekyll-github-metadata (2.9.4) - jekyll (~> 3.1) + jekyll-github-metadata (2.13.0) + jekyll (>= 3.4, < 5.0) octokit (~> 4.0, != 4.4.0) - jekyll-last-modified-at (1.0.1) - jekyll (~> 3.3) + jekyll-include-cache (0.2.1) + jekyll (>= 3.7, < 5.0) + jekyll-last-modified-at (1.3.0) + jekyll (>= 3.7, < 5.0) posix-spawn (~> 0.3.9) - jekyll-mentions (1.4.1) + jekyll-mentions (1.6.0) html-pipeline (~> 2.3) - jekyll (~> 3.0) - jekyll-optional-front-matter (0.3.0) - jekyll (~> 3.0) + jekyll (>= 3.7, < 5.0) + jekyll-optional-front-matter (0.3.2) + jekyll (>= 3.0, < 5.0) jekyll-paginate (1.1.0) - jekyll-readme-index (0.2.0) - jekyll (~> 3.0) - jekyll-redirect-from (0.14.0) - jekyll (~> 3.3) - jekyll-relative-links (0.5.3) - jekyll (~> 3.3) - jekyll-remote-theme (0.3.1) - jekyll (~> 3.5) - rubyzip (>= 1.2.1, < 3.0) + jekyll-readme-index (0.3.0) + jekyll (>= 3.0, < 5.0) + jekyll-redirect-from (0.16.0) + jekyll (>= 3.3, < 5.0) + jekyll-relative-links (0.6.1) + jekyll (>= 3.3, < 5.0) + jekyll-remote-theme (0.4.3) + addressable (~> 2.0) + jekyll (>= 3.5, < 5.0) + jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0) + rubyzip (>= 1.3.0, < 3.0) jekyll-sass-converter (1.5.2) sass (~> 3.4) - jekyll-seo-tag (2.5.0) - jekyll (~> 3.3) - jekyll-sitemap (1.2.0) - jekyll (~> 3.3) - jekyll-swiss (0.4.0) - jekyll-theme-architect (0.1.1) - jekyll (~> 3.5) + jekyll-seo-tag (2.8.0) + jekyll (>= 3.8, < 5.0) + jekyll-sitemap (1.4.0) + jekyll (>= 3.7, < 5.0) + jekyll-swiss (1.0.0) + jekyll-theme-architect (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-cayman (0.1.1) - jekyll (~> 3.5) + jekyll-theme-cayman (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-dinky (0.1.1) - jekyll (~> 3.5) + jekyll-theme-dinky (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-hacker (0.1.1) - jekyll (~> 3.5) + jekyll-theme-hacker (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-leap-day (0.1.1) - jekyll (~> 3.5) + jekyll-theme-leap-day (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-merlot (0.1.1) - jekyll (~> 3.5) + jekyll-theme-merlot (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-midnight (0.1.1) - jekyll (~> 3.5) + jekyll-theme-midnight (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-minimal (0.1.1) - jekyll (~> 3.5) + jekyll-theme-minimal (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-modernist (0.1.1) - jekyll (~> 3.5) + jekyll-theme-modernist (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-primer (0.5.3) - jekyll (~> 3.5) + jekyll-theme-primer (0.6.0) + jekyll (> 3.5, < 5.0) jekyll-github-metadata (~> 2.9) jekyll-seo-tag (~> 2.0) - jekyll-theme-slate (0.1.1) - jekyll (~> 3.5) + jekyll-theme-slate (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-tactile (0.1.1) - jekyll (~> 3.5) + jekyll-theme-tactile (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-time-machine (0.1.1) - jekyll (~> 3.5) + jekyll-theme-time-machine (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-titles-from-headings (0.5.1) - jekyll (~> 3.3) - jekyll-watch (2.0.0) + jekyll-titles-from-headings (0.5.3) + jekyll (>= 3.3, < 5.0) + jekyll-watch (2.2.1) listen (~> 3.0) - jemoji (0.10.1) + jemoji (0.12.0) gemoji (~> 3.0) html-pipeline (~> 2.2) - jekyll (~> 3.0) - json (2.1.0) - kramdown (1.17.0) - launchy (2.4.3) - addressable (~> 2.3) - liquid (4.0.0) - listen (3.1.5) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) - ruby_dep (~> 1.2) + jekyll (>= 3.0, < 5.0) + json (2.7.1) + kramdown (2.3.2) + rexml + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + launchy (2.5.2) + addressable (~> 2.8) + liquid (4.0.4) + listen (3.8.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) mercenary (0.3.6) - mini_portile2 (2.3.0) - minima (2.5.0) - jekyll (~> 3.5) + minima (2.5.1) + jekyll (>= 3.5, < 5.0) jekyll-feed (~> 0.9) jekyll-seo-tag (~> 2.1) - minitest (5.11.3) - multipart-post (2.0.0) - nokogiri (1.8.5) - mini_portile2 (~> 2.3.0) - octokit (4.12.0) - sawyer (~> 0.8.0, >= 0.5.3) - parallel (1.12.1) - pathutil (0.16.1) + minitest (5.21.1) + mutex_m (0.2.0) + net-http (0.4.1) + uri + nokogiri (1.16.0-x64-mingw-ucrt) + racc (~> 1.4) + octokit (4.25.1) + faraday (>= 1, < 3) + sawyer (~> 0.9) + pathutil (0.16.2) forwardable-extended (~> 2.6) - posix-spawn (0.3.13) - progressbar (1.10.0) - public_suffix (2.0.5) - rb-fsevent (0.10.3) - rb-inotify (0.9.10) - ffi (>= 0.5.0, < 2) - rouge (2.2.1) - ruby-enum (0.7.2) - i18n - ruby_dep (1.5.0) - rubyzip (1.2.2) - safe_yaml (1.0.4) - sass (3.6.0) + pdf-reader (2.12.0) + Ascii85 (~> 1.0) + afm (~> 0.2.1) + hashery (~> 2.0) + ruby-rc4 + ttfunk + posix-spawn (0.3.15) + progressbar (1.13.0) + public_suffix (4.0.7) + racc (1.7.3) + rainbow (3.1.1) + rb-fsevent (0.11.2) + rb-inotify (0.10.1) + ffi (~> 1.0) + rexml (3.2.6) + rouge (3.26.0) + ruby-rc4 (0.1.5) + ruby2_keywords (0.0.5) + rubyzip (2.3.2) + safe_yaml (1.0.5) + sass (3.7.4) sass-listen (~> 4.0.0) sass-listen (4.0.0) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) - sawyer (0.8.1) - addressable (>= 2.3.5, < 2.6) - faraday (~> 0.8, < 1.0) + sawyer (0.9.2) + addressable (>= 2.3.5) + faraday (>= 0.17.3, < 3) + simpleidn (0.2.1) + unf (~> 0.1.4) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) - thread_safe (0.3.6) - typhoeus (1.3.0) + timers (4.3.5) + ttfunk (1.7.0) + typhoeus (1.4.1) ethon (>= 0.9.0) - tzinfo (1.2.5) - thread_safe (~> 0.1) - unicode-display_width (1.4.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unf (0.1.4) + unf_ext + unf_ext (0.0.9.1-x64-mingw-ucrt) + unicode-display_width (1.8.0) + uri (0.13.0) verbal_expressions (0.1.5) - yell (2.0.7) + wdm (0.1.1) + webrick (1.8.1) + yell (2.2.2) + zeitwerk (2.6.12) PLATFORMS - ruby + x64-mingw-ucrt DEPENDENCIES devdocs! @@ -294,6 +343,8 @@ DEPENDENCIES jekyll-algolia (~> 1.0) jekyll-last-modified-at launchy + wdm + webrick (~> 1.8) BUNDLED WITH - 1.16.5 + 2.5.4 diff --git a/_config.yml b/_config.yml index 806c3b871c..727bd755bc 100644 --- a/_config.yml +++ b/_config.yml @@ -33,10 +33,14 @@ defaults: feedback_link: true - scope: - path: guides/m1x + path: guides/v19.x + values: + layout: null + - + scope: + path: guides/v1.8 values: layout: null - - scope: path: mftf/2.2 @@ -94,8 +98,8 @@ algolia: index_name: devdocs api_key: d2d0f33ab73e291ef8d88d8b565e754c files_to_exclude: - - guides/m1x/**/*.html - - guides/m1x/**/*.md + - guides/v19.x/**/*.html + - guides/v19.x/**/*.md - swagger - redoc @@ -111,8 +115,12 @@ check_links: true ############# # # 1.x variables -m1xgdeurl: /guides/m1x/ -m1xgithuburl: https://github.com/OpenMage/devdocs/blob/master/ + +v1xgdeurl: /guides/v19.x +v1xgithuburl: https://github.com/OpenMage/devdocs/tree/master +v18xgdeurl: /guides/v1.8/ + +gdeurl23: https://devdocs.magento.com/redoc/2.3/ ########## # Ignore # @@ -136,3 +144,4 @@ exclude: - rakelib - README.md - _plugins + diff --git a/common/css/stylesheet.css b/common/css/stylesheet.css index d32d22ff92..750762b747 100644 --- a/common/css/stylesheet.css +++ b/common/css/stylesheet.css @@ -2,216 +2,398 @@ /* CSS Document */ body { - background-color: #ffffff; margin: 0 auto; font-size:15px; font: 15px/22px 'Poppins' , Arial, sans-serif; color:#5A5A5A; font-weight:normal; width:1024px; - } + background-color: #ffffff; + /*margin: 0 5vw;*/ + font-size: 15px; + font: 15px/22px 'Poppins', Arial, sans-serif; + color: #5A5A5A; + font-weight: normal; +} /* containers */ -#container { width:940px; margin:0; } -#content { width:940px; margin:15px 45px 0 45px; } +#container { + width: 940px; + margin: 0; +} + +#content { + width: 100%; + margin: 15px 45px 0 45px; +} /* left nav style */ .show { - display:block; position: relative; + display: block; + position: relative; top: -15px; } -.hide{ - display:none; position: relative; + +.hide { + display: none; + position: relative; top: -15px; } -#container-2 { width:940px; margin:10px 0 0 45px; } -#left-nav { float:left; width:220px; margin-right:20px; display:block; } -#left-nav ul { margin:10px 0 8px; } -#left-nav li { border-bottom:2px dotted #dddddd; list-style: none; - padding: 10px 5px; margin: 10px 0; } -#content-2 { float:right; width:700px; } -#left-nav h4 { color:#666666; border-bottom:2px dotted #dddddd; padding:5px; } + +#container-2 { + width: 940px; + margin: 10px 0 0 45px; +} + +#left-nav { + float: left; + width: 220px; + margin-right: 20px; + display: block; +} + +#left-nav ul { + margin: 10px 0 8px; +} + +#left-nav li { + border-bottom: 2px dotted #dddddd; + list-style: none; + padding: 10px 5px; + margin: 10px 0; +} + +#content-2 { + float: right; + width: 1024px; +} + +#left-nav h4 { + color: #666666; + border-bottom: 2px dotted #dddddd; + padding: 5px; +} + #left-nav li.highlight a:link, #left-nav li.highlight a:visited { color: #FB0E70; text-decoration: none; } + #left-nav ul.related-documents li { - font-size:13px; border:0; padding: 5px; margin: 5px 0 5px 5px; - background-image: url(".bullet-round-orange.png"); - background-repeat: no-repeat; - background-position: 0 9px; - padding-left: 16px; - } + font-size: 13px; + border: 0; + padding: 5px; + margin: 5px 0 5px 5px; + background-image: url(".bullet-round-orange.png"); + background-repeat: no-repeat; + background-position: 0 9px; + padding-left: 16px; +} /* end left nav styles */ /* HEADINGS */ -h1, h2 { font-weight:100; margin:30px 0 10px 0; } +h1, h2 { + font-weight: 100; + margin: 30px 0 10px 0; +} -h3, h4, h5, h6 { font-weight:100; color:#444444; margin:25px 0 2px 0; } +h3, h4, h5, h6 { + font-weight: 100; + color: #444444; + margin: 25px 0 2px 0; +} -h1 { font: 36px/46px 'Poppins', Helvetica, sans-serif; letter-spacing: 0; } +h1 { + font: 36px/46px 'Poppins', Helvetica, sans-serif; + letter-spacing: 0; +} -h2 { font: 31px/41px 'Poppins', Helvetica, sans-serif; color:#A447A4; } +h2 { + font: 31px/41px 'Poppins', Helvetica, sans-serif; + color: #A447A4; +} -h3 { font: 27px/37px 'Poppins', Helvetica, sans-serif; letter-spacing: 0;} +h3 { + font: 27px/37px 'Poppins', Helvetica, sans-serif; + letter-spacing: 0; +} -h4 { font: 23px/37px 'Poppins', Helvetica, sans-serif; letter-spacing: 0; } +h4 { + font: 23px/37px 'Poppins', Helvetica, sans-serif; + letter-spacing: 0; +} -h5 { font: 17px/27px 'Poppins', Helvetica, sans-serif; letter-spacing: 0; font-weight:bold; margin-top:25px; } +h5 { + font: 17px/27px 'Poppins', Helvetica, sans-serif; + letter-spacing: 0; + font-weight: bold; + margin-top: 25px; +} -h6 { font: 14px/24px 'Poppins', Helvetica, sans-serif; letter-spacing: 0; font-weight:bold; margin-top:25px; } +h6 { + font: 14px/24px 'Poppins', Helvetica, sans-serif; + letter-spacing: 0; + font-weight: bold; + margin-top: 25px; +} /* TOC */ -div.toc { background: url(blend-toc.png) repeat-x; border-left:1px solid #effbfd; padding:0 10px; margin-bottom:20px; } -div.section.toc { position:relative; top:-15px; } -h4.toc { font-weight:300; padding: 10px 0 10px ; } -p.toc { color: #cccccc; margin-bottom:10px; } -p.toc a { padding: 0 5px 0 5px; } -p.toc a.first { padding-left: 0; } +div.toc { + background: url(blend-toc.png) repeat-x; + border-left: 1px solid #effbfd; + padding: 0 10px; + margin-bottom: 20px; +} + +div.section.toc { + position: relative; + top: -15px; +} + +h4.toc { + font-weight: 300; + padding: 10px 0 10px; +} + +p.toc { + color: #cccccc; + margin-bottom: 10px; +} + +p.toc a { + padding: 0 5px 0 5px; +} + +p.toc a.first { + padding-left: 0; +} /*PARAGRAPHS */ -p { letter-spacing: 0.05px; color:#555555; line-height:20px; margin: 8px 0 17px;} +p { + letter-spacing: 0.05px; + color: #555555; + line-height: 20px; + margin: 8px 0 17px; +} /* LINKS */ -a:link, a:visited { color:#A447A4; text-decoration:none; } -a:hover{ color:#FB0E70; } -a:active{ color:#FB0E70; } +a:link, a:visited { + color: #A447A4; + text-decoration: none; +} + +a:hover { + color: #FB0E70; +} + +a:active { + color: #FB0E70; +} /* NOTES TIPS ETC. */ .msg-box, .sm-note-box { - margin:2px 0 15px 0; - width:85%; - border-radius: 8px 0 8px 0; - font-size:14px; - color:#777777; + margin: 2px 0 15px 0; + width: 85%; + border-radius: 8px 0 8px 0; + font-size: 14px; + color: #777777; padding: 8px 12px 8px 0; text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); - min-height:35px; } + min-height: 35px; +} .sm-note-box { - min-height:15px; + min-height: 15px; background-color: rgba(0, 0, 0, 0.025); - border:1px solid #888888; - border-left: 3px solid #888888; } + border: 1px solid #888888; + border-left: 3px solid #888888; +} -.sm-note-box span { padding: 0 10px 0 10px; } +.sm-note-box span { + padding: 0 10px 0 10px; +} -.msg-text { color:#777777; font-size:14px; } +.msg-text { + color: #777777; + font-size: 14px; +} .caution { background-color: rgba(240, 171, 0, .05); border: 1px solid #f7941d; - border-left: 5px solid #f7941d; } + border-left: 5px solid #f7941d; +} .important { background-color: rgba(251, 14, 112, 0.04); - border:1px solid #FB0E70; - border-left: 5px solid #FB0E70; } + border: 1px solid #FB0E70; + border-left: 5px solid #FB0E70; +} .note { background-color: rgba(0, 0, 0, 0.025); - border:1px solid #888888; - border-left: 5px solid #888888; } + border: 1px solid #888888; + border-left: 5px solid #888888; +} .tip { background-color: rgba(246, 220, 27, .05); - border:1px solid #fbe841; - border-left: 5px solid #fbe841; } + border: 1px solid #fbe841; + border-left: 5px solid #fbe841; +} + +.msg-box span { + position: relative; + left: 10px; + padding-right: 5px; +} + +.msg-box img { + position: relative; + top: -3px; + left: 5px; + width: 30px +} + -.msg-box span { position:relative; left:10px; padding-right:5px; } -.msg-box img { position:relative; top:-3px; left:5px; width:30px } +pre { + font-size: 14px; + line-height: 20px; + overflow: auto; +} +pre strong { + color: #333333; +} -pre { font-size:14px; line-height:20px; overflow: auto; } -pre strong{ color:#333333; } -pre, .link-callout { padding:10px; margin:0 0 20px; - background-color: #F3F6F9; border:1px solid #ebeff2; border-radius: 4px; } +pre, .link-callout { + padding: 10px; + margin: 0 0 20px; + background-color: #F3F6F9; + border: 1px solid #ebeff2; + border-radius: 4px; +} -.link-callout { width:450px; color:#666666; } +.link-callout { + width: 450px; + color: #666666; +} /* LISTS */ -ul, ol ul li, ul ol li, ol li ol li ul.level1 li -{ - list-style-type: none; - padding: 0; - margin: 0; +ul, ol ul li, ul ol li, ol li ol li ul.level1 li { + list-style-type: none; + padding: 0; + margin: 0; } ul li li, ul li li li, ol li li { - margin-left: 5px; + margin-left: 5px; } - ul li li li { - margin-left: 10px; + +ul li li li { + margin-left: 10px; } -ul.level1 li, ul li.level1, ol li ol li ul.level1 li -{ - background-image: url("bullet-round-orange.png"); - background-repeat: no-repeat; - background-position: 0 7px; - padding-left: 16px; - margin-bottom:6px; +ul.level1 li, ul li.level1, ol li ol li ul.level1 li { + background-image: url("bullet-round-orange.png"); + background-repeat: no-repeat; + background-position: 0 7px; + padding-left: 16px; + margin-bottom: 6px; +} + +ul.level2 li, ul li.level2, ol li ol li ul.level2 li { + background-image: url("bullet-round-gold.png"); + background-repeat: no-repeat; + background-position: 0 6px; + padding-left: 16px; + margin-bottom: 6px; } -ul.level2 li, ul li.level2, ol li ol li ul.level2 li -{ - background-image: url("bullet-round-gold.png"); - background-repeat: no-repeat; - background-position: 0 6px; - padding-left: 16px; - margin-bottom:6px; + +ul.level3 li, ul li.level3, ol li ol li ul.level3 li { + background-image: url("bullet-round-gray.png"); + background-repeat: no-repeat; + background-position: 0 6px; + padding-left: 16px; + margin-bottom: 6px; } -ul.level3 li, ul li.level3, ol li ol li ul.level3 li -{ - background-image: url("bullet-round-gray.png"); - background-repeat: no-repeat; - background-position: 0 6px; - padding-left: 16px; - margin-bottom:6px; + +ol li, ul ol li { + list-style: decimal outside none; + background-image: none; } -ol li, ul ol li { list-style: decimal outside none; background-image:none; } -ol li { margin-bottom:8px; } -li p { margin:2px 0 8px; font-size:14px; } -dl dd { padding: 5px 0 6px 0; margin-left:24px } -dd p { margin:4px 0 0; } +ol li { + margin-bottom: 8px; +} -dl, ul, ol { margin:10px 0 8px 20px; } +li p { + margin: 2px 0 8px; + font-size: 14px; +} -ol ol, ul ol { margin:10px 0 8px 5px; } +dl dd { + padding: 5px 0 6px 0; + margin-left: 24px +} -ol ol li, ul li ol li { list-style-type:lower-alpha; } -ol ol ol li, ul li ol li ol li { list-style-type:lower-roman; padding-left:2px; } +dd p { + margin: 4px 0 0; +} -dl { margin-left:0; } +dl, ul, ol { + margin: 10px 0 8px 20px; +} -/* list overrides - SRJ */ +ol ol, ul ol { + margin: 10px 0 8px 5px; +} +ol ol li, ul li ol li { + list-style-type: lower-alpha; +} + +ol ol ol li, ul li ol li ol li { + list-style-type: lower-roman; + padding-left: 2px; +} + +dl { + margin-left: 0; +} + +/* list overrides - SRJ */ /* This sort of works */ -ul li -{ - background-image: url("bullet-round-orange.png"); - background-repeat: no-repeat; - background-position: 0 7px; - padding-left: 16px; - margin-bottom:6px; - margin-left: 5px; +ul li { + /* + background-image: url("bullet-round-orange.png"); + background-repeat: no-repeat; + background-position: 0 7px; + */ + padding-left: 16px; + margin-bottom: 6px; + margin-left: 5px; } -ul li, ul li -{ - background-image: url("bullet-round-gold.png"); - background-repeat: no-repeat; - background-position: 0 6px; - padding-left: 16px; - margin-bottom:6px; - margin-left: 10px; + +ul li, ul li { + /* + background-image: url("bullet-round-gold.png"); + background-repeat: no-repeat; + background-position: 0 6px; + */ + padding-left: 16px; + margin-bottom: 6px; + margin-left: 10px; } -ul li, ul li, ul li -{ - background-image: url("bullet-round-gray.png"); - background-repeat: no-repeat; - background-position: 0 6px; - padding-left: 16px; - margin-bottom:6px; + +ul li, ul li, ul li { + /* + background-image: url("bullet-round-gray.png"); + background-repeat: no-repeat; + background-position: 0 6px; + */ + padding-left: 16px; + margin-bottom: 6px; } ol li ul li { @@ -246,25 +428,49 @@ ul:nth-child(3) li { /* NUMBERED CODE */ -pre.lineCount { background: #F3F6F9 url("gutter.png") 2.1em 0 repeat-y; - border:1px solid #ebeff2; line-height:0; margin:0; padding:0; } +pre.lineCount { + background: #F3F6F9 url("gutter.png") 2.1em 0 repeat-y; + border: 1px solid #ebeff2; + line-height: 0; + margin: 0; + padding: 0; +} ol.listLineCount { - overflow:auto; - margin:0; - padding-left: 32px; - color:#ffffff; - line-height:5px; + overflow: auto; + margin: 0; + padding-left: 32px; + color: #ffffff; + line-height: 5px; } -.lineCount ol li { color:#333333; font-weight:400; } -.lineCount ol li.odd { background-color:#F3F6F9; } +.lineCount ol li { + color: #333333; + font-weight: 400; +} + +.lineCount ol li.odd { + background-color: #F3F6F9; +} -pre.lineCount li code { color:#333333; line-height:15px;} +pre.lineCount li code { + color: #333333; + line-height: 15px; +} -caption, p.caption { font-family:Georgia, "Times New Roman", Times, serif; color:#787878; font-size: 14px; font-style:italic; padding:0; margin:10px 0 15px 3px;} +caption, p.caption { + font-family: Georgia, "Times New Roman", Times, serif; + color: #787878; + font-size: 14px; + font-style: italic; + padding: 0; + margin: 10px 0 15px 3px; +} -caption {margin:0px 0 2px 0; padding:20px 0 5px 0; } +caption { + margin: 0px 0 2px 0; + padding: 20px 0 5px 0; +} /* TABLE */ @@ -272,10 +478,12 @@ table { border-collapse: collapse; text-align: left; width: 100%; - color:#555555; + color: #555555; } + table td, table th, table tr, table tr th { - padding:8px;} + padding: 8px; +} table tr:nth-child(odd) { /*(odd) or (2n 1)*/ background-color: #F3F6F9; @@ -288,6 +496,7 @@ table tr:nth-child(odd) { /*(odd) or (2n 1)*/ color: #222222; vertical-align: top; } + table tr:nth-child(even) { /*(even) or (2n 0)*/ border-bottom: medium none; border-color: transparent #ffffff; @@ -298,14 +507,16 @@ table tr:nth-child(even) { /*(even) or (2n 0)*/ color: #222222; vertical-align: top; } + table th { border: #dce5ed 1px solid; color: #444444; font-weight: 600; padding: 5px; vertical-align: top; - font-size:13px; + font-size: 13px; } + /*table .table-headings th { background-color: rgba(220, 229, 237, .50); border:1px solid #dce5ed; @@ -332,10 +543,10 @@ tr.even, tr.odd { padding: 0.1em 0.6em; }*/ td { - border:1px solid #dce5ed; + border: 1px solid #dce5ed; color: #555555; padding: 10px; - font-size:13px; + font-size: 13px; } /* Back to Top */ @@ -343,255 +554,301 @@ td { p.btt a:link, p.btt a:active, p.btt a:visited { background: url("btt.png") no-repeat scroll 0 2px transparent; float: right; - position:relative; - top:-35px; + position: relative; + top: -35px; font-size: 12px; padding: 0 0 0 15px; text-decoration: none; } -p.btt.last {position:relative;top:40px;} + +p.btt.last { + position: relative; + top: 40px; +} /* paging */ p.paging { float: right; - position:relative; - top:-42px; + position: relative; + top: -42px; font-size: 12px; - font-weight:600; - color:#777777; + font-weight: 600; + color: #777777; padding: 0 0 0 15px; text-decoration: none; } -p.paging span { padding:5px; } + +p.paging span { + padding: 5px; +} + p.paging a { - font-weight:100; padding:5px; } + font-weight: 100; + padding: 5px; +} + p.paging.bottom { - position:relative; - top:42px; } + position: relative; + top: 42px; +} /* Rules */ -.dotted-rule { border-top: 2px dotted #dddddd; margin: 15px 0 15px 0; } -hr { border: 0; color: #e2e2e2; background-color: #e2e2e2; height: 2px; margin: 15px 0 15px 0; } +.dotted-rule { + border-top: 2px dotted #dddddd; + margin: 15px 0 15px 0; +} + +hr { + border: 0; + color: #e2e2e2; + background-color: #e2e2e2; + height: 2px; + margin: 15px 0 15px 0; +} /* Images */ .images { - margin-left: -20px; - list-style: none; - *zoom: 1; + margin-left: -20px; + list-style: none; + *zoom: 1; } .images:before, .images:after { - display: table; - content: ""; + display: table; + content: ""; } .images:after { - clear: both; + clear: both; } .row-fluid .images { - margin-left: 0; + margin-left: 0; } .images > li { - float: left; - margin-bottom: 5px; - margin-left: 20px; + float: left; + margin-bottom: 5px; + margin-left: 20px; } .image { - display: block; - margin-top:10px; - padding: 8px; - line-height: 1; - border: 1px solid #ddd; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); - -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); - box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); -} -.image.medium { width:680px; } - -.image.large { width:920px; } + display: block; + margin-top: 10px; + padding: 8px; + line-height: 1; + border: 1px solid #ddd; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); + -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); +} + +.image.medium { + width: 680px; +} + +.image.large { + width: 920px; +} a.image:hover { - border-color: #0088cc; - -webkit-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25); - -moz-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25); - box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25); + border-color: #0088cc; + -webkit-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25); + -moz-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25); + box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25); } .image > img { - display: block; - max-width: 100%; - margin-right: auto; - margin-left: auto; -} - - -.expandimg img:hover -{ - -webkit-transform:scale(1.6); - -moz-transform:scale(1.6); - -o-transform:scale(1.6); - box-shadow:0 0 30px gray; - -webkit-box-shadow:0 0 30px gray; - -moz-box-shadow:0 0 30px gray; - opacity: 1; - margin-left: 145px; -} - -.expandimg-border img:hover -{ - -webkit-transform:scale(1.6); - -moz-transform:scale(1.6); - -o-transform:scale(1.6); - border: 1px solid #ddd; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); - -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); - box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); - opacity: 1; - margin-left: 195px; -} - -.expandimg-thumb img:hover -{ - -webkit-transform:scale(2.5); - -moz-transform:scale(2.5); - -o-transform:scale(2.5); - border: 1px solid #ddd; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); - -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); - box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); - opacity: 1; + display: block; + max-width: 100%; + margin-right: auto; + margin-left: auto; +} + + +.expandimg img:hover { + -webkit-transform: scale(1.6); + -moz-transform: scale(1.6); + -o-transform: scale(1.6); + box-shadow: 0 0 30px gray; + -webkit-box-shadow: 0 0 30px gray; + -moz-box-shadow: 0 0 30px gray; + opacity: 1; + margin-left: 145px; +} + +.expandimg-border img:hover { + -webkit-transform: scale(1.6); + -moz-transform: scale(1.6); + -o-transform: scale(1.6); + border: 1px solid #ddd; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); + -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); + opacity: 1; + margin-left: 195px; +} + +.expandimg-thumb img:hover { + -webkit-transform: scale(2.5); + -moz-transform: scale(2.5); + -o-transform: scale(2.5); + border: 1px solid #ddd; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); + -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); + opacity: 1; } .expandimg-border, .expandimg-thumb { - display: block; - margin-top:10px; - padding: 8px; - line-height: 1; - border: 1px solid #ddd; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); - -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); - box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); + display: block; + margin-top: 10px; + padding: 8px; + line-height: 1; + border: 1px solid #ddd; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); + -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); } /* TABS */ .nav { - margin-bottom: 18px; - margin-left: 0; - list-style: none; + margin-bottom: 18px; + margin-left: 0; + list-style: none; } .nav > li > a { - display: block; + display: block; } .nav > li > a:hover { - text-decoration: none; - background-color: rgba(0, 144, 192, 0.03); + text-decoration: none; + background-color: rgba(0, 144, 192, 0.03); } -.nav-tabs{ - *zoom: 1; +.nav-tabs { + *zoom: 1; } .nav-tabs:before, -.nav-tabs:after{ - display: table; - content: ""; +.nav-tabs:after { + display: table; + content: ""; } .nav-tabs:after { - clear: both; + clear: both; } .nav-tabs > li { - float: left; + float: left; } .nav-tabs > li > a { - padding-right: 12px; - padding-left: 12px; - margin-right: 2px; - line-height: 14px; + padding-right: 12px; + padding-left: 12px; + margin-right: 2px; + line-height: 14px; } .nav-tabs { - border-bottom: 1px solid #ddd; + border-bottom: 1px solid #ddd; } .nav-tabs > li { - margin-bottom: -1px; + margin-bottom: -1px; } .nav-tabs > li > a { - padding-top: 8px; - padding-bottom: 8px; - line-height: 18px; - border: 1px solid transparent; - -webkit-border-radius: 4px 4px 0 0; - -moz-border-radius: 4px 4px 0 0; - border-radius: 4px 4px 0 0; + padding-top: 8px; + padding-bottom: 8px; + line-height: 18px; + border: 1px solid transparent; + -webkit-border-radius: 4px 4px 0 0; + -moz-border-radius: 4px 4px 0 0; + border-radius: 4px 4px 0 0; } .nav-tabs > li > a:hover { - border-color: #caecf3 #caecf3 #dddddd; + border-color: #caecf3 #caecf3 #dddddd; } .nav-tabs > .active > a, .nav-tabs > .active > a:hover { - color: #555555; - cursor: default; - background-color: #ffffff; - border: 1px solid #ddd; - border-bottom-color: transparent; + color: #555555; + cursor: default; + background-color: #ffffff; + border: 1px solid #ddd; + border-bottom-color: transparent; +} + +.tab-content { + border-bottom: 1px solid #DDDDDD; + padding: 0 0 5px; } -.tab-content { border-bottom: 1px solid #DDDDDD; padding: 0 0 5px; } -.tab-content p { color:#777777; } +.tab-content p { + color: #777777; +} /* CODE COMMENTS AND MANDATORY STEPS */ -.comments { color:#339900 } -.mandatory { color:#EE6933 } +.comments { + color: #339900 +} + +.mandatory { + color: #EE6933 +} .soap-api-menu { float: left; - margin-left: -45px; - width: 250px; + width: 20%; } + .soap-api-menu { border-bottom: 1px solid #cac2b5; padding-bottom: 10px; margin-top: 20px; } + +.soap-api-menu-header { + text-transform: uppercase; + font-weight: 700; + letter-spacing: 0.06em; + margin-top: 12px; +} + .soap-api-menu > ul { margin-left: 0; } + .soap-api-menu ul { margin-top: 5px; margin-bottom: 5px; } + .soap-api-menu > ul > ul { margin-left: 0; } + .soap-api-menu ul li { background: none; margin-bottom: 0; @@ -601,26 +858,32 @@ a.image:hover { overflow: hidden; font-size: 14px; } + .soap-api-menu ul li a { color: #444; } + .soap-api-menu ul li a:hover, .soap-api-menu ul li a.active { color: #FB0E70; } + .soap-api-content { - float: right; - width: 750px; + float: left; + width: 70%; clear: right; - margin-right: -45px; + margin-left: 2vw; } + .soap-api-menu li.parent, .soap-api-menu > ul > li { margin-bottom: 5px; } + .soap-api-menu li.parent > ul { display: none; } + .soap-api-menu li.parent > .arr { display: inline-block; width: 12px; @@ -628,6 +891,7 @@ a.image:hover { position: relative; cursor: pointer; } + .soap-api-menu li.parent > .arr:before { position: absolute; width: 0; @@ -638,6 +902,7 @@ a.image:hover { border: 7px outset transparent; border-top: 7px solid #FB0E70; } + .soap-api-menu li.parent > .arr:after { position: absolute; width: 0; @@ -648,69 +913,75 @@ a.image:hover { border: 7px outset transparent; border-top: 7px solid #fff; } + .soap-api-menu li.parent.open > .arr:before { border: 7px outset transparent; border-bottom: 7px solid #FB0E70; top: -3px; } + .soap-api-menu li.parent.open > .arr:after { border: 7px outset transparent; border-bottom: 7px solid #fff; top: -1px; } + .soap-api-menu li.parent > a, .soap-api-menu > ul > li > a { font-weight: bold; } + .introduction-menu { background: #fdf2fd; margin-left: 0; padding: 20px; list-style: none; } + .introduction-menu li { background: none; padding: 0; } + .introduction-menu a { color: #A447A4 } + .introduction-menu a:hover { color: #FB0E70 } .message-banner { - background: #FBEDD9; - border: 1px solid #C27500; - padding: 20px; - border-radius: 2px; - margin: 10px 0; + background: #FBEDD9; + border: 1px solid #FB0E70; + padding: 20px; + border-radius: 2px; + margin: 10px 0; } .message-banner h4 { - margin-top: 0; + margin-top: 0; } + .message-banner p:last-child { - margin-bottom: 0; + margin-bottom: 0; } -.site-title h4 -{ - float:right; +.site-title h4 { + width: 50%; + float: right; } -#footer -{ - clear: both; - margin-top: 4em; - padding: 1em; - width:100%; - background-color: #000537; - box-sizing: border-box; +#footer { + clear: both; + margin-top: 4em; + padding: 1em; + width: 100%; + background-color: #000537; + box-sizing: border-box; } -#imprint -{ - position: absolute; - right: 1em; +#imprint { + position: absolute; + right: 1em; } diff --git a/guides/m1x/magefordev/mage-for-dev-1.html b/guides/m1x/magefordev/mage-for-dev-1.html index 395772e5a8..cf67075558 100644 --- a/guides/m1x/magefordev/mage-for-dev-1.html +++ b/guides/m1x/magefordev/mage-for-dev-1.html @@ -1,507 +1,806 @@ --- --- - - + - - - - - - - Magento for Developers: Part 1—Introduction to Magento - + + + + + OpenMage for Developers: Part 1—Introduction to OpenMage + + + + + + + + + + + + + + + - -header -{% include m1x/eol_message.html %} - -
- -
-

Magento for Developers: Part 1—Introduction to Magento

-

by Alan Storm, updated for Magento 1.12

-

Edit this page on GitHub

- - -

Other articles in this series:

- - -

What is Magento? It's the most powerful online eCommerce platform in the universe and is changing the face of eCommerce forever. :-)

- -

Of course, you already know that. What you may not realize is Magento's also an object-oriented PHP Framework that can be used to develop modern, dynamic web applications that tap into Magento's powerful eCommerce features.

- -

This is the first in a series of articles in which we're going to go on a whirlwind tour of Magento's programming framework features. Don't worry if you don't follow everything immediately. As you study the system more everything in this article will start to make sense, and you'll soon be the envy of your colleagues stuck working with more primitive PHP systems.

- -

In this article...

- - -

Or for the more visually oriented Magento_MVC.pdf.

- -
- -

Code Organized in Modules

- -

Magento organizes its code into individual Modules. In a typical PHP Model-View-Controller (MVC) application, all the Controllers will be in one folder, all the Models in another, etc. In Magento, files are grouped together based on functionality, which are called modules in Magento.

- -

Magento's Code

-

For example, you'll find Controllers, Models, Helpers, Blocks, etc. related to Magento's checkout functionality in

-
app/code/core/Mage/Checkout
- -

You'll find Controllers, Models, Helpers, Blocks, etc. related to Magento's Google Checkout functionality in

-
app/code/core/Mage/GoogleCheckout
- -

Your Code

-

When you want to customize or extend Magento, rather than editing core files directly, or even placing your new Controllers, Models, Helpers, Blocks, etc. next to Magento code, you'll create your own Modules in

-
app/code/local/Package/Modulename
- -

Package (also often referred to as a Namespace) is a unique name that identifies your company or organization. The intent is that each member of the world-wide Magento community will use their own Package name when creating modules in order to avoid colliding with another user's code.

- -

When you create a new Module, you need to tell Magento about it. This is done by adding an XML file to the folder:

-
app/etc/modules
- -

There are two kinds of files in this folder, the first enables an individual Module, and is named in the form:

-Packagename_Modulename.xml - -

The second is a file that will enable multiple Modules from a Package/Namespace, and is named in the form:

-Packagename_All.xml. Note it is only used by the core team with the file Mage_All.xml. It is not recommended to activate several modules in a single file, as this breaks the modularity of your modules. - - -

Configuration-Based MVC

- -

Magento is a configuration-based MVC system. The alternative to this would a convention-based MVC system.

- -

In a convention-based MVC system, if you wanted to add, say, a new Controller or maybe a new Model, you'd just create the file/class, and the system would pick it up automatically.

- -

In a configuration-based system, like Magento, in addition to adding the new file/class to the codebase, you often need to explicitly tell the system about the new class, or new group of classes. In Magento, each Module has a file named config.xml. This file contains all the relevant configuration for a Magento Module. At runtime, all these files are loaded into one large configuration tree.

- -

For example, want to use Models in your custom Module? You'll need to add some code to config.xml that tells Magento you want to use Models, as well as what the base class name for all your Models should be.

- -
-    <models>
-         <packagename>
-              <class>Packagename_Modulename_Model</class>
-        </packagename>
-    </models>
-
-

The same goes for Helpers, Blocks, Routes for your Controllers, Event Handlers, and more. Almost anytime you want to tap into the power of the Magento system, you'll need to make some change or addition to your config file.

- - -

Controllers

- -

In any PHP system, the main PHP entry point remains a PHP file. Magento is no different, and that file is index.php.

- -

However, you never CODE in index.php. In an MVC system, index.php will contain code/calls to code that does the following:

-
    -
  1. Examines the URL

  2. -
  3. Based on some set of rules, turns this URL into a Controller class and an Action method (called Routing)

  4. -
  5. Instantiates the Controller class and calls the Action method (called dispatching)

  6. -
- -

This means the practical entry point in Magento (or any MVC-based system) is a method in a Controller file. Consider the following URL:

- -
http://example.com/catalog/category/view/id/25
- -

Each portion of the path after the server name is parsed as follows.

- -

Front Name - catalog

-

The first portion of the URL is called the front name. This, more or less, tells magento which Module it can find a Controller in. In the above example, the front name is catalog, which corresponds to the Module located at:

-
app/code/core/Mage/Catalog
- -

Controller Name - category

-

The second portion of the URL tells Magento which Controller it should use. Each Module with Controllers has a special folder named 'controllers' which contains all the Controllers for a module. In the above example, the URL portion category is translated into the Controller file

-
app/code/core/Mage/Catalog/controllers/CategoryController.php
- -

Which looks like

-
-class Mage_Catalog_CategoryController extends Mage_Core_Controller_Front_Action
-{
-}
-
- -

All Controllers in the Magento cart application extend from Mage_Core_Controller_Front_Action.

- -

Action Name - view

- -

Third in our URL is the action name. In our example, this is "view". The word "view" is used to create the Action Method. So, in our example, "view" would be turned into "viewAction"

- -
-class Mage_Catalog_CategoryController extends Mage_Core_Controller_Front_Action
-{
-    public function viewAction()
-    {
-        //main entry point
-    }
-}
-
- -

People familiar with the Zend Framework will recognize the naming convention here.

- -

Parameter/Value - id/25

- -

Any path portions after the action name will be considered key/value GET request variables. So, in our example, the "id/25" means there will get a GET variable named "id", with a value of "25".

- -

As previously mentioned, if you want your Module to use Controllers, you'll need to configure them. Below is the configuration chunk that enables Controllers for the Catalog Module

- -
-<frontend>
-    <routers>
-        <catalog>
-            <use>standard</use>
-            <args>
-                <module>Mage_Catalog</module>
-                <frontName>catalog</frontName>
-            </args>
-        </catalog>
-    </routers>
-</frontend>
-
- -

Don't worry too much about the specifics right now, but notice the

- -<frontName>catalog</frontName> - - -

This is what links a Module with a URL frontname. Most Magento core Modules choose a frontname that is the same as their Module name, but this is not required.

- - -

Multiple Routers

- -

The routing described above is for the Magento cart application (often called the frontend). If Magento doesn't find a valid Controller/Action for a URL, it tries again, this time using a second set of Routing rules for the Admin application. If Magento doesn't find a valid Admin Controller/Action, it uses a special Controller named Mage_Cms_IndexController.

- -

The CMS Controller checks Magento's content Management system to see if there's any content that should be loaded. If it finds some, it loads it, otherwise the user will be presented with a 404 page.

- -

For example, the main magento "index" page is one that uses the CMS Controller, which can often throw newcomers for a loop.

- - -

Context-Based URI Model Loading

- -

Now that we're in our Action method entry point, we'll want to start instantiating classes that do things. Magento offers a special way to instantiate Models, Helpers and Blocks using static factory methods on the global Mage class. For example:

- -
-Mage::getModel('catalog/product');
-Mage::helper('catalog/product');
-
- -

The string 'catalog/product' is called a Grouped Class Name. It's also often called a URI. The first portion of any Grouped Class Name (in this case, catalog), is used to lookup which Module the class resides in. The second portion ('product' above) is used to determine which class should be loaded.

- -

So, in both of the examples above, 'catalog' resolves to the Module app/code/core/Mage/Catalog.

- -

Meaning our class name will start with Mage_Catalog.

- -

Then, product is added to get the final class name

- -
-Mage::getModel('catalog/product');
-Mage_Catalog_Model_Product
-
-Mage::helper('catalog/product');
-Mage_Catalog_Helper_Product
-
- -

These rules are bound by what's been setup in each Module's config file. When you create your own custom Module, you'll have your own grouped classnames (also calles classgroups) to work with Mage::getModel('myspecialprefix/modelname');.

- -

You don't have to use Grouped Class Names to instantiate your classes. However, as we'll learn later, there are certain advantages to doing so.

- - - -

Magento Models

- -

Magento, like most frameworks these days, offers an Object Relational Mapping (ORM) system. ORMs get you out of the business of writing SQL and allow you to manipulate a datastore purely through PHP code. For example:

- -
-$model = Mage::getModel('catalog/product')->load(27);
-$price = $model->getPrice();
-$price += 5;
-$model->setPrice($price)->setSku('SK83293432');
-$model->save();
-
- -

In the above example we're calling the methods "getPrice" and "setPrice" on our Product. However, the Mage_Catalog_Model_Product class has no methods with these names. That's because Magento's ORM uses PHP's magic __call method to implement getters and setters.

- -

Calling the method $product->getPrice(); will "get" the Model attribute "price".

- -

Calling $product->setPrice(); will "set" the Model attribute "price". All of this assumes the Model class doesn't already have methods named getPrice or setPrice. If it does, the magic methods will be bypassed. If you're interested in the implementation of this, checkout the Varien_Object class, which all Models inherit from.

- -

If you wanted to get all the available data on a Model, call $product->getData(); to get an array of all the attributes.

- -

You'll also notice it's possible to chain together several calls to the set method:

-$model->setPrice($price)->setSku('SK83293432'); - -

That's because each set method returns an instance of the Model. This is a pattern you'll see used in much of the Magento codebase.

- -

Magento's ORM also contains a way to query for multiple Objects via a Collections interface. The following would get us a collection of all products that cost $5.00

- -
-$products_collection = Mage::getModel('catalog/product')
-->getCollection()
-->addAttributeToSelect('*')
-->addFieldToFilter('price','5.00');
-
- -

Again, you'll notice Magento's implemented a chaining interface here. Collections use the PHP Standard Library to implement Objects that have array like properties.

- -
-foreach($products_collection as $product)
-{
-    echo $product->getName();
-}
-
- -

You may be wondering what the "addAttributeToSelect" method is for. Magento has two broad types of Model objects. One is a traditional "One Object, One Table" Active Record style Model. When you instantiate these Models, all attributes are automatically selected.

- -

The second type of Model is an Entity Attribute Value (EAV) Model. EAV Models spread data across several different tables in the database. This gives the Magento system the flexibility to offer its flexible product attribute system without having to do a schema change each time you add an attribute. When creating a collection of EAV objects, Magento is conservative in the number of columns it will query for, so you can use addAttributeToSelect to get the columns you want, or addAttributeToSelect('*') to get all columns.

- - -

Helpers

- -

Magento's Helper classes contain utility methods that will allow you to perform common tasks on objects and variables. For example:

> -$helper = Mage::helper('catalog'); - -

You'll notice we've left off the second part of the grouped class name. Each Module has a default Data Helper class. The following is equivalent to the above:

-$helper = Mage::helper('catalog/data'); - -

Most Helpers inherit form Mage_Core_Helper_Abstract, which gives you several useful methods by default.

-
-$translated_output =  $helper->__('Magento is Great'); //gettext style translations
-if($helper->isModuleOutputEnabled()): //is output for this module on or off?
-
- - - -

Layouts

- -

So, we've seen Controllers, Models, and Helpers. In a typical PHP MVC system, after we've manipulated our Models we would

- -
    -
  1. Set some variables for our view
  2. -
  3. The system would load a default "outer" HTML layout>
  4. -
  5. The system would then load our view inside that outer layout
  6. -
- -

However, if you look at a typical Magento Controller action, you don't see any of this:

- -
-/**
- * View product gallery action
- */
-public function galleryAction()
-{
-    if (!$this->_initProduct()) {
-        if (isset($_GET['store']) && !$this->getResponse()->isRedirect()) {
-            $this->_redirect('');
-        } elseif (!$this->getResponse()->isRedirect()) {
-            $this->_forward('noRoute');
-        }
-        return;
-    }
-    $this->loadLayout();
-    $this->renderLayout();
-}
-
- -

Instead, the Controller action ends with two calls

- -
-$this->loadLayout();
-$this->renderLayout();
-
- -

So, the "V" in Magento's MVC already differs from what you're probably used to, in that you need to explicitly kick off rendering the layout.

- -

The layout itself also differs. A Magento Layout is an object that contains a nested/tree collection of "Block" objects. Each Block object will render a specific bit of HTML. Block objects do this through a combination of PHP code, and including PHP .phtml template files.

- -

Blocks objects are meant to interact with the Magento system to retrieve data from Models, while the phtml template files will produce the HTML needed for a page.

- -

For example, the page header Block app/code/core/Mage/Page/Block/Html/Head.php uses the head.phtml file page/html/head.phtml.

- -

Another way of thinking about it is the Block classes are almost like little mini-controllers, and the .phtml files are the view.

- -

By default, when you call

- -
-$this->loadLayout();
-$this->renderLayout();
-
- -

Magento will load up a Layout with a skeleton site structure. There will be Structure Blocks to give you your html, head, and body, as well as HTML to setup single or multiple columns of Layout. Additionally, there will be a few Content Blocks for the navigation, default welcome message, etc.

- -

"Structure" and "Content" are arbitrary designations in the Layout system. A Block doesn't programmatically know if it's Structure or Content, but it's useful to think of a Block as one or the other.

- -

To add Content to this Layout you need to tell the Magento system something like

- -
"Hey, Magento, add these additional Blocks under the "content" Block of the skeleton"
- -or - -
"Hey, Magento, add these additional Blocks under the "left column" Block of the skeleton"
- -

This can be done programmatically in a Controller action

- -
-public function indexAction()
-{
-    $this->loadLayout();
-    $block = $this->getLayout()->createBlock('adminhtml/system_account_edit')
-    $this->getLayout()->getBlock('content')->append($block);
-    $this->renderLayout();
-}
-
- -

but more commonly (at least in the frontend cart application), is use of the XML Layout system.

- -

The Layout XML files in a theme allow you, on a per Controller basis, to remove Blocks that would normally be rendered, or add Blocks to that default skeleton areas. For example, consider this Layout XML file:

- -
-<catalog_category_default>
-    <reference name="left">
-        <block type="catalog/navigation" name="catalog.leftnav" after="currency" template="catalog/navigation/left.phtml"/>
-    </reference>
-</catalog_category_default>
-
- -

It's saying in the catalog Module, in the category Controller, and the default Action, insert the catalog/navigation Block into the "left" structure Block, using the catalog/navigation/left.phtml template.

- -

One last important thing about Blocks. You'll often see code in templates that looks like this:

-$this->getChildHtml('order_items') - -

This is how a Block renders a nested Block. However, a Block can only render a child Block if the child Block is included as a nested Block in the Layout XML file. In the example above our catalog/navigation Block has no nested Blocks. This means any call to $this->getChildHtml() in left.phtml will render as blank.

- -

If, however, we had something like:

-
-<catalog_category_default>
-    <reference name="left">
-        <block type="catalog/navigation" name="catalog.leftnav" after="currency" template="catalog/navigation/left.phtml">
-            <block type="core/template" name="foobar" template="foo/baz/bar.phtml"/>
-        </block>
-    </reference>
-</catalog_category_default>
-
- -

From the catalog/navigation Block, we'd be able to call

-$this->getChildHtml('foobar');
- - - -

Observers

- -

Like any good object-oriented system, Magento implements an Event/Observer pattern for end users to hook into. As certain actions happen during a Page request (a Model is saved, a user logs in, etc.), Magento will issue an event signal.

- -

When creating your own Modules, you can "listen" for these events. Say you wanted to get an email every time a certain customer logged into the store. You could listen for the "customer_login" event (setup in config.xml)

- -
-<events>
-    <customer_login>
-        <observers>
-            <unique_name>
-                <type>singleton</type>
-                <class>mymodule/observer</class>
-                <method>iSpyWithMyLittleEye</method>
-            </unique_name>
-        </observers>
-    </customer_login>
-</events>
-
- -

and then write some code that would run whenever a user logged in:

- -
-class Packagename_Mymodule_Model_Observer
-{
-    public function iSpyWithMyLittleEye($observer)
-    {
-        $data = $observer->getData();
-        //code to check observer data for our user,
-        //and take some action goes here
-    }
-}
-
- - -

Class Overrides

- -

Finally, the Magento System offers you the ability to replace Model, Helper and Block classes from the core modules with your own. This is a feature that's similar to "Duck Typing" or "Monkey Patching" in a language like Ruby or Python.

- -

Here's an example to help you understand. The Model class for a product is Mage_Catalog_Model_Product.

- -

Whenever the following code is called, a Mage_Catalog_Model_Product object is created

- -
$product = Mage::getModel('catalog/product');
- -

This is a factory pattern.

- -

What Magento's class override system does is allow you to tell the system

- -
"Hey, whenever anyone asks for a catalog/product, instead of giving them a Mage_Catalog_Model_Product,
-give them a Packagename_Modulename_Model_Foobazproduct instead".
- -

Then, if you want, your Packagename_Modulename_Model_Foobazproduct class can extend the original product class

- -
-class Packagename_Modulename_Model_Foobazproduct extends Mage_Catalog_Model_Product
-{
-}
-
- -

Which will allow you to change the behavior of any method on the class, but keep the functionality of the existing methods.

- -
class Packagename_Modulename_Model_Foobazproduct extends Mage_Catalog_Model_Product
-{
-    public function validate()
-    {
-        //add custom validation functionality here
-        return $this;
-    }
-
-}
-
- -

As you might expect, this overriding (or rewriting) is done in the config.xml file.

- -
-<models>
-    <!-- does the override for catalog/product-->
-    <catalog>
-        <rewrite>
-            <product>Packagename_Modulename_Model_Foobazproduct</product>
-        </rewrite>
-    </catalog>
-</models>
-
- -

One thing that's important to note here. Individual classes in your Module are overriding individual classes in other Modules. You are not, however, overriding the entire Module. This allows you to change specific method behavior without having to worry what the rest of the Module is doing.

- - - -

Wrap Up

- -

We hope you've enjoyed this whirlwind tour of some of the features the Magento eCommerce system offers to developers. It can be a little overwhelming at first, especially if this is your first experience with a modern, object-oriented PHP system. If you start to get frustrated, take a deep breath, remind yourself that this is new, and new things are hard, but at the end of the day it's just a different way of coding. Once you get over the learning curve you'll find yourself loath to return to other, less powerful systems.

- + + +
+
+ +
+ + + +
+
+ +
+

Introduction to OpenMage

+
+

What is OpenMage? It's the most powerful online eCommerce platform in the universe + and is changing the face of eCommerce forever. :-) +

+

Of course, you already know that. What you may not realize is Magento's also an object-oriented + PHP Framework that can be used to develop modern, dynamic web applications that tap into + Magento's powerful eCommerce features. +

+

This is the first in a series of articles in which we're going to go on a whirlwind tour of + Magento's programming framework features. Don't worry if you don't follow everything + immediately. As you study the system more everything in this article will start to make sense, + and you'll soon be the envy of your colleagues stuck working with more primitive PHP + systems. +

+

Or for the more visually oriented + Magento_MVC.pdf. +

+
+ +

Code Organized in Modules

+

Magento organizes its code into individual Modules. In a typical PHP + + Model-View-Controller (MVC) + + application, all the Controllers will be in one folder, all the Models in another, + etc. In Magento, files are grouped together based on functionality, which are called + modules in Magento. +

+

Magento's Code

+

For example, you'll find Controllers, Models, Helpers, Blocks, etc. related to Magento's checkout + functionality in +

+
app/code/core/Mage/Checkout
+

You'll find Controllers, Models, Helpers, Blocks, etc. related to Magento's Google Checkout + functionality in +

+
app/code/core/Mage/GoogleCheckout
+

Your Code

+

When you want to customize or extend Magento, rather than editing core files directly, or even + placing your new Controllers, Models, Helpers, Blocks, etc. next to Magento code, you'll create + your own Modules in +

+
app/code/local/Package/Modulename
+

Package (also often referred to as a Namespace) is a unique name that identifies + your company or organization. The intent is that each member of the world-wide Magento community + will use their own Package name when creating modules in order to avoid colliding with another + user's code.

+

When you create a new Module, you need to tell Magento about it. This is done by adding an XML + file to the folder:

+
app/etc/modules
+

There are two kinds of files in this folder, the first enables an individual Module, and is named + in the form:

+ Packagename_Modulename.xml +

The second is a file that will enable multiple Modules from a Package/Namespace, and is named in + the form:

+ Packagename_All.xml. Note it is only used by the core team with the file + Mage_All.xml. It is not recommended to activate several modules in a single file, as + this breaks the modularity of your modules. + +

Configuration-Based MVC

+

Magento is a configuration-based MVC system. The alternative to this would a convention-based + MVC system.

+

In a convention-based MVC system, if you wanted to add, say, a new Controller or maybe a new + Model, you'd just create the file/class, and the system would pick it up automatically.

+

In a configuration-based system, like Magento, in addition to adding the new file/class to the + codebase, you often need to explicitly tell the system about the new class, or new group of + classes. In Magento, each Module has a file named config.xml. This file contains all + the relevant configuration for a Magento Module. At runtime, all these files are loaded into one + large configuration tree.

+

For example, want to use Models in your custom Module? You'll need to add some code to config.xml + that tells Magento you want to use Models, as well as what the base class name for all your + Models should be.

+
+                    <models>
+                             <packagename>
+                                  <class>Packagename_Modulename_Model</class>
+                            </packagename>
+                    </models>
+                    
+

The same goes for Helpers, Blocks, Routes for your Controllers, Event Handlers, and more. Almost + anytime you want to tap into the power of the Magento system, you'll need to make some change or + addition to your config file.

+ +

Controllers

+

In any PHP system, the main PHP entry point remains a PHP file. Magento is no different, and that + file is index.php.

+

However, you never CODE in index.php. In an MVC system, index.php will contain code/calls to code + that does the following:

+
    +
  1. Examines the URL

  2. +
  3. Based on some set of rules, turns this URL into a Controller class and an Action method + (called Routing)

  4. +
  5. Instantiates the Controller class and calls the Action method (called dispatching)

    +
  6. +
+

This means the practical entry point in Magento (or any MVC-based system) is a + method in a Controller file. Consider the following URL:

+
http://example.com/catalog/category/view/id/25
+

Each portion of the path after the server name is parsed as follows.

+

Front Name - catalog

+

The first portion of the URL is called the front name. This, more or less, tells magento which + Module it can find a Controller in. In the above example, the front name is catalog, + which corresponds to the Module located at:

+
app/code/core/Mage/Catalog
+

Controller Name - category

+

The second portion of the URL tells Magento which Controller it should use. Each Module with + Controllers has a special folder named 'controllers' which contains all the Controllers for a + module. In the above example, the URL portion category is translated into the Controller + file

+
app/code/core/Mage/Catalog/controllers/CategoryController.php
+

Which looks like

+
+                        class Mage_Catalog_CategoryController extends Mage_Core_Controller_Front_Action
+                        {
+                        }
+                    
+

All Controllers in the Magento cart application extend from + Mage_Core_Controller_Front_Action.

+

Action Name - view

+

Third in our URL is the action name. In our example, this is "view". The word "view" is used to + create the Action Method. So, in our example, "view" would be turned into "viewAction"

+
+                        class Mage_Catalog_CategoryController extends Mage_Core_Controller_Front_Action
+                        {
+                            public function viewAction()
+                            {
+                                //main entry point
+                            }
+                        }
+                    
+

People familiar with the Zend Framework will recognize the naming convention here.

+

Parameter/Value - id/25

+

Any path portions after the action name will be considered key/value GET request variables. So, + in our example, the "id/25" means there will get a GET variable named "id", with a value of + "25".

+

As previously mentioned, if you want your Module to use Controllers, you'll need to configure + them. Below is the configuration chunk that enables Controllers for the Catalog Module

+
+                        <frontend>
+                            <routers>
+                                <catalog>
+                                    <use>standard</use>
+                                    <args>
+                                        <module>Mage_Catalog</module>
+                                        <frontName>catalog</frontName>
+                                    </args>
+                                </catalog>
+                            </routers>
+                        </frontend>
+                    
+

Don't worry too much about the specifics right now, but notice the

+ <frontName>catalog</frontName> + +

This is what links a Module with a URL frontname. Most Magento core Modules choose a frontname + that is the same as their Module name, but this is not required.

+

Multiple Routers

+ +

The routing described above is for the Magento cart application (often called the frontend). If + Magento doesn't find a valid Controller/Action for a URL, it tries again, this time using a + second set of Routing rules for the Admin application. If Magento doesn't find a valid Admin + Controller/Action, it uses a special Controller named Mage_Cms_IndexController.

+ +

The CMS Controller checks Magento's content Management system to see if there's any content that + should be loaded. If it finds some, it loads it, otherwise the user will be presented with a 404 + page.

+ +

For example, the main magento "index" page is one that uses the CMS Controller, which can often + throw newcomers for a loop.

+ + +

Context-Based URI Model Loading

+ +

Now that we're in our Action method entry point, we'll want to start instantiating classes that + do things. Magento offers a special way to instantiate Models, Helpers and Blocks using static + factory methods on the global Mage class. For example: +

+
+                        Mage::getModel('catalog/product');
+                        Mage::helper('catalog/product');
+                    
+

The string 'catalog/product' is called a Grouped Class Name. It's also often called a URI. The + first portion of any Grouped Class Name (in this case, catalog), is used to lookup which Module + the class resides in. The second portion ('product' above) is used to determine which class + should be loaded. +

+

So, in both of the examples above, 'catalog' resolves to the Module + app/code/core/Mage/Catalog. +

+

Meaning our class name will start with Mage_Catalog.

+

Then, product is added to get the final class name

+
+                        Mage::getModel('catalog/product');
+                        Mage_Catalog_Model_Product
+
+                        Mage::helper('catalog/product');
+                        Mage_Catalog_Helper_Product
+                    
+

These rules are bound by what's been setup in each Module's config file. When you create your own + custom Module, you'll have your own grouped classnames (also calles classgroups) to work with + Mage::getModel('myspecialprefix/modelname');. +

+

You don't have to use Grouped Class Names to instantiate your classes. However, + as we'll learn later, there are certain advantages to doing so. +

+ +

Magento Models

+

Magento, like most frameworks these days, offers an Object Relational Mapping (ORM) system. ORMs + get you out of the business of writing SQL and allow you to manipulate a datastore purely + through PHP code. For example: +

+
+                        $model = Mage::getModel('catalog/product')->load(27);
+                        $price = $model->getPrice();
+                        $price += 5;
+                        $model->setPrice($price)->setSku('SK83293432');
+                        $model->save();
+                    
+

In the above example we're calling the methods "getPrice" and "setPrice" on our Product. However, + the Mage_Catalog_Model_Product class has no methods with these names. That's because Magento's + ORM uses PHP's magic __call method to implement getters and setters. +

+

Calling the method $product->getPrice(); will "get" the Model attribute "price".

+

Calling $product->setPrice(); will "set" the Model attribute "price". All of this + assumes the Model class doesn't already have methods named getPrice or setPrice. If it does, the + magic methods will be bypassed. If you're interested in the implementation of this, checkout the + Varien_Object class, which all Models inherit from. +

+

If you wanted to get all the available data on a Model, call $product->getData(); to + get an array of all the attributes. +

+

You'll also notice it's possible to chain together several calls to the set method:

+ $model->setPrice($price)->setSku('SK83293432'); +

That's because each set method returns an instance of the Model. This is a pattern you'll see + used in much of the Magento codebase. +

+

Magento's ORM also contains a way to query for multiple Objects via a Collections interface. The + following would get us a collection of all products that cost $5.00 +

+
+                        $products_collection = Mage::getModel('catalog/product')
+                        ->getCollection()
+                        ->addAttributeToSelect('*')
+                        ->addFieldToFilter('price','5.00');
+                    
+

Again, you'll notice Magento's implemented a chaining interface here. Collections use the PHP + Standard Library to implement Objects that have array like properties. +

+
+                        foreach($products_collection as $product)
+                        {
+                            echo $product->getName();
+                        }
+                    
+

You may be wondering what the "addAttributeToSelect" method is for. Magento has two broad types + of Model objects. One is a traditional "One Object, One Table" Active Record style Model. When + you instantiate these Models, all attributes are automatically selected. +

+

The second type of Model is an Entity Attribute Value (EAV) Model. EAV Models spread data across + several different tables in the database. This gives the Magento system the flexibility to offer + its flexible product attribute system without having to do a schema change each time you add an + attribute. When creating a collection of EAV objects, Magento is conservative in the number of + columns it will query for, so you can use addAttributeToSelect to get the columns you want, or + addAttributeToSelect('*') to get all columns. +

+ +

Helpers

+

Magento's Helper classes contain utility methods that will allow you to perform common tasks on + objects and variables. For example:

+ $helper = Mage::helper('catalog'); +

You'll notice we've left off the second part of the grouped class name. Each Module has a default + Data Helper class. The following is equivalent to the above: +

+ $helper = Mage::helper('catalog/data'); +

Most Helpers inherit form Mage_Core_Helper_Abstract, which gives you several useful methods by + default. +

+
+                        $translated_output =  $helper->__('Magento is Great'); //gettext style translations
+                        if($helper->isModuleOutputEnabled()): //is output for this module on or off?
+                    
+ +

Layouts

+

So, we've seen Controllers, Models, and Helpers. In a typical PHP MVC system, after we've + manipulated our Models we would +

+
    +
  1. Set some variables for our view
  2. +
  3. The system would load a default "outer" HTML layout>
  4. +
  5. The system would then load our view inside that outer layout
  6. +
+

However, if you look at a typical Magento Controller action, you don't see any of this:

+
+                        /**
+                         * View product gallery action
+                         */
+                        public function galleryAction()
+                        {
+                            if (!$this->_initProduct()) {
+                                if (isset($_GET['store']) && !$this->getResponse()->isRedirect()) {
+                                    $this->_redirect('');
+                                } elseif (!$this->getResponse()->isRedirect()) {
+                                    $this->_forward('noRoute');
+                                }
+                                return;
+                            }
+                            $this->loadLayout();
+                            $this->renderLayout();
+                        }
+                    
+

Instead, the Controller action ends with two calls

+
+                        $this->loadLayout();
+                        $this->renderLayout();
+                    
+

So, the "V" in Magento's MVC already differs from what you're probably used to, in that you need + to explicitly kick off rendering the layout. +

+

The layout itself also differs. A Magento Layout is an object that contains a nested/tree + collection of "Block" objects. Each Block object will render a specific bit of HTML. Block + objects do this through a combination of PHP code, and including PHP .phtml template files. +

+

Blocks objects are meant to interact with the Magento system to retrieve data from Models, while + the phtml template files will produce the HTML needed for a page. +

+

For example, the page header Block app/code/core/Mage/Page/Block/Html/Head.php uses the + head.phtml file + page/html/head.phtml. +

+

Another way of thinking about it is the Block classes are almost like little mini-controllers, + and the .phtml files are the view. +

+

By default, when you call

+
+                        $this->loadLayout();
+                        $this->renderLayout();
+                    
+

Magento will load up a Layout with a skeleton site structure. There will be Structure Blocks to + give you your html, head, and body, as well as HTML to setup single + or multiple columns of Layout. Additionally, there will be a few Content Blocks for the + navigation, default welcome message, etc. +

+

"Structure" and "Content" are arbitrary designations in the Layout system. A Block doesn't + programmatically know if it's Structure or Content, but it's useful to think of a Block as one + or the other. +

+

To add Content to this Layout you need to tell the Magento system something like

+
"Hey, Magento, add these additional Blocks under the "content" Block of the skeleton"
+ or +
"Hey, Magento, add these additional Blocks under the "left column" Block of the skeleton"
+

This can be done programmatically in a Controller action

+
+                        public function indexAction()
+                        {
+                            $this->loadLayout();
+                            $block = $this->getLayout()->createBlock('adminhtml/system_account_edit')
+                            $this->getLayout()->getBlock('content')->append($block);
+                            $this->renderLayout();
+                        }
+                    
+

but more commonly (at least in the frontend cart application), is use of the XML Layout system.

+

The Layout XML files in a theme allow you, on a per Controller basis, to remove Blocks that would + normally be rendered, or add Blocks to that default skeleton areas. For example, consider this + Layout XML file: +

+
+                        <catalog_category_default>
+                            <reference name="left">
+                                <block type="catalog/navigation" name="catalog.leftnav" after="currency" template="catalog/navigation/left.phtml"/>
+                            </reference>
+                        </catalog_category_default>
+                    
+

It's saying in the catalog Module, in the category Controller, and the default Action, insert the + catalog/navigation Block into the "left" structure Block, using the + catalog/navigation/left.phtml template. +

+

One last important thing about Blocks. You'll often see code in templates that looks like this:

+ $this->getChildHtml('order_items') +

This is how a Block renders a nested Block. However, a Block can only render a child Block if the + child Block is included as a nested Block in the Layout XML file. In the + example above our catalog/navigation Block has no nested Blocks. This means any call to + $this->getChildHtml() in left.phtml will render as blank. +

+

If, however, we had something like:

+
+                        <catalog_category_default>
+                            <reference name="left">
+                                <block type="catalog/navigation" name="catalog.leftnav" after="currency" template="catalog/navigation/left.phtml">
+                                    <block type="core/template" name="foobar" template="foo/baz/bar.phtml"/>
+                                </block>
+                            </reference>
+                        </catalog_category_default>
+                    
+

From the catalog/navigation Block, we'd be able to call

+ $this->getChildHtml('foobar'); +
+ +

Observers

+

Like any good object-oriented system, Magento implements an Event/Observer pattern for end users + to hook into. As certain actions happen during a Page request (a Model is saved, a user logs in, + etc.), Magento will issue an event signal. +

+

When creating your own Modules, you can "listen" for these events. Say you wanted to get an email + every time a certain customer logged into the store. You could listen for the "customer_login" + event (setup in config.xml) +

+
+                        <events>
+                            <customer_login>
+                                <observers>
+                                    <unique_name>
+                                        <type>singleton</type>
+                                        <class>mymodule/observer</class>
+                                        <method>iSpyWithMyLittleEye</method>
+                                    </unique_name>
+                                </observers>
+                            </customer_login>
+                        </events>
+                    
+

and then write some code that would run whenever a user logged in:

+
+                        class Packagename_Mymodule_Model_Observer
+                        {
+                            public function iSpyWithMyLittleEye($observer)
+                            {
+                                $data = $observer->getData();
+                                //code to check observer data for our user,
+                                //and take some action goes here
+                            }
+                        }
+                    
+ +

Class Overrides

+

Finally, the Magento System offers you the ability to replace Model, Helper and Block classes + from the core modules with your own. This is a feature that's similar to "Duck Typing" or + "Monkey Patching" in a language like Ruby or Python. +

+

Here's an example to help you understand. The Model class for a product is + Mage_Catalog_Model_Product. +

+

Whenever the following code is called, a Mage_Catalog_Model_Product object is created

+
$product = Mage::getModel('catalog/product');
+

This is a factory pattern.

+

What Magento's class override system does is allow you to tell the system

+
"Hey, whenever anyone asks for a catalog/product, instead of giving them a Mage_Catalog_Model_Product,
+                        give them a Packagename_Modulename_Model_Foobazproduct instead".
+                    
+

Then, if you want, your Packagename_Modulename_Model_Foobazproduct class can extend the original + product class

+
+                        class Packagename_Modulename_Model_Foobazproduct extends Mage_Catalog_Model_Product
+                        {
+                        }
+                    
+

Which will allow you to change the behavior of any method on the class, but keep the + functionality of the existing methods.

+
class Packagename_Modulename_Model_Foobazproduct extends Mage_Catalog_Model_Product
+                        {
+                            public function validate()
+                            {
+                                //add custom validation functionality here
+                                return $this;
+                            }
+                        }
+                    
+

As you might expect, this overriding (or rewriting) is done in the config.xml file.

+
+                        <models>
+                            <!-- does the override for catalog/product-->
+                            <catalog>
+                                <rewrite>
+                                    <product>Packagename_Modulename_Model_Foobazproduct</product>
+                                </rewrite>
+                            </catalog>
+                        </models>
+                    
+

One thing that's important to note here. Individual classes in your Module are + overriding individual classes in other Modules. You are not, however, + overriding the entire Module. This allows you to change specific method behavior without having + to worry what the rest of the Module is doing. +

+ +

Wrap Up

+

We hope you've enjoyed this whirlwind tour of some of the features the Magento eCommerce system + offers to developers. It can be a little overwhelming at first, especially if this is your first + experience with a modern, object-oriented PHP system. If you start to get frustrated, take a + deep breath, remind yourself that this is new, and new things are hard, but at the end of the + day it's just a different way of coding. Once you get over the learning curve you'll find + yourself loath to return to other, less powerful systems. +

+
+ + +
+ +
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/guides/m1x/magefordev/mage-for-dev-2.html b/guides/m1x/magefordev/mage-for-dev-2.html index 3c94eee744..c4c7f0c0ed 100644 --- a/guides/m1x/magefordev/mage-for-dev-2.html +++ b/guides/m1x/magefordev/mage-for-dev-2.html @@ -1,237 +1,495 @@ --- --- - - + - - - - - - - Magento for Developers: Part 2—The Magento Config - + + + + + OpenMage for Developers: Part 2—The OpenMage Config + + + + + + + + + + + + + + + - -header -{% include m1x/eol_message.html %} - -
-

Magento for Developers: Part 2—The Magento Config

-

by Alan Storm, updated for Magento 1.12

-

Edit this page on GitHub

- - - - -
Other articles in this series:
- + + + + +
+
+ +
+ + + +
+
+ +
+

The OpenMage Config

+
+

The config is the beating heart of the Magento System. It describes, in whole, + almost any module, model, class, template, etc. than you'll need to access. It's a level of + abstraction that most PHP developers aren't used to working with, and while it adds development + time in the form of confusion and head scratching, it also allows you an unprecedented amount of + flexibility as far as overriding default system behaviors go.

+

To start with, we're going to create a Magento module that will let us view the system config in + our web browser. Follow along by copying and pasting the code below, it's worth going through on + your own as a way to start getting comfortable with things you'll be doing while working with + Magento, as well as learning key terminology.

+ +

Setting up a Module Directory Structure

+

We're going to be creating a Magento module. A module is a group of php and xml files meant to + extend the system with new functionality, or override core system behavior. This may meaning + adding additional data models to track sales information, changing the behavior of existing + classes, or adding entirely new features.

+

It's worth noting that most of the base Magento system is built using the same module system + you'll be using. If you look in

+
app/code/core/Mage
+

each folder is a separate module built by the Magento team. Together, these modules form the + community shopping cart system you're using. Your modules should be placed in the following + folder

+
app/code/local/Packagename
+

"Packagename" should be a unique string to Namespace/Package your code. It's an unofficial + convention that this should be the name of your company. The idea is to pick a string that no + one else in the world could possibly be using.

+
app/code/local/Microsoft
+

We'll use "Magentotutorial".

+

So, to add a module to your Magento system, create the following directory structure

+
app/code/local/Magentotutorial/Configviewer/Block
+                    app/code/local/Magentotutorial/Configviewer/controllers
+                    app/code/local/Magentotutorial/Configviewer/etc
+                    app/code/local/Magentotutorial/Configviewer/Helper
+                    app/code/local/Magentotutorial/Configviewer/Model
+                    app/code/local/Magentotutorial/Configviewer/sql
+

You won't need all these folder for every module, but setting them all up now is a smart + idea.

+

Next, there's two files you'll need to create. The first, config.xml, goes in the etc folder you + just created.

+
app/code/local/Magentotutorial/Configviewer/etc/config.xml
+

The second file should be created at the following location

+
app/etc/modules/Magentotutorial_Configviewer.xml
+

The naming convention for this files is Packagename_Modulename.xml.

+

The config.xml file should contain the following XML. Don't worry too much about what all this + does for now, we'll get there eventually

+
+                    <config>
+                        <modules>
+                            <Magentotutorial_Configviewer>
+                                <version>0.1.0</version>
+                            </Magentotutorial_Configviewer>
+                        </modules>
+                    </config>
+                    
+

Finally, Magentotutorial_Configviewer.xml should contain the following xml.

+
+                    <config>
+                        <modules>
+                            <Magentotutorial_Configviewer>
+                                <active>true</active>
+                                <codePool>local</codePool>
+                            </Magentotutorial_Configviewer>
+                        </modules>
+                    </config>
+                    
+

That's it. You now have a bare bones module that won't do anything, but that Magento will be + aware of. To make sure you've done everything right, do the following: +

    +
  1. Clear your Magento cache
  2. +
  3. In the Magento Admin, go to System->Configuration->Advanced
  4. +
  5. In the "Disable modules output" panel verify that Magentotutorial_Configviewer shows up
  6. +
+

Congratulations, you've built your first Magento module!

+ +

Creating a Module Config

+

Of course, this module doesn't do anything yet. When we're done, our module will +

    +
  1. Check for the existence of a "showConfig" query string variable
  2. +
  3. If showConfig is present, display our Magento config and halt normal execution
  4. +
  5. Check for the existence of an additional query string variable, showConfigFormat that will + let us specify text or xml output. +
  6. +
+

First, we're going to add the following <global> section to our config.xml file.

+
+                    <config>
+                        <modules>...</modules>
+                        <global>
+                            <events>
+                                <controller_front_init_routers>
+                                    <observers>
+                                        <Magentotutorial_configviewer_model_observer>
+                                            <type>singleton</type>
+                                            <class>Magentotutorial_Configviewer_Model_Observer</class>
+                                            <method>checkForConfigRequest</method>
+                                        </Magentotutorial_configviewer_model_observer>
+                                    </observers>
+                                </controller_front_init_routers>
+                            </events>
+                        </global>
+                    </config>
+                    
+

Then, create a file at

+
Magentotutorial/Configviewer/Model/Observer.php
+

and place the following code inside

+
+                    <?php
+                        class Magentotutorial_Configviewer_Model_Observer {
+                            const FLAG_SHOW_CONFIG = 'showConfig';
+                            const FLAG_SHOW_CONFIG_FORMAT = 'showConfigFormat';
+
+                            private $request;
+
+                            public function checkForConfigRequest($observer) {
+                                $this->request = $observer->getEvent()->getData('front')->getRequest();
+                                if($this->request->{self::FLAG_SHOW_CONFIG} === 'true'){
+                                    $this->setHeader();
+                                    $this->outputConfig();
+                                }
+                            }
+
+                            private function setHeader() {
+                                $format = isset($this->request->{self::FLAG_SHOW_CONFIG_FORMAT}) ?
+                                $this->request->{self::FLAG_SHOW_CONFIG_FORMAT} : 'xml';
+                                switch($format){
+                                    case 'text':
+                                        header("Content-Type: text/plain");
+                                        break;
+                                    default:
+                                        header("Content-Type: text/xml");
+                                }
+                            }
+
+                            private function outputConfig() {
+                                die(Mage::app()->getConfig()->getNode()->asXML());
+                            }
+                        }
+                    
+

That's it. Clear your Magento cache again and then load any Magento URL with a showConfig=true + query string

+
http://magento.example.com/?showConfig=true
+ +

What am I looking at?

+

You should be looking at a giant XML file. This describes the state of your Magento system. It + lists all modules, models, classes, event listeners or almost anything else you could think + of.

+

For example, consider the config.xml file you created above. If you search the XML file in your + browser for the text Configviewer_Model_Observer you'll find your class listed. Every + module's config.xml file is parsed by Magento and included in the global config.

+ +

Why Do I Care?

+

Right now this may seem esoteric, but this config is key to understanding Magento. Every module + you'll be creating will add to this config, and anytime you need to access a piece of core + system functionality, Magento will be referring back to the config to look something up.

+

A quick example: As an MVC developer, you've likely worked with some kind of helper class, + instantiated something like

+
+                    $helper_sales = new HelperSales();
+                    
+

One of the things Magento has done is abstract away PHP's class declaration. In Magento, the + above code looks something like

+
+                    $helper_sales = Mage::helper('sales');
+                    
+

In plain english, the static helper method will: +

    +
  1. Look in the <helpers /> section of the Config.
  2. +
  3. Within <helpers />, look for a <sales /> section
  4. +
  5. Within the <sales /> section look for a <class /> section
  6. +
  7. Append the part after the slash to the value found in #3 (defaulting to data in + this case) +
  8. +
  9. Instantiate the class found in #4 (Mage_Sales_Helper_Data)
  10. +
+

While this seems like a lot of work (and it is), the key advantage is by always looking to the + config file for class names, we can override core Magento functionality without + changing or adding to the core code. This level of meta programming, not usually found in PHP, + allows you to cleanly extend only the parts of the system you need to.

+
+ + +
+ +
+
+
-

The config is the beating heart of the Magento System. It describes, in whole, almost any module, model, class, template, etc. than you'll need to access. It's a level of abstraction that most PHP developers aren't used to working with, and while it adds development time in the form of confusion and head scratching, it also allows you an unprecedented amount of flexibility as far as overriding default system behaviors go.

- -

To start with, we're going to create a Magento module that will let us view the system config in our web browser. Follow along by copying and pasting the code below, it's worth going through on your own as a way to start getting comfortable with things you'll be doing while working with Magento, as well as learning key terminology.

- -
In this article...
- - - -

Setting up a Module Directory Structure

- -

We're going to be creating a Magento module. A module is a group of php and xml files meant to extend the system with new functionality, or override core system behavior. This may meaning adding additional data models to track sales information, changing the behavior of existing classes, or adding entirely new features.

- -

It's worth noting that most of the base Magento system is built using the same module system you'll be using. If you look in

- -
app/code/core/Mage
- -

each folder is a separate module built by the Magento team. Together, these modules form the community shopping cart system you're using. Your modules should be placed in the following folder

- -
app/code/local/Packagename
- -

"Packagename" should be a unique string to Namespace/Package your code. It's an unofficial convention that this should be the name of your company. The idea is to pick a string that no one else in the world could possibly be using.

- -
app/code/local/Microsoft
- -

We'll use "Magentotutorial".

- -

So, to add a module to your Magento system, create the following directory structure

- -
app/code/local/Magentotutorial/Configviewer/Block
-app/code/local/Magentotutorial/Configviewer/controllers
-app/code/local/Magentotutorial/Configviewer/etc
-app/code/local/Magentotutorial/Configviewer/Helper
-app/code/local/Magentotutorial/Configviewer/Model
-app/code/local/Magentotutorial/Configviewer/sql
- -

You won't need all these folder for every module, but setting them all up now is a smart idea.

- -

Next, there's two files you'll need to create. The first, config.xml, goes in the etc folder you just created.

- -
app/code/local/Magentotutorial/Configviewer/etc/config.xml
- -

The second file should be created at the following location

- -
app/etc/modules/Magentotutorial_Configviewer.xml
- -

The naming convention for this files is Packagename_Modulename.xml.

- -

The config.xml file should contain the following XML. Don't worry too much about what all this does for now, we'll get there eventually

- -
-<config>
-    <modules>
-        <Magentotutorial_Configviewer>
-            <version>0.1.0</version>
-        </Magentotutorial_Configviewer>
-    </modules>
-</config>
-
- -

Finally, Magentotutorial_Configviewer.xml should contain the following xml.

- -
-<config>
-    <modules>
-        <Magentotutorial_Configviewer>
-            <active>true</active>
-            <codePool>local</codePool>
-        </Magentotutorial_Configviewer>
-    </modules>
-</config>
-
- -

That's it. You now have a bare bones module that won't do anything, but that Magento will be aware of. To make sure you've done everything right, do the following: -

    -
  1. Clear your Magento cache
  2. -
  3. In the Magento Admin, go to System->Configuration->Advanced
  4. -
  5. In the "Disable modules output" panel verify that Magentotutorial_Configviewer shows up
  6. -
- -

Congratulations, you've built your first Magento module!

- - -

Creating a Module Config

- -

Of course, this module doesn't do anything yet. When we're done, our module will -

    -
  1. Check for the existence of a "showConfig" query string variable
  2. -
  3. If showConfig is present, display our Magento config and halt normal execution
  4. -
  5. Check for the existence of an additional query string variable, showConfigFormat that will let us specify text or xml output.
  6. -
- -

First, we're going to add the following <global> section to our config.xml file.

- -
-<config>
-    <modules>...</modules>
-    <global>
-        <events>
-            <controller_front_init_routers>
-                <observers>
-                    <Magentotutorial_configviewer_model_observer>
-                        <type>singleton</type>
-                        <class>Magentotutorial_Configviewer_Model_Observer</class>
-                        <method>checkForConfigRequest</method>
-                    </Magentotutorial_configviewer_model_observer>
-                </observers>
-            </controller_front_init_routers>
-        </events>
-    </global>
-</config>
-
- -

Then, create a file at

- -
Magentotutorial/Configviewer/Model/Observer.php
- -

and place the following code inside

- -
-<?php
-    class Magentotutorial_Configviewer_Model_Observer {
-        const FLAG_SHOW_CONFIG = 'showConfig';
-        const FLAG_SHOW_CONFIG_FORMAT = 'showConfigFormat';
-
-        private $request;
-
-        public function checkForConfigRequest($observer) {
-            $this->request = $observer->getEvent()->getData('front')->getRequest();
-            if($this->request->{self::FLAG_SHOW_CONFIG} === 'true'){
-                $this->setHeader();
-                $this->outputConfig();
-            }
-        }
-
-        private function setHeader() {
-            $format = isset($this->request->{self::FLAG_SHOW_CONFIG_FORMAT}) ?
-            $this->request->{self::FLAG_SHOW_CONFIG_FORMAT} : 'xml';
-            switch($format){
-                case 'text':
-                    header("Content-Type: text/plain");
-                    break;
-                default:
-                    header("Content-Type: text/xml");
-            }
-        }
-
-        private function outputConfig() {
-            die(Mage::app()->getConfig()->getNode()->asXML());
-        }
-    }
-
- -

That's it. Clear your Magento cache again and then load any Magento URL with a showConfig=true query string

- -
http://magento.example.com/?showConfig=true
- - -

What am I looking at?

- -

You should be looking at a giant XML file. This describes the state of your Magento system. It lists all modules, models, classes, event listeners or almost anything else you could think of.

- -

For example, consider the config.xml file you created above. If you search the XML file in your browser for the text Configviewer_Model_Observer you'll find your class listed. Every module's config.xml file is parsed by Magento and included in the global config.

- - -

Why Do I Care?

- -

Right now this may seem esoteric, but this config is key to understanding Magento. Every module you'll be creating will add to this config, and anytime you need to access a piece of core system functionality, Magento will be referring back to the config to look something up.

- -

A quick example: As an MVC developer, you've likely worked with some kind of helper class, instantiated something like

- -
-$helper_sales = new HelperSales();
-
- -

One of the things Magento has done is abstract away PHP's class declaration. In Magento, the above code looks something like

- -
-$helper_sales = Mage::helper('sales');
-
- -

In plain english, the static helper method will: -

    -
  1. Look in the <helpers /> section of the Config.
  2. -
  3. Within <helpers />, look for a <sales /> section
  4. -
  5. Within the <sales /> section look for a <class /> section
  6. -
  7. Append the part after the slash to the value found in #3 (defaulting to data in this case)
  8. -
  9. Instantiate the class found in #4 (Mage_Sales_Helper_Data)
  10. -
+ + + + + -

While this seems like a lot of work (and it is), the key advantage is by always looking to the config file for class names, we can override core Magento functionality without changing or adding to the core code. This level of meta programming, not usually found in PHP, allows you to cleanly extend only the parts of the system you need to.

+ + + + + \ No newline at end of file diff --git a/guides/m1x/magefordev/mage-for-dev-3.html b/guides/m1x/magefordev/mage-for-dev-3.html index 5e09a7e138..c5ebee5f10 100644 --- a/guides/m1x/magefordev/mage-for-dev-3.html +++ b/guides/m1x/magefordev/mage-for-dev-3.html @@ -1,318 +1,599 @@ --- --- - - + - - - - - - Magento for Developers: Part 3—Magento Controller Dispatch - + + + + + OpenMage for Developers: Part 3—OpenMage Controller Dispatch + + + + + + + + + + + + + + + - -header -{% include m1x/eol_message.html %} - -
- - -

The Model-View-Controller (MVC) architecture traces its origins back to the Smalltalk Programming language and Xerox Parc. Since then, there have been many systems that describe their architecture as MVC. Each system is slightly different, but all have the goal of separating data access, business logic, and user-interface code from one another.

- -

The architecture of most PHP MVC frameworks will looks something like this.

- -
    -
  1. A URL is intercepted by a single PHP file (usually called a Front Controller).
  2. -
  3. This PHP file will examine the URL, and derive a Controller name and an Action name (a process that's often called routing).
  4. -
  5. The derived Controller is instantiated.
  6. -
  7. The method name matching the derived Action name is called on the Controller.
  8. -
  9. This Action method will instantiate and call methods on models, depending on the request variables.
  10. -
  11. The Action method will also prepare a data structure of information. This data structure is passed on to the view.
  12. -
  13. The view then renders HTML, using the information in the data structure it has received from the Controller.
  14. -
- -

While this pattern was a great leap forward from the "each php file is a page" pattern established early on, for some software engineers, it's still an ugly hack. Common complaints are:

- -
    -
  • The Front Controller PHP file still operates in the global namespace.
  • -
  • Convention over configuration leads to less modularity. -
      -
    • URLs routing is often inflexible.
    • -
    • Controllers are often bound to specific views.
    • -
    • Even when a system offers a way to override these defaults, the convention leads to applications where it's difficult/impossible to drop in a new model, view, or Controller implementation without massive re-factoring.
    • -
    -
- -

As you've probably guessed, the Magento team shares this world view and has created a more abstract MVC pattern that looks something like this:. - -

    -
  1. A URL is intercepted by a single PHP file.
  2. -
  3. This PHP file instantiates a Magento application.
  4. -
  5. The Magento application instantiates a Front Controller object.
  6. -
  7. Front Controller instantiates any number of Router objects (specified in global config).
  8. -
  9. Routers check the request URL for a "match".
  10. -
  11. If a match is found, an Action Controller and Action are derived.
  12. -
  13. The Action Controller is instantiated and the method name matching the Action Name is called.
  14. -
  15. This Action method will instantiate and call methods on models, depending on the request.
  16. -
  17. This Action Controller will then instantiate a Layout Object.
  18. -
  19. This Layout Object will, based some request variables and system properties (also known as "handles"), create a list of Block objects that are valid for this request.
  20. -
  21. Layout will also call an output method on certain Block objects, which start a nested rendering (Blocks will include other Blocks).
  22. -
  23. Each Block has a corresponding Template file. Blocks contain PHP logic, templates contain HTML and PHP output code.
  24. -
  25. Blocks refer directly back to the models for their data. In other words, the Action Controller does not pass them a data structure.
  26. -
- -

We'll eventually touch on each part of this request, but for now we're concerned with the Front Controller -> Routers -> Action Controller section. - -


-

Hello World

-

Enough theory, it's time for Hello World. We're going to

-
    -
  1. Create a Hello World module in the Magento system
  2. -
  3. Configure this module with routes
  4. -
  5. Create Action Controller(s) for our routes
  6. -
- -

Create Hello World Module

- -

First, we'll create a directory structure for this module. Our directory structure should look as follows: - -

app/code/local/Magentotutorial/Helloworld/Block
-app/code/local/Magentotutorial/Helloworld/controllers
-app/code/local/Magentotutorial/Helloworld/etc   
-app/code/local/Magentotutorial/Helloworld/Helper
-app/code/local/Magentotutorial/Helloworld/Model
-app/code/local/Magentotutorial/Helloworld/sql
-
- -

Then create a configuration file for the module (at path app/code/local/Magentotutorial/Helloworld/etc/config.xml): -

-<config>    
-    <modules>
-        <Magentotutorial_Helloworld>
-            <version>0.1.0</version>
-        </Magentotutorial_Helloworld>
-    </modules>
-</config>
-
- -

Then create a file to activate the module (at path app/etc/modules/Magentotutorial_Helloworld.xml): -

-<config>
-    <modules>
-        <Magentotutorial_Helloworld>
-            <active>true</active>
-            <codePool>local</codePool>
-        </Magentotutorial_Helloworld>
-    </modules>
-</config>
-
- -

Finally, we ensure the module is active: -

    -
  1. Clear your Magento cache.
  2. -
  3. In the Magento Admin, go to System->Configuration->Advanced.
  4. -
  5. Expand "Disable Modules Output" (if it isn't already).
  6. -
  7. Ensure that Magentotutorial_Helloworld shows up.
  8. -
- -

Configuring Routes

- -

Next, we're going to configure a route. A route will turn a URL into an Action Controller and a method. Unlike other convention based PHP MVC systems, with Magento you need to explicitly define a route in the global Magento config. - -

In your config.xml file(at path app/code/local/Magentotutorial/Helloworld/etc/config.xml), add the following section: - -

-<config>    
-    ...
-    <frontend>
-        <routers>
-            <helloworld>
-                <use>standard</use>
-                <args>
+
+
+
+
+
+
+ +
+ + + +
+
+ +
+

OpenMage Controller Dispatch

+
+

The Model-View-Controller (MVC) architecture traces its origins back to the + Smalltalk Programming language and Xerox Parc. Since then, there have been many systems that + describe their architecture as MVC. Each system is slightly different, but all have the goal of + separating data access, business logic, and user-interface code from one another.

+

The architecture of most PHP MVC frameworks will looks something like + this.

+
    +
  1. A URL is intercepted by a single PHP file (usually called a Front Controller).
  2. +
  3. This PHP file will examine the URL, and derive a Controller name and an Action name (a + process that's often called routing). +
  4. +
  5. The derived Controller is instantiated.
  6. +
  7. The method name matching the derived Action name is called on the Controller.
  8. +
  9. This Action method will instantiate and call methods on models, depending on the request + variables. +
  10. +
  11. The Action method will also prepare a data structure of information. This data structure is + passed on to the view. +
  12. +
  13. The view then renders HTML, using the information in the data structure it has received from + the Controller. +
  14. +
+

While this pattern was a great leap forward from the "each php file is a page" pattern + established early on, for some software engineers, it's still an ugly hack. Common complaints + are:

+
    +
  • The Front Controller PHP file still operates in the global namespace.
  • +
  • Convention over configuration leads to less modularity. +
      +
    • URLs routing is often inflexible.
    • +
    • Controllers are often bound to specific views.
    • +
    • Even when a system offers a way to override these defaults, the convention leads to + applications where it's difficult/impossible to drop in a new model, view, or + Controller implementation without massive re-factoring. +
    • +
    +
+

As you've probably guessed, the Magento team shares this world view and has created a more + abstract MVC pattern that looks something like this:. +

    +
  1. A URL is intercepted by a single PHP file.
  2. +
  3. This PHP file instantiates a Magento application.
  4. +
  5. The Magento application instantiates a Front Controller object.
  6. +
  7. Front Controller instantiates any number of Router objects (specified in global config). +
  8. +
  9. Routers check the request URL for a "match".
  10. +
  11. If a match is found, an Action Controller and Action are derived.
  12. +
  13. The Action Controller is instantiated and the method name matching the Action Name is + called. +
  14. +
  15. This Action method will instantiate and call methods on models, depending on the request. +
  16. +
  17. This Action Controller will then instantiate a Layout Object.
  18. +
  19. This Layout Object will, based some request variables and system properties (also known as + "handles"), create a list of Block objects that are valid for this request. +
  20. +
  21. Layout will also call an output method on certain Block objects, which start a nested + rendering (Blocks will include other Blocks). +
  22. +
  23. Each Block has a corresponding Template file. Blocks contain PHP logic, templates contain + HTML and PHP output code. +
  24. +
  25. Blocks refer directly back to the models for their data. In other words, the Action + Controller does not pass them a data structure.
  26. +
+

We'll eventually touch on each part of this request, but for now we're concerned with the Front + Controller -> Routers -> Action Controller section. +


+

Hello World

+

Enough theory, it's time for Hello World. We're going to

+
    +
  1. Create a Hello World module in the Magento system
  2. +
  3. Configure this module with routes
  4. +
  5. Create Action Controller(s) for our routes
  6. +
+

Create Hello World Module

+

First, we'll create a directory structure for this module. Our directory structure should look as + follows: +

app/code/local/Magentotutorial/Helloworld/Block
+                    app/code/local/Magentotutorial/Helloworld/controllers
+                    app/code/local/Magentotutorial/Helloworld/etc
+                    app/code/local/Magentotutorial/Helloworld/Helper
+                    app/code/local/Magentotutorial/Helloworld/Model
+                    app/code/local/Magentotutorial/Helloworld/sql
+                    
+

Then create a configuration file for the module (at path app/code/local/Magentotutorial/Helloworld/etc/config.xml): +

+                    <config>
+                        <modules>
+                            <Magentotutorial_Helloworld>
+                                <version>0.1.0</version>
+                            </Magentotutorial_Helloworld>
+                        </modules>
+                    </config>
+                    
+

Then create a file to activate the module (at path app/etc/modules/Magentotutorial_Helloworld.xml): +

+                    <config>
+                        <modules>
+                            <Magentotutorial_Helloworld>
+                                <active>true</active>
+                                <codePool>local</codePool>
+                            </Magentotutorial_Helloworld>
+                        </modules>
+                    </config>
+                    
+

Finally, we ensure the module is active: +

    +
  1. Clear your Magento cache.
  2. +
  3. In the Magento Admin, go to System->Configuration->Advanced.
  4. +
  5. Expand "Disable Modules Output" (if it isn't already).
  6. +
  7. Ensure that Magentotutorial_Helloworld shows up.
  8. +
+

Configuring Routes

+

Next, we're going to configure a route. A route will turn a URL into an Action Controller and a + method. Unlike other convention based PHP MVC systems, with Magento you need to explicitly + define a route in the global Magento config. +

In your config.xml file(at path app/code/local/Magentotutorial/Helloworld/etc/config.xml), add + the following section: +

+                    <config>
+                        ...
+                        <frontend>
+                            <routers>
+                                <helloworld>
+                                    <use>standard</use>
+                                    <args>
+                                        <module>Magentotutorial_Helloworld</module>
+                                        <frontName>helloworld</frontName>
+                                    </args>
+                                </helloworld>
+                            </routers>
+                        </frontend>
+                        ...
+                    </config>
+                    
+

We have a lot of new terminology here, let's break it down.

+

What is a <frontend>?

+

The <frontend> tag refers to a Magento Area. For now, think of Areas as individual Magento + applications. The "frontend" Area is the public facing Magento shopping cart application. The + "admin" Area is the private administrative console application. The "install" Area is the + application you use to run though installing Magento the first time.

+

Why a <routers> tags if we're configuring individual routes?

+

There's a famous quote about computer science, often attributed to Phil Karlton:

+
+

"There are only two hard things in Computer Science: cache invalidation and naming + things"

+
+

Magento, like all large systems, suffers from the naming problem in spades. You'll find there are + many places in the global config, and the system in general, where the naming conventions seem + unintuitive or even ambiguous. This is one of those places. Sometimes the + <routers> tag will enclose configuration information about routers, other times + it will enclose configuration information about the actual router objects that do the routing. + This is going to seem counter intuitive at first, but as you start to work with Magento more and + more, you'll start to understand its world view a little better. (Or, in the words of Han Solo, + "Hey, trust me!").

+

What is a <frontName>?

+

When a router parses a URL, it gets separated as follows

+
http://example.com/frontName/actionControllerName/actionMethod/
+

So, by defining a value of "helloworld" in the <frontName> tags, we're telling Magento that + we want the system to respond to URLs in the form of

+
http://example.com/helloworld/*
+

Many developers new to Magento confuse this frontName with the Front Controller object. They are + not the same thing. The frontName belongs solely to routing.

+

What's the <helloworld> tag for?

+

This tag should be the lowercase version of you module name. Our module name is Helloworld, this + tag is helloworld. Technically this tag defines our route name

+

You'll also notice our frontName matches our module name. It's a loose convention to have + frontNames match the module names, but it's not a requirement. In your own modules, it's + probably better to use a route name that's a combination of your module name and package name to + avoid possible namespace collisions.

+

What's <module>Magentotutorial_Helloworld</module> for?

+

This module tag should be the full name of your module, including its package/namespace name. + This will be used by the system to locate your Controller files.

+

Create Action Controller(s) for our Routes

+

One last step to go, and we'll have our Action Controller. Create a file at +

app/code/local/Magentotutorial/Helloworld/controllers/IndexController.php
+

That contains the following +

+                    <?php
+                    class Magentotutorial_Helloworld_IndexController extends Mage_Core_Controller_Front_Action {
+                        public function indexAction() {
+                            echo 'Hello World';
+                        }
+                    }
+                    
+

Clear your config cache, and load the following URL

+
http://example.com/helloworld/index/index
+

You should also be able to load

+
http://example.com/helloworld/index/
+                    http://example.com/helloworld/
+

You should see a blank page with the text "Hello World". Congratulations, you've setup your first + Magento Controller!

+

Where do Action Controllers go?

+

Action Controllers should be placed in a module's controllers (lowercase c) folder. This + is where the system will look for them.

+

How should Action Controllers be named?

+

Remember the <module> tag back in config.xml? +

                     <module>Magentotutorial_Helloworld</module>
-                    <frontName>helloworld</frontName>
-                </args>
-            </helloworld>
-        </routers>  
-    </frontend>
-    ...
-</config>
-
- -

We have a lot of new terminology here, let's break it down.

- -

What is a <frontend>?

- -

The <frontend> tag refers to a Magento Area. For now, think of Areas as individual Magento applications. The "frontend" Area is the public facing Magento shopping cart application. The "admin" Area is the private administrative console application. The "install" Area is the application you use to run though installing Magento the first time.

- -

Why a <routers> tags if we're configuring individual routes?

- -

There's a famous quote about computer science, often attributed to Phil Karlton:

-
-

"There are only two hard things in Computer Science: cache invalidation and naming things"

-
- -

Magento, like all large systems, suffers from the naming problem in spades. You'll find there are many places in the global config, and the system in general, where the naming conventions seem unintuitive or even ambiguous. This is one of those places. Sometimes the <routers> tag will enclose configuration information about routers, other times it will enclose configuration information about the actual router objects that do the routing. This is going to seem counter intuitive at first, but as you start to work with Magento more and more, you'll start to understand its world view a little better. (Or, in the words of Han Solo, "Hey, trust me!").

- -

What is a <frontName>?

- -

When a router parses a URL, it gets separated as follows

- -
http://example.com/frontName/actionControllerName/actionMethod/
- -

So, by defining a value of "helloworld" in the <frontName> tags, we're telling Magento that we want the system to respond to URLs in the form of

- -
http://example.com/helloworld/*
- -

Many developers new to Magento confuse this frontName with the Front Controller object. They are not the same thing. The frontName belongs solely to routing.

- -

What's the <helloworld> tag for?

- -

This tag should be the lowercase version of you module name. Our module name is Helloworld, this tag is helloworld. Technically this tag defines our route name

- -

You'll also notice our frontName matches our module name. It's a loose convention to have frontNames match the module names, but it's not a requirement. In your own modules, it's probably better to use a route name that's a combination of your module name and package name to avoid possible namespace collisions.

- -

What's <module>Magentotutorial_Helloworld</module> for?

- -

This module tag should be the full name of your module, including its package/namespace name. This will be used by the system to locate your Controller files.

- -

Create Action Controller(s) for our Routes

- -

One last step to go, and we'll have our Action Controller. Create a file at - -

app/code/local/Magentotutorial/Helloworld/controllers/IndexController.php
- -

That contains the following - -

-<?php
-class Magentotutorial_Helloworld_IndexController extends Mage_Core_Controller_Front_Action {        
-    public function indexAction() {
-        echo 'Hello World';
-    }
-}
-
- -

Clear your config cache, and load the following URL

- -
http://example.com/helloworld/index/index
- -

You should also be able to load

- -
http://example.com/helloworld/index/
-http://example.com/helloworld/
- -

You should see a blank page with the text "Hello World". Congratulations, you've setup your first Magento Controller!

- - -

Where do Action Controllers go?

- -

Action Controllers should be placed in a module's controllers (lowercase c) folder. This is where the system will look for them.

- -

How should Action Controllers be named?

- -

Remember the <module> tag back in config.xml? -

-<module>Magentotutorial_Helloworld</module>
-
- -

An Action Controller's name will -

    -
  1. Start with this <module> string specified in config.xml (Magentotutorial_Helloworld)
  2. -
  3. Be followed by an underscore (Magentotutorial_Helloworld_)
  4. -
  5. Which will be followed by the Action Controller's name (Magentotutorial_Helloworld_Index)
  6. -
  7. And finally, the word "Controller" (Magentotutorial_Helloworld_IndexController)
  8. -
- -

All Action Controllers need Mage_Core_Controller_Front_Action as an ancestor.

- -

What's that index/index nonsense?

- -

As we previously mentioned, Magento URLs are routed (by default) as follows -

http://example.com/frontName/actionControllerName/actionMethod/
- -

So in the URL -

http://example.com/helloworld/index/index
- -

the URI portion "helloworld" is the frontName, which is followed by index (The Action Controller name), which is followed by another index, which is the name of the Action Method that will be called. (an Action of index will call the method public function indexAction(){...}.

- -

If a URL is incomplete, Magento uses "index" as the default, which is why the following URLs are equivalent. -

-http://example.com/helloworld/index
-http://example.com/helloworld
-
- -

If we had a URL that looked like this

-
http://example.com/checkout/cart/add
- -

Magento would

-
    -
  1. Consult the global config to find the module to use for the frontName checkout (Mage_Checkout)
  2. -
  3. Look for the cart Action Controller (Mage_Checkout_CartController)
  4. -
  5. Call the addAction method on the cart Action Controller
  6. -
- -

Other Action Controller Tricks

- -

Let's try adding a non-default method to our Action Controller. Add the following code to IndexController.php

- -
-public function goodbyeAction() {
-    echo 'Goodbye World!';
-}
-
- -

And then visit the URL to test it out: -

http://example.com/helloworld/index/goodbye 
- -

Because we're extending the Mage_Core_Controller_Front_Action class, we get some methods for free. For example, additional URL elements are automatically parsed into key/value pairs for us. Add the following method to your Action Controller. - -

-public function paramsAction() {
-    echo '<dl>';            
-    foreach($this->getRequest()->getParams() as $key=>$value) {
-        echo '<dt><strong>Param:</strong>'.$key.'</dt>';
-        echo '<dt><strong>Value: </strong>'.$value.'</dt>';
-    }
-    echo '</dl>';
-}
-
- -

and visit the following URL

-
http://example.com/helloworld/index/params?foo=bar&baz=eof
- -

You should see each parameter and value printed out.

- -

Finally, what would we do if we wanted a URL that responded at -

http://example.com/helloworld/messages/goodbye
- -

Here our Action Controller's name is messages, so we'd create a file at -

app/code/local/Magentotutorial/Helloworld/controllers/MessagesController.php
- -

with an Action Controller named Magentotutorial_Helloworld_MessagesController and an Action Method that looked something like -

-public function goodbyeAction()         
-{
-    echo 'Another Goodbye';
-}
-
+
+

An Action Controller's name will +

    +
  1. Start with this <module> string specified in config.xml (Magentotutorial_Helloworld) +
  2. +
  3. Be followed by an underscore (Magentotutorial_Helloworld_)
  4. +
  5. Which will be followed by the Action Controller's name + (Magentotutorial_Helloworld_Index) +
  6. +
  7. And finally, the word "Controller" (Magentotutorial_Helloworld_IndexController) +
  8. +
+

All Action Controllers need Mage_Core_Controller_Front_Action as an ancestor.

+

What's that index/index nonsense?

+

As we previously mentioned, Magento URLs are routed (by default) as follows +

http://example.com/frontName/actionControllerName/actionMethod/
+

So in the URL +

http://example.com/helloworld/index/index
+

the URI portion "helloworld" is the frontName, which is followed by index (The Action Controller + name), which is followed by another index, which is the name of the Action Method that will be + called. (an Action of index will call the method public function indexAction(){...}. +

+

If a URL is incomplete, Magento uses "index" as the default, which is why the following URLs are + equivalent. +

+                    http://example.com/helloworld/index
+                    http://example.com/helloworld
+                    
+

If we had a URL that looked like this

+
http://example.com/checkout/cart/add
+

Magento would

+
    +
  1. Consult the global config to find the module to use for the frontName checkout (Mage_Checkout) +
  2. +
  3. Look for the cart Action Controller (Mage_Checkout_CartController)
  4. +
  5. Call the addAction method on the cart Action Controller
  6. +
+

Other Action Controller Tricks

+

Let's try adding a non-default method to our Action Controller. Add the following code to + IndexController.php

+
+                    public function goodbyeAction() {
+                        echo 'Goodbye World!';
+                    }
+                    
+

And then visit the URL to test it out: +

http://example.com/helloworld/index/goodbye 
+

Because we're extending the Mage_Core_Controller_Front_Action class, we get some methods + for free. For example, additional URL elements are automatically parsed into key/value pairs for + us. Add the following method to your Action Controller. +

+                    public function paramsAction() {
+                        echo '<dl>';
+                        foreach($this->getRequest()->getParams() as $key=>$value) {
+                            echo '<dt><strong>Param:</strong>'.$key.'</dt>';
+                            echo '<dt><strong>Value: </strong>'.$value.'</dt>';
+                        }
+                        echo '</dl>';
+                    }
+                    
+

and visit the following URL

+
http://example.com/helloworld/index/params?foo=bar&baz=eof
+

You should see each parameter and value printed out.

+

Finally, what would we do if we wanted a URL that responded at +

http://example.com/helloworld/messages/goodbye
+

Here our Action Controller's name is messages, so we'd create a file at +

app/code/local/Magentotutorial/Helloworld/controllers/MessagesController.php
+

with an Action Controller named Magentotutorial_Helloworld_MessagesController and an + Action Method that looked something like +

+                    public function goodbyeAction()
+                    {
+                        echo 'Another Goodbye';
+                    }
+                    
+

And that, in a nutshell, is how Magento implements the Controller portion of MVC. While it's a + little more complicated than other PHP MVC framework's, it's a highly flexible system that will + allow you build almost any URL structure you want.

+
+ + + + +
+ + + + + + + + -

And that, in a nutshell, is how Magento implements the Controller portion of MVC. While it's a little more complicated than other PHP MVC framework's, it's a highly flexible system that will allow you build almost any URL structure you want.

+ + + + + \ No newline at end of file diff --git a/guides/m1x/magefordev/mage-for-dev-4.html b/guides/m1x/magefordev/mage-for-dev-4.html index ec76055920..8040950668 100644 --- a/guides/m1x/magefordev/mage-for-dev-4.html +++ b/guides/m1x/magefordev/mage-for-dev-4.html @@ -1,487 +1,747 @@ --- --- - - + - - - - - - - Magento for Developers: Part 4—Magento Layouts, Blocks and Templates - + + + + + OpenMage for Developers: Part 4—OpenMage Layouts, Blocks and Templates + + + + + + + + + + + + + + + - -header -{% include m1x/eol_message.html %} - -
- -
-

Magento for Developers: Part 4—Magento Layouts, Blocks and Templates

-

by Alan Storm, updated for Magento 1.12

-

Edit this page on GitHub

- - - - - -
Other articles in this series:
- + + + + +
+
+ +
+ + + +
+
+ +
+

OpenMage Layouts, Blocks and Templates

+
+

Developers new to Magento are often confused by the Layout and View system. This + article will take a look at Magento's Layout/Block approach, and show you how it fits into + Magento MVC worldview.

+

Unlike many popular MVC systems, Magento's Action Controller does not pass a + data object to the view or set properties on the view object (with a few exceptions). Instead, + the View component directly references system models to get the information it needs for + display.

+

One consequence of this design decision is that the View has been separated into Blocks and + Templates. Blocks are PHP objects, Templates are "raw" PHP files (with a .phtml extension) that + contain a mix of HTML and PHP (where PHP is used as a templating language). Each Block is tied + to a single Template file. Inside a phtml file, PHP's $this keyword will contain a + reference to the Template's Block object.

+
+

A quick example

+

Take a look at the default product Template at the file at

+
app/design/frontend/base/default/template/catalog/product/list.phtml
+

You'll see the following PHP template code.

+
+                    <?php $_productCollection=$this->getLoadedProductCollection() ?>
+                    <?php if(!$_productCollection->count()): ?> <div class="note-msg">
+                        <?php echo $this->__("There are no products matching the selection.") ?>
+                    </div> <?php else: ?>
+                    ...
+                    
+

The getLoadedProductCollection method can be found in the Template's Block class, Mage_Catalog_Block_Product_List + as shown: +

File: app/code/core/Mage/Catalog/Block/Product/List.php
+
+                    ...
+                    public function getLoadedProductCollection()
+                    {
+                        return $this->_getProductCollection();
+                    }
+                    ...
+                    
+

The block's _getProductCollection then instantiates models and reads their data, + returning a result to the template.

+

Nesting Blocks

+

The real power of Blocks/Templates come with the getChildHtml method. This allows you to + include the contents of a secondary Block/Template inside of a primary Block/Template.

+

Blocks calling Blocks calling Blocks is how the entire HTML layout for your page is created. Take + a look at the one column layout Template.

+
File: app/design/frontend/base/default/template/page/1column.phtml
+
+                    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+                    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<?php echo $this->getLang() ?>" lang="<?php echo $this->getLang() ?>">
+                    <head>
+                    <?php echo $this->getChildHtml('head') ?>
+                    </head>
+                    <body class="page-popup <?php echo $this->getBodyClass()?$this->getBodyClass():'' ?>">
+                        <?php echo $this->getChildHtml('content') ?>
+                        <?php echo $this->getChildHtml('before_body_end') ?>
+                        <?php echo $this->getAbsoluteFooter() ?>
+                    </body>
+                    
+

The template itself is only 28 lines long. However, each call to + $this->getChildHtml(...) will include and render another Block. These Blocks will, in + turn, use getChildHtml to render other Blocks. It's Blocks all the way down.

+

The Layout

+

So, Blocks and Templates are all well and good, but you're probably wondering

+
    +
  1. How do I tell Magento which Blocks I want to use on a page?
  2. +
  3. How do I tell Magento which Block I should start rendering with?
  4. +
  5. How do I specify a particular Block in getChildHtml(...)? Those argument strings + don't look like Block names to me. +
  6. +
+

This is where the Layout Object enters the picture. The Layout Object is an XML object that will + define which Blocks are included on a page, and which Block(s) should kick off the rendering + process.

+

Last time + we were echoing content directly from our Action Methods. This time let's create a simple HTML + template for our Hello World module.

+

First, create a file at

+
app/design/frontend/base/default/layout/local.xml
+

with the following contents

+
+                    <layout version="0.1.0">
+                        <default>
+                            <block type="page/html" name="root" output="toHtml" template="magentotutorial/helloworld/simple_page.phtml" />
+                        </default>
+                    </layout>
+                    
+

Then, create a file at

+
app/design/frontend/base/default/template/magentotutorial/helloworld/simple_page.phtml
+

with the following contents +

+                    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+                            "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+                    <html xmlns="http://www.w3.org/1999/xhtml">
+                    <head>
+                        <title>Hello World</title>
+                        <style type="text/css">
+                            body {
+                                background-color:#f00;
+                            }
+                        </style>
+                    </head>
+                    <body>
+
+                    </body>
+                    </html>
+                    
+

Finally, each Action Controller is responsible for kicking off the layout process. We'll need to + add two method calls to the Action Method.

+
+                    public function indexAction() {
+                        //remove our previous echo
+                        //echo 'Hello Index!';
+                        $this->loadLayout();
+                        $this->renderLayout();
+                    }
+                    
+

Clear your Magento cache and reload your Hello World controller page. You should now see a + website with a bright red background and an HTML source that matches what's in + simple_page.phtml.

+

What's Going On

+

So, that's a lot of voodoo and cryptic incantations. Let's take a look at what's going on.

+

First, you'll want to install the Layoutviewer + module. This is a module similar to the Configviewer + module you built in the Hello World article that will let us peek at some of Magento's + internals.

+

Once you've installed the module (similar to how you setup the Configviewer + module), go to the following URL

+
http://example.com/helloworld/index/index?showLayout=page
+

This is the layout xml for your page/request. It's made up of <block />, <reference /> + and <remove /> tags. When you call the loadLayout method of your Action + Controller, Magento will

+
    +
  1. Generate this Layout XML

  2. +
  3. Instantiate a Block class for each <block /> tag, looking up the class using the + tag's type attribute as a global config path and store it in the internal _blocks + array of the layout object, using the tag's name attribute as the array key.

  4. +
  5. If the <block /> tag contains an output attribute, its value is added to the + internal _output array of the layout object.

  6. +
+

Then, when you call the renderLayout method in your Action Controller, Magento will + iterate over all the Blocks in the _output array, using the value of the output + attribute as a callback method. This is always toHtml, and means the starting point for + output will be that Block's Template.

+

The following sections will cover how Blocks are instantiated, how this layout file is generated, + and finishes up with kicking off the output process.

+

Block Instantiation

+

So, within a Layout XML file, a <block /> has a "type" that's actually a Grouped Class Name + URI

+
+                    <block type="page/html" ...
+                    <block type="page/template_links" ...
+                    
+

The URI references a location in the (say it with me) global config. The first portion of the URI + (in the above examples page) will be used to query the global config to find the page + class name. The second portion of the URI (in the two examples above, html and template_links) + will be appended to the base class name to create the class name Magento should instantiate.

+

We'll go through page/html as an example. First, Magento looks for the global config + node at file

+
app/code/core/Mage/Page/etc/config.xml
+

and finds +

+                    <page>
+                        <class>Mage_Page_Block</class>
+                    </page>
+                    
+

This gives us our base class prefix Mage_Page_Block. Then, the second part of the URI + (html) is appended to the class name to give us our final Block class name Mage_Page_Block_Html. + This is the class that will be instantiated.

+

If we create a block with the same name as an already existing block, the new block instance will + replace the original instance. This is what we've done in our local.xml file from above.

+
+                    <layout version="0.1.0">
+                        <default>
+                            <block type="page/html" name="root" output="toHtml" template="magentotutorial/helloworld/simple_page.phtml" />
+                        </default>
+                    </layout>
+                    
+

The Block named root has been replaced with our Block, which points at a different phtml + Template file.

+

Using references

+

<reference name="" /> will hook all contained XML declarations into an existing block with + the specified name. Contained <block /> nodes will be assigned as child blocks to the + referenced parent block.

+
+                    <layout version="0.1.0">
+                        <default>
+                            <block type="page/html" name="root" output="toHtml" template="page/2columns-left.phtml">
+                                <!-- ... sub blocks ... -->
+                            </block>
+                        </default>
+                    </layout>
+                    
+

In a different layout file:

+
+                    <layout version="0.1.0">
+                        <default>
+                            <reference name="root">
+                                <!-- ... another sub block ... -->
+                                <block type="page/someothertype" name="some.other.block.name" template="path/to/some/other/template" />
+                            </reference>
+                        </default>
+                    </layout>
+                    
+

Even though the root block is declared in a separate layout XML file, the new block is added as a + child block. Magento initially creates a page/html Block named root. Then, + when it later encounters the reference with the same name (root), it will assign the + new block some.other.block.name as a child of the root block. +

+

How Layout Files are Generated

+

So, we have a slightly better understanding of what's going on with the Layout XML, but where is + this XML file coming from? To answer that question, we need to introduce two new concepts; + Handles and the Package Layout.

+

Handles

+

Each page request in Magento will generate several unique Handles. The Layoutview module can show + you these Handles by using a URL something like

+
http://example.com/helloworld/index/index?showLayout=handles
+

You should see a list similar to the following (depending on your configuration)

+
    +
  1. default
  2. +
  3. STORE_bare_us
  4. +
  5. THEME_frontend_default_default
  6. +
  7. helloworld_index_index
  8. +
  9. customer_logged_out
  10. +
+

Each of these is a Handle. Handles are set in a variety of places within the Magento system. The + two we want to pay attention to are default and helloworld_index_index. The + default Handle is present in every request into the Magento system. + The helloworld_index_index Handle is created by combining the route name (helloworld), + Action Controller name (index), and Action Controller Action Method (index) into a single + string. This means each possible method on an Action Controller has a Handle associated with it. +

+

Remember that "index" is the Magento default for both Action Controllers and Action Methods, so + the following request

+
http://example.com/helloworld/?showLayout=handles
+

Will also produce a Handle named helloworld_index_index

+

Package Layout

+

You can think of the Package Layout similar to the global config. It's a large XML file that + contains every possible layout configuration for a particular Magento install. + Let's take a look at it using the Layoutview module

+
http://example.com/helloworld/index/index?showLayout=package
+

This may take a while to load. If your browser is choking on the XML rendering, try the text + format

+
http://example.com/helloworld/index/index?showLayout=package&showLayoutFormat=text
+

You should see a very large XML file. This is the Package Layout. This XML file is created by + combining the contents of all the XML layout files for the current theme (or package). For the + default install, this is at

+
app/design/frontend/base/default/layout/
+

Behind the scenes there are <frontend><layout><updates /> and + <adminhtml><layout><updates /> sections of the global config + that contains nodes with all the file names to load for the respective area. Once the files + listed in the config have been combined, Magento will merge in one last xml file, local.xml. + This is the file where you're able to add your customizations to your Magento install.

+

Combining Handles and The Package Layout

+

So, if you look at the Package Layout, you'll see some familiar tags such as <block /> and + <reference />, but they're all surrounded by tags that look like

+
+                    <default />
+                    <catalogsearch_advanced_index />
+                    etc...
+                    
+

These are all Handle tags. The Layout for an individual request is generated by grabbing all the + sections of the Package Layout that match any Handles for the request. So, in our example above, + our layout is being generated by grabbing tags from the following sections

+
+                    <default />
+                    <STORE_bare_us />
+                    <THEME_frontend_default_default />
+                    <helloworld_index_index />
+                    <customer_logged_out />
+                    
+

There's one additional tag you'll need to be aware of in the Package Layout. The <update /> + tag allows you to include another Handle's tags. For example

+
+                    <customer_account_index>
+                        <!-- ... -->
+                        <update handle="customer_account"/>
+                        <!-- ... -->
+                    </customer_account_index>
+                    
+

Is saying that requests with a customer_account_index Handle should include <blocks />s + from the <customer_account /> Handle.

+

Applying What We've Learned

+

OK, that's a lot of theory. Lets get back to what we did earlier. Knowing what we know now, + adding

+
+                    <layout version="0.1.0">
+                        <default>
+                            <block type="page/html" name="root" output="toHtml" template="magentotutorial/helloworld/simple_page.phtml" />
+                        </default>
+                    </layout>
+                    
+

to local.xml means we've overridden the "root" tag. with a different Block. By placing + this in the <default /> Handle we've ensured that this override will happen for every + page request in the system. That's probably not what we want.

+

If you go to any other page in your Magento site, you'll notice they're either blank white, or + have the same red background that your hello world page does. Let's change your + local.xml file so it only applies to the hello world page. We'll do this by changing + default to use the full action name handle (helloworld_index_index).

+
+                    <layout version="0.1.0">
+                        <helloworld_index_index>
+                            <block type="page/html" name="root" output="toHtml" template="magentotutorial/helloworld/simple_page.phtml" />
+                        </helloworld_index_index>
+                    </layout>
+                    
+

Clear your Magento cache, and the rest of your pages should be restored.

+

Right now this only applies to our index Action Method. Let's add it to the goodbye Action Method + as well. In your Action Controller, modify the goodbye action so it looks like

+
+                    public function goodbyeAction() {
+                        $this->loadLayout();
+                        $this->renderLayout();
+                    }
+                    
+

If you load up the following URL, you'll notice you're still getting the default Magento + layout.

+
http://example.com/helloworld/index/goodbye
+

We need to add a Handle for the full action name (helloworld_index_goodbye) to our + local.xml file. Rather than specify a new <block />, lets use the update tag to include + the helloworld_index_index Handle.

+
+                    <layout version="0.1.0">
+                        <!-- ... -->
+                        <helloworld_index_goodbye>
+                            <update handle="helloworld_index_index" />
+                        </helloworld_index_goodbye>
+                    </layout>
+                    
+

Loading the following pages (after clearing your Magento cache) should now produce identical + results.

+
http://example.com/helloworld/index/index
+                    http://example.com/helloworld/index/goodbye
+

Starting Output and getChildHtml

+

In a standard configuration, output starts on the Block named root (because it has an output + attribute). We've overridden root's Template with our own

+
template="magentotutorial/helloworld/simple_page.phtml"
+

Templates are referenced from the root folder of the current theme. In this case, that's

+
app/design/frontend/base/default
+

so we need to drill down to our custom page. Most Magento Templates are stored in

+
app/design/frontend/base/default/templates
+

Combining this gives us the full path

+
app/design/frontend/base/default/templates/magentotutorial/helloworld/simple_page.phtml
+

Adding Content Blocks

+

A simple red page is pretty boring. Let's add some content to this page. Change your <helloworld_index_index + /> Handle in local.xml so it looks like the following

+
+                    <helloworld_index_index>
+                        <block type="page/html" name="root" output="toHtml" template="magentotutorial/helloworld/simple_page.phtml">
+                            <block type="customer/form_register" name="customer_form_register" template="customer/form/register.phtml"/>
+                        </block>
+                    </helloworld_index_index>
+                    
+

We're adding a new Block nested within our root. This is a Block that's distributed with Magento, + and will display a customer registration form. By nesting this Block within our root Block, + we've made it available to be pulled into our simple_page.html Template. Next, we'll + use the Block's getChildHtml method in our simple_page.phtml file. Edit simple_page.html + so it looks like this

+
+                    <body>
+                        <?php echo $this->getChildHtml('customer_form_register'); ?>
+                    </body>
+                    
+

Clear your Magento cache and reload the page and you should see the customer registration form on + your red background. Magento also has a Block named top.links. Let's try including that. Change + your simple_page.html file so it reads

+
+                    <body>
+                        <h1>Links</h1>
+                        <?php echo $this->getChildHtml('top.links'); ?>
+                    </body>
+                    
+

When you reload the page, you'll notice that your <h1>Links</h1> title is rendering, + but nothing is rendering for top.links. That's because we didn't add it to local.xml. The getChildHtml + method can only include Blocks that are specified as sub-Blocks in the Layout. This allows + Magento to only instantiate the Blocks it needs, and also allows you to set difference Templates + for Blocks based on context.

+

Let's add the top.links Block to our local.xml

+
+                    <helloworld_index_index>
+                        <block type="page/html" name="root" output="toHtml" template="magentotutorial/helloworld/simple_page.phtml">
+                            <block type="page/template_links" name="top.links"/>
+                            <block type="customer/form_register" name="customer_form_register" template="customer/form/register.phtml"/>
+                        </block>
+                    </helloworld_index_index>
+                    
+

Clear your cache and reload the page. You should now see the top.links module.

+

Time for action

+

There is one more important concept to cover before we wrap up this lesson, and that is the <action + /> tag. Using the <action /> tag enables us to call public PHP methods of the block + classes. So instead of changing the template of the root block by replacing the block instance + with our own, we can use a call to setTemplate instead.

+
+                    <layout version="0.1.0">
+                        <helloworld_index_index>
+                            <reference name="root">
+                                <action method="setTemplate">
+                                    <template>magentotutorial/helloworld/simple_page.phtml</template>
+                                </action>
+                                <block type="page/template_links" name="top.links"/>
+                                <block type="customer/form_register" name="customer_form_register" template="customer/form/register.phtml"/>
+                            </reference>
+                        </helloworld_index_index>
+                    </layout>
+                    
+

This layout XML will first set the template property of the root block, and then will add the two + blocks we use as child blocks. Once we clear the cache, the result should look just as before. + The benefit of using the <action /> is the same block instance is used that was created + earlier, and all other parent/child associations still exist. For that reason this is a more + upgrade proof way of implementing our changes.

+

All arguments to the action's method need to be wrapped in an individual child node of the <action + /> tag. The name of that node doesn't matter, only the order of the nodes. We could have + written the action node from the previous example as follows with the same effect. +

+
+                    <action method="setTemplate">
+                        <some_new_template>magentotutorial/helloworld/simple_page.phtml</some_new_template>
+                    </action>
+                    
+

This is just to illustrate that the action's argument node names are arbitrary.

+

Wrapup

+

That covers Layout fundamentals. We have covered the tags <block />, <reference />, + <update /> and <action />, and also layout update handles like <default /> and + <cms_index_index />. These make up most of the layout configuration used in Magento. If + you found it somewhat daunting, don't worry, you'll rarely need to work with layouts on such a + fundamental level. Magento provides a number of pre-built layouts which can be modified and + skinned to meet the needs of your store. Understanding how the entire Layout system works can be + a great help when you're trouble shooting Layout issues, or adding new functionality to an + existing Magento system.

+
+ + +
+ +
+
+
-

Developers new to Magento are often confused by the Layout and View system. This article will take a look at Magento's Layout/Block approach, and show you how it fits into Magento MVC worldview.

- -

Unlike many popular MVC systems, Magento's Action Controller does not pass a data object to the view or set properties on the view object (with a few exceptions). Instead, the View component directly references system models to get the information it needs for display.

- -

One consequence of this design decision is that the View has been separated into Blocks and Templates. Blocks are PHP objects, Templates are "raw" PHP files (with a .phtml extension) that contain a mix of HTML and PHP (where PHP is used as a templating language). Each Block is tied to a single Template file. Inside a phtml file, PHP's $this keyword will contain a reference to the Template's Block object.

-
- -

A quick example

- -

Take a look at the default product Template at the file at

- -
app/design/frontend/base/default/template/catalog/product/list.phtml
- -

You'll see the following PHP template code.

- -
-<?php $_productCollection=$this->getLoadedProductCollection() ?>
-<?php if(!$_productCollection->count()): ?> <div class="note-msg">
-    <?php echo $this->__("There are no products matching the selection.") ?>
-</div> <?php else: ?>
-...
-
- -

The getLoadedProductCollection method can be found in the Template's Block class, Mage_Catalog_Block_Product_List as shown: - -

File: app/code/core/Mage/Catalog/Block/Product/List.php
- -
-...
-public function getLoadedProductCollection()
-{
-    return $this->_getProductCollection();
-}
-...
-
- -

The block's _getProductCollection then instantiates models and reads their data, returning a result to the template.

- -

Nesting Blocks

- -

The real power of Blocks/Templates come with the getChildHtml method. This allows you to include the contents of a secondary Block/Template inside of a primary Block/Template.

- -

Blocks calling Blocks calling Blocks is how the entire HTML layout for your page is created. Take a look at the one column layout Template.

- -
File: app/design/frontend/base/default/template/page/1column.phtml
- -
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<?php echo $this->getLang() ?>" lang="<?php echo $this->getLang() ?>">
-<head>
-<?php echo $this->getChildHtml('head') ?>
-</head>
-<body class="page-popup <?php echo $this->getBodyClass()?$this->getBodyClass():'' ?>">
-    <?php echo $this->getChildHtml('content') ?>
-    <?php echo $this->getChildHtml('before_body_end') ?>
-    <?php echo $this->getAbsoluteFooter() ?>
-</body>
-
- -

The template itself is only 28 lines long. However, each call to $this->getChildHtml(...) will include and render another Block. These Blocks will, in turn, use getChildHtml to render other Blocks. It's Blocks all the way down.

- -

The Layout

- -

So, Blocks and Templates are all well and good, but you're probably wondering

- -
    -
  1. How do I tell Magento which Blocks I want to use on a page?
  2. -
  3. How do I tell Magento which Block I should start rendering with?
  4. -
  5. How do I specify a particular Block in getChildHtml(...)? Those argument strings don't look like Block names to me.
  6. -
- -

This is where the Layout Object enters the picture. The Layout Object is an XML object that will define which Blocks are included on a page, and which Block(s) should kick off the rendering process.

- -

Last time we were echoing content directly from our Action Methods. This time let's create a simple HTML template for our Hello World module.

- -

First, create a file at

- -
app/design/frontend/base/default/layout/local.xml
- -

with the following contents

- -
-<layout version="0.1.0">
-    <default>
-        <block type="page/html" name="root" output="toHtml" template="magentotutorial/helloworld/simple_page.phtml" />
-    </default>
-</layout>
-
- -

Then, create a file at

- -
app/design/frontend/base/default/template/magentotutorial/helloworld/simple_page.phtml
- -

with the following contents -

-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
-        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head>
-    <title>Hello World</title>
-    <style type="text/css">
-        body {
-            background-color:#f00;
-        }
-    </style>
-</head>
-<body>
-
-</body>
-</html>
-
- -

Finally, each Action Controller is responsible for kicking off the layout process. We'll need to add two method calls to the Action Method.

- -
-public function indexAction() {
-    //remove our previous echo
-    //echo 'Hello Index!';
-    $this->loadLayout();
-    $this->renderLayout();
-}
-
- -

Clear your Magento cache and reload your Hello World controller page. You should now see a website with a bright red background and an HTML source that matches what's in simple_page.phtml.

- -

What's Going On

- -

So, that's a lot of voodoo and cryptic incantations. Let's take a look at what's going on.

- -

First, you'll want to install the Layoutviewer module. This is a module similar to the Configviewer module you built in the Hello World article that will let us peek at some of Magento's internals.

- -

Once you've installed the module (similar to how you setup the Configviewer module), go to the following URL

- -
http://example.com/helloworld/index/index?showLayout=page
- -

This is the layout xml for your page/request. It's made up of <block />, <reference /> and <remove /> tags. When you call the loadLayout method of your Action Controller, Magento will

- -
    -
  1. Generate this Layout XML

  2. -
  3. Instantiate a Block class for each <block /> tag, looking up the class using the tag's type attribute as a global config path and store it in the internal _blocks array of the layout object, using the tag's name attribute as the array key.

  4. -
  5. If the <block /> tag contains an output attribute, its value is added to the internal _output array of the layout object.

  6. -
- -

Then, when you call the renderLayout method in your Action Controller, Magento will iterate over all the Blocks in the _output array, using the value of the output attribute as a callback method. This is always toHtml, and means the starting point for output will be that Block's Template.

- -

The following sections will cover how Blocks are instantiated, how this layout file is generated, and finishes up with kicking off the output process.

- -

Block Instantiation

- -

So, within a Layout XML file, a <block /> has a "type" that's actually a Grouped Class Name URI

-
-<block type="page/html" ...
-<block type="page/template_links" ...
-
- -

The URI references a location in the (say it with me) global config. The first portion of the URI (in the above examples page) will be used to query the global config to find the page class name. The second portion of the URI (in the two examples above, html and template_links) will be appended to the base class name to create the class name Magento should instantiate.

- -

We'll go through page/html as an example. First, Magento looks for the global config node at file

- -
app/code/core/Mage/Page/etc/config.xml
- -

and finds -

-<page>
-    <class>Mage_Page_Block</class>
-</page>
-
- -

This gives us our base class prefix Mage_Page_Block. Then, the second part of the URI (html) is appended to the class name to give us our final Block class name Mage_Page_Block_Html. This is the class that will be instantiated.

- -

If we create a block with the same name as an already existing block, the new block instance will replace the original instance. This is what we've done in our local.xml file from above.

- -
-<layout version="0.1.0">
-    <default>
-        <block type="page/html" name="root" output="toHtml" template="magentotutorial/helloworld/simple_page.phtml" />
-    </default>
-</layout>
-
- -

The Block named root has been replaced with our Block, which points at a different phtml Template file.

- -

Using references

- -

<reference name="" /> will hook all contained XML declarations into an existing block with the specified name. Contained <block /> nodes will be assigned as child blocks to the referenced parent block.

- -
-<layout version="0.1.0">
-    <default>
-        <block type="page/html" name="root" output="toHtml" template="page/2columns-left.phtml">
-            <!-- ... sub blocks ... -->
-        </block>
-    </default>
-</layout>
-
- -

In a different layout file:

- -
-<layout version="0.1.0">
-    <default>
-        <reference name="root">
-            <!-- ... another sub block ... -->
-            <block type="page/someothertype" name="some.other.block.name" template="path/to/some/other/template" />
-        </reference>
-    </default>
-</layout>
-
- -

Even though the root block is declared in a separate layout XML file, the new block is added as a child block. Magento initially creates a page/html Block named root. Then, when it later encounters the reference with the same name (root), it will assign the new block some.other.block.name as a child of the root block. -

- -

How Layout Files are Generated

- -

So, we have a slightly better understanding of what's going on with the Layout XML, but where is this XML file coming from? To answer that question, we need to introduce two new concepts; Handles and the Package Layout.

- -

Handles

- -

Each page request in Magento will generate several unique Handles. The Layoutview module can show you these Handles by using a URL something like

- -
http://example.com/helloworld/index/index?showLayout=handles
- -

You should see a list similar to the following (depending on your configuration)

- -
    -
  1. default
  2. -
  3. STORE_bare_us
  4. -
  5. THEME_frontend_default_default
  6. -
  7. helloworld_index_index
  8. -
  9. customer_logged_out
  10. -
- -

Each of these is a Handle. Handles are set in a variety of places within the Magento system. The two we want to pay attention to are default and helloworld_index_index. The default Handle is present in every request into the Magento system. The helloworld_index_index Handle is created by combining the route name (helloworld), Action Controller name (index), and Action Controller Action Method (index) into a single string. This means each possible method on an Action Controller has a Handle associated with it.

- -

Remember that "index" is the Magento default for both Action Controllers and Action Methods, so the following request

- -
http://example.com/helloworld/?showLayout=handles
- -

Will also produce a Handle named helloworld_index_index

- -

Package Layout

- -

You can think of the Package Layout similar to the global config. It's a large XML file that contains every possible layout configuration for a particular Magento install. Let's take a look at it using the Layoutview module

- -
http://example.com/helloworld/index/index?showLayout=package
- -

This may take a while to load. If your browser is choking on the XML rendering, try the text format

- -
http://example.com/helloworld/index/index?showLayout=package&showLayoutFormat=text
- -

You should see a very large XML file. This is the Package Layout. This XML file is created by combining the contents of all the XML layout files for the current theme (or package). For the default install, this is at

- -
app/design/frontend/base/default/layout/
- -

Behind the scenes there are <frontend><layout><updates /> and <adminhtml><layout><updates /> sections of the global config that contains nodes with all the file names to load for the respective area. Once the files listed in the config have been combined, Magento will merge in one last xml file, local.xml. This is the file where you're able to add your customizations to your Magento install.

- -

Combining Handles and The Package Layout

- -

So, if you look at the Package Layout, you'll see some familiar tags such as <block /> and <reference />, but they're all surrounded by tags that look like

-
-<default />
-<catalogsearch_advanced_index />
-etc...
-
- -

These are all Handle tags. The Layout for an individual request is generated by grabbing all the sections of the Package Layout that match any Handles for the request. So, in our example above, our layout is being generated by grabbing tags from the following sections

- -
-<default />
-<STORE_bare_us />
-<THEME_frontend_default_default />
-<helloworld_index_index />
-<customer_logged_out />
-
- -

There's one additional tag you'll need to be aware of in the Package Layout. The <update /> tag allows you to include another Handle's tags. For example

- -
-<customer_account_index>
-    <!-- ... -->
-    <update handle="customer_account"/>
-    <!-- ... -->
-</customer_account_index>
-
- -

Is saying that requests with a customer_account_index Handle should include <blocks />s from the <customer_account /> Handle.

- - -

Applying What We've Learned

- -

OK, that's a lot of theory. Lets get back to what we did earlier. Knowing what we know now, adding

-
-<layout version="0.1.0">
-    <default>
-        <block type="page/html" name="root" output="toHtml" template="magentotutorial/helloworld/simple_page.phtml" />
-    </default>
-</layout>
-
- -

to local.xml means we've overridden the "root" tag. with a different Block. By placing this in the <default /> Handle we've ensured that this override will happen for every page request in the system. That's probably not what we want.

- - -

If you go to any other page in your Magento site, you'll notice they're either blank white, or have the same red background that your hello world page does. Let's change your local.xml file so it only applies to the hello world page. We'll do this by changing default to use the full action name handle (helloworld_index_index).

- -
-<layout version="0.1.0">
-    <helloworld_index_index>
-        <block type="page/html" name="root" output="toHtml" template="magentotutorial/helloworld/simple_page.phtml" />
-    </helloworld_index_index>
-</layout>
-
- -

Clear your Magento cache, and the rest of your pages should be restored.

- -

Right now this only applies to our index Action Method. Let's add it to the goodbye Action Method as well. In your Action Controller, modify the goodbye action so it looks like

-
-public function goodbyeAction() {
-    $this->loadLayout();
-    $this->renderLayout();
-}
-
- -

If you load up the following URL, you'll notice you're still getting the default Magento layout.

- -
http://example.com/helloworld/index/goodbye
- -

We need to add a Handle for the full action name (helloworld_index_goodbye) to our local.xml file. Rather than specify a new <block />, lets use the update tag to include the helloworld_index_index Handle.

- -
-<layout version="0.1.0">
-    <!-- ... -->
-    <helloworld_index_goodbye>
-        <update handle="helloworld_index_index" />
-    </helloworld_index_goodbye>
-</layout>
-
- -

Loading the following pages (after clearing your Magento cache) should now produce identical results.

- -
http://example.com/helloworld/index/index
-http://example.com/helloworld/index/goodbye
- -

Starting Output and getChildHtml

- -

In a standard configuration, output starts on the Block named root (because it has an output attribute). We've overridden root's Template with our own

- -
template="magentotutorial/helloworld/simple_page.phtml"
- -

Templates are referenced from the root folder of the current theme. In this case, that's

- -
app/design/frontend/base/default
- -

so we need to drill down to our custom page. Most Magento Templates are stored in

- -
app/design/frontend/base/default/templates
- -

Combining this gives us the full path

- -
app/design/frontend/base/default/templates/magentotutorial/helloworld/simple_page.phtml
- -

Adding Content Blocks

- -

A simple red page is pretty boring. Let's add some content to this page. Change your <helloworld_index_index /> Handle in local.xml so it looks like the following

- -
-<helloworld_index_index>
-    <block type="page/html" name="root" output="toHtml" template="magentotutorial/helloworld/simple_page.phtml">
-        <block type="customer/form_register" name="customer_form_register" template="customer/form/register.phtml"/>
-    </block>
-</helloworld_index_index>
-
- -

We're adding a new Block nested within our root. This is a Block that's distributed with Magento, and will display a customer registration form. By nesting this Block within our root Block, we've made it available to be pulled into our simple_page.html Template. Next, we'll use the Block's getChildHtml method in our simple_page.phtml file. Edit simple_page.html so it looks like this

- -
-<body>
-    <?php echo $this->getChildHtml('customer_form_register'); ?>
-</body>
-
- -

Clear your Magento cache and reload the page and you should see the customer registration form on your red background. Magento also has a Block named top.links. Let's try including that. Change your simple_page.html file so it reads

- -
-<body>
-    <h1>Links</h1>
-    <?php echo $this->getChildHtml('top.links'); ?>
-</body>
-
- -

When you reload the page, you'll notice that your <h1>Links</h1> title is rendering, but nothing is rendering for top.links. That's because we didn't add it to local.xml. The getChildHtml method can only include Blocks that are specified as sub-Blocks in the Layout. This allows Magento to only instantiate the Blocks it needs, and also allows you to set difference Templates for Blocks based on context.

- -

Let's add the top.links Block to our local.xml

- -
-<helloworld_index_index>
-    <block type="page/html" name="root" output="toHtml" template="magentotutorial/helloworld/simple_page.phtml">
-        <block type="page/template_links" name="top.links"/>
-        <block type="customer/form_register" name="customer_form_register" template="customer/form/register.phtml"/>
-    </block>
-</helloworld_index_index>
-
- -

Clear your cache and reload the page. You should now see the top.links module.

- -

Time for action

- -

There is one more important concept to cover before we wrap up this lesson, and that is the <action /> tag. Using the <action /> tag enables us to call public PHP methods of the block classes. So instead of changing the template of the root block by replacing the block instance with our own, we can use a call to setTemplate instead.

- -
-<layout version="0.1.0">
-    <helloworld_index_index>
-        <reference name="root">
-            <action method="setTemplate">
-                <template>magentotutorial/helloworld/simple_page.phtml</template>
-            </action>
-            <block type="page/template_links" name="top.links"/>
-            <block type="customer/form_register" name="customer_form_register" template="customer/form/register.phtml"/>
-        </reference>
-    </helloworld_index_index>
-</layout>
-
- -

This layout XML will first set the template property of the root block, and then will add the two blocks we use as child blocks. Once we clear the cache, the result should look just as before. The benefit of using the <action /> is the same block instance is used that was created earlier, and all other parent/child associations still exist. For that reason this is a more upgrade proof way of implementing our changes.

- -

All arguments to the action's method need to be wrapped in an individual child node of the <action /> tag. The name of that node doesn't matter, only the order of the nodes. We could have written the action node from the previous example as follows with the same effect. -

- -
-<action method="setTemplate">
-    <some_new_template>magentotutorial/helloworld/simple_page.phtml</some_new_template>
-</action>
-
- -

This is just to illustrate that the action's argument node names are arbitrary.

- -

Wrapup

+ + + + + -

That covers Layout fundamentals. We have covered the tags <block />, <reference />, <update /> and <action />, and also layout update handles like <default /> and <cms_index_index />. These make up most of the layout configuration used in Magento. If you found it somewhat daunting, don't worry, you'll rarely need to work with layouts on such a fundamental level. Magento provides a number of pre-built layouts which can be modified and skinned to meet the needs of your store. Understanding how the entire Layout system works can be a great help when you're trouble shooting Layout issues, or adding new functionality to an existing Magento system.

+ + + + + \ No newline at end of file diff --git a/guides/m1x/magefordev/mage-for-dev-5.html b/guides/m1x/magefordev/mage-for-dev-5.html index d47c4189ec..8738ed8fe9 100644 --- a/guides/m1x/magefordev/mage-for-dev-5.html +++ b/guides/m1x/magefordev/mage-for-dev-5.html @@ -1,458 +1,726 @@ --- --- - - + - - - - - - - Magento for Developers: Part 5—Magento Models and ORM Basics - + + + + + OpenMage for Developers: Part 5—OpenMage Models and ORM Basics + + + + + + + + + + + + + + + - -header -{% include m1x/eol_message.html %} - -
-

Magento for Developers: Part 5—Magento Models and ORM Basics

-

by Alan Storm, updated for Magento 1.12

-

Edit this page on GitHub

- - - - - -
Other articles in this series:
- + + + + +
+
+ +
+ + + +
+
+ +
+

OpenMage Layouts, Blocks and Templates

+
+

The implementation of a "Models Tier" is a huge part of any MVC framework. It + represents the data of your application, and most applications are useless without data. Magento + Models play an even bigger role, as they typically contain the "Business Logic" that's often + relegated to the Controller or Helper methods in other PHP MVC frameworks.

+

Traditional PHP MVC Models

+

If the definition of MVC is somewhat fuzzy, the definition of a Model is even fuzzier. Prior to + the wide adoption of the MVC pattern by PHP developers, data access was usually raw SQL + statements and/or an SQL abstraction layer. Developers would write queries and not think too + much about what objects they were modeling.

+

In this day and age, raw SQL is mostly frowned upon, but many PHP frameworks are still SQL + centric. Models will be objects that provide some layer of abstraction, but behind the scenes + developers are still writing SQL and/or calling SQL like abstraction methods to read and + write-down their data.

+

Other frameworks eschew SQL and take the Object Relational Mapping (ORM) approach. Here, a + developer is dealing strictly with Objects. Properties are set, and when a save method is called + on the Object, the data is automatically written to the database. Some ORMs will attempt to + divine object properties from the database, others require the user to specify them in some way, + (usually in an abstract data language such as YAML). One of the most famous and popular + implementations of this approach is ActiveRecord.

+

This definition of ORM should suffice for now, but like everything Computer Science these days, + the strict definition of ORM has blurred over the years. It's beyond the scope of this article + to settle that dispute, but suffice it say we're generalizing a bit.

+

Magento Models

+

It should be no surprise that Magento takes the ORM approach. While the Zend Framework SQL + abstractions are available, most of your data access will be via the built in Magento Models, + and Models you build yourself. It should also come as no surprise that Magento has a highly + flexible, highly abstract, concept of what a Model is.

+

Anatomy of a Magento Model

+

Most Magento Models can be categorized in one of two ways. There's a basic, + ActiveRecord-like/one-object-one-table Model, and there's also an Entity Attribute Value (EAV) + Model. Each Model also gets a Model Collection. Collections are PHP objects used to hold a + number of individual Magento Model instances. The Magento team has implemented the PHP Standard + Library interfaces of IteratorAggregate and Countable to allow each Model type to have it's own + collection type. If you're not familiar with the PHP Standard Library, think of Model + Collections as arrays that also have methods attached.

+

Magento Models don't contain any code for connecting to the database. Instead, each Model uses a + modelResource class, that is used to communicate with the database server (via one read + and one write adapter object). By decoupling the logical Model and the code that talks to the + database, it's theoretically possible to write new resource classes for a different database + schemas and platforms while keeping your Models themselves untouched.

+

Enable developer mode

+

Something you should do in development—but never in production—is to enable + Magento's developer mode which, among other things, displays exceptions in your + browser. It's useful for debugging your code.

+

Enable developer mode in any of the following ways:

+
    +
  • + developer + mode
  • +
  • Edit the .htaccess in the Magento root directory file to add SetEnv + MAGE_IS_DEVELOPER_MODE "true" +
  • +
+

Creating a Basic Model

+

To begin, we're going to create a basic Magento Model. PHP MVC tradition insists we model a + weblog post. The steps we'll need to take are

+
    +
  1. Create a new "Weblog" module
  2. +
  3. Create a database table for our Model
  4. +
  5. Add Model information to the config for a Model named Blogpost
  6. +
  7. Add Model Resource information to the config for the Blogpost Model
  8. +
  9. Add a Read Adapter to the config for the Blogpost Model
  10. +
  11. Add a Write Adapter to the config for the Blogpost Model
  12. +
  13. Add a PHP class file for the Blogpost Model
  14. +
  15. Add a PHP class file for the Blogpost Resource Model
  16. +
  17. Instantiate the Model
  18. +
+

Create a Weblog Module

+

You should be an old hat at creating empty modules at this point, so we'll skip the details and + assume you can create an empty module named Weblog. After you've done that, we'll setup a route + for an index Action Controller with an action named "testModel". As always, the following + examples assume a Package Name of "Magentotutorial".

+

In `Magentotutorial/Weblog/etc/config.xml`, setup the following route

+
+                    <frontend>
+                        <routers>
+                            <weblog>
+                                <use>standard</use>
+                                <args>
+                                    <module>Magentotutorial_Weblog</module>
+                                    <frontName>weblog</frontName>
+                                </args>
+                            </weblog>
+                        </routers>
+                    </frontend>
+                    
+

And then add the following Action Controller in

+
+                    class Magentotutorial_Weblog_IndexController extends Mage_Core_Controller_Front_Action {
+                        public function testModelAction() {
+                            echo 'Setup!';
+                        }
+                    }
+                    
+

at Magentotutorial/Weblog/controllers/IndexController.php. Clear your Magento cache and + load the following URL to ensure everything's been setup correctly.

+
http://example.com/weblog/index/testModel
+

You should see the word "Setup" on a white background.

+

Creating the Database Table

+

Magento has a system for automatically creating and changing your database schemas, but for the + time being we'll just manually create a table for our Model.

+

Using the command-line or your favorite MySQL GUI application, create a table with the following + schema

+
+                    CREATE TABLE `blog_posts` (
+                      `blogpost_id` int(11) NOT NULL auto_increment,
+                      `title` text,
+                      `post` text,
+                      `date` datetime default NULL,
+                      `timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP,
+                      PRIMARY KEY  (`blogpost_id`)
+                    )
+                    
+

And then populate it with some data

+
INSERT INTO `blog_posts` VALUES (1,'My New Title','This is a blog post','2010-07-01 00:00:00','2010-07-02 23:12:30');
+

The Global Config and Creating The Model

+

There are three individual things we need to setup for a Model in our config.

+
    +
  1. Enabling Models in our Module
  2. +
  3. Enabling Model Resources in our Module
  4. +
  5. Add an "entity" table configuration to our Model Resource.
  6. +
+

When you instantiate a Model in Magento, you make a call like this

+
$model = Mage::getModel('weblog/blogpost');
+

The first part of the URI you pass into get Model is the Model Group Name. + Because it is a good idea to follow conventions, this should be the (lowercase) name of your + module, or to be safeguarded against conflicts use the packagename and modulename (also in + lowercase). The second part of the URI is the lowercase version of your Model name.

+

So, let's add the following XML to our module's `config.xml`.

+
+                    <global>
+                        <!-- ... -->
+                        <models>
+                            <weblog>
+                                <class>Magentotutorial_Weblog_Model</class>
+                                <!--
+                                need to create our own resource, can't just
+                                use core_resource
+                                -->
+                                <resourceModel>weblog_resource</resourceModel>
+                            </weblog>
+                        </models>
+                        <!-- ... -->
+                    </global>
+                    
+

The outer <weblog /> tag is your Group Name, which should match your module name. <class + /> is the BASE name all Models in the weblog group will have, also called Class + Prefix. The <resourceModel /> tag indicates which Resource Model that weblog + group Models should use. We talk more about this later on in this page. For now, remember your + Group Name and the literal string "resource".

+

So, we're not done yet, but let's see what happens if we clear our Magento cache and attempt to + instantiate a blogpost Model. In your `testModelAction` method, use the following code

+
+                        public function testModelAction() {
+                            $blogpost = Mage::getModel('weblog/blogpost');
+                            echo get_class($blogpost);
+                        }
+                    
+

and reload your page. You should see an error like the following:

+
+                    include(Magentotutorial/Weblog/Model/Blogpost.php) [function.include]: failed to open stream: No such file or directory
+                    
+

By attempting to retrieve a weblog/blogpost Model, you told Magento to instantiate a + class with the name

+
+                    Magentotutorial_Weblog_Model_Blogpost
+                    
+

Magento is trying to __autoload include this Model, but can't find the file. Let's create it! + Create the following class at the following location

+
File: app/code/local/Magentotutorial/Weblog/Model/Blogpost.php
+
+                    class Magentotutorial_Weblog_Model_Blogpost extends Mage_Core_Model_Abstract
+                    {
+                        protected function _construct()
+                        {
+                            $this->_init('weblog/blogpost');
+                        }
+                    }
+                    
+

Reload your page, and the exception should be replaced with the name of your class.

+

All basic Models that interact with the database should extend the + Mage_Core_Model_Abstract class. This abstract class forces you to implement a single + method named _construct (NOTE: this is not PHP's constructor __construct). + This method should call the class's _init method with the same identifying URI you'll be using + in the Mage::getModel method call.

+

The Global Config and Resources

+

So, we've setup our Model. Next, we need to setup our Model Resource. Model Resources contain the + code that actually talks to our database. In the last section, we included the following in our + config.

+
+                    <resourceModel>weblog_resource</resourceModel>
+                    
+

The value in <resourceModel /> will be used to instantiate a Model Resource class. Although + you'll never need to call it yourself, when any Model in the weblog group needs to talk to the + database, Magento will make the following method call to get the Model resource

+
+                    Mage::getResourceModel('weblog/blogpost');
+                    
+

Again, weblog is the Group Name, and blogpost is the Model. The Mage::getResourceModel + method will use the weblog/blogpost URI to inspect the global config and pull out the + value in <resourceModel> (in this case, weblog_resource). Then, a model class + will be instantiated with the following URI

+
+                    weblog_resource/blogpost
+                    
+

So, if you followed that all the way, what this means is, resource models are configured + in the same section of the XML config as normal Models. This can be confusing to + newcomers and old-hands alike.

+

So, with that in mind, let's configure our resource. In our <models> section add

+
+                    <global>
+                        <!-- ... -->
+                        <models>
+                            <!-- ... -->
+                            <weblog_resource>
+                                <class>Magentotutorial_Weblog_Model_Resource</class>
+                            </weblog_resource>
+                        </models>
+                    </global>
+                    
+

You're adding the <weblog_resource /> tag, which is the value of the <resourceModel /> + tag you just setup. The value of <class /> is the base name that all your resource models + will have, and should be named with the following format

+
+                    Packagename_Modulename_Model_Resource
+                    
+

So, we have a configured resource, let's try loading up some Model data. Change your action to + look like the following

+
+                    public function testModelAction() {
+                        $params = $this->getRequest()->getParams();
+                        $blogpost = Mage::getModel('weblog/blogpost');
+                        echo("Loading the blogpost with an ID of ".$params['id']);
+                        $blogpost->load($params['id']);
+                        $data = $blogpost->getData();
+                        var_dump($data);
+                    }
+                    
+

And then load the following URL in your browser (after clearing your Magento cache)

+
http://example.com/weblog/index/testModel/id/1
+

You should see an exception something like the following

+
Warning: include(Magentotutorial/Weblog/Model/Resource/Blogpost.php) [function.include]: failed to open stream: No such file ....
+

As you've likely intuited, we need to add a resource class for our Model. Every + Model has its own resource class. Add the following class at the following location

+
File: app/code/local/Magentotutorial/Weblog/Model/Resource/Blogpost.php
+
+                    class Magentotutorial_Weblog_Model_Resource_Blogpost extends Mage_Core_Model_Resource_Db_Abstract{
+                        protected function _construct()
+                        {
+                            $this->_init('weblog/blogpost', 'blogpost_id');
+                        }
+                    }
+                    
+

Again, the first parameter of the init method is the URL used to identify the + Model. The second parameter is the database field that uniquely identifies any + particular column. In most cases, this should be the primary key. Clear your cache, reload, and + you should see

+
Can't retrieve entity config: weblog/blogpost
+

Another exception! When we use the Model URI weblog/blogpost, we're telling Magento we + want the Model Group weblog, and the blogpost Entity. In the context + of simple Models that extend Mage_Core_Model_Resource_Db_Abstract, an entity + corresponds to a table. In this case, the table named blog_post that we created above. + Let's add that entity to our XML config.

+
+                        <models>
+                            <!-- ... --->
+                            <weblog_resource>
+                                <class>Magentotutorial_Weblog_Model_Resource</class>
+                                <entities>
+                                    <blogpost>
+                                        <table>blog_posts</table>
+                                    </blogpost>
+                                </entities>
+                            </weblog_resource>
+                        </models>
+                    
+

We've added a new <entities /> section to the resource Model section of our config. This, + in turn, has a section named after our entity (<blogpost />) that specifies the name of + the database table we want to use for this Model.

+

Clear your Magento cache, cross your fingers, reload the page and ...

+
Loading the blogpost with an ID of 1
+
+                    array
+                      'blogpost_id' => string '1' (length=1)
+                      'title' => string 'My New Title' (length=12)
+                      'post' => string 'This is a blog post' (length=19)
+                      'date' => string '2009-07-01 00:00:00' (length=19)
+                      'timestamp' => string '2009-07-02 16:12:30' (length=19)
+                    
+

Eureka! We've managed to extract our data and, more importantly, completely configure a Magento + Model.

+

Basic Model Operations

+

All Magento Models inherit from the Varien_Object class. This class is part of the Magento system + library and not part of any Magento core module. You can find this object at +

+
lib/Varien/Object.php
+

Magento Models store their data in a protected _data property. The Varien_Object class gives us + several methods we can use to extract this data. You've already seen getData, which + will return an array of key/value pairs. This method can also be passed a string key to get a + specific field.

+
+                    $model->getData();
+                    $model->getData('title');
+                    
+

There's also a getOrigData method, which will return the Model data as it was when the object was + initially populated, (working with the protected _origData method).

+
+                    $model->getOrigData();
+                    $model->getOrigData('title');
+                    
+

The Varien_Object also implements some special methods via PHP's magic __call method. + You can get, set, unset, or check for the existence of any property using a method that begins + with the word get, set, unset or has and is followed by the camel cased name of a property.

+
+                    $model->getBlogpostId();
+                    $model->setBlogpostId(25);
+                    $model->unsBlogpostId();
+                    if($model->hasBlogpostId()){...}
+                    
+

For this reason, you'll want to name all your database columns with lower case characters and use + underscores to separate characters.

+

CRUD, the Magento Way

+

Magento Models support the basic Create, Read, Update, and Delete functionality of CRUD with load, + save, and delete methods. You've already seen the load method in action. When + passed a single parameter, the load method will return a record whose id field (set in the + Model's resource) matches the passed in value.

+
$blogpost->load(1);
+

The save method will allow you to both INSERT a new Model into the database, or UPDATE an + existing one. Add the following method to your Controller

+
+                    public function createNewPostAction() {
+                        $blogpost = Mage::getModel('weblog/blogpost');
+                        $blogpost->setTitle('Code Post!');
+                        $blogpost->setPost('This post was created from code!');
+                        $blogpost->save();
+                        echo 'post with ID ' . $blogpost->getId() . ' created';
+                    }
+                    
+

and then execute your Controller Action by loading the following URL

+
http://example.com/weblog/index/createNewPost
+

You should now see an additional saved post in your database table. Next, try the following to + edit your post

+
+                    public function editFirstPostAction() {
+                        $blogpost = Mage::getModel('weblog/blogpost');
+                        $blogpost->load(1);
+                        $blogpost->setTitle("The First post!");
+                        $blogpost->save();
+                        echo 'post edited';
+                    }
+                    
+

And finally, you can delete your post using very similar syntax.

+
+                    public function deleteFirstPostAction() {
+                        $blogpost = Mage::getModel('weblog/blogpost');
+                        $blogpost->load(1);
+                        $blogpost->delete();
+                        echo 'post removed';
+                    }
+                    
+

Model Collections

+

So, having a single Model is useful, but sometimes we want to grab list of Models. Rather than + returning a simple array of Models, each Magento Model type has a unique collection object + associated with it. These objects implement the PHP IteratorAggregate and Countable interfaces, + which means they can be passed to the count function, and used in for each + constructs.

+

We'll cover Collections in full in a later article, but for now let's look at basic setup and + usage. Add the following action method to your Controller, and load it in your browser.

+
+                    public function showAllBlogPostsAction() {
+                        $posts = Mage::getModel('weblog/blogpost')->getCollection();
+                        foreach($posts as $blogpost){
+                            echo '<h3>'.$blogpost->getTitle().'</h3>';
+                            echo nl2br($blogpost->getPost());
+                        }
+                    }
+                    
+

Load the action URL,

+
http://example.com/weblog/index/showAllBlogPosts
+

and you should see a (by now) familiar exception.

+
Warning: include(Magentotutorial/Weblog/Model/Resource/Blogpost/Collection.php) [function.include]: failed to open stream
+

You're not surprised, are you? We need to add a PHP class file that defines our Blogpost + collection. Every Model has a protected property named _resourceCollectionName that + contains a URI that's used to identify our collection.

+
+                      protected '_resourceCollectionName' => string 'weblog/blogpost_collection'
+                    
+

By default, this is the same URI that's used to identify our Resource Model, with the string + "_collection" appended to the end. Magento considers Collections part of the Resource, so this + URI is converted into the class name.

+
Magentotutorial_Weblog_Model_Resource_Blogpost_Collection
+

Add the following PHP class at the following location

+
File: app/code/local/Magentotutorial/Weblog/Model/Resource/Blogpost/Collection.php
+
+                    class Magentotutorial_Weblog_Model_Resource_Blogpost_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract {
+                        protected function _construct()
+                        {
+                                $this->_init('weblog/blogpost');
+                        }
+                    }
+                    
+

Just as with our other classes, we need to init our Collection with the Model URI. + (weblog/blogpost). Rerun your Controller Action, and you should see your post information.

+

Wrapup

+

Congratulations, you've created and configured your first Magento Model. In a later article we'll + take a look at Magento's Entity Attribute Value Models (EAV), which expand on what we've learned + here.

+
+ + +
+ +
+
+
+ + + + + -

The implementation of a "Models Tier" is a huge part of any MVC framework. It represents the data of your application, and most applications are useless without data. Magento Models play an even bigger role, as they typically contain the "Business Logic" that's often relegated to the Controller or Helper methods in other PHP MVC frameworks.

- -

Traditional PHP MVC Models

- -

If the definition of MVC is somewhat fuzzy, the definition of a Model is even fuzzier. Prior to the wide adoption of the MVC pattern by PHP developers, data access was usually raw SQL statements and/or an SQL abstraction layer. Developers would write queries and not think too much about what objects they were modeling.

- -

In this day and age, raw SQL is mostly frowned upon, but many PHP frameworks are still SQL centric. Models will be objects that provide some layer of abstraction, but behind the scenes developers are still writing SQL and/or calling SQL like abstraction methods to read and write-down their data.

- -

Other frameworks eschew SQL and take the Object Relational Mapping (ORM) approach. Here, a developer is dealing strictly with Objects. Properties are set, and when a save method is called on the Object, the data is automatically written to the database. Some ORMs will attempt to divine object properties from the database, others require the user to specify them in some way, (usually in an abstract data language such as YAML). One of the most famous and popular implementations of this approach is ActiveRecord.

- -

This definition of ORM should suffice for now, but like everything Computer Science these days, the strict definition of ORM has blurred over the years. It's beyond the scope of this article to settle that dispute, but suffice it say we're generalizing a bit.

- -

Magento Models

- -

It should be no surprise that Magento takes the ORM approach. While the Zend Framework SQL abstractions are available, most of your data access will be via the built in Magento Models, and Models you build yourself. It should also come as no surprise that Magento has a highly flexible, highly abstract, concept of what a Model is.

- -

Anatomy of a Magento Model

- -

Most Magento Models can be categorized in one of two ways. There's a basic, ActiveRecord-like/one-object-one-table Model, and there's also an Entity Attribute Value (EAV) Model. Each Model also gets a Model Collection. Collections are PHP objects used to hold a number of individual Magento Model instances. The Magento team has implemented the PHP Standard Library interfaces of IteratorAggregate and Countable to allow each Model type to have it's own collection type. If you're not familiar with the PHP Standard Library, think of Model Collections as arrays that also have methods attached.

- -

Magento Models don't contain any code for connecting to the database. Instead, each Model uses a modelResource class, that is used to communicate with the database server (via one read and one write adapter object). By decoupling the logical Model and the code that talks to the database, it's theoretically possible to write new resource classes for a different database schemas and platforms while keeping your Models themselves untouched.

- -

Enable developer mode

-

Something you should do in development—but never in production—is to enable Magento's developer mode which, among other things, displays exceptions in your browser. It's useful for debugging your code.

-

Enable developer mode in any of the following ways:

-
  • developer mode
  • -
  • Edit the .htaccess in the Magento root directory file to add SetEnv MAGE_IS_DEVELOPER_MODE "true"
  • -
-

Creating a Basic Model

- -

To begin, we're going to create a basic Magento Model. PHP MVC tradition insists we model a weblog post. The steps we'll need to take are

- -
    -
  1. Create a new "Weblog" module
  2. -
  3. Create a database table for our Model
  4. -
  5. Add Model information to the config for a Model named Blogpost
  6. -
  7. Add Model Resource information to the config for the Blogpost Model
  8. -
  9. Add a Read Adapter to the config for the Blogpost Model
  10. -
  11. Add a Write Adapter to the config for the Blogpost Model
  12. -
  13. Add a PHP class file for the Blogpost Model
  14. -
  15. Add a PHP class file for the Blogpost Resource Model
  16. -
  17. Instantiate the Model
  18. -
- -

Create a Weblog Module

- -

You should be an old hat at creating empty modules at this point, so we'll skip the details and assume you can create an empty module named Weblog. After you've done that, we'll setup a route for an index Action Controller with an action named "testModel". As always, the following examples assume a Package Name of "Magentotutorial".

- -

In `Magentotutorial/Weblog/etc/config.xml`, setup the following route

- -
-<frontend>
-    <routers>
-        <weblog>
-            <use>standard</use>
-            <args>
-                <module>Magentotutorial_Weblog</module>
-                <frontName>weblog</frontName>
-            </args>
-        </weblog>
-    </routers>
-</frontend>
-
- -

And then add the following Action Controller in

-
-class Magentotutorial_Weblog_IndexController extends Mage_Core_Controller_Front_Action {
-    public function testModelAction() {
-        echo 'Setup!';
-    }
-}
-
- -

at Magentotutorial/Weblog/controllers/IndexController.php. Clear your Magento cache and load the following URL to ensure everything's been setup correctly.

- -
http://example.com/weblog/index/testModel
- -

You should see the word "Setup" on a white background.

- -

Creating the Database Table

- -

Magento has a system for automatically creating and changing your database schemas, but for the time being we'll just manually create a table for our Model.

- -

Using the command-line or your favorite MySQL GUI application, create a table with the following schema

-
-CREATE TABLE `blog_posts` (
-  `blogpost_id` int(11) NOT NULL auto_increment,
-  `title` text,
-  `post` text,
-  `date` datetime default NULL,
-  `timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP,
-  PRIMARY KEY  (`blogpost_id`)
-)
-
- -

And then populate it with some data

- -
INSERT INTO `blog_posts` VALUES (1,'My New Title','This is a blog post','2010-07-01 00:00:00','2010-07-02 23:12:30');
- -

The Global Config and Creating The Model

- -

There are three individual things we need to setup for a Model in our config.

- -
    -
  1. Enabling Models in our Module
  2. -
  3. Enabling Model Resources in our Module
  4. -
  5. Add an "entity" table configuration to our Model Resource.
  6. -
- -

When you instantiate a Model in Magento, you make a call like this

- -
$model = Mage::getModel('weblog/blogpost');
- -

The first part of the URI you pass into get Model is the Model Group Name. Because it is a good idea to follow conventions, this should be the (lowercase) name of your module, or to be safeguarded against conflicts use the packagename and modulename (also in lowercase). The second part of the URI is the lowercase version of your Model name.

- -

So, let's add the following XML to our module's `config.xml`.

-
-<global>
-    <!-- ... -->
-    <models>
-        <weblog>
-            <class>Magentotutorial_Weblog_Model</class>
-            <!--
-            need to create our own resource, can't just
-            use core_resource
-            -->
-            <resourceModel>weblog_resource</resourceModel>
-        </weblog>
-    </models>
-    <!-- ... -->
-</global>
-
- -

The outer <weblog /> tag is your Group Name, which should match your module name. <class /> is the BASE name all Models in the weblog group will have, also called Class Prefix. The <resourceModel /> tag indicates which Resource Model that weblog group Models should use. We talk more about this later on in this page. For now, remember your Group Name and the literal string "resource".

- -

So, we're not done yet, but let's see what happens if we clear our Magento cache and attempt to instantiate a blogpost Model. In your `testModelAction` method, use the following code

-
-    public function testModelAction() {
-        $blogpost = Mage::getModel('weblog/blogpost');
-        echo get_class($blogpost);
-    }
-
- -

and reload your page. You should see an error like the following:

- -
-include(Magentotutorial/Weblog/Model/Blogpost.php) [function.include]: failed to open stream: No such file or directory
-
- -

By attempting to retrieve a weblog/blogpost Model, you told Magento to instantiate a class with the name

- -
-Magentotutorial_Weblog_Model_Blogpost
-
- -

Magento is trying to __autoload include this Model, but can't find the file. Let's create it! Create the following class at the following location

- -
File: app/code/local/Magentotutorial/Weblog/Model/Blogpost.php
-
-class Magentotutorial_Weblog_Model_Blogpost extends Mage_Core_Model_Abstract
-{
-    protected function _construct()
-    {
-        $this->_init('weblog/blogpost');
-    }
-}
-
- -

Reload your page, and the exception should be replaced with the name of your class.

- -

All basic Models that interact with the database should extend the Mage_Core_Model_Abstract class. This abstract class forces you to implement a single method named _construct (NOTE: this is not PHP's constructor __construct). This method should call the class's _init method with the same identifying URI you'll be using in the Mage::getModel method call.

- -

The Global Config and Resources

- -

So, we've setup our Model. Next, we need to setup our Model Resource. Model Resources contain the code that actually talks to our database. In the last section, we included the following in our config.

- -
-<resourceModel>weblog_resource</resourceModel>
-
- -

The value in <resourceModel /> will be used to instantiate a Model Resource class. Although you'll never need to call it yourself, when any Model in the weblog group needs to talk to the database, Magento will make the following method call to get the Model resource

- -
-Mage::getResourceModel('weblog/blogpost');
-
- -

Again, weblog is the Group Name, and blogpost is the Model. The Mage::getResourceModel method will use the weblog/blogpost URI to inspect the global config and pull out the value in <resourceModel> (in this case, weblog_resource). Then, a model class will be instantiated with the following URI

- -
-weblog_resource/blogpost
-
- -

So, if you followed that all the way, what this means is, resource models are configured in the same section of the XML config as normal Models. This can be confusing to newcomers and old-hands alike.

- -

So, with that in mind, let's configure our resource. In our <models> section add

- -
-<global>
-    <!-- ... -->
-    <models>
-        <!-- ... -->
-        <weblog_resource>
-            <class>Magentotutorial_Weblog_Model_Resource</class>
-        </weblog_resource>
-    </models>
-</global>
-
- -

You're adding the <weblog_resource /> tag, which is the value of the <resourceModel /> tag you just setup. The value of <class /> is the base name that all your resource models will have, and should be named with the following format

- -
-Packagename_Modulename_Model_Resource
-
- -

So, we have a configured resource, let's try loading up some Model data. Change your action to look like the following

- -
-public function testModelAction() {
-    $params = $this->getRequest()->getParams();
-    $blogpost = Mage::getModel('weblog/blogpost');
-    echo("Loading the blogpost with an ID of ".$params['id']);
-    $blogpost->load($params['id']);
-    $data = $blogpost->getData();
-    var_dump($data);
-}
-
- -

And then load the following URL in your browser (after clearing your Magento cache)

- -
http://example.com/weblog/index/testModel/id/1
- -

You should see an exception something like the following

- -
Warning: include(Magentotutorial/Weblog/Model/Resource/Blogpost.php) [function.include]: failed to open stream: No such file ....
- -

As you've likely intuited, we need to add a resource class for our Model. Every Model has its own resource class. Add the following class at the following location

- -
File: app/code/local/Magentotutorial/Weblog/Model/Resource/Blogpost.php
-
-class Magentotutorial_Weblog_Model_Resource_Blogpost extends Mage_Core_Model_Resource_Db_Abstract{
-    protected function _construct()
-    {
-        $this->_init('weblog/blogpost', 'blogpost_id');
-    }
-}
-
- -

Again, the first parameter of the init method is the URL used to identify the Model. The second parameter is the database field that uniquely identifies any particular column. In most cases, this should be the primary key. Clear your cache, reload, and you should see

- -
Can't retrieve entity config: weblog/blogpost
- -

Another exception! When we use the Model URI weblog/blogpost, we're telling Magento we want the Model Group weblog, and the blogpost Entity. In the context of simple Models that extend Mage_Core_Model_Resource_Db_Abstract, an entity corresponds to a table. In this case, the table named blog_post that we created above. Let's add that entity to our XML config.

- -
-    <models>
-        <!-- ... --->
-        <weblog_resource>
-            <class>Magentotutorial_Weblog_Model_Resource</class>
-            <entities>
-                <blogpost>
-                    <table>blog_posts</table>
-                </blogpost>
-            </entities>
-        </weblog_resource>
-    </models>
-
- -

We've added a new <entities /> section to the resource Model section of our config. This, in turn, has a section named after our entity (<blogpost />) that specifies the name of the database table we want to use for this Model.

- -

Clear your Magento cache, cross your fingers, reload the page and ...

- -
Loading the blogpost with an ID of 1
-
-array
-  'blogpost_id' => string '1' (length=1)
-  'title' => string 'My New Title' (length=12)
-  'post' => string 'This is a blog post' (length=19)
-  'date' => string '2009-07-01 00:00:00' (length=19)
-  'timestamp' => string '2009-07-02 16:12:30' (length=19)
-
- -

Eureka! We've managed to extract our data and, more importantly, completely configure a Magento Model.

- -

Basic Model Operations

- -

All Magento Models inherit from the Varien_Object class. This class is part of the Magento system library and not part of any Magento core module. You can find this object at

- -
lib/Varien/Object.php
- -

Magento Models store their data in a protected _data property. The Varien_Object class gives us several methods we can use to extract this data. You've already seen getData, which will return an array of key/value pairs. This method can also be passed a string key to get a specific field.

- -
-$model->getData();
-$model->getData('title');
-
- -

There's also a getOrigData method, which will return the Model data as it was when the object was initially populated, (working with the protected _origData method).

- -
-$model->getOrigData();
-$model->getOrigData('title');
-
- -

The Varien_Object also implements some special methods via PHP's magic __call method. You can get, set, unset, or check for the existence of any property using a method that begins with the word get, set, unset or has and is followed by the camel cased name of a property.

- -
-$model->getBlogpostId();
-$model->setBlogpostId(25);
-$model->unsBlogpostId();
-if($model->hasBlogpostId()){...}
-
- -

For this reason, you'll want to name all your database columns with lower case characters and use underscores to separate characters.

- -

CRUD, the Magento Way

- -

Magento Models support the basic Create, Read, Update, and Delete functionality of CRUD with load, save, and delete methods. You've already seen the load method in action. When passed a single parameter, the load method will return a record whose id field (set in the Model's resource) matches the passed in value.

- -
$blogpost->load(1);
- -

The save method will allow you to both INSERT a new Model into the database, or UPDATE an existing one. Add the following method to your Controller

- -
-public function createNewPostAction() {
-    $blogpost = Mage::getModel('weblog/blogpost');
-    $blogpost->setTitle('Code Post!');
-    $blogpost->setPost('This post was created from code!');
-    $blogpost->save();
-    echo 'post with ID ' . $blogpost->getId() . ' created';
-}
-
- -

and then execute your Controller Action by loading the following URL

- -
http://example.com/weblog/index/createNewPost
- -

You should now see an additional saved post in your database table. Next, try the following to edit your post

- -
-public function editFirstPostAction() {
-    $blogpost = Mage::getModel('weblog/blogpost');
-    $blogpost->load(1);
-    $blogpost->setTitle("The First post!");
-    $blogpost->save();
-    echo 'post edited';
-}
-
- -

And finally, you can delete your post using very similar syntax.

- -
-public function deleteFirstPostAction() {
-    $blogpost = Mage::getModel('weblog/blogpost');
-    $blogpost->load(1);
-    $blogpost->delete();
-    echo 'post removed';
-}
-
- -

Model Collections

- -

So, having a single Model is useful, but sometimes we want to grab list of Models. Rather than returning a simple array of Models, each Magento Model type has a unique collection object associated with it. These objects implement the PHP IteratorAggregate and Countable interfaces, which means they can be passed to the count function, and used in for each constructs.

- -

We'll cover Collections in full in a later article, but for now let's look at basic setup and usage. Add the following action method to your Controller, and load it in your browser.

- -
-public function showAllBlogPostsAction() {
-    $posts = Mage::getModel('weblog/blogpost')->getCollection();
-    foreach($posts as $blogpost){
-        echo '<h3>'.$blogpost->getTitle().'</h3>';
-        echo nl2br($blogpost->getPost());
-    }
-}
-
- -

Load the action URL,

- -
http://example.com/weblog/index/showAllBlogPosts
- -

and you should see a (by now) familiar exception.

- -
Warning: include(Magentotutorial/Weblog/Model/Resource/Blogpost/Collection.php) [function.include]: failed to open stream
- -

You're not surprised, are you? We need to add a PHP class file that defines our Blogpost collection. Every Model has a protected property named _resourceCollectionName that contains a URI that's used to identify our collection.

- -
-  protected '_resourceCollectionName' => string 'weblog/blogpost_collection'
-
- -

By default, this is the same URI that's used to identify our Resource Model, with the string "_collection" appended to the end. Magento considers Collections part of the Resource, so this URI is converted into the class name.

- -
Magentotutorial_Weblog_Model_Resource_Blogpost_Collection
- -

Add the following PHP class at the following location

-
File: app/code/local/Magentotutorial/Weblog/Model/Resource/Blogpost/Collection.php
-
-class Magentotutorial_Weblog_Model_Resource_Blogpost_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract {
-    protected function _construct()
-    {
-            $this->_init('weblog/blogpost');
-    }
-}
-
- -

Just as with our other classes, we need to init our Collection with the Model URI. (weblog/blogpost). Rerun your Controller Action, and you should see your post information.

- -

Wrapup

- -

Congratulations, you've created and configured your first Magento Model. In a later article we'll take a look at Magento's Entity Attribute Value Models (EAV), which expand on what we've learned here.

- + + + + \ No newline at end of file diff --git a/guides/m1x/magefordev/mage-for-dev-6.html b/guides/m1x/magefordev/mage-for-dev-6.html index bb1fbea0bb..157cf863c7 100644 --- a/guides/m1x/magefordev/mage-for-dev-6.html +++ b/guides/m1x/magefordev/mage-for-dev-6.html @@ -1,408 +1,693 @@ --- --- - - + - - - - - - - Magento for Developers: Part 6—Magento Setup Resources - + + + + + OpenMage for Developers: Part 6—OpenMage Setup Resources + + + + + + + + + + + + + + + - -header -{% include m1x/eol_message.html %} - -
-

Magento for Developers: Part 6—Magento Setup Resources

-

by Alan Storm, updated for Magento 1.12

-

Edit this page on GitHub

- - - - - -
Other articles in this series:
- - - -

On any fast paced software development project, the task of keeping the development and production databases in sync become a sticky wicket. Magento offers a system to create versioned resource migration scripts that can help your team deal with this often contentious part of the development process.

- -

In the ORM article we created a model for a weblog post. At the time, we ran our CREATE TABLE statements directly against the database. This time, we'll create a Setup Resource for our module that will create the table for us. We'll also create an upgrade script for our module that will update an already installed module. The steps we'll need to take are

- -
    -
  1. Add the Setup Resource to our config
  2. -
  3. Create our resource class file
  4. -
  5. Create our installer script
  6. -
  7. Create our upgrade script
  8. -
-
- -

Adding the Setup Resource

-

So, let's continue with the weblog module we created last time. In our <global /> section, add the following

- -
-<global>
-    <!-- ... -->
-    <resources>
-        <weblog_setup>
-            <setup>
-                <module>Magentotutorial_Weblog</module>
-                <class>Magentotutorial_Weblog_Model_Resource_Setup</class>
-            </setup>
-        </weblog_setup>
-    </resources>
-    <!-- ... -->
-</global>
-
- -

The <weblog_setup> tag will be used to uniquely identify this Setup Resource. It's encouraged, but not necessary, that you use the modelname_setup naming convention. The <module>Magentotutorial_Weblog</module> tag block should contain the Packagename_Modulename of your module. Finally, <class>Magentotutorial_Weblog_Model_Resource_Setup</class> should contain the name of the class we'll be creating for our Setup Resource. For basic setup scripts it's not necessary to create a custom class, but by doing it now you'll give yourself more flexibility down the line.

- -

After adding the above section to your config, clear your Magento cache and try to load any page of your Magento site. You'll see an exception something like

- -
Fatal error: Class 'Magentotutorial_Weblog_Model_Resource_Setup' not found in
- -

Magento just tried to instantiate the class you specified in your config, but couldn't find it. You'll want to create the following file, with the following contents.

- -

File: app/code/local/Magentotutorial/Weblog/Model/Resource/Setup.php

-
-class Magentotutorial_Weblog_Model_Resource_Setup extends Mage_Core_Model_Resource_Setup {
-}
-
- -

Now, reload any page of your Magento site. The exception should be gone, and your page should load as expected.

- -

Creating our Installer Script

- -

Next, we'll want to create our installer script. This is the script that will contain any CREATE TABLE or other SQL code that needs to be run to initialize our module.

- -

First, take a look at your config.xml file

- -
-<modules>
-    <Magentotutorial_Weblog>
-        <version>0.1.0</version>
-    </Magentotutorial_Weblog>
-</modules>
-
- -

This section is required in all config.xml files, and identifies the module as well as the its version number. Your installer script's name will be based on this version number. The following assumes the current version of your module is 0.1.0.

- -

Create the following file at the following location

- -

File: app/code/local/Magentotutorial/Weblog/sql/weblog_setup/mysql4-install-0.1.0.php

-
-echo 'Running This Upgrade: '.get_class($this)."\n <br /> \n";
-die("Exit for now");
-
- -

The weblog_setup portion of the path should match the tag you created in your config.xml file (<weblog_setup />). The 0.1.0 portion of the filename should match the starting version of your module. Clear your Magento cache and reload any page in your Magento site and you should see something like

- -
-Running This Upgrade: Magentotutorial_Weblog_Model_Resource_Setup
-Exit for now
- ...
-
- -

Which means your update script ran. Eventually we'll put our SQL update scripts here, but for now we're going to concentrate on the setup mechanism itself. Remove the "die" statement from your script so it looks like the following

- -
echo 'Running This Upgrade: '.get_class($this)."\n <br /> \n";
- -

Reload your page. You should see your upgrade message displayed at the top of the page. Reload again, and your page should be displayed as normal.

- -

Resource Versions

- -

Magento's Setup Resources allow you to simply drop your install scripts (and upgrade scripts, which we'll get to in a bit) onto the server, and have the system automatically run them. This allows you to have all your database migrations scripts stored in the system in a consistent format.

- -

Using your favorite database client, take a look at the core_setup table

- -
mysql> select * from core_resource;
-    +-------------------------+------------+--------------+
-    | code                    | version    | data_version |
-    +-------------------------+------------+--------------+
-    | adminnotification_setup | 1.6.0.0    | 1.6.0.0      |
-    | admin_setup             | 1.6.1.0    | 1.6.1.0      |
-    | api2_setup              | 1.0.0.0    | 1.0.0.0      |
-    | api_setup               | 1.6.0.0    | 1.6.0.0      |
-    | backup_setup            | 1.6.0.0    | 1.6.0.0      |
-    | bundle_setup            | 1.6.0.0.1  | 1.6.0.0.1    |
-    | captcha_setup           | 1.7.0.0.0  | 1.7.0.0.0    |
-    | catalogindex_setup      | 1.6.0.0    | 1.6.0.0      |
-    | cataloginventory_setup  | 1.6.0.0.2  | 1.6.0.0.2    |
-    | catalogrule_setup       | 1.6.0.3    | 1.6.0.3      |
-    | catalogsearch_setup     | 1.6.0.0    | 1.6.0.0      |
-    | catalog_setup           | 1.6.0.0.14 | 1.6.0.0.14   |
-    | checkout_setup          | 1.6.0.0    | 1.6.0.0      |
-    | cms_setup               | 1.6.0.0.1  | 1.6.0.0.1    |
-    | compiler_setup          | 1.6.0.0    | 1.6.0.0      |
-    | contacts_setup          | 1.6.0.0    | 1.6.0.0      |
-    | core_setup              | 1.6.0.2    | 1.6.0.2      |
-    | cron_setup              | 1.6.0.0    | 1.6.0.0      |
-    | customer_setup          | 1.6.2.0.1  | 1.6.2.0.1    |
-    | dataflow_setup          | 1.6.0.0    | 1.6.0.0      |
-    | directory_setup         | 1.6.0.1    | 1.6.0.1      |
-    | downloadable_setup      | 1.6.0.0.2  | 1.6.0.0.2    |
-    | eav_setup               | 1.6.0.0    | 1.6.0.0      |
-    | giftmessage_setup       | 1.6.0.0    | 1.6.0.0      |
-    | googleanalytics_setup   | 0.1.0      | 0.1.0        |
-    | googlecheckout_setup    | 1.6.0.1    | 1.6.0.1      |
-    | importexport_setup      | 1.6.0.2    | 1.6.0.2      |
-    | index_setup             | 1.6.0.0    | 1.6.0.0      |
-    | log_setup               | 1.6.0.0    | 1.6.0.0      |
-    | moneybookers_setup      | 1.6.0.0    | 1.6.0.0      |
-    | newsletter_setup        | 1.6.0.1    | 1.6.0.1      |
-    | oauth_setup             | 1.0.0.0    | 1.0.0.0      |
-    | paygate_setup           | 1.6.0.0    | 1.6.0.0      |
-    | payment_setup           | 1.6.0.0    | 1.6.0.0      |
-    | paypaluk_setup          | 1.6.0.0    | 1.6.0.0      |
-    | paypal_setup            | 1.6.0.2    | 1.6.0.2      |
-    | persistent_setup        | 1.0.0.0    | 1.0.0.0      |
-    | poll_setup              | 1.6.0.0    | 1.6.0.0      |
-    | productalert_setup      | 1.6.0.0    | 1.6.0.0      |
-    | rating_setup            | 1.6.0.0    | 1.6.0.0      |
-    | reports_setup           | 1.6.0.0.1  | 1.6.0.0.1    |
-    | review_setup            | 1.6.0.0    | 1.6.0.0      |
-    | salesrule_setup         | 1.6.0.3    | 1.6.0.3      |
-    | sales_setup             | 1.6.0.7    | 1.6.0.7      |
-    | sendfriend_setup        | 1.6.0.0    | 1.6.0.0      |
-    | shipping_setup          | 1.6.0.0    | 1.6.0.0      |
-    | sitemap_setup           | 1.6.0.0    | 1.6.0.0      |
-    | tag_setup               | 1.6.0.0    | 1.6.0.0      |
-    | tax_setup               | 1.6.0.3    | 1.6.0.3      |
-    | usa_setup               | 1.6.0.1    | 1.6.0.1      |
-    | weblog_setup            | 0.1.0      | 0.1.0        |
-    | weee_setup              | 1.6.0.0    | 1.6.0.0      |
-    | widget_setup            | 1.6.0.0    | 1.6.0.0      |
-    | wishlist_setup          | 1.6.0.0    | 1.6.0.0      |
-    | xmlconnect_setup        | 1.6.0.0    | 1.6.0.0      |
-    +-------------------------+------------+--------------+
-    55 rows in set (0.00 sec)
-
- -

This table contains a list of all the installed modules, along with the installed version number. You can see our module near the end

- -
| weblog_setup            | 0.1.0      | 0.1.0        |
- -

This is how Magento knows not to re-run your script on the second, and on all successive, page loads. The weblog_setup is already installed, so it won't be updated. If you want to re-run your installer script (useful when you're developing), just delete the row for your module from this table. Let's do that now, and actually add the SQL to create our table. So first, run the following SQL.

- -
DELETE from core_resource where code = 'weblog_setup';
- -

We'll also want to drop the table we manually created in the ORM article.

- -
DROP TABLE blog_posts;
- -

Then, add the following code to your setup script.

- -
$installer = $this;
-$installer->startSetup();
-$installer->run("
-    CREATE TABLE `{$installer->getTable('weblog/blogpost')}` (
-      `blogpost_id` int(11) NOT NULL auto_increment,
-      `title` text,
-      `post` text,
-      `date` datetime default NULL,
-      `timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP,
-      PRIMARY KEY  (`blogpost_id`)
-    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-
-    INSERT INTO `{$installer->getTable('weblog/blogpost')}` VALUES (1,'My New Title','This is a blog post','2009-07-01 00:00:00','2009-07-02 23:12:30');
-");
-$installer->endSetup();
-
- -

Clear your Magento cache and reload any page in the system. You should have a new blog_posts table with a single row.

- -

Anatomy of a Setup Script

- -

So, let's go over the script line-by-line. First, there's this (or is that $this?)

- -
$installer = $this;
- -

Each installer script is run from the context of a Setup Resource class, the class you created above. That means any reference to $this from within the script will be a reference to an object instantiated from this class. While not necessary, most setup scripts in the core modules will alias $this to a variable called installer, which is what we've done here. While not necessary, it is the convention and it's always best to follow the convention unless you have a good reason for breaking it.

- -

Next, you'll see our queries are bookended by the following two method calls.

- -
$installer->startSetup();
-//...
-$installer->endSetup();
-
- -

If you take a look at the Mage_Core_Model_Resource_Setup class in app/code/core/Mage/Core/Model/Resource/Setup.php (which your setup class inherits from) you can see that these methods do some basic SQL setup

- -
-    public function startSetup()
-    {
-        $this->getConnection()->startSetup()
-        return $this;
-    }
-
-    public function endSetup()
-    {
-        $this->getConnection()->endSetup();
-        return $this;
-    }
-
- -

You can look into Varien_Db_Adapter_Pdo_Mysql in lib/Varien/Db/Adapter/Pdo/Mysql.php to find the real SQL setup executed for MySQL connections in the startSetup() and endSetup() methods.

- -

Finally, there's the call to the run method

- -
$installer->run(...);
- -

which accepts a string containing the SQL needed to setup your database table(s). You may specify any number of queries, separated by a semi-colon. You also probably noticed the following

- -
$installer->getTable('weblog/blogpost')
- -

The getTable method allows you to pass in a Magento Model URI and get its table name. While not necessary, using this method ensures that your script will continue to run, even if someone changes the name of their table in the config file. The Mage_Core_Model_Resource_Setup class contains many useful helper methods like this. The best way to become familiar with everything that's possible is to study the installer scripts used by the core Magento modules.

- -

RDBMS Agnostic Scripts

- -

Since version 1.6, Magento (in theory) supports more database backends then only MySQL. Since our setup script contains raw SQL statements, it may not run correctly on a different database system, say MSSQL. For that reason the setup script name is prefixt with the string mysql4-

- -

In order to make setup scripts cross-database compatible, Magento offers a DDL (Data Definition Language) Table object. Here is an alternative version of our setup script that would run on any supported RDBMS.

- -

File: app/code/local/Magentotutorial/Weblog/sql/weblog_setup/mysql4-install-0.1.0.php

-
-$installer = $this;
-$installer->startSetup();
-$table = $installer->getConnection()->newTable($installer->getTable('weblog/blogpost'))
-    ->addColumn('blogpost_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
-        'unsigned' => true,
-        'nullable' => false,
-        'primary' => true,
-        'identity' => true,
-        ), 'Blogpost ID')
-    ->addColumn('title', Varien_Db_Ddl_Table::TYPE_TEXT, null, array(
-        'nullable' => false,
-        ), 'Blogpost Title')
-    ->addColumn('post', Varien_Db_Ddl_Table::TYPE_TEXT, null, array(
-        'nullable' => true,
-        ), 'Blogpost Body')
-    ->addColumn('date', Varien_Db_Ddl_Table::TYPE_DATETIME, null, array(
-        ), 'Blogpost Date')
-    ->addColumn('timestamp', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array(
-        ), 'Timestamp')
-    ->setComment('Magentotutorial weblog/blogpost entity table');
-$installer->getConnection()->createTable($table);
-
-$installer->endSetup();
-
- -

As you can see, there is no raw SQL in this version of the setup script. So which version should you use? If you want your Modules to run on any RDBMS backend, use the new DDL style upgrade scripts. If you are concerned about backward compatibility, use the raw SQL flavor, that is still supported by Magento 1.6 and 1.7 (and probably will be supported by any 1.x Magento release).

- -

Module Upgrades

- -

So, that's how you create a script that will setup your initial database tables, but what if you want to alter the structure of an existing module? Magento's Setup Resources support a simple versioning scheme that will let you automatically run scripts to upgrade your modules.

- -

Once Magento runs an installer script for a module, it will never run another installer for that module again (short of manually deleting the reference in the core_resource table). Instead, you'll need to create an upgrade script. Upgrade scripts are very similar to installer scripts, with a few key differences.

- -

To get started, we'll create a script at the following location, with the following contents

- -

File: app/code/local/Magentotutorial/Weblog/sql/weblog_setup/upgrade-0.1.0-0.2.0.php:

-
-echo 'Testing our upgrade script (upgrade-0.1.0-0.2.0.php) and halting execution to avoid updating the system version number 
'; -die(); -
- -

Upgrade scripts are placed in the same folder as your installer script, but named slightly differently. First, and most obviously, the file name contains the word upgrade. Secondly, you'll notice there are two version numbers, separated by a "-". The first (0.1.0) is the module version that we're upgrading from. The second (0.2.0) is the module version we're upgrading to.

- -

If we cleared our Magento cache and reloaded a page, our script wouldn't run. We need to update the version number in our module's config.xml file to trigger the upgrade

- -
-<modules>
-    <Magentotutorial_Weblog>
-        <version>0.2.0</version>
-    </Magentotutorial_Weblog>
-</modules>
-
- -

With the new version number in place, we'll need to clear our Magento cache and load any page in our Magento site. You should now see output from your upgrade script.

- -

By the way, we also could have names our upgrade script mysql4-upgrade-0.1.0-0.2.0.php. This would indicate our upgrade would contain MySQL specific SQL.

- -

Before we continue and actually implement the upgrade script, there's one important piece of behavior you'll want to be aware of. Create another upgrade file at the following location with the following contents.

- -

File: app/code/local/Magentotutorial/Weblog/sql/weblog_setup/upgrade-0.1.0-0.1.5.php:

-
-echo 'Testing our upgrade script (upgrade-0.1.0-0.1.5.php) and NOT halting execution <br />';
-
- -

If you reload a page, you'll notice you see BOTH messages. When Magento notices the version number of a module has changed, it will run through all the setup scripts needed to bring that version up to date. Although we never really created a version 0.1.5 of the Weblog module, Magento sees the upgrade script, and will attempt to run it. Scripts will be run in order from lowest to highest. If you take a peek at the core_resource table,

- -
mysql> select * from core_resource where code = 'weblog_setup';
-+--------------+---------+--------------+
-| code         | version | data_version |
-+--------------+---------+--------------+
-| weblog_setup | 0.1.5   | 0.1.5        |
-+--------------+---------+--------------+
-1 row in set (0.00 sec)
-
- -

you'll notice Magento considers the version number to be 1.5. That's because we completed executing the 1.0 to 1.5 upgrade, but did not complete execution of the 1.0 to 2.0 upgrade.

- -

So, with all that out of the way, writing our actual upgrade script is identical to writing an installer script. Let's change the 0.1.0-0.2.0 script to read

- -
-$installer = $this;
-$installer->startSetup();
-$installer->getConnection()
-    ->changeColumn($installer->getTable('weblog/blogpost'), 'post', 'post', array(
-        'type' => Varien_Db_Ddl_Table::TYPE_TEXT,
-        'nullable' => false,
-        'comment' => 'Blogpost Body'
-    )
-);
-$installer->endSetup();
-die("You'll see why this is here in a second");
-
- -

Try refreshing a page in your Magento site and ... nothing. The upgrade script didn't run. The post field in our table still allows null values, and more importantly, the call to die() did not halt execution. Here's what happened

- -
    -
  1. The weblog_setup resource was at version 0.1.0
  2. -
  3. We upgraded our module to version 0.2.0
  4. -
  5. Magento saw the upgraded module, and saw there were two upgrade scripts to run; 0.1.0 to 0.1.5 and 0.1.0 to 0.2.0
  6. -
  7. Magento queued up both scripts to run
  8. -
  9. Magento ran the 0.1.0 to 0.1.5 script
  10. -
  11. The weblog_setup resource is now at version 0.1.5
  12. -
  13. Magento ran the 0.1.0 to 0.2.0 script, execution was halted
  14. -
  15. On the next page load, Magento saw weblog_setup at version 0.1.5 and did not see any upgrade scripts to run since both scripts indicated they should be run from 0.1.0
  16. -
- -

The correct way to achieve what we wanted would have been to name our scripts as follows

- -
upgrade-0.1.0-0.1.5.php #This goes from 0.1.0 to 0.1.5
-upgrade-0.1.5-0.2.0.php #This goes 0.1.5 to 0.2.0
- -

Magento is smart enough to run both scripts on a single page load. You can go back in time and give this a try by updating the core_resource table

- -
UPDATE core_resource SET version = '0.1.0', data_version = '0.1.0' WHERE code = 'weblog_setup';
-...
- -

It's one of the odd quirks of the Magento system that the updates will run as previously configured. This means you'll want to be careful with multiple developers adding update scripts to the system. You'll either want a build-meister/deployment-manager type in charge of the upgrade scripts or (heaven forbid) developers will need to talk to one another.

- -

Wrap-up

- -

You should now know the basics of how to use Magento Setup Resources to create versioned database migration scripts, as well as understand the scripts provided in the core modules. Beyond having a standard way for developers to write migration scripts, Setup Resources become much more important when creating and modifying Entity Attribute Value models.

- + + + + +
+
+ +
+ + + +
+
+ +
+

OpenMage Setup Resources

+
+

On any fast paced software development project, the task of keeping the development + and production databases in sync become a sticky wicket. Magento offers a system to create + versioned resource migration scripts that can help your team deal with this often contentious + part of the development process.

+

In the ORM article we created a + model for a weblog post. At the time, we ran our CREATE TABLE statements directly + against the database. This time, we'll create a Setup Resource for our module that will create + the table for us. We'll also create an upgrade script for our module that will update an already + installed module. The steps we'll need to take are

+
    +
  1. Add the Setup Resource to our config
  2. +
  3. Create our resource class file
  4. +
  5. Create our installer script
  6. +
  7. Create our upgrade script
  8. +
+
+

Adding the Setup Resource

+

So, let's continue with the weblog module we created last time. In our + <global /> section, add the following

+
+                    <global>
+                        <!-- ... -->
+                        <resources>
+                            <weblog_setup>
+                                <setup>
+                                    <module>Magentotutorial_Weblog</module>
+                                    <class>Magentotutorial_Weblog_Model_Resource_Setup</class>
+                                </setup>
+                            </weblog_setup>
+                        </resources>
+                        <!-- ... -->
+                    </global>
+                    
+

The <weblog_setup> tag will be used to uniquely identify this Setup Resource. It's + encouraged, but not necessary, that you use the modelname_setup naming convention. The + <module>Magentotutorial_Weblog</module> tag block should contain the Packagename_Modulename + of your module. Finally, <class>Magentotutorial_Weblog_Model_Resource_Setup</class> + should contain the name of the class we'll be creating for our Setup Resource. For basic setup + scripts it's not necessary to create a custom class, but by doing it now you'll give yourself + more flexibility down the line.

+

After adding the above section to your config, clear your Magento cache and try to load any page + of your Magento site. You'll see an exception something like

+
Fatal error: Class 'Magentotutorial_Weblog_Model_Resource_Setup' not found in
+

Magento just tried to instantiate the class you specified in your config, but couldn't find it. + You'll want to create the following file, with the following contents.

+

File: app/code/local/Magentotutorial/Weblog/Model/Resource/Setup.php

+
+                    class Magentotutorial_Weblog_Model_Resource_Setup extends Mage_Core_Model_Resource_Setup {
+                    }
+                    
+

Now, reload any page of your Magento site. The exception should be gone, and your page should + load as expected.

+

Creating our Installer Script

+

Next, we'll want to create our installer script. This is the script that will contain any CREATE + TABLE or other SQL code that needs to be run to initialize our module.

+

First, take a look at your config.xml file

+
+                    <modules>
+                        <Magentotutorial_Weblog>
+                            <version>0.1.0</version>
+                        </Magentotutorial_Weblog>
+                    </modules>
+                    
+

This section is required in all config.xml files, and identifies the module as well as the its + version number. Your installer script's name will be based on this version number. The following + assumes the current version of your module is 0.1.0.

+

Create the following file at the following location

+

File: app/code/local/Magentotutorial/Weblog/sql/weblog_setup/mysql4-install-0.1.0.php +

+
+                    echo 'Running This Upgrade: '.get_class($this)."\n <br /> \n";
+                    die("Exit for now");
+                    
+

The weblog_setup portion of the path should match the tag you created in your config.xml + file (<weblog_setup />). The 0.1.0 portion of the filename should match + the starting version of your module. Clear your Magento cache and reload any page in your + Magento site and you should see something like

+
+                    Running This Upgrade: Magentotutorial_Weblog_Model_Resource_Setup
+                    Exit for now
+                     ...
+                    
+

Which means your update script ran. Eventually we'll put our SQL update scripts here, but for now + we're going to concentrate on the setup mechanism itself. Remove the "die" statement from your + script so it looks like the following

+
echo 'Running This Upgrade: '.get_class($this)."\n <br /> \n";
+

Reload your page. You should see your upgrade message displayed at the top of the page. Reload + again, and your page should be displayed as normal.

+

Resource Versions

+

Magento's Setup Resources allow you to simply drop your install scripts (and upgrade scripts, + which we'll get to in a bit) onto the server, and have the system automatically run them. This + allows you to have all your database migrations scripts stored in the system in a consistent + format.

+

Using your favorite database client, take a look at the core_setup table

+
mysql> select * from core_resource;
+                        +-------------------------+------------+--------------+
+                        | code                    | version    | data_version |
+                        +-------------------------+------------+--------------+
+                        | adminnotification_setup | 1.6.0.0    | 1.6.0.0      |
+                        | admin_setup             | 1.6.1.0    | 1.6.1.0      |
+                        | api2_setup              | 1.0.0.0    | 1.0.0.0      |
+                        | api_setup               | 1.6.0.0    | 1.6.0.0      |
+                        | backup_setup            | 1.6.0.0    | 1.6.0.0      |
+                        | bundle_setup            | 1.6.0.0.1  | 1.6.0.0.1    |
+                        | captcha_setup           | 1.7.0.0.0  | 1.7.0.0.0    |
+                        | catalogindex_setup      | 1.6.0.0    | 1.6.0.0      |
+                        | cataloginventory_setup  | 1.6.0.0.2  | 1.6.0.0.2    |
+                        | catalogrule_setup       | 1.6.0.3    | 1.6.0.3      |
+                        | catalogsearch_setup     | 1.6.0.0    | 1.6.0.0      |
+                        | catalog_setup           | 1.6.0.0.14 | 1.6.0.0.14   |
+                        | checkout_setup          | 1.6.0.0    | 1.6.0.0      |
+                        | cms_setup               | 1.6.0.0.1  | 1.6.0.0.1    |
+                        | compiler_setup          | 1.6.0.0    | 1.6.0.0      |
+                        | contacts_setup          | 1.6.0.0    | 1.6.0.0      |
+                        | core_setup              | 1.6.0.2    | 1.6.0.2      |
+                        | cron_setup              | 1.6.0.0    | 1.6.0.0      |
+                        | customer_setup          | 1.6.2.0.1  | 1.6.2.0.1    |
+                        | dataflow_setup          | 1.6.0.0    | 1.6.0.0      |
+                        | directory_setup         | 1.6.0.1    | 1.6.0.1      |
+                        | downloadable_setup      | 1.6.0.0.2  | 1.6.0.0.2    |
+                        | eav_setup               | 1.6.0.0    | 1.6.0.0      |
+                        | giftmessage_setup       | 1.6.0.0    | 1.6.0.0      |
+                        | googleanalytics_setup   | 0.1.0      | 0.1.0        |
+                        | googlecheckout_setup    | 1.6.0.1    | 1.6.0.1      |
+                        | importexport_setup      | 1.6.0.2    | 1.6.0.2      |
+                        | index_setup             | 1.6.0.0    | 1.6.0.0      |
+                        | log_setup               | 1.6.0.0    | 1.6.0.0      |
+                        | moneybookers_setup      | 1.6.0.0    | 1.6.0.0      |
+                        | newsletter_setup        | 1.6.0.1    | 1.6.0.1      |
+                        | oauth_setup             | 1.0.0.0    | 1.0.0.0      |
+                        | paygate_setup           | 1.6.0.0    | 1.6.0.0      |
+                        | payment_setup           | 1.6.0.0    | 1.6.0.0      |
+                        | paypaluk_setup          | 1.6.0.0    | 1.6.0.0      |
+                        | paypal_setup            | 1.6.0.2    | 1.6.0.2      |
+                        | persistent_setup        | 1.0.0.0    | 1.0.0.0      |
+                        | poll_setup              | 1.6.0.0    | 1.6.0.0      |
+                        | productalert_setup      | 1.6.0.0    | 1.6.0.0      |
+                        | rating_setup            | 1.6.0.0    | 1.6.0.0      |
+                        | reports_setup           | 1.6.0.0.1  | 1.6.0.0.1    |
+                        | review_setup            | 1.6.0.0    | 1.6.0.0      |
+                        | salesrule_setup         | 1.6.0.3    | 1.6.0.3      |
+                        | sales_setup             | 1.6.0.7    | 1.6.0.7      |
+                        | sendfriend_setup        | 1.6.0.0    | 1.6.0.0      |
+                        | shipping_setup          | 1.6.0.0    | 1.6.0.0      |
+                        | sitemap_setup           | 1.6.0.0    | 1.6.0.0      |
+                        | tag_setup               | 1.6.0.0    | 1.6.0.0      |
+                        | tax_setup               | 1.6.0.3    | 1.6.0.3      |
+                        | usa_setup               | 1.6.0.1    | 1.6.0.1      |
+                        | weblog_setup            | 0.1.0      | 0.1.0        |
+                        | weee_setup              | 1.6.0.0    | 1.6.0.0      |
+                        | widget_setup            | 1.6.0.0    | 1.6.0.0      |
+                        | wishlist_setup          | 1.6.0.0    | 1.6.0.0      |
+                        | xmlconnect_setup        | 1.6.0.0    | 1.6.0.0      |
+                        +-------------------------+------------+--------------+
+                        55 rows in set (0.00 sec)
+                    
+

This table contains a list of all the installed modules, along with the installed version number. + You can see our module near the end

+ +
| weblog_setup            | 0.1.0      | 0.1.0        |
+

This is how Magento knows not to re-run your script on the second, and on all successive, page + loads. The weblog_setup is already installed, so it won't be updated. If you want to + re-run your installer script (useful when you're developing), just delete the row for your + module from this table. Let's do that now, and actually add the SQL to create our table. So + first, run the following SQL.

+
DELETE from core_resource where code = 'weblog_setup';
+

We'll also want to drop the table we manually created in the ORM article.

+
DROP TABLE blog_posts;
+

Then, add the following code to your setup script.

+
$installer = $this;
+                    $installer->startSetup();
+                    $installer->run("
+                        CREATE TABLE `{$installer->getTable('weblog/blogpost')}` (
+                          `blogpost_id` int(11) NOT NULL auto_increment,
+                          `title` text,
+                          `post` text,
+                          `date` datetime default NULL,
+                          `timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP,
+                          PRIMARY KEY  (`blogpost_id`)
+                        ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+                        INSERT INTO `{$installer->getTable('weblog/blogpost')}` VALUES (1,'My New Title','This is a blog post','2009-07-01 00:00:00','2009-07-02 23:12:30');
+                    ");
+                    $installer->endSetup();
+                    
+

Clear your Magento cache and reload any page in the system. You should have a new blog_posts + table with a single row.

+

Anatomy of a Setup Script

+

So, let's go over the script line-by-line. First, there's this (or is that $this?)

+
$installer = $this;
+

Each installer script is run from the context of a Setup Resource class, the class you created + above. That means any reference to $this from within the script will be a reference to + an object instantiated from this class. While not necessary, most setup scripts in the core + modules will alias $this to a variable called installer, which is what we've done here. + While not necessary, it is the convention and it's always best to follow the convention unless + you have a good reason for breaking it.

+

Next, you'll see our queries are bookended by the following two method calls.

+
$installer->startSetup();
+                    //...
+                    $installer->endSetup();
+                    
+

If you take a look at the Mage_Core_Model_Resource_Setup class in app/code/core/Mage/Core/Model/Resource/Setup.php + (which your setup class inherits from) you can see that these methods do some basic SQL setup +

+
+                        public function startSetup()
+                        {
+                            $this->getConnection()->startSetup()
+                            return $this;
+                        }
+
+                        public function endSetup()
+                        {
+                            $this->getConnection()->endSetup();
+                            return $this;
+                        }
+                    
+

You can look into Varien_Db_Adapter_Pdo_Mysql in + lib/Varien/Db/Adapter/Pdo/Mysql.php to find the real SQL setup executed for MySQL + connections in the startSetup() and endSetup() methods.

+

Finally, there's the call to the run method

+
$installer->run(...);
+

which accepts a string containing the SQL needed to setup your database table(s). You may specify + any number of queries, separated by a semi-colon. You also probably noticed the following

+
$installer->getTable('weblog/blogpost')
+

The getTable method allows you to pass in a Magento Model URI and get its table name. + While not necessary, using this method ensures that your script will continue to run, even if + someone changes the name of their table in the config file. The Mage_Core_Model_Resource_Setup + class contains many useful helper methods like this. The best way to become familiar with + everything that's possible is to study the installer scripts used by the core Magento modules. +

+

RDBMS Agnostic Scripts

+

Since version 1.6, Magento (in theory) supports more database backends then only MySQL. Since our + setup script contains raw SQL statements, it may not run correctly on a different database + system, say MSSQL. For that reason the setup script name is prefixt with the string + mysql4-

+

In order to make setup scripts cross-database compatible, Magento offers a DDL (Data Definition + Language) Table object. Here is an alternative version of our setup script that would run on any + supported RDBMS.

+

File: + app/code/local/Magentotutorial/Weblog/sql/weblog_setup/mysql4-install-0.1.0.php

+
+                    $installer = $this;
+                    $installer->startSetup();
+                    $table = $installer->getConnection()->newTable($installer->getTable('weblog/blogpost'))
+                        ->addColumn('blogpost_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
+                            'unsigned' => true,
+                            'nullable' => false,
+                            'primary' => true,
+                            'identity' => true,
+                            ), 'Blogpost ID')
+                        ->addColumn('title', Varien_Db_Ddl_Table::TYPE_TEXT, null, array(
+                            'nullable' => false,
+                            ), 'Blogpost Title')
+                        ->addColumn('post', Varien_Db_Ddl_Table::TYPE_TEXT, null, array(
+                            'nullable' => true,
+                            ), 'Blogpost Body')
+                        ->addColumn('date', Varien_Db_Ddl_Table::TYPE_DATETIME, null, array(
+                            ), 'Blogpost Date')
+                        ->addColumn('timestamp', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array(
+                            ), 'Timestamp')
+                        ->setComment('Magentotutorial weblog/blogpost entity table');
+                    $installer->getConnection()->createTable($table);
+
+                    $installer->endSetup();
+                    
+

As you can see, there is no raw SQL in this version of the setup script. So which version should + you use? If you want your Modules to run on any RDBMS backend, use the new DDL style upgrade + scripts. If you are concerned about backward compatibility, use the raw SQL flavor, that is + still supported by Magento 1.6 and 1.7 (and probably will be supported by any 1.x Magento + release).

+

Module Upgrades

+

So, that's how you create a script that will setup your initial database tables, but what if you + want to alter the structure of an existing module? Magento's Setup Resources support a simple + versioning scheme that will let you automatically run scripts to upgrade your + modules.

+

Once Magento runs an installer script for a module, it will never run another + installer for that module again (short of manually deleting the reference in the core_resource + table). Instead, you'll need to create an upgrade script. Upgrade scripts are very similar to + installer scripts, with a few key differences.

+

To get started, we'll create a script at the following location, with the following contents

+

File: app/code/local/Magentotutorial/Weblog/sql/weblog_setup/upgrade-0.1.0-0.2.0.php:

+
+                    echo 'Testing our upgrade script (upgrade-0.1.0-0.2.0.php) and halting execution to avoid updating the system version number 
'; + die(); +
+

Upgrade scripts are placed in the same folder as your installer script, but named slightly + differently. First, and most obviously, the file name contains the word upgrade. Secondly, + you'll notice there are two version numbers, separated by a "-". The first + (0.1.0) is the module version that we're upgrading from. The second + (0.2.0) is the module version we're upgrading to.

+

If we cleared our Magento cache and reloaded a page, our script wouldn't run. We need to update + the version number in our module's config.xml file to trigger the upgrade

+
+                    <modules>
+                        <Magentotutorial_Weblog>
+                            <version>0.2.0</version>
+                        </Magentotutorial_Weblog>
+                    </modules>
+                    
+

With the new version number in place, we'll need to clear our Magento cache and load any page in + our Magento site. You should now see output from your upgrade script.

+

By the way, we also could have names our upgrade script mysql4-upgrade-0.1.0-0.2.0.php. + This would indicate our upgrade would contain MySQL specific SQL.

+

Before we continue and actually implement the upgrade script, there's one important piece of + behavior you'll want to be aware of. Create another upgrade file at the following location with + the following contents.

+

File: app/code/local/Magentotutorial/Weblog/sql/weblog_setup/upgrade-0.1.0-0.1.5.php:

+
+                    echo 'Testing our upgrade script (upgrade-0.1.0-0.1.5.php) and NOT halting execution <br />';
+                    
+

If you reload a page, you'll notice you see BOTH messages. When Magento notices the version + number of a module has changed, it will run through all the setup scripts + needed to bring that version up to date. Although we never really created a version 0.1.5 of the + Weblog module, Magento sees the upgrade script, and will attempt to run it. Scripts will be run + in order from lowest to highest. If you take a peek at the core_resource table,

+
mysql> select * from core_resource where code = 'weblog_setup';
+                    +--------------+---------+--------------+
+                    | code         | version | data_version |
+                    +--------------+---------+--------------+
+                    | weblog_setup | 0.1.5   | 0.1.5        |
+                    +--------------+---------+--------------+
+                    1 row in set (0.00 sec)
+                    
+

you'll notice Magento considers the version number to be 1.5. That's because we completed + executing the 1.0 to 1.5 upgrade, but did not complete execution of the 1.0 to 2.0 upgrade.

+

So, with all that out of the way, writing our actual upgrade script is identical to writing an + installer script. Let's change the 0.1.0-0.2.0 script to read

+
+                    $installer = $this;
+                    $installer->startSetup();
+                    $installer->getConnection()
+                        ->changeColumn($installer->getTable('weblog/blogpost'), 'post', 'post', array(
+                            'type' => Varien_Db_Ddl_Table::TYPE_TEXT,
+                            'nullable' => false,
+                            'comment' => 'Blogpost Body'
+                        )
+                    );
+                    $installer->endSetup();
+                    die("You'll see why this is here in a second");
+                    
+

Try refreshing a page in your Magento site and ... nothing. The upgrade script didn't run. The + post field in our table still allows null values, and more importantly, the call to + die() did not halt execution. Here's what happened

+
    +
  1. The weblog_setup resource was at version 0.1.0
  2. +
  3. We upgraded our module to version 0.2.0
  4. +
  5. Magento saw the upgraded module, and saw there were two upgrade scripts to run; + 0.1.0 to 0.1.5 and 0.1.0 to 0.2.0
  6. +
  7. Magento queued up both scripts to run
  8. +
  9. Magento ran the 0.1.0 to 0.1.5 script
  10. +
  11. The weblog_setup resource is now at version 0.1.5
  12. +
  13. Magento ran the 0.1.0 to 0.2.0 script, execution was halted
  14. +
  15. On the next page load, Magento saw weblog_setup at version 0.1.5 and did not see + any upgrade scripts to run since both scripts indicated they should be run + from 0.1.0
  16. +
+

The correct way to achieve what we wanted would have been to name our scripts as follows

+
upgrade-0.1.0-0.1.5.php #This goes from 0.1.0 to 0.1.5
+                    upgrade-0.1.5-0.2.0.php #This goes 0.1.5 to 0.2.0
+

Magento is smart enough to run both scripts on a single page load. You can go back in time and + give this a try by updating the core_resource table

+
UPDATE core_resource SET version = '0.1.0', data_version = '0.1.0' WHERE code = 'weblog_setup';
+                    ...
+

It's one of the odd quirks of the Magento system that the updates will run as previously + configured. This means you'll want to be careful with multiple developers adding update scripts + to the system. You'll either want a build-meister/deployment-manager type in charge of the + upgrade scripts or (heaven forbid) developers will need to talk to one another.

+

Wrap-up

+

You should now know the basics of how to use Magento Setup Resources to create versioned database + migration scripts, as well as understand the scripts provided in the core modules. Beyond having + a standard way for developers to write migration scripts, Setup Resources become much more + important when creating and modifying Entity Attribute Value models.

+
+ + +
+ +
+
+ +
+ + + + - + + + + + + + \ No newline at end of file diff --git a/guides/m1x/magefordev/mage-for-dev-7.html b/guides/m1x/magefordev/mage-for-dev-7.html index 0445954489..c7650ea4fc 100644 --- a/guides/m1x/magefordev/mage-for-dev-7.html +++ b/guides/m1x/magefordev/mage-for-dev-7.html @@ -1,64 +1,281 @@ --- --- - - + - - - - - - - Magento for Developers: Part 7—Advanced ORM: Entity Attribute Value - + + + + + OpenMage for Developers: Part 7—Advanced ORM: Entity Attribute Value + + + + + + + + + + + + + + + - -header -{% include m1x/eol_message.html %} - - - -

In the first ORM article we told you there were two kinds of Models in Magento. Regular, or "simple" Models, and Entity Attribute Value (or EAV) Models. We also told you this was a bit of a fib. Here's where we come clean.

- -

ALL Magento Models interacting with the database inherit from the Mage_Core_Model_Abstract / Varien_Object chain. What makes something either a simple Model or an EAV Model is its Model Resource. While all resources extend the base Mage_Core_Model_Resource_Abstract class, simple Models have a resource that inherits from Mage_Core_Model_Resource_Db_Abstract, and EAV Models have a resource that inherits from Mage_Eav_Model_Entity_Abstract

- -

If you think about it, this makes sense. As the end-programmer-user of the system you want a set of methods you can use to talk to and manipulate your Models. You don't care what the backend storage looks like, you just want to get properties and invoke methods that trigger business rules.

- -

What is EAV

- -

Wikipedia defines EAV as

-
Entity-Attribute-Value model (EAV), also known as object-attribute-value model and open schema is a data model that is used in circumstances where the number of attributes (properties, parameters) that can be used to describe a thing (an "entity" or "object") is potentially very vast, but the number that will actually apply to a given entity is relatively modest. In mathematics, this model is known as a sparse matrix.
- -

Another metaphor that helps me wrap my head around it is "EAV brings some aspects of normalization to the database table schema". In a traditional database, tables have a fixed number of columns

- -
+------------------+
+
+
+
+
+
+
+ +
+ + + +
+
+ +
+

Advanced ORM: Entity Attribute Value

+
+ +

In the first ORM article we told you there were two kinds of Models in Magento. Regular, or "simple" Models, and Entity Attribute Value (or EAV) Models. We also told you this was a bit of a fib. Here's where we come clean.

+ +

ALL Magento Models interacting with the database inherit from the Mage_Core_Model_Abstract / Varien_Object chain. What makes something either a simple Model or an EAV Model is its Model Resource. While all resources extend the base Mage_Core_Model_Resource_Abstract class, simple Models have a resource that inherits from Mage_Core_Model_Resource_Db_Abstract, and EAV Models have a resource that inherits from Mage_Eav_Model_Entity_Abstract

+ +

If you think about it, this makes sense. As the end-programmer-user of the system you want a set of methods you can use to talk to and manipulate your Models. You don't care what the backend storage looks like, you just want to get properties and invoke methods that trigger business rules.

+ +

What is EAV

+ +

Wikipedia defines EAV as

+
Entity-Attribute-Value model (EAV), also known as object-attribute-value model and open schema is a data model that is used in circumstances where the number of attributes (properties, parameters) that can be used to describe a thing (an "entity" or "object") is potentially very vast, but the number that will actually apply to a given entity is relatively modest. In mathematics, this model is known as a sparse matrix.
+ +

Another metaphor that helps me wrap my head around it is "EAV brings some aspects of normalization to the database table schema". In a traditional database, tables have a fixed number of columns

+ +
+------------------+
 | products         |
 +------------------+
 | product_id       |
@@ -76,38 +293,38 @@ 

What is EAV

+------------+----------------+------------------+---------+
-

Every product has a name, every product has a price, etc.

+

Every product has a name, every product has a price, etc.

-

In an EAV Model, each "entity" (product) being modeled has a different set of attributes. EAV makes a lot of sense for a generic eCommerce solution. A store that sells laptops (which have a CPU speed, color, ram amount, etc) is going to have a different set of needs than a store that sells yarn (yarn has a color, but no CPU speed, etc.). Even within our hypothetical yarn store, some products will have length (balls of yarn), and others will have diameter (knitting needles).

+

In an EAV Model, each "entity" (product) being modeled has a different set of attributes. EAV makes a lot of sense for a generic eCommerce solution. A store that sells laptops (which have a CPU speed, color, ram amount, etc) is going to have a different set of needs than a store that sells yarn (yarn has a color, but no CPU speed, etc.). Even within our hypothetical yarn store, some products will have length (balls of yarn), and others will have diameter (knitting needles).

-

There aren't many open source or commercial databases that use EAV by default. There are none that are available on a wide variety of web hosting platforms. Because of that, the Magento engineers have built an EAV system out of PHP objects that use MySQL as a data-store. In other words, they've built an EAV database system on top of a traditional relational database.

+

There aren't many open source or commercial databases that use EAV by default. There are none that are available on a wide variety of web hosting platforms. Because of that, the Magento engineers have built an EAV system out of PHP objects that use MySQL as a data-store. In other words, they've built an EAV database system on top of a traditional relational database.

-

In practice this means any Model that uses an EAV resource has its attributes spread out over a number of MySQL tables.
- +

In practice this means any Model that uses an EAV resource has its attributes spread out over a number of MySQL tables.
+ -

The above diagram is a rough layout of the database tables Magento consults when it looks up an EAV record for the catalog_product entity. Each individual product has a row in catalog_product_entity. All the available attributes in the entire system (not just for products) are stored in eav_attribute, and the actual attribute values are stored in tables with names like catalog_product_entity_varchar, catalog_product_entity_decimal, catalog_product_entity_etc..

+

The above diagram is a rough layout of the database tables Magento consults when it looks up an EAV record for the catalog_product entity. Each individual product has a row in catalog_product_entity. All the available attributes in the entire system (not just for products) are stored in eav_attribute, and the actual attribute values are stored in tables with names like catalog_product_entity_varchar, catalog_product_entity_decimal, catalog_product_entity_etc..

-

Beyond the mental flexibility an EAV system gives you, there's also the practical benefit of avoiding ALTER TABLE statements. When you add a new attribute for your products, a new row is inserted into eav_attribute. In a traditional relational database/single-table system, you'd need to ALTER the actual database structure, which can be a time consuming/risky proposition for tables with large data-sets.

+

Beyond the mental flexibility an EAV system gives you, there's also the practical benefit of avoiding ALTER TABLE statements. When you add a new attribute for your products, a new row is inserted into eav_attribute. In a traditional relational database/single-table system, you'd need to ALTER the actual database structure, which can be a time consuming/risky proposition for tables with large data-sets.

-

The downside is there's no one single simple SQL query you can use to get at all your product data. Several single SQL queries or one large join need to be made.

+

The downside is there's no one single simple SQL query you can use to get at all your product data. Several single SQL queries or one large join need to be made.

-

Implementing EAV

+

Implementing EAV

-

That's EAV in a nutshell. The rest of this articles is a run-through of what's needed to create a new EAV Model in Magento. It's the hairiest thing you'll read about Magento and it's something that 95% of people working with the system will never need to do. However, understanding what it takes to build an EAV Model Resource will help you understand what's going on with the EAV Resources that Magento uses.

+

That's EAV in a nutshell. The rest of this articles is a run-through of what's needed to create a new EAV Model in Magento. It's the hairiest thing you'll read about Magento and it's something that 95% of people working with the system will never need to do. However, understanding what it takes to build an EAV Model Resource will help you understand what's going on with the EAV Resources that Magento uses.

-

Because the EAV information is so dense, we're going to assume you're already very familiar with Magento's MVC and grouped class name features. We'll help you along the way, but training wheels are off.

+

Because the EAV information is so dense, we're going to assume you're already very familiar with Magento's MVC and grouped class name features. We'll help you along the way, but training wheels are off.

-

Weblog, EAV Style

+

Weblog, EAV Style

-

We're going to create another Model for a weblog post, but this time using an EAV Resource. To start with, setup and create a new module which responds at the following URL

+

We're going to create another Model for a weblog post, but this time using an EAV Resource. To start with, setup and create a new module which responds at the following URL

-
http://example.com/complexworld
+
http://example.com/complexworld
-

If you're unsure how to do this, be sure you've mastered the concepts in the previous tutorials.

+

If you're unsure how to do this, be sure you've mastered the concepts in the previous tutorials.

-

Next, we'll create a new Model named Weblogeav. Remember, it's the Resource that's considered EAV. We design and configure our Model the exact same way, so let's configure a Model similar to one we created in the first ORM article.

+

Next, we'll create a new Model named Weblogeav. Remember, it's the Resource that's considered EAV. We design and configure our Model the exact same way, so let's configure a Model similar to one we created in the first ORM article.

-
+                    
 <global>
     <!-- ... -->
     <models>
@@ -122,11 +339,11 @@ 

Weblog, EAV Style

</global>
-

You'll notice so far there is no difference to setting up a regular Model and flat table resource Model.

+

You'll notice so far there is no difference to setting up a regular Model and flat table resource Model.

-

We'll still need to let Magento know about this resource. Similar to basic Models, EAV Resources are configured in the same <model/> node with everything else.

+

We'll still need to let Magento know about this resource. Similar to basic Models, EAV Resources are configured in the same <model/> node with everything else.

-
+                    
 <global>
     <!-- ... -->
     <models>
@@ -145,13 +362,13 @@ 

Weblog, EAV Style

</global>
-

Again, so far this is setup similar to our regular Model Resource. We provide a <class/> that configures a PHP class, as well as an <entities/> section that will let Magento know the base table for an individual Model we want to create. The <eavblogpost/> tag is the name of the specific Model we want to create, and its inner <table/> tag specifies the base table this Model will use (more on this later).

+

Again, so far this is setup similar to our regular Model Resource. We provide a <class/> that configures a PHP class, as well as an <entities/> section that will let Magento know the base table for an individual Model we want to create. The <eavblogpost/> tag is the name of the specific Model we want to create, and its inner <table/> tag specifies the base table this Model will use (more on this later).

-

Where Does That File Go?

+

Where Does That File Go?

-

Until wide adoption of PHP 5.3 and namespaces, one of the trickier (and tedious) parts of Magento will remain remembering how <classname/>s relate to file paths, and then ensuring you create the correctly named directory structure and class files. After configuring any <classname/>s or URIs, you may find it useful to attempt to instantiate an instance of the class in a controller without first creating the class files. This way PHP will throw an exception telling me it can't find a file, along with the file location. Give the following a try in your Index Controller.

+

Until wide adoption of PHP 5.3 and namespaces, one of the trickier (and tedious) parts of Magento will remain remembering how <classname/>s relate to file paths, and then ensuring you create the correctly named directory structure and class files. After configuring any <classname/>s or URIs, you may find it useful to attempt to instantiate an instance of the class in a controller without first creating the class files. This way PHP will throw an exception telling me it can't find a file, along with the file location. Give the following a try in your Index Controller.

-
+                    
 public function indexAction() {
     $weblog2 = Mage::getModel('complexworld/eavblogpost');
     $weblog2->load(1);
@@ -159,25 +376,25 @@ 

Where Does That File Go?

}
-

As predicted, a warning should be thrown

+

As predicted, a warning should be thrown

-
-Warning: include(Magentotutorial/Complexworld/Model/Eavblogpost.php) [function.include]: 
+                    
+Warning: include(Magentotutorial/Complexworld/Model/Eavblogpost.php) [function.include]:
 failed to open stream: No such file or directory  in /Users/username/Sites/magento.dev/lib/Varien/Autoload.php on line 93
 
-

In addition to telling us the path where we'll need to define the new resource class this also serves as a configuration check. If we'd been warned with the following

+

In addition to telling us the path where we'll need to define the new resource class this also serves as a configuration check. If we'd been warned with the following

-
-Warning: include(Mage/Complexworld/Model/Eavblogpost.php) [function.include]: 
+                    
+Warning: include(Mage/Complexworld/Model/Eavblogpost.php) [function.include]:
 failed to open stream: No such file or directory  in /Users/username/Sites/magento.dev/lib/Varien/Autoload.php on line 93
 
-

we'd know our Model was misconfigured, as Magento was looking for the Model in code/core/Mage instead of code/local/Magentotutorial.

+

we'd know our Model was misconfigured, as Magento was looking for the Model in code/core/Mage instead of code/local/Magentotutorial.

-

So, lets create our Model class

-

File: app/code/local/Magentotutorial/Complexworld/Model/Eavblogpost.php: -

+                    

So, lets create our Model class

+

File: app/code/local/Magentotutorial/Complexworld/Model/Eavblogpost.php: +

 class Magentotutorial_Complexworld_Model_Eavblogpost extends Mage_Core_Model_Abstract {
     protected function _construct()
     {
@@ -186,16 +403,16 @@ 

Where Does That File Go?

}
-

Remember, the Model itself is resource independent. A regular Model and an EAV Model both extend from the same class. It's the resource that makes them different.

+

Remember, the Model itself is resource independent. A regular Model and an EAV Model both extend from the same class. It's the resource that makes them different.

-

Clear your Magento cache, reload your page, and you should see a new warning.

+

Clear your Magento cache, reload your page, and you should see a new warning.

-
Warning: include(Magentotutorial/Complexworld/Model/Resource/Eavblogpost.php)
+
Warning: include(Magentotutorial/Complexworld/Model/Resource/Eavblogpost.php)
-

As expected, we need to create a class for our Model's resource. Let's do it!

+

As expected, we need to create a class for our Model's resource. Let's do it!

-

File: app/code/local/Magentotutorial/Complexworld/Model/Resource/Eavblogpost.php: -

+                    

File: app/code/local/Magentotutorial/Complexworld/Model/Resource/Eavblogpost.php: +

 class Magentotutorial_Complexworld_Model_Resource_Eavblogpost extends Mage_Eav_Model_Entity_Abstract
 {
     protected function _construct()
@@ -210,21 +427,21 @@ 

Where Does That File Go?

}
-

So, already we're seeing a few differences between a simple Model Resource and an EAV Model Resource. First off, we're extending the Mage_Eav_Model_Entity_Abstract class. While Mage_Eav_Model_Entity_Abstract uses the same _construct concept as a regular Model Resource, there's no _init method. Instead, we need to handle the init ourselves. This means telling the resource what connection-resources it should use, and passing a unique identifier into the setType method of our object.

+

So, already we're seeing a few differences between a simple Model Resource and an EAV Model Resource. First off, we're extending the Mage_Eav_Model_Entity_Abstract class. While Mage_Eav_Model_Entity_Abstract uses the same _construct concept as a regular Model Resource, there's no _init method. Instead, we need to handle the init ourselves. This means telling the resource what connection-resources it should use, and passing a unique identifier into the setType method of our object.

-

Another difference in Mage_Eav_Model_Entity_Abstract is _construct is not an abstract method, primarily for reasons of backwards compatibility with older versions of the system.

+

Another difference in Mage_Eav_Model_Entity_Abstract is _construct is not an abstract method, primarily for reasons of backwards compatibility with older versions of the system.

-

So, with that, let's clear the Magento cache and reload the page. You should see a new exception which reads

+

So, with that, let's clear the Magento cache and reload the page. You should see a new exception which reads

-
Invalid entity_type specified: complexworld_eavblogpost
+
Invalid entity_type specified: complexworld_eavblogpost
-

Magento is complaining that it can't find a entity_type named complexworld_eavblogpost. This is the value you set above

+

Magento is complaining that it can't find a entity_type named complexworld_eavblogpost. This is the value you set above

-
$this->setType('complexworld_eavblogpost');
+
$this->setType('complexworld_eavblogpost');
-

Every entity has a type. Types will, among other things, let the EAV system know which attributes a Model uses, and allow the system to link to tables that store the values for attributes. We'll need to let Magento know that we're adding a new entity type. Take a look in the MySQL table named eav_entity_type.

+

Every entity has a type. Types will, among other things, let the EAV system know which attributes a Model uses, and allow the system to link to tables that store the values for attributes. We'll need to let Magento know that we're adding a new entity type. Take a look in the MySQL table named eav_entity_type.

-
+                    
 mysql> select * from eav_entity_type;
 *************************** 1. row ***************************
           entity_type_id: 1
@@ -258,23 +475,23 @@ 

Where Does That File Go?

increment_pad_char: 0
-

This table contains a list of all the entity_types in the system. The unique identifier complexworld_eavblogpost corresponds to the entity_type_code column.

+

This table contains a list of all the entity_types in the system. The unique identifier complexworld_eavblogpost corresponds to the entity_type_code column.

-

Systems and Applications

+

Systems and Applications

-

This illustrates the single most important Magento concept, one that many people struggle to learn.

+

This illustrates the single most important Magento concept, one that many people struggle to learn.

-

Consider the computer in front of you. The OS (Mac OS X, Windows, Linux, etc.) is the software system. Your web browser (Firefox, Safari, IE, Opera) is the application. Magento is a system first, and an application second. You build eCommerce applications using the Magento system. What gets confusing is, there's a lot of places in Magento where the system code is exposed in a really raw form to the application code. The EAV system configuration living in the same database as your store's data is an example of this.

+

Consider the computer in front of you. The OS (Mac OS X, Windows, Linux, etc.) is the software system. Your web browser (Firefox, Safari, IE, Opera) is the application. Magento is a system first, and an application second. You build eCommerce applications using the Magento system. What gets confusing is, there's a lot of places in Magento where the system code is exposed in a really raw form to the application code. The EAV system configuration living in the same database as your store's data is an example of this.

-

If you're going to get deep into Magento, you need to treat it like it's an old Type 650 machine. That is to say, it's the kind of thing you can't effectively program applications in unless unless you have a deep understanding of the system itself.

+

If you're going to get deep into Magento, you need to treat it like it's an old Type 650 machine. That is to say, it's the kind of thing you can't effectively program applications in unless unless you have a deep understanding of the system itself.

-

Creating a Setup Resource

+

Creating a Setup Resource

-

So, it's theoretically possible to manually insert the rows you'll need into the Magento database to get your Model working, but it's not recommended. Fortunately, Magento provides a specialized Setup Resource that provides a number of helper method that will automatically create the needed records to get the system up and running.

+

So, it's theoretically possible to manually insert the rows you'll need into the Magento database to get your Model working, but it's not recommended. Fortunately, Magento provides a specialized Setup Resource that provides a number of helper method that will automatically create the needed records to get the system up and running.

-

So, for starters, configure the Setup Resource like you would any other.

+

So, for starters, configure the Setup Resource like you would any other.

-
+                    
 <global>
     <!-- ... -->
     <resources>
@@ -289,34 +506,34 @@ 

Creating a Setup Resource

</global>
-

Next, create its class file.

+

Next, create its class file.

-

File: app/code/local/Magentotutorial/Complexworld/Model/Resource/Setup.php: -

+                    

File: app/code/local/Magentotutorial/Complexworld/Model/Resource/Setup.php: +

 class Magentotutorial_Complexworld_Model_Resource_Setup extends Mage_Eav_Model_Entity_Setup {
 }
 
-

Take note that we're extending from Mage_Eav_Model_Entity_Setup rather than Mage_Core_Model_Resource_Setup.

+

Take note that we're extending from Mage_Eav_Model_Entity_Setup rather than Mage_Core_Model_Resource_Setup.

-

Finally, we'll set up our installer script. If you're not familiar with the naming conventions here, you'll want to review the setup resource tutorial on Setup Resources.

+

Finally, we'll set up our installer script. If you're not familiar with the naming conventions here, you'll want to review the setup resource tutorial on Setup Resources.

-

File: app/code/local/Magentotutorial/Complexworld/sql/complexworld_setup/install-0.1.0.php: -

+                    

File: app/code/local/Magentotutorial/Complexworld/sql/complexworld_setup/install-0.1.0.php: +

 <?php
 $installer = $this;
 throw new Exception("This is an exception to stop the installer from completing");
 
-

Clear your Magento Cache, reload you page, and the above exception should be thrown, meaning you've correctly configured your Setup Resource.

+

Clear your Magento Cache, reload you page, and the above exception should be thrown, meaning you've correctly configured your Setup Resource.

-

NOTE: We'll be building up our install script piece by piece. If you've read the previous tutorial, you'll know you need to remove the setup's row from the core_resource table and clear your cache to make an installer script re-run. For the remainder of this tutorial, please remember that anytime we add or remove an item from our installer and re-run it, you'll need to remove this row from the database and clear your Magento cache. Normally you would create this file and run it once, a tutorial is something of an edge case.

+

NOTE: We'll be building up our install script piece by piece. If you've read the previous tutorial, you'll know you need to remove the setup's row from the core_resource table and clear your cache to make an installer script re-run. For the remainder of this tutorial, please remember that anytime we add or remove an item from our installer and re-run it, you'll need to remove this row from the database and clear your Magento cache. Normally you would create this file and run it once, a tutorial is something of an edge case.

-

Adding the Entity Type

+

Adding the Entity Type

-

To begin, add the following to your Setup Resource installer script, and then run the script by loading any page (after removing the above exception)

+

To begin, add the following to your Setup Resource installer script, and then run the script by loading any page (after removing the above exception)

-
+                    
 $installer = $this;
 $installer->startSetup();
 $installer->addEntityType('complexworld_eavblogpost', array(
@@ -330,29 +547,29 @@ 

Adding the Entity Type

$installer->endSetup();
-

We're calling the addEntityType method on our installer object. This method allows us to pass in the entity type (complexworld_eavblogpost) along with a list of parameters to set its default values. If you've run this script, you'll notice new rows in the eav_attribute_group, eav_attribute_set, and eav_entity_type tables.

+

We're calling the addEntityType method on our installer object. This method allows us to pass in the entity type (complexworld_eavblogpost) along with a list of parameters to set its default values. If you've run this script, you'll notice new rows in the eav_attribute_group, eav_attribute_set, and eav_entity_type tables.

-

So, with that in place, if we reload our complexworld page, we'll get a new error.

+

So, with that in place, if we reload our complexworld page, we'll get a new error.

-
SQLSTATE[42S02]: Base table or view not found: 1146 Table 'magento.eavblog_posts' doesn't exist
+
SQLSTATE[42S02]: Base table or view not found: 1146 Table 'magento.eavblog_posts' doesn't exist
-

Creating the Data Tables

+

Creating the Data Tables

-

So, we've told Magento about our new entity type. Next, we need to add the MySQL tables that will be used to store all the entity values, as well as configure the system so it knows about these tables.

+

So, we've told Magento about our new entity type. Next, we need to add the MySQL tables that will be used to store all the entity values, as well as configure the system so it knows about these tables.

-

Our EAV Setup Resource has a method named createEntityTables which will automatically setup the tables we need, as well as add some configuration rows to the system. Let's add the following line to our setup resource.

+

Our EAV Setup Resource has a method named createEntityTables which will automatically setup the tables we need, as well as add some configuration rows to the system. Let's add the following line to our setup resource.

-
+                    
 $installer->createEntityTables(
     $this->getTable('complexworld/eavblogpost')
 );
 
-

The createEntityTables method accepts two parameters. The first is the base table name, the second is a list of options. We're using the Setup Resource's getTable method to pull the table name from our config. If you've been following along, you know this should resolve to the string eavblog_posts. We've omitted the second parameter which is an array of options you'll only need to used it for advanced situations that are beyond the scope of this tutorial.

+

The createEntityTables method accepts two parameters. The first is the base table name, the second is a list of options. We're using the Setup Resource's getTable method to pull the table name from our config. If you've been following along, you know this should resolve to the string eavblog_posts. We've omitted the second parameter which is an array of options you'll only need to used it for advanced situations that are beyond the scope of this tutorial.

-

After running the above script, you should have the following new tables in your database

+

After running the above script, you should have the following new tables in your database

-
+                    
 eavblog_posts
 eavblog_posts_char
 eavblog_posts_datetime
@@ -362,9 +579,9 @@ 

Creating the Data Tables

eavblog_posts_varchar
-

You'll also have an additional row in the eav_attribute_set table

+

You'll also have an additional row in the eav_attribute_set table

-
mysql> select * from eav_attribute_set order by attribute_set_id DESC LIMIT 1 \G
+                    
mysql> select * from eav_attribute_set order by attribute_set_id DESC LIMIT 1 \G
 *************************** 1. row ***************************
   attribute_set_id: 65
     entity_type_id: 37
@@ -372,23 +589,23 @@ 

Creating the Data Tables

sort_order: 6
-

So, let's go back to our page and reload.

+

So, let's go back to our page and reload.

-
http://example.com/complexworld
+
http://example.com/complexworld
-

Success! You should see no errors or warnings, and a dumped Magentotutorial_Complexworld_Model_Eavblogpost --- with no data.

+

Success! You should see no errors or warnings, and a dumped Magentotutorial_Complexworld_Model_Eavblogpost --- with no data.

-

Adding Attributes

+

Adding Attributes

-

The last step we need to take in our Setup Resource is telling Magento what attributes we want our EAV Model to have. This would be equivalent to adding new columns in a single database table setup. Again, the Setup Resource will help us. The method we're interested in is addAttribute.

+

The last step we need to take in our Setup Resource is telling Magento what attributes we want our EAV Model to have. This would be equivalent to adding new columns in a single database table setup. Again, the Setup Resource will help us. The method we're interested in is addAttribute.

-

The code from the previous section was simply telling Magento about a type of entity that we add to the system. These next bits of code are what will actually add possible attributes for our new type to the system.

+

The code from the previous section was simply telling Magento about a type of entity that we add to the system. These next bits of code are what will actually add possible attributes for our new type to the system.

-

We do that with the method addAttribute. When we call addAttribute, Magento will need to do several things to install your entities.

+

We do that with the method addAttribute. When we call addAttribute, Magento will need to do several things to install your entities.

-

To start with, we'll give our Eavblogpost a single attribute named title.

+

To start with, we'll give our Eavblogpost a single attribute named title.

-
+                    
 /* ... */
 $this->addAttribute('complexworld_eavblogpost', 'title', array(
     //the EAV attribute type, NOT a MySQL varchar
@@ -407,19 +624,19 @@ 

Adding Attributes

/* ... */
-

All right, that's a small pile of code. Let's break it apart.

+

All right, that's a small pile of code. Let's break it apart.

-

The first argument to addAttribute is the entity type code. It has to match the code specified when calling addEntityType. It tells Magento which entity we are adding the attribute to, in our example it is our complexworld_eavblogpost entity. To see other available entities that come shipped with Magento, remember you can look into the eav_entity_type table at the entity_type_code column.

+

The first argument to addAttribute is the entity type code. It has to match the code specified when calling addEntityType. It tells Magento which entity we are adding the attribute to, in our example it is our complexworld_eavblogpost entity. To see other available entities that come shipped with Magento, remember you can look into the eav_entity_type table at the entity_type_code column.

-

The second argument to addAttribute is the attribute code. It has to be unique within the given entity.

+

The second argument to addAttribute is the attribute code. It has to be unique within the given entity.

-

The third argument is where it get real interesting. This is an array of key value pairs, describing the attribute properties. For the sake of simplicity we've chose to define a single attribute, but you could go on to define as many as you'd like, by adding additional addAttribute calls to the setup script.

+

The third argument is where it get real interesting. This is an array of key value pairs, describing the attribute properties. For the sake of simplicity we've chose to define a single attribute, but you could go on to define as many as you'd like, by adding additional addAttribute calls to the setup script.

-

Array of Key Value Pairs that Define the Attribute

+

Array of Key Value Pairs that Define the Attribute

-

Finally, we have a long list of attribute properties.

+

Finally, we have a long list of attribute properties.

-
+                    
 //the EAV attribute type, NOT a MySQL varchar
 'type'              => 'varchar',
 'label'             => 'Title',
@@ -434,14 +651,14 @@ 

Array of Key Value Pairs that Define the Attribute

'unique' => false,
-

Most of these define how Magento would build a backend form element for this attribute, and probably you'll won't have to deal with the,. That said, the one important property you'll want to make note of is

-
+                    

Most of these define how Magento would build a backend form element for this attribute, and probably you'll won't have to deal with the,. That said, the one important property you'll want to make note of is

+
 'type' => 'varchar'
 
-

This defines the type of the value that the attribute will contain. You'll recall that we added table for each attribute type

+

This defines the type of the value that the attribute will contain. You'll recall that we added table for each attribute type

-
+                    
 eavblog_posts_datetime
 eavblog_posts_decimal
 eavblog_posts_int
@@ -449,11 +666,11 @@ 

Array of Key Value Pairs that Define the Attribute

eavblog_posts_varchar
-

While these do not refer to the MySQL column types, (but instead the EAV attribute types), their names (varchar, datetime, etc.) are indicative of the values they'll hold.

+

While these do not refer to the MySQL column types, (but instead the EAV attribute types), their names (varchar, datetime, etc.) are indicative of the values they'll hold.

-

All of these attribute properties are optional, if we wouldn't have specified them, Magento would have used a default value. These default values are defined in the _prepareValues method of the Mage_Eav_Model_Entity_Setup class (inherited by our setup class).

+

All of these attribute properties are optional, if we wouldn't have specified them, Magento would have used a default value. These default values are defined in the _prepareValues method of the Mage_Eav_Model_Entity_Setup class (inherited by our setup class).

-
+                    
 // Mage_Eav_Model_Entity_Setup
 protected function _prepareValues($attr)
 {
@@ -480,13 +697,13 @@ 

Array of Key Value Pairs that Define the Attribute

}
-

The second argument to the method calls to _getValue is the array key from our addAttribute argument array, and the third is the default value. So by default Magento would assume you are adding a varchar attribute with a text input.

+

The second argument to the method calls to _getValue is the array key from our addAttribute argument array, and the third is the default value. So by default Magento would assume you are adding a varchar attribute with a text input.

-

Adding the other attributes

+

Adding the other attributes

-

Lets add attributes for the blog post content and the post date. This is what the complete install script looks like.

+

Lets add attributes for the blog post content and the post date. This is what the complete install script looks like.

-
+                    
     $installer = $this;
     $installer->startSetup();
 
@@ -532,21 +749,21 @@ 

Adding the other attributes

$installer->endSetup();
-

So, now that we have everything in place, lets refresh things one last time to run our installer script. After calling addAttribute, we should have

+

So, now that we have everything in place, lets refresh things one last time to run our installer script. After calling addAttribute, we should have

-
    -
  1. A new row in eav_entity_type for the complexworld_eavblogpost entity type
  2. -
  3. A new row in eav_attribute for the title attribute
  4. -
  5. A new row in eav_attribute for the content attribute
  6. -
  7. A new row in eav_attribute for the date attribute
  8. -
  9. A new row in eav_entity_attribute
  10. -
+
    +
  1. A new row in eav_entity_type for the complexworld_eavblogpost entity type
  2. +
  3. A new row in eav_attribute for the title attribute
  4. +
  5. A new row in eav_attribute for the content attribute
  6. +
  7. A new row in eav_attribute for the date attribute
  8. +
  9. A new row in eav_entity_attribute
  10. +
-

Tying it all Together

+

Tying it all Together

-

This is clearly the lamest.blogmodel.ever, but lets try adding some rows and iterating through a collection and get the heck out of here before our heads explode. Add the following two actions to your Index Controller.

+

This is clearly the lamest.blogmodel.ever, but lets try adding some rows and iterating through a collection and get the heck out of here before our heads explode. Add the following two actions to your Index Controller.

-
+                    
 public function populateEntriesAction() {
     for ($i=0;$i<10;$i++) {
         $weblog2 = Mage::getModel('complexworld/eavblogpost');
@@ -576,13 +793,13 @@ 

Tying it all Together

}
-

Let's populate some entries! Load up the following URL

+

Let's populate some entries! Load up the following URL

-
http://magento.dev/index.php/complexworld/index/populateEntries
+
http://magento.dev/index.php/complexworld/index/populateEntries
-

If you take a look at your database, you should see 10 new rows in the eavblog_posts table.

+

If you take a look at your database, you should see 10 new rows in the eavblog_posts table.

-
+                    
 mysql> SELECT * FROM eavblog_posts ORDER BY entity_id DESC;
 +-----------+----------------+------------------+--------------+-----------+----------+---------------------+---------------------+-----------+
 | entity_id | entity_type_id | attribute_set_id | increment_id | parent_id | store_id | created_at          | updated_at          | is_active |
@@ -600,9 +817,9 @@ 

Tying it all Together

+-----------+----------------+------------------+--------------+-----------+----------+---------------------+---------------------+-----------+
-

as well as 10 new rows in the eavblog_posts_varchar table.

+

as well as 10 new rows in the eavblog_posts_varchar table.

-
+                    
 mysql> SELECT * FROM eavblog_posts_varchar ORDER BY value_id DESC;
 +----------+----------------+--------------+----------+-----------+------------------+
 | value_id | entity_type_id | attribute_id | store_id | entity_id | value            |
@@ -620,21 +837,21 @@ 

Tying it all Together

+----------+----------------+--------------+----------+-----------+------------------+
-

Notice that eavblog_posts_varchar is linked to eavblog_posts by the entity_id column.

+

Notice that eavblog_posts_varchar is linked to eavblog_posts by the entity_id column.

-

Finally, let's pull our Models back out. Load the following URL in your browser

+

Finally, let's pull our Models back out. Load the following URL in your browser

-
http://magento.dev/index.php/complexworld/index/showCollection
+
http://magento.dev/index.php/complexworld/index/showCollection
-

This should give us a

+

This should give us a

-
Warning: include(Magentotutorial/Complexworld/Model/Resource/Eavblogpost/Collection.php) [function.include]: 
+                    
Warning: include(Magentotutorial/Complexworld/Model/Resource/Eavblogpost/Collection.php) [function.include]:
   failed to open stream: No such file or directory  in /Users/username/Sites/magento.dev/lib/Varien/Autoload.php on line 93
-

So Close! We didn't make a class for our collection object! Fortunately, doing so is just as easy as with a regular Model Resource. Add the following file with the following contents

+

So Close! We didn't make a class for our collection object! Fortunately, doing so is just as easy as with a regular Model Resource. Add the following file with the following contents

-

File: Magentotutorial/Complexworld/Model/Resource/Eavblogpost/Collection.php: -

+                    

File: Magentotutorial/Complexworld/Model/Resource/Eavblogpost/Collection.php: +

 class Magentotutorial_Complexworld_Model_Resource_Eavblogpost_Collection extends Mage_Eav_Model_Entity_Collection_Abstract
 {
     protected function _construct()
@@ -644,30 +861,85 @@ 

Tying it all Together

}
-

This is just a standard Magento _construct method to initialize the Model. With this in place, reload the page, and we'll see all the titles and the content outputted. But notice, the date value is missing!

+

This is just a standard Magento _construct method to initialize the Model. With this in place, reload the page, and we'll see all the titles and the content outputted. But notice, the date value is missing!

-

Which Attributes?

+

Which Attributes?

-

Those of you with sharp eyes may have noticed something slightly different about the collection loading.

+

Those of you with sharp eyes may have noticed something slightly different about the collection loading.

-
+                    
 $entries = $weblog2->getCollection()
     ->addAttributeToSelect('title')
     ->addAttributeToSelect('content');
 
-

Because querying for EAV data can be SQL intensive, you'll need to specify which attributes it is you want your Models to fetch for you. This way the system can make only the queries it needs. If you're willing to suffer the performance consequences, you can use a wild card to grab all the attributes

- -
$entries = $weblog2->getCollection()->addAttributeToSelect('*');
- -

Jumping Off

- -

So, that should give you enough information to be dangerous, or at least enough information so you're not drowning the next time you're trying to figure out why the yellow shirts aren't showing up in your store. There's still plenty to learn about EAV; here's a few topics I would have liked to cover in greater detail, and may talk about in future articles

- -
    -
  1. EAV Attributes: Attributes aren't limited to datetime, decimal, int, text and varchar. You can create your own class files to model different attributes. This is what the attribute_model entity property is for.
  2. -
  3. Collection Filtering: Filtering on EAV collections can get tricky, especially when you're dealing with the above mentioned non-simple attributes. You need to use the addAttributeToFilter method on your collection before loading.
  4. -
  5. The Magento EAV Hierarchy: Magento has taken their basic EAV Model and built up a hierarchy that's very tied to store functionality, as well as including strategies to reduce the number of queries an EAV Model generates (the concept of a flat Model, for example)
  6. -
+

Because querying for EAV data can be SQL intensive, you'll need to specify which attributes it is you want your Models to fetch for you. This way the system can make only the queries it needs. If you're willing to suffer the performance consequences, you can use a wild card to grab all the attributes

+ +
$entries = $weblog2->getCollection()->addAttributeToSelect('*');
+ +

Jumping Off

+ +

So, that should give you enough information to be dangerous, or at least enough information so you're not drowning the next time you're trying to figure out why the yellow shirts aren't showing up in your store. There's still plenty to learn about EAV; here's a few topics I would have liked to cover in greater detail, and may talk about in future articles

+ +
    +
  1. EAV Attributes: Attributes aren't limited to datetime, decimal, int, text and varchar. You can create your own class files to model different attributes. This is what the attribute_model entity property is for.
  2. +
  3. Collection Filtering: Filtering on EAV collections can get tricky, especially when you're dealing with the above mentioned non-simple attributes. You need to use the addAttributeToFilter method on your collection before loading.
  4. +
  5. The Magento EAV Hierarchy: Magento has taken their basic EAV Model and built up a hierarchy that's very tied to store functionality, as well as including strategies to reduce the number of queries an EAV Model generates (the concept of a flat Model, for example)
  6. +
+ +

EAV Models are, without a doubt, the most complicated part of the Magento system that an ecommerce web developer will need to deal with. Remember to take deep breaths and that, at the end of the day, its just programming. Everything happens for a concrete reason, you just need to figure out why.

+
+ + +
+ +
+
+ +
+ + + + + -

EAV Models are, without a doubt, the most complicated part of the Magento system that an ecommerce web developer will need to deal with. Remember to take deep breaths and that, at the end of the day, its just programming. Everything happens for a concrete reason, you just need to figure out why.

\ No newline at end of file + + + + + \ No newline at end of file diff --git a/guides/m1x/magefordev/mage-for-dev-8.html b/guides/m1x/magefordev/mage-for-dev-8.html index 130f445977..308226dc29 100644 --- a/guides/m1x/magefordev/mage-for-dev-8.html +++ b/guides/m1x/magefordev/mage-for-dev-8.html @@ -1,73 +1,289 @@ --- --- - - + - - - - - - - Magento for Developers: Part 8—Varien Data Collections - + + + + + OpenMage for Developers: Part 8—Varien Data Collections + + + + + + + + + + + + + + + - -header -{% include m1x/eol_message.html %} - - - -

Originally, as a PHP programmer, if you wanted to collect together a group of related variables you had one choice, the venerable Array. While it shares a name with C's array of memory addresses, a PHP array is a general purpose dictionary like object combined with the behaviors of a numerically indexed mutable array.

- -

In other languages the choice isn't so simple. You have multiple data structures to chose from, each offering particular advantages in storage, speed and semantics. The PHP philosophy was to remove this choice from the client programmer and give them one useful data structure that was "good enough".

- -

All of this is galling to a certain type of software developer, and PHP 5 set out to change the status quo by offering built-in classes and interfaces that allow you to create your own data structures.

- -
+
+
+
+
+
+
+ +
+ + + +
+
+ +
+

Varien Data Collections

+
+ +

Originally, as a PHP programmer, if you wanted to collect together a group of related variables you had one choice, the venerable Array. While it shares a name with C's array of memory addresses, a PHP array is a general purpose dictionary like object combined with the behaviors of a numerically indexed mutable array.

+ +

In other languages the choice isn't so simple. You have multiple data structures to chose from, each offering particular advantages in storage, speed and semantics. The PHP philosophy was to remove this choice from the client programmer and give them one useful data structure that was "good enough".

+ +

All of this is galling to a certain type of software developer, and PHP 5 set out to change the status quo by offering built-in classes and interfaces that allow you to create your own data structures.

+ +
 $array = new ArrayObject();
 class MyCollection extends ArrayObject{...}
 $collection = new MyCollection();
 $collection[] = 'bar';
 
-

While this is still galling to a certain type of software developer, as you don't have access to low level implementation details, you do have the ability to create array-like Objects with methods that encapsulate specific functionality. You can also setup rules to offer a level of type safety by only allowing certain kinds of Objects into your Collection.

+

While this is still galling to a certain type of software developer, as you don't have access to low level implementation details, you do have the ability to create array-like Objects with methods that encapsulate specific functionality. You can also setup rules to offer a level of type safety by only allowing certain kinds of Objects into your Collection.

-

It should come as no surprise that Magento offers you a number of these Collections. In fact, every Model object that follows the Magento interfaces gets a Collection type for free. Understanding how these Collections work is a key part to being an effective Magento programmer. We're going to take a look at Magento Collections, starting from the bottom and working our way up. Set up a controller action where you can run arbitrary code, and let's get started.

+

It should come as no surprise that Magento offers you a number of these Collections. In fact, every Model object that follows the Magento interfaces gets a Collection type for free. Understanding how these Collections work is a key part to being an effective Magento programmer. We're going to take a look at Magento Collections, starting from the bottom and working our way up. Set up a controller action where you can run arbitrary code, and let's get started.

-

A Collection of Things

+

A Collection of Things

-

First, we're going to create a few new Objects.

+

First, we're going to create a few new Objects.

-
+                    
 $thing_1 = new Varien_Object();
 $thing_1->setName('Richard');
 $thing_1->setAge(24);
@@ -82,35 +298,35 @@ 

A Collection of Things

$thing_3->setAge(7);
-

The Varien_Object class defines the object all Magento Models inherit from. This is a common pattern in object oriented systems, and ensures you'll always have a way to easily add methods/functionally to every object in your system without having to edit every class file.

+

The Varien_Object class defines the object all Magento Models inherit from. This is a common pattern in object oriented systems, and ensures you'll always have a way to easily add methods/functionally to every object in your system without having to edit every class file.

-

Any Object that extends from Varien_Object has magic getter and setters that can be used to set data properties. Give this a try

+

Any Object that extends from Varien_Object has magic getter and setters that can be used to set data properties. Give this a try

-
var_dump($thing_1->getName());
+
var_dump($thing_1->getName());
-

If you don't know what the property name you're after is, you can pull out all the data as an array

+

If you don't know what the property name you're after is, you can pull out all the data as an array

-
var_dump($thing_3->getData());
+
var_dump($thing_3->getData());
-

The above will give you an array something like

+

The above will give you an array something like

-
+                    
 array
 'name' => string 'Spot' (length=4)
 'last_name' => string 'The Dog' (length=7)
 'age' => int 7
 
-

Notice the property named "last_name"? If there's an underscore separated property, you camel case it if you want to use the getter and setter magic.

+

Notice the property named "last_name"? If there's an underscore separated property, you camel case it if you want to use the getter and setter magic.

-
$thing_1->setLastName('Smith');
+
$thing_1->setLastName('Smith');
-

The ability to do these kinds of things is part of the power of PHP5, and the development style a certain class of people mean when they say "Object Oriented Programming".

+

The ability to do these kinds of things is part of the power of PHP5, and the development style a certain class of people mean when they say "Object Oriented Programming".

-

So, now that we have some Objects, let's add them to a Collection. Remember, a Collection is like an Array, but is defined by a PHP programmer.

+

So, now that we have some Objects, let's add them to a Collection. Remember, a Collection is like an Array, but is defined by a PHP programmer.

-
+                    
 $collection_of_things = new Varien_Data_Collection();
 $collection_of_things
     ->addItem($thing_1)
@@ -118,44 +334,44 @@ 

A Collection of Things

->addItem($thing_3);
-

The Varien_Data_Collection is the Collection that most Magento data Collections inherit from. Any method you can call on a Varien_Data_Collection you can call on Collections higher up the chain (We'll see more of this later)

+

The Varien_Data_Collection is the Collection that most Magento data Collections inherit from. Any method you can call on a Varien_Data_Collection you can call on Collections higher up the chain (We'll see more of this later)

-

What can we do with a Collection? For one, with can use foreach to iterate over it

+

What can we do with a Collection? For one, with can use foreach to iterate over it

-
+                    
 foreach($collection_of_things as $thing)
 {
     var_dump($thing->getData());
 }
 
-

There are also shortcuts for pulling out the first and last items

+

There are also shortcuts for pulling out the first and last items

-
+                    
 var_dump($collection_of_things->getFirstItem()->getData());
 var_dump($collection_of_things->getLastItem()->getData());
 
-

Want your Collection data as XML? There's a method for that

+

Want your Collection data as XML? There's a method for that

-
var_dump( $collection_of_things->toXml() );
+
var_dump( $collection_of_things->toXml() );
-

Only want a particular field?

+

Only want a particular field?

-
var_dump($collection_of_things->getColumnValues('name'));
+
var_dump($collection_of_things->getColumnValues('name'));
-

The team at Magento have even given us some rudimentary filtering capabilities.

+

The team at Magento have even given us some rudimentary filtering capabilities.

-
var_dump($collection_of_things->getItemsByColumnValue('name','Spot'));
+
var_dump($collection_of_things->getItemsByColumnValue('name','Spot'));
-

Neat stuff.

+

Neat stuff.

-

Model Collections

-

So, this is an interesting exercise, but why do we care?

+

Model Collections

+

So, this is an interesting exercise, but why do we care?

-

We care because all of Magento's built in data Collections inherit from this object. That means if you have, say, a product Collection you can do the same sort of things. Let's take a look

+

We care because all of Magento's built in data Collections inherit from this object. That means if you have, say, a product Collection you can do the same sort of things. Let's take a look

-
+                    
 public function testAction()
 {
     $collection_of_products = Mage::getModel('catalog/product')->getCollection();
@@ -163,13 +379,13 @@ 

Model Collections

}
-

Most Magento Model objects have a method named getCollection which will return a collection that, by default, is initialized to return every Object of that type in the system.

+

Most Magento Model objects have a method named getCollection which will return a collection that, by default, is initialized to return every Object of that type in the system.

-

A Quick Note: Magento's Data Collections contain a lot of complicated logic that handles when to use an index or cache, as well as the logic for the EAV entity system. Successive method calls to the same Collection over its life can often result in unexpected behavior. Because of that, all the of the following examples are wrapped in a single method action. I'd recommend doing the same while you're experimenting. Also, XDebug's var_dump is a godsend when working with Magento Objects and Collections, as it will (usually) intelligently short circuit showing hugely recursive Objects, but still display a useful representation of the Object structure to you.

+

A Quick Note: Magento's Data Collections contain a lot of complicated logic that handles when to use an index or cache, as well as the logic for the EAV entity system. Successive method calls to the same Collection over its life can often result in unexpected behavior. Because of that, all the of the following examples are wrapped in a single method action. I'd recommend doing the same while you're experimenting. Also, XDebug's var_dump is a godsend when working with Magento Objects and Collections, as it will (usually) intelligently short circuit showing hugely recursive Objects, but still display a useful representation of the Object structure to you.

-

The products Collection, as well as many other Magento Collections, also have the Varien_Data_Collection_Db class in their ancestor chain. This gives us a lot of useful methods. For example, if you want to see the select statement your Collection is using

+

The products Collection, as well as many other Magento Collections, also have the Varien_Data_Collection_Db class in their ancestor chain. This gives us a lot of useful methods. For example, if you want to see the select statement your Collection is using

-
+                    
 public function testAction()
 {
     $collection_of_products = Mage::getModel('catalog/product')->getCollection();
@@ -177,9 +393,9 @@ 

Model Collections

}
-

The output of the above will be

+

The output of the above will be

-
+                    
 object(Varien_Db_Select)[94]
   protected '_bind' =>
     array
@@ -188,9 +404,9 @@ 

Model Collections

...
-

Whoops! Since Magento is using the Zend database abstraction layer, your Select is also an Object. Let's see that as a more useful string.

+

Whoops! Since Magento is using the Zend database abstraction layer, your Select is also an Object. Let's see that as a more useful string.

-
+                    
 public function testAction()
 {
     $collection_of_products = Mage::getModel('catalog/product')->getCollection();
@@ -201,40 +417,40 @@ 

Model Collections

}
-

Sometimes this is going to result in a simple select

+

Sometimes this is going to result in a simple select

-
'SELECT `e`.* FROM `catalog_product_entity` AS `e`'
+
'SELECT `e`.* FROM `catalog_product_entity` AS `e`'
-

Other times, something a bit more complex

+

Other times, something a bit more complex

-
-string 'SELECT `e`.*, `price_index`.`price`, `price_index`.`final_price`, IF(`price_index`.`tier_price`, 
-LEAST(`price_index`.`min_price`, `price_index`.`tier_price`), `price_index`.`min_price`) AS `minimal_price`, 
-`price_index`.`min_price`, `price_index`.`max_price`, `price_index`.`tier_price` FROM `catalog_product_entity` 
-AS `e` INNER JOIN `catalog_product_index_price` AS `price_index` ON price_index.entity_id = e.entity_id AND 
+                    
+string 'SELECT `e`.*, `price_index`.`price`, `price_index`.`final_price`, IF(`price_index`.`tier_price`,
+LEAST(`price_index`.`min_price`, `price_index`.`tier_price`), `price_index`.`min_price`) AS `minimal_price`,
+`price_index`.`min_price`, `price_index`.`max_price`, `price_index`.`tier_price` FROM `catalog_product_entity`
+AS `e` INNER JOIN `catalog_product_index_price` AS `price_index` ON price_index.entity_id = e.entity_id AND
 price_index.website_id = '1' AND price_index.customer_group_id = 0'
 
-

The discrepancy depends on which attributes you're selecting, as well as the aforementioned indexing and cache. If you've been following along with the other articles in this series, you know that many Magento models (including the Product Model) use an EAV system. By default, a EAV Collection will not include all of an Object's attributes. You can add them all by using the addAttributeToSelect method

+

The discrepancy depends on which attributes you're selecting, as well as the aforementioned indexing and cache. If you've been following along with the other articles in this series, you know that many Magento models (including the Product Model) use an EAV system. By default, a EAV Collection will not include all of an Object's attributes. You can add them all by using the addAttributeToSelect method

-
+                    
 $collection_of_products = Mage::getModel('catalog/product')
     ->getCollection()
     ->addAttributeToSelect('*');  //the asterisk is like a SQL SELECT * FROM ...
 
-

Or, you can add just one

+

Or, you can add just one

-
+                    
 //or just one
 $collection_of_products = Mage::getModel('catalog/product')
     ->getCollection()
     ->addAttributeToSelect('meta_title');
 
-

or chain together several

+

or chain together several

-
+                    
 //or just one
 $collection_of_products = Mage::getModel('catalog/product')
     ->getCollection()
@@ -242,40 +458,40 @@ 

Model Collections

->addAttributeToSelect('price');
-

Lazy Loading

+

Lazy Loading

-

One thing that will trip up PHP developers new to Magento's ORM system is when Magento makes its database calls. When you're writing literal SQL, or even when you're using a basic ORM system, SQL calls are often made immediately when instantiating an Object.

+

One thing that will trip up PHP developers new to Magento's ORM system is when Magento makes its database calls. When you're writing literal SQL, or even when you're using a basic ORM system, SQL calls are often made immediately when instantiating an Object.

-
+                    
 $model = new Customer();
 //SQL Calls being made to Populate the Object
 echo 'Done'; //execution continues
 
-

Magento doesn't work that way. Instead, the concept of Lazy Loading is used. In simplified terms, Lazy loading means that no SQL calls are made until the client-programmer needs to access the data. That means when you do something something like this

+

Magento doesn't work that way. Instead, the concept of Lazy Loading is used. In simplified terms, Lazy loading means that no SQL calls are made until the client-programmer needs to access the data. That means when you do something something like this

-
+                    
 $collection_of_products = Mage::getModel('catalog/product')
     ->getCollection();
 
-

Magento actually hasn't gone out to the database yet. You can safely add attributes later

+

Magento actually hasn't gone out to the database yet. You can safely add attributes later

-
+                    
 $collection_of_products = Mage::getModel('catalog/product')
     ->getCollection();
 $collection_of_products->addAttributeToSelect('meta_title');
 
-

and not have to worry that Magento is making a database query each time a new attribute is added. The database query will not be made until you attempt to access an item in the Collection.

+

and not have to worry that Magento is making a database query each time a new attribute is added. The database query will not be made until you attempt to access an item in the Collection.

-

In general, try not to worry too much about the implementation details in your day to day work. It's good to know that there's s SQL backend and Magento is doing SQLy things, but when you're coding up a feature try to forget about it, and just treat the objects as block boxes that do what you need.

+

In general, try not to worry too much about the implementation details in your day to day work. It's good to know that there's s SQL backend and Magento is doing SQLy things, but when you're coding up a feature try to forget about it, and just treat the objects as block boxes that do what you need.

-

Filtering Database Collections

+

Filtering Database Collections

-

The most important method on a database Collection is addFieldToFilter. This adds your WHERE clauses to the SQL query being used behind the scenes. Consider this bit of code, run against the sample data database (substitute your own SKU is you're using a different set of product data)

+

The most important method on a database Collection is addFieldToFilter. This adds your WHERE clauses to the SQL query being used behind the scenes. Consider this bit of code, run against the sample data database (substitute your own SKU is you're using a different set of product data)

-
+                    
 public function testAction()
 {
     $collection_of_products = Mage::getModel('catalog/product')
@@ -288,21 +504,21 @@ 

Filtering Database Collections

}
-

The first parameter of addFieldToFilter is the attribute you wish to filter by. The second is the value you're looking for. Here's we're adding a sku filter for the value n2610.

+

The first parameter of addFieldToFilter is the attribute you wish to filter by. The second is the value you're looking for. Here's we're adding a sku filter for the value n2610.

-

The second parameter can also be used to specify the type of filtering you want to do. This is where things get a little complicated, and worth going into with a little more depth.

+

The second parameter can also be used to specify the type of filtering you want to do. This is where things get a little complicated, and worth going into with a little more depth.

-

So by default, the following

+

So by default, the following

-
$collection_of_products->addFieldToFilter('sku','n2610');
+
$collection_of_products->addFieldToFilter('sku','n2610');
-

is (essentially) equivalent to

+

is (essentially) equivalent to

-
WHERE sku = "n2610"
+
WHERE sku = "n2610"
-

Take a look for yourself. Running the following

+

Take a look for yourself. Running the following

-
+                    
 public function testAction()
 {
     var_dump(
@@ -314,13 +530,13 @@ 

Filtering Database Collections

}
-

will yield

+

will yield

-
SELECT `e`.* FROM `catalog_product_entity` AS `e` WHERE (e.sku = 'n2610')'
+
SELECT `e`.* FROM `catalog_product_entity` AS `e` WHERE (e.sku = 'n2610')'
-

Keep in mind, this can get complicated fast if you're using an EAV attribute. Add an attribute

+

Keep in mind, this can get complicated fast if you're using an EAV attribute. Add an attribute

-
+                    
 var_dump(
     (string)
     Mage::getModel('catalog/product')
@@ -331,9 +547,9 @@ 

Filtering Database Collections

);
-

and the query gets gnarly.

+

and the query gets gnarly.

-
+                    
 SELECT `e`.*, IF(_table_meta_title.value_id>0, _table_meta_title.value, _table_meta_title_default.value) AS `meta_title`
 FROM `catalog_product_entity` AS `e`
 INNER JOIN `catalog_product_entity_varchar` AS `_table_meta_title_default`
@@ -345,15 +561,15 @@ 

Filtering Database Collections

WHERE (IF(_table_meta_title.value_id>0, _table_meta_title.value, _table_meta_title_default.value) = 'my title')
-

Not to belabor the point, but try not to think too much about the SQL if you're on deadline.

+

Not to belabor the point, but try not to think too much about the SQL if you're on deadline.

-

Other Comparison Operators

+

Other Comparison Operators

-

I'm sure you're wondering "what if I want something other than an equals by query"? Not equal, greater than, less than, etc. The addFieldToFilter method's second parameter has you covered there as well. It supports an alternate syntax where, instead of passing in a string, you pass in a single element Array.

+

I'm sure you're wondering "what if I want something other than an equals by query"? Not equal, greater than, less than, etc. The addFieldToFilter method's second parameter has you covered there as well. It supports an alternate syntax where, instead of passing in a string, you pass in a single element Array.

-

The key of this array is the type of comparison you want to make. The value associated with that key is the value you want to filter by. Let's redo the above filter, but with this explicit syntax

+

The key of this array is the type of comparison you want to make. The value associated with that key is the value you want to filter by. Let's redo the above filter, but with this explicit syntax

-
public function testAction()
+                    
public function testAction()
 {
     var_dump(
         (string)
@@ -365,17 +581,17 @@ 

Other Comparison Operators

}
-

Calling out our filter

+

Calling out our filter

-
addFieldToFilter('sku',array('eq'=>'n2610'))
+
addFieldToFilter('sku',array('eq'=>'n2610'))
-

As you can see, the second parameter is a PHP Array. Its key is eq, which stands for equals. The value for this key is n2610, which is the value we're filtering on.

+

As you can see, the second parameter is a PHP Array. Its key is eq, which stands for equals. The value for this key is n2610, which is the value we're filtering on.

-

Magento has a number of these english language like filters that will bring a tear of remembrance (and perhaps pain) to any old perl developers in the audience.

+

Magento has a number of these english language like filters that will bring a tear of remembrance (and perhaps pain) to any old perl developers in the audience.

-

Listed below are all the filters, along with an example of their SQL equivalents.

+

Listed below are all the filters, along with an example of their SQL equivalents.

-
+                    
 array("eq"=>'n2610')
 WHERE (e.sku = 'n2610')
 
@@ -425,30 +641,30 @@ 

Other Comparison Operators

WHERE e.sku >= '10' and e.sku <= '20'
-

Most of these are self explanatory, but a few deserve a special callout

+

Most of these are self explanatory, but a few deserve a special callout

-

in, nin, find_in_set

+

in, nin, find_in_set

-

The in, nin and finset conditionals allow you to pass in an Array of values. That is, the value portion of your filter array is itself allowed to be an array.

+

The in, nin and finset conditionals allow you to pass in an Array of values. That is, the value portion of your filter array is itself allowed to be an array.

-
+                    
 array("in"=>array('n2610','ABC123')
 WHERE (e.sku in ('n2610','ABC123'))
 
-

notnull, null

+

notnull, null

-

The keyword NULL is special in most flavors of SQL. It typically won't play nice with the standard equality (=) operator. Specifying notnull or null as your filter type will get you the correct syntax for a NULL comparison while ignoring whatever value you pass in

+

The keyword NULL is special in most flavors of SQL. It typically won't play nice with the standard equality (=) operator. Specifying notnull or null as your filter type will get you the correct syntax for a NULL comparison while ignoring whatever value you pass in

-
+                    
 array("notnull"=>true)
 WHERE (e.sku is NOT NULL)
 
-

from - to filter

-

This is another special format that breaks the standard rule. Instead of a single element array, you specify a two element array. One element has the key from, the other element has the key to. As the keys indicated, this filter allows you to construct a from/to range without having to worry about greater than and less than symbols

+

from - to filter

+

This is another special format that breaks the standard rule. Instead of a single element array, you specify a two element array. One element has the key from, the other element has the key to. As the keys indicated, this filter allows you to construct a from/to range without having to worry about greater than and less than symbols

-
+                    
 public function testAction
 {
     var_dump(
@@ -461,14 +677,14 @@ 

from - to filter

}
-

The above yields

-
WHERE (_table_price.value >= '10' and _table_price.value <= '20')'
+

The above yields

+
WHERE (_table_price.value >= '10' and _table_price.value <= '20')'
-

AND or OR, or is that OR and AND?

+

AND or OR, or is that OR and AND?

-

Finally, we come to the boolean operators. It's the rare moment where we're only filtering by one attribute. Fortunately, Magento's Collections have us covered. You can chain together multiple calls to addFieldToFilter to get a number of "AND" queries.

+

Finally, we come to the boolean operators. It's the rare moment where we're only filtering by one attribute. Fortunately, Magento's Collections have us covered. You can chain together multiple calls to addFieldToFilter to get a number of "AND" queries.

-
+                    
 function testAction()
 {
     echo
@@ -481,15 +697,15 @@ 

AND or OR, or is that OR and AND?

}
-

By chaining together multiple calls as above, we'll produce a where clause that looks something like the following

+

By chaining together multiple calls as above, we'll produce a where clause that looks something like the following

-
WHERE (e.sku like 'a%') AND (e.sku like 'b%')
+
WHERE (e.sku like 'a%') AND (e.sku like 'b%')
-

To those of you that just raised your hand, yes, the above example would always return 0 records. No sku can begin with BOTH an a and a b. What we probably want here is an OR query. This brings us to another confusing aspect of addFieldToFilter's second parameter.

+

To those of you that just raised your hand, yes, the above example would always return 0 records. No sku can begin with BOTH an a and a b. What we probably want here is an OR query. This brings us to another confusing aspect of addFieldToFilter's second parameter.

-

If you want to build an OR query, you need to pass an Array of filter Arrays in as the second parameter. I find it's best to assign your individual filter Arrays to variables

+

If you want to build an OR query, you need to pass an Array of filter Arrays in as the second parameter. I find it's best to assign your individual filter Arrays to variables

-
+                    
 public function testAction()
 {
     $filter_a = array('like'=>'a%');
@@ -497,8 +713,8 @@ 

AND or OR, or is that OR and AND?

}
-

and then assign an array of all my filter variables

-
+                    

and then assign an array of all my filter variables

+
 public function testAction()
 {
     $filter_a = array('like'=>'a%');
@@ -512,13 +728,69 @@ 

AND or OR, or is that OR and AND?

}
-

In the interest of being explicit, here's the aforementioned array of filter arrays.

-
array($filter_a, $filter_b)
- -

This will gives us a WHERE clause that looks something like the following

- -
WHERE (((e.sku like 'a%') or (e.sku like 'b%'))) 
- -

Wrap Up

+

In the interest of being explicit, here's the aforementioned array of filter arrays.

+
array($filter_a, $filter_b)
+ +

This will gives us a WHERE clause that looks something like the following

+ +
WHERE (((e.sku like 'a%') or (e.sku like 'b%'))) 
+ +

Wrap Up

+ +

You're now a Magento developer walking around with some serious firepower. Without having to write a single line of SQL you now know how to query Magento for any Model your store or application might need.

+ +
+ + +
+ +
+
+ +
+ + + + + -

You're now a Magento developer walking around with some serious firepower. Without having to write a single line of SQL you now know how to query Magento for any Model your store or application might need.

+ + + + + \ No newline at end of file diff --git a/guides/m1x/magefordev/mage-for-frontend-dev-1.html b/guides/m1x/magefordev/mage-for-frontend-dev-1.html new file mode 100644 index 0000000000..afbea55dff --- /dev/null +++ b/guides/m1x/magefordev/mage-for-frontend-dev-1.html @@ -0,0 +1,421 @@ +--- +--- + + + + + + + + Frontend Developer Guide + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+

Introduction

+
+ +

Magento Enterprise Edition 1.14 and Magento Community Edition 1.9 come with a new theme named rwd that + implements Responsive Web Design + (RWD) practices. This guide discusses what you need to know when customizing your own responsive theme. +

+

Magento CE's and EE's responsive theme uses a number of new technologies:

+
    +
  • Sass/Compass: A CSS pre-compiler that provides + organizable, reusable CSS. +
  • +
  • jQuery: Used for all custom JavaScript in the responsive + theme. jQuery operates in noConflict() mode so it doesn't conflict with Magento's existing + JavaScript library. +
  • +
  • Additional JavaScript libraries discussed in more detail in + + Working With JavaScript + . +
  • +
+

If you're not familiar with Sass or Compass, you should familiarize yourself with them before continuing with + this guide.

+ +

The new responsive theme code is contained in the following directories (relative to your Magento installation + directory):

+
    +
  • app/design/frontend/rwd
  • +
  • skin/frontend/rwd
  • +
+

See one of the following sections for more information:

+ +

Intended Audience

+

This guide assumes that you have a good understanding of Magento frontend development and Responsive Web Design + techniques.

+ +

Documentation Conventions and Assumptions

+

This section discusses some basic information that applies to this guide.

+ +

Documentation Conventions

+

This guide uses the names custompackage and customtheme for the design package and theme. When + you build your own responsive theme, use a more descriptive name, such as the name of your company.

+

The variable value [your Magento install dir] refers to the directory in which you installed Magento CE + or EE. Typical values follow:

+
    +
  • Ubuntu: /var/www/magento
  • +
  • CentOS: /var/www/html/magento
  • +
+ +

Assumption: How You Customize CSS

+

You have two choices to write your custom CSS styles:

+
    +
  • Write CSS in the Sass stylesheets using the same structure that the Magento responsive theme uses. This + approach requires setting up a Compass compiler, but is strongly recommended. This guide assumes + you'll use this approach. +
  • +
  • Directly modify the CSS files that ship with the responsive theme and ignore the Sass files. While this + approach might be simpler in the short run, the CSS files in the responsive theme were output by the Sass + compiler and are therefore not conducive to being customized directly. This approach is not + recommended. +
  • +
+ +

Exceptions to Responsive Web Design

+

Responsive design was not implemented for the following:

+
    +
  • Multiple address checkout
  • +
  • Popular search terms page
  • +
  • Popular search terms cloud
  • +
  • Site map
  • +
  • My Account—Billing Agreements
  • +
  • My Account—Recurring Profiles
  • +
  • E-mail templates
  • +
  • Tags
  • +
  • Polls
  • +
  • Captcha
  • +
  • The gift registry (EE only)
  • +
  • Multiple wishlists (EE only)
  • +
  • Place order by SKU (EE only)
  • +
+
noteNote: Magento still supports other non-responsive themes as well. You don't need to use this guide to customize those themes. For more information, see the Designer's Guide to Magento. + +
+
+ + +
+ +
+
+ +
+ + + + + + + + + + + diff --git a/guides/m1x/magefordev/mage-for-frontend-dev-10.html b/guides/m1x/magefordev/mage-for-frontend-dev-10.html new file mode 100644 index 0000000000..85e6d614c7 --- /dev/null +++ b/guides/m1x/magefordev/mage-for-frontend-dev-10.html @@ -0,0 +1,335 @@ +--- +--- + + + + + + + + Frontend Developer Guide + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+

Getting Help With Your Theme

+
+ +

If you encounter issues building a custom responsive theme, you can post your question on the community magento.stackexchange.com site and + tag it with rwd-theme.

+

To browse the list of questions, see http://magento.stackexchange.com/questions/tagged/rwd-theme. +

+
+ + +
+ +
+
+ +
+ + + + + + + + + + + diff --git a/guides/m1x/magefordev/mage-for-frontend-dev-2.html b/guides/m1x/magefordev/mage-for-frontend-dev-2.html new file mode 100644 index 0000000000..ab3583014a --- /dev/null +++ b/guides/m1x/magefordev/mage-for-frontend-dev-2.html @@ -0,0 +1,350 @@ +--- +--- + + + + + + + + Frontend Developer Guide + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+

Supported Devices and Browsers

+
+ +

Magento's responsive theme was developed with full support for the following devices and browsers:

+
    +
  • Windows: +
      +
    • Internet Explorer 9 and later
    • +
    • Latest stable version of Chrome
    • +
    • Latest stable version of Firefox
    • +
    +
  • +
  • OS X: +
      +
    • Latest stable version of Chrome
    • +
    • Latest stable version of Firefox
    • +
    • Latest stable version of Safari
    • +
    +
  • +
  • iOs (iPhone and iPad): Safari on iOs 7
  • +
  • Android: Chrome browser on Android 4.0+ (also known as Ice Cream Sandwich)
  • +
+

Basic compatibility should be expected on Android 2.3+ (and other mobile devices) and Internet Explorer 8, but + full support is not guaranteed.

+
+ + +
+ +
+
+ +
+ + + + + + + + + + + diff --git a/guides/m1x/magefordev/mage-for-frontend-dev-3.html b/guides/m1x/magefordev/mage-for-frontend-dev-3.html new file mode 100644 index 0000000000..177b637e04 --- /dev/null +++ b/guides/m1x/magefordev/mage-for-frontend-dev-3.html @@ -0,0 +1,380 @@ +--- +--- + + + + + + + + Frontend Developer Guide + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+

Understanding the Changes in the Responsive Theme

+
+ +

The new responsive theme in EE 1.14 and CE 1.9 is based on the existing base/default theme. The new + responsive theme has about 130 template files that have been modified to better handle the needs of responsive + development. Following are some of the changes that have been made in these template files:

+
    +
  • Rearranged elements to work better with smaller viewports.
  • +
  • Changed the Doctype to html to enable use of HTML5 elements. (To avoid large amounts of + template modifications, HTML5 tags were implemented only when necessary.) +
  • +
  • Refactored attributes of <input> elements to HTML5 standards.
  • +
  • Refactored product lists to show varying number of products per column.
  • +
  • Removed all <fieldset> elements because they don't work well in a responsive context.
  • +
+ +

Changes to Layout Files

+

A number of the core layout files (catalog.xml, customer.xml) were slightly modified to account + for responsive changes. There are only a few notable changes to the layout files:

+
    +
  • + + [your Magento install dir] + + /app/design/frontend/rwd/default/layout/page.xml + was modified to include all of the new responsive assets. +
  • +
  • Because two and three column layouts collapse to a single column on smaller viewports, all content in the + left block are displayed under the main content. This is a problem because blocks—like the + navigation on all My Account pages—must display above the main content on smaller viewports. To + address this, a new left_first block was added that looks like the left column on larger viewports, + but on smaller viewports, it displays above the main content. +
  • +
+ +

Using the New Theme/Layout Structure—Magento EE only

+

If you've worked with a previous version of Magento EE, you're aware that the enterprise/default theme + contains copies of many of the layout and template files from base/default—some with + very minor changes. This is not an optimal approach because as it results in duplicated content.

+

The Magento EE responsive theme's rwd/enterprise directory includes only template and layout files that + contain Magento EE-only features.

+

Instead of copying layout files as the enterprise/default theme does, rwd/enterprise isolates + changes in their own layout files in [your Magento install + dir]app/design/frontend/rwd/enterprise/layout/enterprise

+

The rwd/enterprise theme replaces the enterprise/default theme and contains far fewer + files than its predecessor (156 compared to 280). This new approach makes it easier for you to see what + functionality is unique to Magento EE.

+ +

Changes to Magento Theme Fallback

+

Magento CE 1.9 and EE 1.14 both introduce a theme configuration file named theme.xml. This file allows + theme developers to specify a parent theme which results in a more predictable and useful fallback than earlier + versions.

+

For more information, see Alan Storm's + Parent/Child Theme article.

+
+ + +
+ +
+
+ +
+ + + + + + + + + + + diff --git a/guides/m1x/magefordev/mage-for-frontend-dev-4.html b/guides/m1x/magefordev/mage-for-frontend-dev-4.html new file mode 100644 index 0000000000..7ff58c530c --- /dev/null +++ b/guides/m1x/magefordev/mage-for-frontend-dev-4.html @@ -0,0 +1,348 @@ +--- +--- + + + + + + + + Frontend Developer Guide + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+

Installing Compass and Sass

+
+ +

Sass is a powerful CSS pre-compiler that extends CSS supports variables, mixins, and placeholders. Compass is a + library for Sass that adds hundreds of useful mixins and functions to Sass—everything from color blending + to sprite generation.

+

Because the new Magento responsive theme uses the Compass library to compile its Sass files into CSS, you must + install Compass in your development environment in any of the following ways:

+
    +
  • Command line
  • +
  • Using any of the following third-party applications: + +
  • +
  • Compile using your code editor (PhpStorm, Sublime, and so on): Many code editors run Compass from the editor + so that you don't have to use the command line to re-compile when you make changes. However, you must + install Compass using the command line. +
  • +
+
+ + +
+ +
+
+ +
+ + + + + + + + + + + diff --git a/guides/m1x/magefordev/mage-for-frontend-dev-5.html b/guides/m1x/magefordev/mage-for-frontend-dev-5.html new file mode 100644 index 0000000000..51c79a80d6 --- /dev/null +++ b/guides/m1x/magefordev/mage-for-frontend-dev-5.html @@ -0,0 +1,351 @@ +--- +--- + + + + + + + + Frontend Developer Guide + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+

Disabling the Magento Cache

+
+ +

A good practice during development on Magento is to disable Magento's cache. Although doing this adversely + impacts performance, it frees you from having to manually clear the cache when you make changes to files or + configuration settings that are being cached.

+

You should always enable the cache in a production environment.

+

To disable the cache:

+
    +
  1. Log in to the Magento Admin Panel as an administrator.
  2. +
  3. Click System > Configuration > Cache Management.
  4. +
  5. On the Cache Management page, click Select All.
    + Note: The Page Cache option at the bottom of the list is available only in + Magento EE. +
  6. +
  7. From the Actions list in the upper right corner of the page, click Disable. +
  8. +
  9. Click Submit.
    + The following figure shows an example.
    +
  10. +
  11. Click Submit.
    +

    The following figures shows all cache types disabled.
    + +

  12. +
+
+ + +
+ +
+
+ +
+ + + + + + + + + + + diff --git a/guides/m1x/magefordev/mage-for-frontend-dev-6.html b/guides/m1x/magefordev/mage-for-frontend-dev-6.html new file mode 100644 index 0000000000..deac573fc8 --- /dev/null +++ b/guides/m1x/magefordev/mage-for-frontend-dev-6.html @@ -0,0 +1,569 @@ +--- +--- + + + + + + + + Frontend Developer Guide + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+

Copying Files to Create Your Custom Theme

+
+ +

This section discusses how to start building a custom responsive theme.

+ +

Step 1: Creating a Directory Structure for Your Responsive Theme

+

You'll do your responsive theme development using a copy of the responsive theme provided with Magento CE and EE. + This section discusses how to create new top-level directories for your theme and to copy files to those + directories.

+

The rwd/default theme falls back directly to the base/default theme, so themes in the default + package will never be included as a part of the fallback chain.

+
noteNote: Magento EE only. The enterprise/default theme is completely replaced by rwd/enterprise and therefore, enterprise/default is not included as a part of the fallback chain. + +
+ + +

The top-level directories you'll need are:

+
    +
  • [your Magento install dir]/app/design/frontend/custompackage/customtheme
  • +
  • [your Magento install dir]/skin/frontend/custompackage/customtheme
  • +
+

To create those directories and to add your theme configuration:

+
    +
  1. Using a command shell, log in to your Magento host as a user with privileges to write to the web server + docroot. (Typically, this is the web server user.) +
  2. +
  3. Enter the following commands in the order shown: +
    cd [your Magento install dir]
    +mkdir -p app/design/frontend/custompackage/customtheme
    +mkdir -p skin/frontend/custompackage/customtheme
    +
  4. +
  5. Create another directory for your theme configuration and change to that directory: +
    mkdir app/design/frontend/custompackage/customtheme/etc
    +cd app/design/frontend/custompackage/customtheme/etc
    +
  6. +
  7. Use vim or another text editor to create your theme configuration file named theme.xml + there: +
    vim theme.xml
    +
  8. +
  9. theme.xml must have different contents for Magento CE or Magento EE: +
      +
    • Magento CE: +
      +                        
      +<?xml version="1.0"?>
      +<theme>
      +    <parent>rwd/default</parent>
      +</theme>
      +                        
      +                    
      +
    • +
    • Magento EE: +
      +                        
      +<?xml version="1.0"?>
      +<theme>
      +    <parent>rwd/enterprise</parent>
      +</theme>
      +                        
      +                    
      +
    • +
      importantImportant: It's very important you have a theme.xml in the app/design/frontend/custompackage/customtheme/etc directory with exactly the contents shown. Failure to configure theme.xml correctly prevents Magento from loading your theme. +
      + +
    +
  10. +
+ +

Step 2: Copying Files For Your Theme

+

Use the following commands to copy files for your custom theme:

+
cd [your Magento install dir]
+cp -R skin/frontend/rwd/default/images skin/frontend/custompackage/customtheme
+cp -R skin/frontend/rwd/default/scss skin/frontend/custompackage/customtheme
+                        
+

Copying the images directory is important because the CSS uses relative image paths. To avoid + duplicating Sass files, review the information discussed in + + Sass Fallback Structure + .

+

The following figure shows how your custom theme directory structure looks after copying these files and + directories.

+ + +

Step 3: Configuring a Compass Watcher

+

You can configure Compass to watch your skin directories for changes to CSS and JavaScript and to compile them + when something changes.

+

You can do this in either of the following ways:

+ +

For more information, see Compass + Command Line Documentation.

+ +

Success Messages

+

Messages similar to the following display to indicate the command completed successfully:

+
>>> Change detected at 09:57:12 to: madisonisland-ie8.scss
+   create ./../css/madisonisland-ie8.css
+   create ./../css/styles.css
+   create ./../css/madisonisland.css
+   create ./../css/styles-ie8.css
+   create ./../css/scaffold-forms.css
+>>> Compass is polling for changes. Press Ctrl-C to Stop.
+
+ +

Failure Message

+

If the following message displays, it likely indicates that config.rb is not in the correct location: +

+
Nothing to compile. If you're trying to start a new project, you have left off the directory argument.
+

Enter the following commands to verify you have config.rb in the correct location:

+
cd [your Magento install dir]
+ls skin/frontend/custompackage/customtheme/scss
+

If there is no config.rb in that location, enter the following command:

+
cp skin/frontend/rwd/default/scss/config.rb skin/frontend/custompackage/customtheme/scss
+

Compass requires a config.rb file located in the directory from which you run this command. The sample + provided with Magento CE and EE follows:

+

+# note: this should never truly be referenced since we are using relative assets
+http_path = "/skin/frontend/rwd/default/"
+css_dir = "../css"
+sass_dir = "../scss"
+images_dir = "../images"
+javascripts_dir = "../js"
+relative_assets = true
+
+output_style = :expanded
+environment = :production
+        
+

After correcting the issue, try the compass watch commands discussed in Step 3: + Configuring a Compass Watcher.

+ +

Step 4: Configuring Magento to Load Your Theme

+

This section discusses how to configure Magento to load your new theme.

+
    +
  1. Log in to the Magento Admin Panel as an administrator.
  2. +
  3. Click System > Configuration > GENERAL > Design.
  4. +
  5. In the right pane, click Package to expand it.
  6. +
  7. In the Current Package Name field, enter custompackage
  8. +
  9. In the right pane, expand Themes.
  10. +
  11. In the Default field, enter customtheme
    + The following figure shows an example.
    + +
  12. +
  13. In the top right corner of the page, click Save Config.
    + If an error displays when you attempt to save the configuration, see Troubleshooting + Theme Configuration. +
  14. +
  15. If prompted, flush the cache: +
      +
    1. Click System > Configuration > Cache Management. +
    2. +
    3. At the top of the page, click Flush Magento Cache.
    4. +
    +
  16. +
+ +

Step 5: Verifying Your Custom Theme

+

To verify that Magento loads your theme:

+
    +
  1. Navigate to your web store in a web browser.
  2. +
  3. View the web page source.
  4. +
  5. If you see a reference to /skin/frontend/custompackage/customtheme/css/styles.css, you are ready to + begin building your new theme. +
  6. +
+
noteNote: If your theme fails to load properly, see the next section. + +
+ + +

Troubleshooting Theme Configuration

+

See one of the following sections for troubleshooting assistance:

+ + +

General Troubleshooting

+

The following sections assist you in determining the cause of errors you might experience after setting up your + custom theme.

+ +
No formatting on the web store
+

Description: Most likely an issue with theme.xml. You can confirm this by viewing the page + source and searching for styles.css. If styles.css is loading from a path similar to the + following, theme.xml is most likely not loading.

+
http://www.example.com/magento/skin/frontend/base/default/css/styles.css
+

Solution: Enter the following command:

+
cat [your Magento install dir]/app/design/frontend/custompackage/customtheme/etc/theme.xml
+

If the error No such file or directory displays, check your directory paths and create + theme.xml as discussed in Step 2: Copying Files For Your Theme.

+

If you have a theme.xml, check to make sure it's configured exactly as shown here.

+ +
Error saving the custom package and theme in the Admin Panel
+

Symptom: The following error displays:

+
An error occurred while saving this configuration: package with this name does not exist and cannot be set.
+

Description: The package and theme names you entered in the Admin Panel must be directories in a + specific location on the file system. These directories don't exist. (See the figure in Directory and File Reference.) Check the path and spelling.

+

Solution: Repeat the tasks discussed in Step 1: Creating a Directory + Structure for Your Responsive Theme.

+ +
Wrong formatting in the web store
+

Symptom: Examples:

+
    +
  • No images display
  • +
  • Only a list of categories displays on the left side of the page, with images after the text
  • +
+

Description: There is a different theme.xml for CE and EE. You might have used an EE theme.xml + with CE or vice versa.

+

You can verify this using a web browser inspector:

+
    +
  • styles.css loads from the correct directory
  • +
  • Some HTTP 403 (Forbidden) errors loading images
  • +
  • Some HTTP 404 (Not Found) errors loading images from skin/frontend/base/default/images/media (which + doesn't exist) +
  • +
+

Solution: Review the information discussed in Step 2: Copying Files For Your + Theme.

+

Make sure theme.xml for CE or EE is configured exactly as shown here. +

+ +

Directory and File Reference

+

The following figure shows the directory structure in a properly configured system, as well as the location of + theme.xml, the custom theme configuration file.

+ +
+ + +
+ +
+
+ +
+ + + + + + + + + + + diff --git a/guides/m1x/magefordev/mage-for-frontend-dev-7.html b/guides/m1x/magefordev/mage-for-frontend-dev-7.html new file mode 100644 index 0000000000..3f50c436c8 --- /dev/null +++ b/guides/m1x/magefordev/mage-for-frontend-dev-7.html @@ -0,0 +1,646 @@ +--- +--- + + + + + + + + Frontend Developer Guide + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+

Responsive Theme Reference

+
+ +

This section discusses information you need to customize the responsive theme you started in + Copying + Files to Create Your Custom Theme.

+ +

Basic Recommendations for Development

+

We recommend you do the following things when you start developing your responsive theme:

+
    +
  • Edit [your Magento install dir]/skin/frontend/custompackage/customtheme/scss/config.rb to change + environment = :production to environment = :development.
    + For more information about this setting, see Compass + Configuration Reference. +
  • +
  • Disable the Magento cache as discussed in + Disabling Magento's Cache.
  • +
+ +

Understanding the Sass Structure

+

In previous versions of Magento, all CSS styles were grouped into a massive styles.css file that was + cumbersome and difficult to navigate. The CSS in the responsive theme is organized into individual files + referred to as Sass partials.

+

When Compass compiles the Sass partials, the output is a set of CSS files that are loaded by the browser: styles-ie8.css + is for Internet Explorer 8 and earlier, and styles.css is for all other browsers.

+

Following is the directory structure of the skin/frontend/custompackage/customtheme directory.

+
noteNote: This structure does not include the sample data assets that you will remove later in this guide.
+
+├── css
+│   ├── scaffold-forms.css
+│   ├── styles-ie8.css
+│   └── styles.css
+├── images
+│   ├── ajax-loader.gif
+│   ├── bg_x.png
+│   ├── demo-logo.png
+│   ├── demo_logo.png
+│   ├── icon_sprite.png
+│   ├── icon_sprite@2x.png
+│   ├── logo.png
+│   ├── logo_small.png
+│   ├── opc-ajax-loader.gif
+│   ├── social_icons.png
+│   └── social_icons@2x.png
+└── scss
+    ├── _core.scss
+    ├── _framework.scss
+    ├── _var.scss
+    ├── config.rb
+    ├── content
+    │   ├── _category.scss
+    │   ├── _home.scss
+    ├── core
+    │   ├── _common.scss
+    │   ├── _form.scss
+    │   ├── _reset.scss
+    │   └── _table.scss
+    ├── function
+    │   ├── _black.scss
+    │   └── _white.scss
+    ├── layout
+    │   ├── _footer.scss
+    │   ├── _global.scss
+    │   ├── _header-account.scss
+    │   ├── _header-cart.scss
+    │   ├── _header-nav.scss
+    │   ├── _header-search.scss
+    │   └── _header.scss
+    ├── mixin
+    │   ├── _breakpoint.scss
+    │   ├── _clearfix.scss
+    │   ├── _if-resolution.scss
+    │   ├── _image-replacement.scss
+    │   ├── _loading-overlay.scss
+    │   ├── _menu.scss
+    │   ├── _not-selectable.scss
+    │   ├── _toggle-content.scss
+    │   ├── _triangle.scss
+    │   └── _typography.scss
+    ├── module
+    │   ├── _account-reviews.scss
+    │   ├── _catalog-compare.scss
+    │   ├── _catalog-msrp.scss
+    │   ├── _catalog-product.scss
+    │   ├── _checkout-cart-minicart.scss
+    │   ├── _checkout-cart.scss
+    │   ├── _checkout-onepage.scss
+    │   ├── _checkout-success.scss
+    │   ├── _cms.scss
+    │   ├── _configurableswatches.scss
+    │   ├── _contacts.scss
+    │   ├── _cookies.scss
+    │   ├── _customer.scss
+    │   ├── _paypal.scss
+    │   ├── _pricing_conditions.scss
+    │   ├── _product-list.scss
+    │   ├── _review.scss
+    │   ├── _search.scss
+    │   ├── _slideshow.scss
+    │   └── _wishlist.scss
+    ├── override
+    │   └── _plugin.scss
+    ├── scaffold-forms.scss
+    ├── styles-ie8.scss
+    ├── styles.scss
+    └── vendor
+        └── _normalize.scss
+
+ +

This is an explanation of the files in the preceding list:

+
    +
  • config.rb: Configuration values that tell Compass to look in the scss directory for Sass + files and to export the CSS to the css directory. It also includes information about how Compass + should format the compiled CSS. +
  • +
  • scss/styles.scss: Wrapper that imports the framework.scss and core.scss files. +
  • +
  • scss/styles-ie8.scss: Identical to styles.css file, except it does not include any media + queries because Internet Explorer 8 and earlier don't support media queries. For more information, see Using the bp() mixin for older Internet Explorer Browser Versions. +
  • + +
  • scss/_framework.scss: Imports all of the libraries, variables, functions, and mixins used by the + other stylesheets. The files imported by framework.scss do not output any CSS on their own. Because + of that, this file can be imported by other Sass files that are intended to compile directly to CSS + files.
    + For example, in Magento EE, the skin/frontend/rwd/enterprise/scss/enterprise.scss file imports skin/frontend/rwd/default/scss/_framework.scss + so the Magento EE partials can use the common variables, mixins, and so on imported by + framework.scss. +
      +
    • scss/_var.scss: Contains variables like breakpoints, spacing, colors, and font styles that + are used by the other Sass partials. You should expect to modify this file heavily as you customize + your responsive theme. +
    • +
    • scss/mixins/: This directory contains all of the mixins used by the responsive theme. You + should familiarize yourself with these mixins because they are useful for writing your custom + styles. +
    • +
    • scss/function/: This directory contains functions that the responsive theme uses. Functions + are mixins that return simple values. +
    • +
    +
  • +
  • scss/_core.scss: Imports all of the Sass partials that output CSS content: +
      +
    • scss/core/: Partials that contain all of the global styles. Expect to heavily customize the + partials in this directory. +
    • +
    • scss/layout/: Partials that define the structural layout of the responsive theme (header, + content, and footer). The scss/core/_common.scss partial also contains some layout styles—specifically + the one, two, or three column layouts. +
    • +
    • scss/module/: Partials that contain styles grouped by module. While the correlation between + these module files and Magento core modules is close, it is not a 1:1 relationship. For example, the + _product-list.scss file contains styles for both the Mage_Catalog and Mage_CatalogSearch + modules. +
    • +
    • scss/vendor/: This directory is intended for third-party CSS libraries like the _normalize.scss + partial that normalizes how browsers render elements. +
    • +
    +
  • +
  • scss/scaffold-forms.scss: For more information, see + Scaffold Forms. +
  • +
+ +

Adding Your Own Sass Partials

+

To customize styles for an existing Magento theme, you should edit existing Sass partials. However, if you are + building a highly custom theme, you will likely want to create new partial files to contain styles for new + functionality that you are introducing. Here is how to do that:

+
    +
  1. Add your partials to a new file named skin/frontend/custompackage/customtheme/scss/module/_new-module.scss. +
  2. +
  3. Create a new skin/frontend/custompackage/customtheme/scss/_custom_core.scss file with the following + line to import your new partial: +
    @import "module/new-module";
    + Note: Although you could edit core.scss to import your new partials, you should + create _custom_core.scss to clearly distinguish which partial files are custom versus native. +
  4. +
  5. Add this line to the bottom of styles.scss and styles-ie8.scss files: +
    @import "custom_core";
    +
  6. +
+

The contents of your new Sass partial will now be included in the output CSS files.

+ +

Using Media Query Breakpoints

+

The responsive theme has a set of breakpoints it uses + consistently (except for a few one-off breakpoints). These breakpoints are defined in both _var.scss + and app.js so the CSS and JavaScript functionality stay in sync:

+
$bp-xsmall: 479px;
+$bp-small: 599px;
+$bp-medium: 770px;
+$bp-large: 979px;
+$bp-xlarge: 1199px;
+

A custom bp() mixin includes media query breakpoints.

+

For example, this Sass:

+
+@include bp(max-width, $bp-medium) {
+    /* These styles will only display on viewports smaller than or equal to 770px */
+}
+@include bp(min-width, $bp-medium+1) {
+    /* These styles will only display on viewports larger than or equal to 771px (770px + 1px) */
+}
+    
+

generates this CSS:

+
+@media only screen and (max-width: 770px) {
+    /* These styles will only display on viewports smaller than or equal to 770px */
+}
+@media only screen and (min-width: 771px) {
+    /* These styles will only display on viewports larger than or equal to 771px (770px + 1px) */
+}
+    
+

The breakpoint variables are intended to be used with max-width by default. When you use the breakpoint + with min-width, you should add a pixel ($bp-medium+1) so the min-width and max-width + styles don't both get applied to the same viewport size.

+ +

Using the bp() mixin for older Internet Explorer Browser Versions

+

It is important to use the bp() mixin for your media query breakpoints because Internet Explorer 8 and + earlier do not support media queries. While you can use a polyfill like respond.js to add media query support to IE + versions 6–8, doing so will impact performance. Additionally, using a polyfill doesn't add much value because + nearly all IE 6–8 users view web pages on a large screen device (1024px or larger), so those users won't + benefit from media queries anyway.

+

To address the fact that IE 6–8 doesn't support media queries, the styles-ie8.scss stylesheet + defines a couple of variables:

+
$mq-support: false;
+$mq-fixed-value: 1024px;
+

With those variables defined, the bp() mixin outputs only the styles that would be visible on a 1024px+ + viewport and it does not wrap those styles in a media query so that the IE browsers always render those + styles. Refer to _breakpoint.scss to see exactly how this + is done.

+

While it is acceptable to use the breakpoint variables defined by the responsive theme, when building your own + responsive theme, your breakpoints should be specifically tailored to the content of your site. For example, + let's say your main navigation only has four links and it fits nicely in a horizontal layout on viewports larger + than 400px.

+

The responsive theme changes the horizontal layout to a collapsed drop-down on viewports smaller than 771px. + Rather than defaulting to how the theme collapses the navigation, you should change the navigation to break at + about 400px so users on screens between 400px and 771px get to experience the benefit of viewing the horizontal + navigation.

+ +

Working With Images

+

There are very few images in the responsive theme compared to the other Magento themes because CSS3 is used + whenever possible to accomplish a visual effect.

+ + +

The responsive theme enables you to specify two different logos: one optimized for small screens (narrower than + 770px) and the other for large screens. Place your two logo files in skin/frontend/custompackage/customtheme/images + and specify the path to both logos in the Magento Admin Panel:

+ +
    +
  1. Log in to the Magento Admin Panel as an administrator.
  2. +
  3. Click System > Configuration > GENERAL > Design.
  4. +
  5. In the right pane, click Header to expand it.
  6. +
  7. In the Logo Image Src field, enter the path of your primary logo. For example: images/logo.png. +
  8. +
  9. In the Small Logo Image Src field, enter the path of your logo for small viewports. For + example: images/logo_small.png. +
  10. +
  11. In the top right corner of the page, click Save Config.
  12. +
  13. If prompted, flush the cache: +
      +
    1. Click System > Configuration > Cache Management. +
    2. +
    3. At the top of the page, click Flush Magento Cache.
    4. +
    +
  14. +
+ +

Working With Sprites

+

The responsive theme uses two sprites: one for general icons and the other for social icons.

+

Both sprite images output at 1x and 2x resolutions (for high-resolution screens). Because sprite files are very + difficult to edit without the source files, you should use these Photoshop files to change these sprites: RWD_icon_sprite.psd and RWD_social_icons.psd +

Compass provides support for generating sprites from individual image files, but it was not used to generate + sprites for the responsive theme. However, you might want to take advantage of that feature for your custom + theme. Using this method requires knowledge of Compass.

+ +

Working With JavaScript

+

All of the custom JavaScript in the responsive theme is contained in skin/frontend/rwd/default/js/app.js. + To customize the responsive theme's JavaScript, copy app.js to skin/frontend/custompackage/customtheme/js + and edit it there.

+

All custom JavaScript is based on jQuery (v1.10.2). In addition to jQuery, the following JavaScript libraries are + included in the responsive theme:

+
    +
  • enquire.js: + Used to run JavaScript conditionally, based on viewport size. +
  • +
  • imagesloaded.js: + Used to determine whether images are fully loaded on the product detail page before activating zoom. +
  • +
  • jquery.cycle2.min.js: Powers the rotating banners on the homepage (requires Magento's + sample data). This library can be removed if you aren't using the sample data. If you want to implement + slide shows, Owl Carousel is recommended + over Cycle2, as it has true swipe support and has more options. +
  • +
  • jquery.cycle2.swipe.min.js: Adds pseudo-swipe support to Cycle2.
  • +
  • jquery-1.10.2.min.js: jQuery library
  • +
  • matchMedia.js + and matchMedia.addListener.js: Used by enquire.js to provide support for Internet Explorer 9. +
  • +
  • modernizr.custom.min.js: Adds classes to the <html> + tag to indicate whether browser supports certain CSS3 features and whether device is touch-enabled. The .touch + and .no-touch classes are used in the Sass partials. They provide a Modernizr.mq function + to test a media query in a cross-browser manner. +
  • +
  • selectivizr.js: Adds support for CSS3 selectors in IE + 6–8 +
  • +
  • elevatezoom/jquery.elevateZoom-3.0.8.min.js: Powers image zoom on product detail + pages. +
  • +
+
+ + +
+ +
+
+ +
+ + + + + + + + + + + diff --git a/guides/m1x/magefordev/mage-for-frontend-dev-8.html b/guides/m1x/magefordev/mage-for-frontend-dev-8.html new file mode 100644 index 0000000000..952f437871 --- /dev/null +++ b/guides/m1x/magefordev/mage-for-frontend-dev-8.html @@ -0,0 +1,378 @@ +--- +--- + + + + + + + + Frontend Developer Guide + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+

Removing Sample Data Assets

+
+ +

The responsive theme includes several assets that are intended to power Magento's sample data. We recommend you + remove them before you build your custom theme.

+
    +
  1. Remove the following files and directories: +
    skin/frontend/custompackage/customtheme/images/media
    +skin/frontend/custompackage/customtheme/scss/madisonisland.scss
    +skin/frontend/custompackage/customtheme/scss/madisonisland-ie8.scss
    +skin/frontend/custompackage/customtheme/scss/content/_category.scss
    +skin/frontend/custompackage/customtheme/scss/content/_home.scss
    +skin/frontend/custompackage/customtheme/css/madisonisland.css
    +skin/frontend/custompackage/customtheme/css/madisonisland-ie8.css
    +
  2. +
  3. In your custom layout file (for example, + app/design/frontend/custompackage/customtheme/layout/local.xml), add this XML:
    +
    +<layout version="0.1.0">
    +    <default>
    +        <reference name="head">
    +            <action method="removeItem"><type>skin_js</type><name>js/slideshow.js</name></action>
    +            <action method="removeItem"><type>skin_js</type><name>js/lib/jquery.cycle2.min.js</name></action>
    +            <action method="removeItem"><type>skin_js</type><name>js/lib/jquery.cycle2.swipe.min.js</name></action>
    +            <action method="removeItem"><type>skin_css</type><name>css/madisonisland.css</name></action>
    +            <action method="removeItem"><type>skin_css</type><name>css/madisonisland-ie8.css</name></action>
    +            <action method="removeItem"><type>link_rel</type><name>//fonts.googleapis.com/css?family=Raleway:300,400,500,700,600</name></action>
    +        </reference>
    +    </default>
    +</layout>
    +            
    + The preceding XML: +
      +
    • Removes the Madison Island CSS files
    • +
    • Removes the Raleway font that is used as a part of the default theme
    • +
    • Removes the jQuery Cycle plugin and the slideshow.js file that activates Cycle.
    • +
    +
  4. +
  5. Update this line in skin/frontend/custompackage/customtheme/scss/_var.scss to reference whatever + font you want to use for your custom site font: +
    $f-stack-special: 'Raleway', 'Helvetica Neue', Verdana, Arial, sans-serif;
    +

    If you want to use a custom web font for your theme (such as a Google Font, add + XML like this to the <reference name="head"> section of layout.xml + (see step 3). Customize the <href> value to the value that your custom web font provider + gives you. It is recommended to include the same font weights that are referenced below + (300,400,500,700,600) because the responsive theme uses each of those font weights.

    +
    +<action method="addLinkRel"><rel>stylesheet</rel><href>//fonts.googleapis.com/css?family=Raleway:300,400,500,700,600</href></action>
    +                
    +
  6. +
+
+ + +
+ +
+
+ +
+ + + + + + + + + + + diff --git a/guides/m1x/magefordev/mage-for-frontend-dev-9.html b/guides/m1x/magefordev/mage-for-frontend-dev-9.html new file mode 100644 index 0000000000..73004e6926 --- /dev/null +++ b/guides/m1x/magefordev/mage-for-frontend-dev-9.html @@ -0,0 +1,440 @@ +--- +--- + + + + + + + + Frontend Developer Guide + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ +
+

Advanced Topics

+
+ +

After you get familiar with the new structure, we encourage you to read these topics to fully take advantage of + responsive web design.

+ +

Getting Your Theme Ready for Production

+ +

To prepare your responsive theme for production, you should update config.rb in your theme directory to + use these values:

+
output_style = :compressed
+environment = :production
+ +

After you do this, you should recompile all of your Sass files—you can do this on the command line by + running these commands inside your theme directory: compass clean and then compass compile. + Finally, deploy the compiled CSS to your production system.

+ +

Sass Fallback Structure

+

We recommend copying all of the Sass files from skin/frontend/rwd/default/scss to skin/frontend/custompackage/customtheme/scss + to make it easy to get started with custom theme development. The downside of this approach is that it results + in duplicating many files that you never edit directly.

+

In the end, your custom theme must include all Sass partials you want to override. There are at least two ways to + do this:

+
    +
  • If you followed the setup instructions in Step 1: Creating a Directory Structure + for Your Responsive Theme, you must delete some files from your custom theme.
    + Delete the following directories to remove all the Sass partials that you copied from + rwd/default:
    +
    skin/frontend/custompackage/customtheme/scss/core/
    +skin/frontend/custompackage/customtheme/scss/function/
    +skin/frontend/custompackage/customtheme/scss/layout/
    +skin/frontend/custompackage/customtheme/scss/mixin/
    +skin/frontend/custompackage/customtheme/scss/module/
    +skin/frontend/custompackage/customtheme/scss/override/
    +skin/frontend/custompackage/customtheme/scss/vendor/
    + The following files should now be located in skin/frontend/custompackage/customtheme/scss:
    +
    _var.scss
    +_core.scss
    +styles-ie8.scss
    +styles.scss
    +scaffold-forms.scss
    +_custom_core.scss
    +_framework.scss
    +
  • +
  • If you're familiar with Magento theme development and you did not already copy Sass partials, copy + only the Sass partials you want to override from rwd/default to skin/frontend/custompackage/customtheme/scss. +
  • +
+

Now you can set up a fallback structure:

+
    +
  1. Add the following line to skin/frontend/rwd/default/config.rb (omit the first line if you're using + CE): +
    add_import_path "../../../rwd/enterprise/scss"
    +add_import_path "../../../rwd/default/scss"
    + The preceding causes Compass to look for Sass files in skin/frontend/rwd/enterprise/scss first and + then in skin/frontend/rwd/default/scss if they can't be found in skin/frontend/custompackage/customtheme/scss. +
  2. +
  3. Following is what to do with copies of Sass files from rwd/default:
    +
      +
    • styles.scss, styles-ie8.scss: You must copy these files into your custom theme so + that Compass knows which files to compile into CSS files. +
    • +
    • _core.scss, framework.scss: You must copy these files into your custom theme so + Compass looks in the custompackage/customtheme directory for any of the files imported by + these two files. +
    • +
    • _var.scss: You'll typically edit the values in this file for your custom theme.
      + If you don't like the idea of editing this file and want to override its values with a _var_custom.scss + file, make sure it gets imported immediately after _var.scss. +
    • +
    +
  4. + +
  5. As a reminder, to override Sass partials in rwd/default, you must copy them into your custom theme + so they'll get included by Compass.
    + Note: If you're using compass watch to recompile your Sass, you must stop and + restart Compass any time you copy a new Sass file into your custom theme. Otherwise, the Compass compiler + won't know about the existence of the new file.
    + To avoid having to stop and restart compass watch, start your project by copying all Sass files + from rwd/default into your custom theme, and then delete all unchanged Sass files at the end of + your project. +
  6. +
+ +

Scaffold Forms

+

The Magento responsive theme ships with skin/frontend/rwd/default/scss/scaffold-forms.scss that has + styles that cause the labels of long forms (for example, the Billing Information step of checkout) to display to + the left of their associated input on larger viewports (as opposed to on top). This layout is intended to make + long forms seem less intimidating to users.

+

However, these styles are not currently implemented because the Magento responsive theme must work well with + internationalization (where the length of labels can vary greatly, thereby impacting the scaffold form + layout).

+

It's up to you whether or not to use these styles. To do so, move skin/frontend/rwd/default/scss/scaffold-forms.scss + to core/_scaffold-forms.scss and edit scss/_core.scss to include it.

+ +

High-Definition Images

+

The responsive theme has no high-resolution image solution for product listing pages. Refer to slides + starting at 114 of Brendan Falkowski's Imagine 2013 talk for implementation suggestions. For a 2x solution, + PictureFill (or similar polyfill) could be used, although it would need to be integrated with ElevateZoom.

+
noteNote: Category banners and product detail pages use a 1.5x image solution. + +
+ +

Magento does not resize the main product photo on product detail pages, so you must implement high-resolution + support in your responsive theme. You must be careful with this if you have very large product photos because it + could drastically impact performance.

+

To constrain the size of your main product photo, open app/design/frontend/rwd/default/template/catalog/product/view/media.phtml + and modify this line: +

<?php echo $this->helper('catalog/image')->init($_product, 'image') ?>
+ to something like: +
src="<?php echo $this->helper('catalog/image')->init($_product, 'image')->constrainOnly(true)->resize(1800); ?>"
+ +
+ + +
+ +
+
+ +
+ + + + + + + + + + + diff --git a/index.html b/index.html index 8314908d56..9dc7f54228 100644 --- a/index.html +++ b/index.html @@ -1,6 +1,6 @@ --- layout: home -guide_version: 'm1x' +guide_version: '19.x' ---
@@ -10,10 +10,9 @@

Setup

@@ -21,20 +20,20 @@

Setup

Backend Development

@@ -44,7 +43,6 @@

Release Information

@@ -53,10 +51,9 @@

Release Information

Magento APIs

@@ -65,8 +62,8 @@

Magento APIs

Community

From f2a9d7fd46e8f3127294f6cf2bc3e524016ef533 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20Bre=C3=9Fler?= Date: Tue, 30 Jan 2024 14:47:16 +0100 Subject: [PATCH 3/3] B3-IT: Format pre tags, update links --- guides/m1x/magefordev/mage-for-dev-1.html | 414 ++++++++-------- guides/m1x/magefordev/mage-for-dev-2.html | 216 ++++----- guides/m1x/magefordev/mage-for-dev-3.html | 225 +++++---- guides/m1x/magefordev/mage-for-dev-4.html | 414 ++++++++-------- guides/m1x/magefordev/mage-for-dev-5.html | 427 ++++++++-------- guides/m1x/magefordev/mage-for-dev-6.html | 454 +++++++++--------- guides/m1x/magefordev/mage-for-dev-7.html | 386 ++++++++++----- guides/m1x/magefordev/mage-for-dev-8.html | 193 ++++++-- .../magefordev/mage-for-frontend-dev-7.html | 4 +- .../magefordev/mage-for-frontend-dev-8.html | 4 +- 10 files changed, 1488 insertions(+), 1249 deletions(-) diff --git a/guides/m1x/magefordev/mage-for-dev-1.html b/guides/m1x/magefordev/mage-for-dev-1.html index cf67075558..e925834bcd 100644 --- a/guides/m1x/magefordev/mage-for-dev-1.html +++ b/guides/m1x/magefordev/mage-for-dev-1.html @@ -31,8 +31,6 @@ - -