Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: [SF2][SF] Problem z wydajnością
Forum PHP.pl > Forum > PHP > Frameworki
swiezak
Mam problem z wydajnością sklepu internetowego napisanego w SF2. W tej chwili jest 300 produktów.
Struktura bazy danych jest rozbudowana. Tabela z produktami jest połączona z tabelami dokumentów, zdjęć, producentów, kategorii, atrybutów, wartości atrybutów, zamówieniami, dostępnością.
Wyświetlanie listy produktów odbywa się sprawnie i szybko. Problem zaczyna się, gdy próbuję edytować dany produkt. Profiler pokazuje mi zużycie pamięci na poziomie 184,5 MB. Same zapytania do bazy danych trwają 49 ms, natomiast czas renderingu templatek TWIG 9111 ms.

Poniżej stosowne screeny:




backend.base.html.twig
  1. {% extends "::backendbase.html.twig" %}
  2.  
  3. {% block body %}
  4. <!-- header logo -->
  5. <header class="header">
  6. {{ render(controller('MlBackendBundle:Default:menuheader')) }}
  7. </header>
  8. <div class="wrapper row-offcanvas row-offcanvas-left">
  9. {% include "MlBackendBundle:Default:menuleft.html.twig" %}
  10.  
  11. <!-- Right side column. Contains the navbar and content of the page -->
  12. <aside class="right-side">
  13. <!-- Content Header (Page header) -->
  14. <section class="content-header">
  15. <h1>
  16. Panel administracyjny
  17. <small>Sklep internetowy</small>
  18. </h1>
  19. {% block breadcrumbs -%}{% endblock breadcrumbs -%}
  20. </section>
  21.  
  22. <!-- Main content -->
  23. <section class="content">
  24. {% include "MlBackendBundle:Default:flashes.html.twig" %}
  25.  
  26. {% block backendcontent -%}{% endblock backendcontent -%}
  27. </section><!-- /.content -->
  28. </aside><!-- /.right-side -->
  29. </div><!-- ./wrapper -->
  30.  
  31. <ul class="nav pull-right scroll-top">
  32. <li><a href="#" data-original-title="Przewiń na górę" data-toggle="tooltip"><i class="glyphicon glyphicon-chevron-up"></i></a></li>
  33. </ul>
  34. {% endblock %}


backendbase.html.twig
  1. <!DOCTYPE html>
  2. <html lang="pl">
  3. <head>
  4. <meta http-equiv="content-type" content="text/html; charset=utf-8" />
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  6. <meta name="viewport" content="width=device-width,initial-scale=1">
  7. <meta name="robots" content="noindex, nofollow">
  8. <title>{% block title %}Panel administracyjny - sklep internetowy{% endblock %}</title>
  9. {% block stylesheets %}
  10. <link rel="stylesheet" href="{{ asset('css/jquery-ui.css') }}" />
  11. <link rel="stylesheet" href="{{ asset('css/bootstrap.min.css') }}" />
  12. <link rel="stylesheet" href="{{ asset('css/font-awesome.min.css') }}" />
  13. <link rel="stylesheet" href="{{ asset('css/jquery-ui-timepicker-addon.css') }}" />
  14. <link rel="stylesheet" href="{{ asset('css/theme-backend.css') }}" />
  15. <link rel="stylesheet" href="{{ asset('css/main-backend.css') }}" />
  16. <link rel="stylesheet" href="{{ asset('css/select2-backend.css') }}" />
  17. <link rel="stylesheet" href="{{ asset('css/jquery.fancybox.css') }}" />
  18. <link rel="stylesheet" href="{{ asset('css/colorpicker.css') }}" />
  19. <link rel="stylesheet" href="{{ asset('css/AdminLTE.css') }}" />
  20. <link rel="stylesheet" href="{{ asset('css/ionicons.min.css') }}" />
  21. {% endblock %}
  22. <link rel="icon" type="image/x-icon" href="{{ asset('favicon.ico') }}" />
  23. <link rel="apple-touch-icon-precomposed" sizes="57x57" href="{{ asset('apple-touch-icon-57x57-precomposed.png') }}">
  24. <link rel="apple-touch-icon-precomposed" sizes="72x72" href="{{ asset('apple-touch-icon-72x72-precomposed.png') }}">
  25. <link rel="apple-touch-icon-precomposed" sizes="114x114" href="{{ asset('apple-touch-icon-114x114-precomposed.png') }}">
  26. <link rel="apple-touch-icon-precomposed" sizes="144x144" href="{{ asset('apple-touch-icon-144x144-precomposed.png') }}">
  27. </head>
  28.  
  29. <body class="skin-black">
  30. <noscript>
  31. <div id="msgbox" class="alert alert-danger fade in" role="alert">
  32. <button type="button" class="close" data-dismiss="alert">×</button>
  33. <strong>Informacja</strong> / Twoja przeglądarka nie obsługuje skryptów Java Script lub są one wyłączone.<br />Niektóre opcje nie będą działać.
  34. </div>
  35. </noscript>
  36. {% block body %}
  37. {% block backendcontent -%}{% endblock %}
  38. {% endblock %}
  39. {% block javascripts %}
  40. <script src="{{ asset('js/jquery.min.js') }}"></script>
  41. <script src="{{ asset('js/bootstrap/bootstrap.min.js') }}"></script>
  42. <script src="{{ asset('js/jquery-ui.min.js') }}"></script>
  43. <script src="{{ asset('js/bootstrap/tooltip.js') }}"></script>
  44. <script src="{{ asset('js/backend-template.js') }}"></script>
  45. <script src="{{ asset('js/select2.min.js') }}"></script>
  46. <script src="{{ asset('js/jquery-ui-timepicker-addon.js') }}"></script>
  47. <script src="{{ asset('js/jquery.fancybox.js') }}"></script>
  48. <script src="{{ asset('js/jquery.mousewheel-3.0.6.pack.js') }}"></script>
  49. <script src="{{ asset('js/colorpicker.js') }}"></script>
  50. <script src="{{ asset('js/AdminLTE/app.js') }}"></script>
  51. <!--[if lt IE 9]>
  52. <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
  53. <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
  54. <![endif]-->
  55. {% endblock %}
  56. </body>
  57. </html>


Pytanie do Forumowiczów: od czego zacząć, co poprawić, aby skrócić czas renderowania i obniżyć SF2 apetyt na zasoby pamięciowe?
Będę wdzięczny za wszelkie wskazówki.
destroyerr
Pierwszy raz widzę coś takiego. Pierwsze kwestia: na czym to jest uruchomione (jaki serwer, wersja php, akceleracja itd.)? Ile w tym środowisku zajmuje uruchomienie czystego projektu? Czy masz zainstalowane rozszerzenie php do twiga. W debug toolbarze masz czerwony znaczek przy tłumaczeniach, logi też warto przejrzeć i zrobić porządki. Z twiga przydałby się cały call graph bo nie wiadomo co zabiera tyle czasu, albo skorzystaj z blackfire.io.
Możesz wysłać dane z profilera?
swiezak
Test przeprowadzony został na lokalnym serwerze z wersją PHP 5.5.9 bez akceleratora APC. Nie mam zainstalowanej bilbioteki php_twig.dll.
Wydaje mi się, że ten Profiler w środowsku developerskim tak ciągnie pamięć, bo gdy jestem w środowisku produkcyjnym edycja produktu nie zabiera już tyle czasu i potrzeba zdecydowanie mniej pamięci RAM, jednak nie są to wartości zadowalające. Czekam 3 sekundy, aby móc cokolwiek edytować w formsie.

Poniżej graph renderingu TWIG:
kpt_lucek
Zastanawiam się nad kilkoma możliwymi problemami. Można to gdzieś przeklikać? Chodzi mi o wersję dev i prod.

----EDIT

Pokaż kontroler i templatkę w której to renderujesz smile.gif

----EDIT2

Chodzi mi o MLBackendBundle:Products:edit.html.twig i ::backendlayout.html.twig
ohm
Dobrze byłoby jak byś pokazał więcej kodu, bo masz problem z generowaniem bardzo dużej ilości jednego formularza. Ogólnie mocno wydajność obciąża render/include w twigu, lepiej byłoby znaleźć alternatywne rozwiązania, np generowanie danych przez TwigExtension albo rozszerzanie (extends) bloków.
swiezak
Podaje kod z templatki od edycji formualrza:

MlBackendBundle:Products:edit.html.twig
  1. {% extends '::backendlayout.html.twig' %}
  2.  
  3. {% set imgPath = app_config.products_path %}
  4. {% set noImg = app_config.product_no_photo %}
  5.  
  6. {% block title %}Edytuj produkt | {{ parent() }}{% endblock %}
  7.  
  8. {% block breadcrumbs -%}
  9. <ol class="breadcrumb">
  10. <li><a href="{{ path('backend') }}"><i class="fa fa-dashboard"></i> Strona startowa</a></li>
  11. <li><a href="{{ path('backend_products') }}">Lista produktów</a></li>
  12. <li class="active">Edytuj produkt</li>
  13. </ol>
  14. {% endblock breadcrumbs -%}
  15.  
  16. {% block backendcontent -%}
  17. {% include "MlBackendBundle:Products:top_menu.html.twig" %}
  18.  
  19. <h1><span class="glyphicon glyphicon-briefcase"></span> Edytuj produkt</h1>
  20.  
  21. <form id="jsForm" action="{{ path('backend_products_update', { 'id': entity.id }) }}" class="form-horizontal col-md-12" method="POST" {{form_enctype(form)}}>
  22. <input id="saveClose" type="hidden" name="saveClose" value="no">
  23. <div class="form-group">
  24. <div class="col-md-offset-3 col-md-9">
  25. <button type="submit" name="subvalider" class="btn btn-primary"><span class="glyphicon glyphicon-save"></span> Zapisz</button>
  26. <a href="#" class="btn btn-primary submit-btn"><span class="glyphicon glyphicon-ok"></span> Zapisz i zamknij</a>
  27. <a href="#" id="delete-btn" class="btn btn-danger" data-toggle="modal" data-target="#deleteModal" data-entity-id="{{ entity.id }}"><span class="glyphicon glyphicon-trash"></span> Usuń</a>
  28. <a href="{{ path('backend_products') }}" class="btn btn-success"><span class="glyphicon glyphicon-share-alt"></span> Powrót</a>
  29. </div>
  30. </div>
  31.  
  32. {% include "MlBackendBundle:Products:form.html.twig" %}
  33.  
  34. <div class="form-group">
  35. <div class="col-md-offset-3 col-md-9">
  36. <button type="submit" name="subvalider" class="btn btn-primary"><span class="glyphicon glyphicon-save"></span> Zapisz</button>
  37. <a href="#" class="btn btn-primary submit-btn"><span class="glyphicon glyphicon-ok"></span> Zapisz i zamknij</a>
  38. <a href="#" id="delete-btn" class="btn btn-danger" data-toggle="modal" data-target="#deleteModal" data-entity-id="{{ entity.id }}"><span class="glyphicon glyphicon-trash"></span> Usuń</a>
  39. <a href="{{ path('backend_products') }}" class="btn btn-success"><span class="glyphicon glyphicon-share-alt"></span> Powrót</a>
  40. </div>
  41. </div>
  42. </form>
  43.  
  44. {% include "MlBackendBundle:Default:delete_modal.html.twig" %}
  45.  
  46. <form id="deleteForm" action="{{ path('backend_products_delete', { 'id': entity.id }) }}" method="POST">
  47. {{ form_widget(delete_form) }}
  48. </form>
  49. {% endblock backendcontent -%}
  50.  
  51. {% block javascripts %}
  52. {{ parent() }}
  53. (function($) {
  54. $('#delete-btn').on('click', function () {
  55. $('.remove_item').attr('data-entity-id', entityId);
  56. });
  57. $('.submit-btn').on('click', function () {
  58. $('#saveClose').val("yes");
  59. $('#jsForm').submit();
  60. return false;
  61. });
  62. $(".remove_item").click(function () {
  63. $('#deleteForm').submit();
  64. return false;
  65. });
  66. var forms = $('#jsForm'),
  67. vat = forms.find('#ml_backendbundle_products_vatValue'),
  68. netto = forms.find('#ml_backendbundle_products_priceNetto'),
  69. brutto = forms.find('#ml_backendbundle_products_priceBrutto');
  70.  
  71. netto.change(function() {
  72. var nettoTmp = parseFloat(netto.val());
  73. var vatTmp = parseFloat(vat.val());
  74. if(nettoTmp >= 0 ){
  75. priceBrutto = nettoTmp + (nettoTmp * vatTmp);
  76. brutto.val(priceBrutto.toFixed(2));
  77. }
  78. });
  79.  
  80. brutto.change(function() {
  81. var bruttoTmp = parseFloat(brutto.val());
  82. var vatTmp = parseFloat(vat.val());
  83. if(bruttoTmp >= 0 ){
  84. var priceNetto = bruttoTmp / ((vatTmp*100 + 100)/100);
  85. netto.val(priceNetto.toFixed(2));
  86. }
  87. });
  88.  
  89. // Images
  90. var imageCount = '{{ form.images|length }}';
  91. $('#add-another-image').click(function(e) {
  92. e.preventDefault();
  93.  
  94. var imageList = $('#image-fields-list');
  95.  
  96. // grab the prototype template
  97. var newWidget = imageList.attr('data-prototype');
  98. // replace the "__name__" used in the id and name of the prototype
  99. // with a number that's unique to your images
  100. newWidget = newWidget.replace(/__ml_backendbundle_products[images][0][file]__/g, imageCount);
  101. imageCount++;
  102.  
  103. // create a new list element and add it to the list
  104. var newLi = $('<li></li>').html(newWidget);
  105. newLi.appendTo(imageList);
  106. });
  107.  
  108. // Documents
  109. var documentCount = '{{ form.documents|length }}';
  110. $('#add-another-document').click(function(e) {
  111. e.preventDefault();
  112. var documentList = $('#document-fields-list');
  113. var newWidget = documentList.attr('data-prototype');
  114.  
  115. newWidget = newWidget.replace(/__ml_backendbundle_products[documents][0][file]__/g, documentCount);
  116. documentCount++;
  117.  
  118. var newLi = $('<li></li>').html(newWidget);
  119. newLi.appendTo(documentList);
  120. });
  121.  
  122. // Attributes
  123. var attributeCount = '{{ form.attributes|length }}';
  124. $('#add-another-attribute').click(function(e) {
  125. e.preventDefault();
  126. var attributeList = $('#attribute-fields-list');
  127. var newWidget = attributeList.attr('data-prototype');
  128.  
  129. newWidget = newWidget.replace(/__ml_backendbundle_products[attributes][0][file]__/g, attributeCount);
  130. attributeCount++;
  131.  
  132. var newLi = $('<li></li>').html(newWidget);
  133. newLi.appendTo(attributeList);
  134. });
  135.  
  136. /* Fancybox */
  137. $(".fancybox").fancybox({
  138. helpers: {
  139. title : {
  140. type : 'inside'
  141. }
  142. },
  143. beforeShow : function() {
  144. var title = this.element.find('img').attr('title');
  145. this.inner.find('img').attr('title', title);
  146. this.title = title;
  147. }
  148. });
  149. })(jQuery);
  150. {% endblock %}


Kod formularza:

  1. {% set imgPath = app_config.products_path %}
  2. {% set noImg = app_config.product_no_photo %}
  3. {% set docPath = app_config.products_doc_path %}
  4. {% set noDoc = app_config.product_no_doc %}
  5.  
  6. <div class="tabbable">
  7. <ul class="nav nav-tabs">
  8. <li class="active"><a href="#tab1" data-toggle="tab"><span class="glyphicon glyphicon-cog"></span> Konfiguracja podstawowa</a></li>
  9. <li><a href="#tab2" data-toggle="tab"><span class="glyphicon glyphicon-picture"></span> Galeria zdjęć</a></li>
  10. <li><a href="#tab3" data-toggle="tab"><span class="glyphicon glyphicon-folder-open"></span> Dokumenty</a></li>
  11. <li><a href="#tab4" data-toggle="tab"><span class="glyphicon glyphicon-list-alt"></span> Atrybuty</a></li>
  12. <li><a href="#tab5" data-toggle="tab"><span class="glyphicon glyphicon-globe"></span> Pozycjonowanie</a></li>
  13. </ul><!-- /tabs -->
  14. <div class="tab-content">
  15. <div class="tab-pane active" id="tab1">
  16. <legend>Dane podstawowe</legend>
  17. {{ form_row(form.sku, { 'label' : 'SKU (nr katalogowy)' }) }}
  18. {{ form_row(form.name, { 'label' : 'Nazwa' }) }}
  19. {{ form_row(form.slug, { 'label' : 'Przyjazny link' }) }}
  20. <div class="form-group">
  21. <label for="" class="col-sm-2 control-label">&nbsp;</label>
  22. <div class="col-sm-10">
  23. <span class="help-block">
  24. <ul class="list-unstyled">
  25. <li>
  26. <span class="glyphicon glyphicon-info-sign"></span> Przyjazny link zostanie wykorzystany w adresach URL. Pozostaw to miejsce puste, a system wypełni je wartością domyślną na podstawie nazwy.
  27. </li>
  28. </ul>
  29. </span>
  30. </div>
  31. </div>
  32. {{ form_row(form.shortDescription, { 'label' : 'Skrócony opis' }) }}
  33. <div class="form-group">
  34. <label for="" class="col-sm-2 control-label">&nbsp;</label>
  35. <div class="col-sm-10">
  36. <span class="help-block">
  37. <ul class="list-unstyled">
  38. <li>
  39. <span class="glyphicon glyphicon-info-sign"></span> Maksymalnie 255 znaków.
  40. </li>
  41. </ul>
  42. </span>
  43. </div>
  44. </div>
  45. {{ form_row(form.description, { 'label' : 'Pełny opis' }) }}
  46. <legend>Ceny</legend>
  47. {{ form_row(form.vatValue, { 'label' : 'Stawka VAT', 'attr' : { 'class' : 'js-vat' } }) }}
  48. {{ form_row(form.priceNetto, { 'label' : 'Cena netto' }) }}
  49. {{ form_row(form.priceBrutto, { 'label' : 'Cena brutto' }) }}
  50. {{ form_row(form.oldPriceNetto, { 'label' : 'Stara cena netto', 'attr' : { 'class' : 'js-netto' }}) }}
  51. {{ form_row(form.oldPriceBrutto, { 'label' : 'Stara cena brutto', 'attr' : { 'class' : 'js-brutto' } }) }}
  52. {{ form_row(form.hidePrice, { 'label' : 'Cena ukryta' }) }}
  53. <legend>Statusy</legend>
  54. {{ form_row(form.isNew, { 'label' : 'Nowość' }) }}
  55. {{ form_row(form.isPromo, { 'label' : 'Promocja' }) }}
  56. {{ form_row(form.isPopular, { 'label' : 'Popularny produkt' }) }}
  57. {{ form_row(form.availability, { 'label' : 'Dostępność' }) }}
  58. <legend>Opcje publikacji</legend>
  59. {{ form_row(form.isActive, { 'label' : 'Stan publikacji' }) }}
  60. {{ form_row(form.created, { 'label' : 'Data utworzenia' }) }}
  61. {{ form_row(form.updated, { 'label' : 'Data modyfikacji' }) }}
  62. {{ form_row(form.views, { 'label' : 'Odsłony' }) }}
  63. <legend>Producent i kategorie</legend>
  64. {{ form_row(form.brands, { 'label' : 'Producent' }) }}
  65. {{ form_row(form.getCategories, { 'label' : 'Kategoria' }) }}
  66. </div><!-- /tab1 -->
  67. <div class="tab-pane" id="tab2">
  68. <legend>Zdjęcia</legend>
  69. <ul id="image-fields-list" data-prototype="{{ form_widget(form.images.vars.prototype)|e }}">
  70. {% for imageField in form.images %}
  71. <legend>Zdjęcie nr {{ loop.index }}</legend>
  72. <div class="form-group">
  73. <label for="" class="col-sm-2 control-label">Plik graficzny</label>
  74. <div class="col-sm-10">
  75. {# imageField: {{ dump(imageField) }} #}
  76. {# img class="myavatar" #}
  77. {% if imageField.vars.data.filename %}<a class="fancybox" href="{{ asset(imgPath) }}{{ imageField.vars.data.products.slug }}/{{ imageField.vars.data.filename }}">
  78. <img class="img-responsive thumbnail" src="{{ asset(imgPath) }}{{ imageField.vars.data.products.slug }}/{{ imageField.vars.data.filename }}" width="100" height="100" title="{{ imageField.vars.data.title }}" alt="{{ imageField.vars.data.alt }}" /></a>
  79. {% elseif imageField.vars.data.filename is empty %}<img src="{{ asset(imgPath) }}{{ noImg }}" title="{{ imageField.vars.data.title }}" alt="{{ imageField.vars.data.alt }}" />{% endif %}
  80. </div>
  81. </div>
  82. <li>
  83. {{ form_errors(imageField) }}
  84. {{ form_widget(imageField) }}
  85. </li>
  86. <div class="clearfix list-splitter" style="margin: 30px 0"></div>
  87. {% endfor %}
  88. </ul>
  89. <ul class="hidden-print">
  90. <li>
  91. <a href="#" id="add-another-image">
  92. <span class="glyphicon glyphicon-plus"></span> Dodaj zdjęcie
  93. </a>
  94. </li>
  95. </ul>
  96. </div><!-- /tab2 -->
  97. <div class="tab-pane" id="tab3">
  98. <legend>Załączniki</legend>
  99. {# {{ form_row(form.documents, { 'label' : ' ' }) }} #}
  100. <ul id="document-fields-list" data-prototype="{{ form_widget(form.documents.vars.prototype)|e }}">
  101. {% for documentField in form.documents %}
  102. <legend>Dokument nr {{ loop.index }}</legend>
  103. <div class="form-group">
  104. <label for="" class="col-sm-2 control-label">Plik z dokumentem</label>
  105. <div class="col-sm-10">
  106. {# documentField: {{ dump(documentField) }} #}
  107. {% if documentField.vars.data.filename %}<a href="{{ asset(docPath) }}{{ documentField.vars.data.products.slug }}/{{ documentField.vars.data.filename }}" target="_blank" data-toggle="tooltip" data-placement="top" title="Pobierz dokument" />
  108. {{ documentField.vars.data.filename }}</a>
  109. {% elseif documentField.vars.data.filename %}<img src="{{ asset(docPath) }}{{ noDoc }}" title="{{ documentField.vars.data.title }}" alt="{{ documentField.vars.data.title }}" />{% endif %}
  110. </div>
  111. </div>
  112. <li>
  113. {{ form_errors(documentField) }}
  114. {{ form_widget(documentField) }}
  115. </li>
  116. <div class="clearfix list-splitter" style="margin: 30px 0"></div>
  117. {% endfor %}
  118. </ul>
  119. <ul class="hidden-print">
  120. <li>
  121. <a href="#" id="add-another-document">
  122. <span class="glyphicon glyphicon-plus"></span> Dodaj dokument
  123. </a>
  124. </li>
  125. </ul>
  126. </div><!-- /tab3 -->
  127. <div class="tab-pane" id="tab4">
  128. <legend>Atrybuty</legend>
  129. {# {{ form_row(form.attributes, { 'label' : ' ' }) }} #}
  130. <ul id="attribute-fields-list" data-prototype="{{ form_widget(form.attributes.vars.prototype)|e }}">
  131. {% for attributeField in form.attributes %}
  132. <legend>Atrybut nr {{ loop.index }}</legend>
  133. <li>
  134. {{ form_errors(attributeField) }}
  135. {{ form_widget(attributeField) }}
  136. </li>
  137. <div class="clearfix list-splitter" style="margin: 30px 0"></div>
  138. {% endfor %}
  139. </ul>
  140. <ul class="hidden-print">
  141. <li>
  142. <a href="#" id="add-another-attribute">
  143. <span class="glyphicon glyphicon-plus"></span> Dodaj atrybut
  144. </a>
  145. </li>
  146. </ul>
  147. </div><!-- /tab4 -->
  148. <div class="tab-pane" id="tab5">
  149. <legend>Znaczniki meta</legend>
  150. {{ form_row(form.metaTitle, { 'label' : 'Znacznik meta - tytuł' }) }}
  151. {{ form_row(form.metaKeywords, { 'label' : 'Znacznik meta - słowa kluczowe' }) }}
  152. {{ form_row(form.metaDescription, { 'label' : 'Znacznik meta - opis' }) }}
  153.  
  154. {{ form_rest(form) }}
  155. </div><!-- /tab5 -->
  156. </div>
  157. </div>


Kod
Formularze zostaly wygenerowane automatycznie - panele CRUD. Pozniej poddalem je modyfikacji.

Fragment z kontrolera:
[php]
    /**
     * Displays a form to edit an existing Products entity.
     */
    public function editAction($id)
    {
        $em = $this->getDoctrine()->getManager();

        $entity = $em->getRepository('MlBackendBundle:Products')->find($id);

        if (!$entity) {
            throw $this->createNotFoundException('Unable to find Products entity.');
        }
        $editForm = $this->createForm(new ProductsType(), $entity);
        $deleteForm = $this->createDeleteForm($id);

        return $this->render('MlBackendBundle:Products:edit.html.twig', array(
            'entity'      => $entity,
            'form'        => $editForm->createView(),
            'delete_form' => $deleteForm->createView(),
        ));
    }
[/php]

fragment ProductsType:
[php]
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('sku', 'text', array('label' => 'SKU (nr katalogowy)'))
            ->add('name', 'text', array('label' => 'Nazwa'))
            ->add('slug', 'text', array('label' => 'Przyjazny link', 'required' => false))
            ->add('vatValue', 'choice', array('label' => 'Stawka VAT', 'choices' => array('0.23' => '23%', '0.08' => '8%')))
            ->add('priceNetto', 'text', array('label' => 'Cena netto'))
            ->add('priceBrutto', 'text', array('label' => 'Cena brutto'))
            ->add('oldPriceNetto', 'text', array('label' => 'Stara cena netto', 'required' => true, 'data' => '0.00'))
            ->add('oldPriceBrutto', 'text', array('label' => 'Stara cena brutto', 'required' => true, 'data' => '0.00'))
            ->add('hidePrice', 'choice', array('label' => 'Cena ukryta', 'choices' => array('0' => 'Nie', '1' => 'Tak'), 'required' => true))
            ->add('isActive', 'choice', array('label' => 'Stan publikacji', 'choices' => array('1' => 'Opublikowano', '0' => 'Nie opublikowano'), 'required' => true))
            ->add('isNew', 'choice', array('label' => 'Nowość', 'choices' => array('0' => 'Nie', '1' => 'Tak'), 'required' => true))
            ->add('isPromo', 'choice', array('label' => 'Promocja', 'choices' => array('0' => 'Nie', '1' => 'Tak'), 'required' => true))
            ->add('isPopular', 'choice', array('label' => 'Popularny produkt', 'choices' => array('0' => 'Nie', '1' => 'Tak'), 'required' => true))
            ->add('created', 'datetime', array('widget' => 'single_text', 'format' => 'dd-MM-yyyy hh:mm:ss', 'attr' => array('class' => 'datetimepicker')))
            ->add('updated', 'datetime', array('widget' => 'single_text', 'format' => 'dd-MM-yyyy hh:mm:ss', 'attr' => array('class' => 'datetimepicker')))
            ->add('views', 'integer', array('label' => 'Ilość odsłon', 'required' => false, 'data' => '0'))
            ->add('shortDescription', 'ckeditor', array('label' => 'Skrócony opis', 'required' => false))
            ->add('description', 'ckeditor', array('label' => 'Opis', 'required' => false))
            ->add('metaTitle', 'text', array('label' => 'Znacznik meta - tytuł', 'required' => false))
            ->add('metaKeywords', 'textarea', array('label' => 'Znacznik meta - słowa kluczowe', 'required' => false))
            ->add('metaDescription', 'textarea', array('label' => 'Znacznik meta - opis', 'required' => false))
            ->add('brands', 'entity', array('label' => 'Producent', 'class' => 'MlBackendBundle:Brands', 'required' => true))
            ->add('availability', 'entity', array('label' => 'Dostępność', 'class' => 'MlBackendBundle:Availability', 'required' => true))
            ->add('getCategories', 'entity',
            [
              'class' => 'Ml\BackendBundle\Entity\Categories',
              'property' => 'name',
              'multiple' => true,
              'expanded' => false,
              'label' => 'Kategoria',
            ])
            ->add('images', 'collection', array(
                'type' => new ImagesType(),
                'allow_add'    => true,
                'by_reference' => false,
                'allow_delete' => true,
                                'label' => ' '))

            ->add('documents', 'collection', array(
                'type' => new DocumentsType(),
                'allow_add'    => true,
                'by_reference' => false,
                'allow_delete' => true,
                                'label' => ' '))

            ->add('attributes', 'collection', array(
                'type' => new AttributesType(),
                'allow_add'    => true,
                'by_reference' => false,
                'allow_delete' => true,
                                'label' => ' '))
               ;
    }
[/php]
kpt_lucek
Zabijasz się pętlami + fetchowaniem daych w nich.
Kod
            {% for imageField in form.images %}
                <legend>Zdjęcie nr {{ loop.index }}</legend>
                <div class="form-group">
                    <label for="" class="col-sm-2 control-label">Plik graficzny</label>
                    <div class="col-sm-10">
                        {# imageField: {{ dump(imageField) }} #}
                        {# img class="myavatar" #}
                        {% if imageField.vars.data.filename %}<a class="fancybox" href="{{ asset(imgPath) }}{{ imageField.vars.data.products.slug }}/{{ imageField.vars.data.filename }}">
                            <img class="img-responsive thumbnail" src="{{ asset(imgPath) }}{{ imageField.vars.data.products.slug }}/{{ imageField.vars.data.filename }}" width="100" height="100" title="{{ imageField.vars.data.title }}" alt="{{ imageField.vars.data.alt }}" /></a>
                        {% elseif imageField.vars.data.filename is empty %}<img src="{{ asset(imgPath) }}{{ noImg }}" title="{{ imageField.vars.data.title }}" alt="{{ imageField.vars.data.alt }}" />{% endif %}
                    </div>
                </div>
                <li>
                    {{ form_errors(imageField) }}
                    {{ form_widget(imageField) }}
                </li>
                <div class="clearfix list-splitter" style="margin: 30px 0"></div>
            {% endfor %}


Coś takiego n razy i problem gotowy, przemyśl czy rzeczywiście musisz to robić w ten sposób
swiezak
Nie bardzo wiem jak dolaczac zdjecia, dokumenty, czy atrybuty z wartosciami do danego produktu, nie uzywajac tego typu petli.
kpt_lucek
To opisz co masz i co chcesz uzyskać
swiezak
Sprawa wyglada tak, ze dany produkt moze miec wiele atrybutow: szerokosc, dlugosc, glebokosc, styl, kolor itd. Do tego dochodza wartosci atrybutow, tj. 200cm, 100cm, 20cm, nowoczesny, zielony... Kazdy produkt moze miec min. 10 atrybutow.
Oprocz tego w odrebncyh tabelach na chwile obecna przechowuje dokumenty, zdjecia, dostepnosc produktow (produkt na zamowienie, produkt dostepny etc.) i inne dane. Tych relacji raczej nie przeskocze.
Teraz patrzac przez pryzmat edycji danych z formularza, zastanawiam sie w jaki sposob zoptymalizowac kod. SF2, z tego co zauwazylem, jest zasobozerny. Staram sie korzystac z wbudowanych mechanizmow, tam gdzie moge wykonuje surowe zapytania sql, aby nie generowac duzych obiektow. Mimo wszystko brak mi doswiadczenia, przy tak rozbudowanym projekcie, do tego przyznaje sie bez bicia. Wczesniej pisalem projekty w SF 1.4 i nie mialem takich problemow z wydajnoscia, jak w tej chwili. Dlatego zwrocilem sie do Was po pomoc.
lukaskolista
Pokaż strukturę bazy związaną z wyświetlaniem nazwy pliku "imageField.vars.data.filename" zaczynając od tabeli z obrazkami a kończąc na nazwie pliku.
swiezak


Encja Products:
  1. /**
  2.   * @ORM\OneToMany(targetEntity="Images", mappedBy="products", cascade={"persist"})
  3.   */
  4. protected $images;


Encja Images:
  1. /**
  2.   * @Assert\NotBlank()
  3.   * @ORM\ManyToOne(targetEntity="Ml\BackendBundle\Entity\Products", inversedBy="images", cascade={"persist"})
  4.   * @ORM\JoinColumn(name="product_id", referencedColumnName="id", nullable=false)
  5.   **/
  6. protected $products;


W kontrolerze od edycji obiekt ma spore rozmiary, poniewaz zaciaga niepotrzebne tabele: zamowienia, uzytkownicy itp.
  1. public function editAction($id)
  2. {
  3. $em = $this->getDoctrine()->getManager();
  4.  
  5. $entity = $em->getRepository('MlBackendBundle:Products')->find($id);
  6.  
  7. if (!$entity) {
  8. throw $this->createNotFoundException('Unable to find Products entity.');
  9. }
  10. $editForm = $this->createForm(new ProductsType(), $entity);
  11. $deleteForm = $this->createDeleteForm($id);
  12.  
  13. return $this->render('MlBackendBundle:Products:edit.html.twig', array(
  14. 'entity' => $entity,
  15. 'form' => $editForm->createView(),
  16. 'delete_form' => $deleteForm->createView(),
  17. ));
  18. }


Probuje uzyc query buildera, aby nieco zminimalizowac liczbe joinow:
  1. $qb = $em->getRepository('MlFrontendBundle:Products')->createQueryBuilder('p');
  2. $qb->leftJoin('p.images', 'i')
  3. ->addSelect('i')
  4. ->leftJoin('p.documents', 'd')
  5. ->addSelect('d')
  6. ->leftJoin('p.attributes', 't')
  7. ->addSelect('t')
  8. ->leftJoin('p.brands', 'b')
  9. ->addSelect('b')
  10. ->leftJoin('p.categories', 'c')
  11. ->addSelect('c')
  12. ->leftJoin('p.availability', 'a')
  13. ->addSelect('a')
  14. ->where('p.id = :id')
  15. ->setParameter('id', $id);
  16.  
  17. $entity = $qb->getQuery()->getScalarResult();


Jednak dostaje zwrotke:
  1. The form's view data is expected to be an instance of class Ml\BackendBundle\Entity\Products, but is a(n) array. You can avoid this error by setting the "data_class" option to null or by adding a view transformer that transforms a(n) array to an instance of Ml\BackendBundle\Entity\Products.


Wychodzi na to, ze trzeba dodac do wybranych pol buildera 'data_class' => null.
kpt_lucek
Cytat
  1. $entity = $qb->getQuery()->getScalarResult();


Yyyyyyyyyyyyyyyyyyy ale jak? Po co tak?
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2025 Invision Power Services, Inc.