Category: "Symfony"

mod_proxy_ajp declining URL
Jan 5th
Many hours of searching and suffering were spent trying to resolve this ...
Apache-Error: [file "mod_proxy_ajp.c"] [line 743] [level 7] AH00894: declining URL fcgi://localhost/var/www/html/site/public/index.php
This is a Symfony application (ibexa DXP, formerly eZ Platform, previously eZ Publish), CentOS 8 server, with PHP 7.4, mod_security, selinux enabled, etc.
The error was thrown on the graphql requests to support the sub-items display of the admin interface.
The root cause of the issue was an application error.
The message from Symfony
[2022-01-05T18:34:58.709826+00:00] request.CRITICAL: Uncaught PHP Exception Overblog\GraphQLBundle\Resolver\UnresolvableException: "Could not found type with alias "RepositoryLanguage". Do you forget to define it?" at /var/www/html/site/vendor/overblog/graphql-bundle/src/Resolver/TypeResolver.php line 72 {"exception":"[object] (Overblog\\GraphQLBundle\\Resolver\\UnresolvableException(code: 0): Could not found type with alias \"RepositoryLanguage\". Do you forget to define it? at /var/www/html/site/vendor/overblog/graphql-bundle/src/Resolver/TypeResolver.php:72)"} []
This also took me a long time to unravel - about an hour ... because I haven't worked with eZ in a while
Solution was to copy all these files https://github.com/bgamrat/improved-journey/tree/main/config/graphql/types/ezplatform into the config.
Next, it was time to make this blog post with the goal of helping you!
I changed the username for security, to a word I rarely use. So of course I forgot it. As well as the password. Tried to email a password reset, but that failed too ... selinux, remember?
setsebool -P httpd_can_sendmail 1
I also tried to reset the password at the database level, but that looked like more effort.
So - it was a grand adventure, the installation works and I can go do other things.

Returning custom headers with FOSRestBundle
Jun 27th
The Content-Range header supports a (dgrid) OnDemandGrid
use FOS\RestBundle\View\View as FOSRestView;
...
$view = FOSRestView::create();
$view->setData($data);
$view->setHeader( 'Content-Range', 'items '.$offset.'-'.($offset+$limit).'/'.$count);
$handler = $this->get('fos_rest.view_handler');
return $handler->handle($view);
Ref: https://symfony.com/doc/1.5/bundles/FOSRestBundle/2-the-view-layer.html
Symfony 4

Symfony 4 - Multiple DataFixtures files
May 13th
I recently upgraded a Symfony 3.3 application to Symfony 4
Part of the upgrade was loading the DataFixtures.
Symfony 4 recommends you put all your DataFixtures in a single file. I'll get around to that later. However, due to the way I organized the file system for the project, the Doctrine Fixtures Loader could not find the demo data.
To resolve the issue, I created a services_dev.yaml file with the following:
services:
App\DataFixtures\Demo\:
resource: '../src/DataFixtures/Demo/*'
tags: [ doctrine.fixture.orm ]
Once I added this file to the development server, the data loaded fine.
Ref: https://symfony.com/doc/master/bundles/DoctrineFixturesBundle/index.html#multiple-files

Upgrading from Symfony 3 to 4 - JSON Database Content
Apr 23rd
My latest adventure has been to upgrade a web application from Symfony 3.3 to 4. All the pages load and I am starting to test execution.
This error came up and I scoured the code for instances of AppBundle
Then I checked the database.
One of the attributes is custom_attributes, which is a JSON column. Sample content:
[{"#type":"AppBundle\\Entity\\CustomAttribute","key":"expiration","value":"2018-02-26","valueValidExpiration":true,"valueValidChannels":true},{"#type":"AppBundle\\Entity\
\CustomAttribute","key":"channels","value":6,"valueValidExpiration":true,"valueValidChannels":true}]
I am using https://github.com/dunglas/doctrine-json-odm to provide JSON data within entities.
To change AppBundle to App, I used:
dev=# UPDATE asset SET custom_attributes= REPLACE(custom_attributes::TEXT,'AppBundle','App')::json;
UPDATE 25
dev=# UPDATE model SET custom_attributes= REPLACE(custom_attributes::TEXT,'AppBundle','App')::json;
UPDATE 9

Revised Dijit/Tree Menu for Symfony
Feb 14th
This is the final implementation for the tree menu I'm using for a Symfony application.
The first approach wrote the JSON for the menu store into the template. This was a little awkward and as the application grew, the amount of content increased.
Next, I had an idea that I could create a multi-tiered tree which would have a static top tier, but include content in the lower levels. The value of this was that the user could navigate directly to content, for example the details of a client. Lazy loading was used to limit the number of requests and size of responses. It worked. But it was slow. And I realized that as the amount of data increased, it would get slower. In addition, navigating through three or more levels on a tree is inefficient. So, I wanted something that was faster and streamlined.
The first change I made was to add an autocomplete search box, which allows the user enter the name of the item they need. It could be the name of a person, a barcode or a company. The search code isn't complete.
Then I rewrote the JsonRenderer and the MenuStoreController such that there is only a static menu delivered via Ajax.
services.yml
These are the service configurations.
app.admin.menu_store_controller:
class: AppBundle\Controller\Api\Admin\Common\MenuStoreController
app.menu_renderer:
class: AppBundle\Menu\JsonRenderer
arguments: [ "@twig", "knp_menu.html.twig", "@knp_menu.matcher", {"translator": "@translator" }]
tags:
- { name: knp_menu.renderer, alias: json }
MenuStoreController
The MenuStoreController calls the KnpMenuBuilder, then the JsonRenderer and delivers the result as JSON to the client.
<?php
namespace AppBundle\Controller\Api\Admin\Common;
use FOS\RestBundle\Controller\FOSRestController;
use Symfony\Component\HttpFoundation\Request;
use FOS\RestBundle\Controller\Annotations\View;
class MenuStoreController extends FOSRestController
{
/**
* @View()
*/
public function getAdminmenuAction( Request $request )
{
$this->denyAccessUnlessGranted( 'ROLE_ADMIN', null, 'Unable to access this page!' );
$adminMenu = $this->get( 'app.menu_builder' )->createAdminMenu( [] );
$renderer = $this->get( 'app.menu_renderer' );
return array_values( $renderer->render( $adminMenu ) );
}
}
JsonRenderer
The JsonRenderer iterates through the menu and creates the data for the dijit/Tree.
<?php
namespace AppBundle\Menu;
use Knp\Menu\ItemInterface;
use Knp\Menu\Matcher\MatcherInterface;
use Knp\Menu\Renderer\RendererInterface;
class JsonRenderer implements RendererInterface
{
/**
* @var \Twig_Environment
*/
private $environment;
private $matcher;
private $defaultOptions;
private $translator;
/**
* @param \Twig_Environment $environment
* @param string $template
* @param MatcherInterface $matcher
* @param array $defaultOptions
*/
public function __construct( \Twig_Environment $environment, $template, MatcherInterface $matcher, array $defaultOptions = array() )
{
$this->environment = $environment;
$this->matcher = $matcher;
$this->defaultOptions = array_merge( array(
'depth' => null,
'matchingDepth' => null,
'currentAsLink' => true,
'currentClass' => 'current',
'ancestorClass' => 'current_ancestor',
'firstClass' => 'first',
'lastClass' => 'last',
'template' => $template,
'compressed' => false,
'allow_safe_labels' => false,
'clear_matcher' => true,
'leaf_class' => null,
'branch_class' => null
), $defaultOptions );
}
public function render( ItemInterface $item, array $options = array() )
{
$options = array_merge( $this->defaultOptions, $options );
if( empty( $options['depth'] ) )
{
$options['depth'] = PHP_INT_MAX;
}
$this->translator = $options['translator'];
$itemIterator = new \Knp\Menu\Iterator\RecursiveItemIterator( $item );
$iterator = new \RecursiveIteratorIterator( $itemIterator, \RecursiveIteratorIterator::SELF_FIRST );
$tree = [];
$parent = null;
$levelParent[0] = null;
$lastLevel = null;
foreach( $iterator as $item )
{
$translatedLabel = $this->translator->trans( $item->getLabel() );
$id = strtolower( $item->getName() );
$level = $item->getLevel();
if( $level <= $options['depth'] )
{
$node = [];
$node['id'] = $id;
$node['name'] = $translatedLabel;
$node['uri'] = $item->getUri();
$node['has_children'] = $item->hasChildren();
$node['level'] = $level;
if( $lastLevel !== null )
{
if( $level > $lastLevel )
{
$parent = $levelParent[$level] = $lastNode['id'];
}
else
{
if( $level < $lastLevel )
{
$parent = $levelParent[$level];
}
}
$lastParent = $parent;
}
$node['parent'] = $parent;
$tree[$id] = $node;
$lastLevel = $level;
$lastNode = $node;
}
}
return $tree;
}
}
menu.js
menu.js requests the menu data and renders it on the page.
define([
"dojo/dom",
"dojo/request/xhr",
"dojo/store/Memory",
"dijit/tree/ObjectStoreModel",
"dijit/Tree",
"dojo/store/JsonRest",
"dijit/form/ComboBox",
"dojo/i18n!app/nls/core",
"dojo/domReady!"
], function (dom,
xhr, Memory, ObjectStoreModel, Tree, JsonRest, ComboBox, core) {
//"use strict";
function run() {
var searchInput, searchStore;
xhr.get("/api/menustore/adminmenus/", {
handleAs: "json"
}).then(function (res) {
var i, l, store = [], memory, model;
l = res.length;
for( i = 0; i < l; i++ ) {
store.push(res[i]);
}
memory = new Memory({
data: store,
getChildren: function (object) {
return this.query({parent: object.id});
}
});
// Create the model
var model = new ObjectStoreModel({
store: memory,
query: {id: 'admin'},
mayHaveChildren: function(object) {
return typeof object.has_children !== "undefined" && object.has_children;
}
});
// Create the Tree.
var tree = new Tree({
id: "admin-menu",
model: model,
persist: true,
showRoot: false,
onClick: function (item) {
if( typeof item.uri !== "undefined" && item.uri !== null ) {
location.href = item.uri;
}
},
getIconClass: function (item, opened) {
return (item && item.has_children) ? (opened ? "dijitFolderOpened" : "dijitFolderClosed") : "dijitLeaf"
}
}, "admin-left-menu");
tree.startup();
});
searchStore = new JsonRest({
target: '/api/store/search',
useRangeHeaders: false,
idProperty: 'id'});
searchInput = new ComboBox({
trim: true,
"class": "search",
store: searchStore,
searchAttr: "name",
placeholder: core.search
}, "search");
searchInput.startup();
searchInput.on("change", function (evt) {
console.log(evt);
console.log(searchInput.store);
});
}
return {
run: run
};
});
//# sourceURL=menu.js