Calling the default exceptionHandler equivalent in SilverStripe 4 - silverstripe-4

I've searched through the documentation and the API for SilverStripe 4 but I'm having trouble using the appropriate class to call the default exception handler.
How it worked before SilverStripe 4 within my Page.php controller's init()
parent::init();
set_exception_handler(function($exception) {
if( get_class($exception) == 'CustomException' ) {
Controller::curr()->handleCustomException($exception);
}
return exceptionHandler($exception);
});
How I expect it to work with SilverStripe 4
parent::init();
set_exception_handler(function($exception) {
if( get_class($exception) == 'CustomException' ) {
Controller::curr()->handleCustomException($exception);
}
return <SomeMonologClass>::handleException($exception);
});
I'm aware of SilverStripe 4 now using Monolog and I've come across the handleException function which I believe is what I need to call instead of exceptionHandler($exception).
Any advice would be much appreciated.

use SilverStripe\Control\Controller;
class MyController extends Controller
{
private static $dependencies = [
'logger' => '%$Psr\Log\LoggerInterface',
];
// This will be set automatically, as long as MyController is instantiated via Injector
public $logger;
protected function init()
{
$this->logger->debug("MyController::init() called");
parent::init();
}
}
As described here:
https://docs.silverstripe.org/en/4/developer_guides/debugging/error_handling/#accessing-the-logger-via-dependency-injection
And even more info here: https://www.silverstripe.org/learn/lessons/v4/advanced-environment-configuration-1

Related

Link to some action of a different CRUD controller

Is there any workaround to link a new action, in a CRUD controller made with EasyAdmin 4.x , to an action in another CRUD controller with which it has a OneToMany relation ?
class FirstEntityCrudController extends AbstractCrudController
{
...
public function configureActions(Actions $actions): Actions
{
return $actions
->add(Crud::PAGE_INDEX, Action::new('add-second-entity','Add a second entity')
->linkToCrudAction(Action::NEW ???)
)
;
}
}
The docs say that I can use:
linkToCrudAction(): to execute some method of the current CRUD controller;
But there seems to be no indication on how to "execute some method of a different CRUD controller".
Note:
There is a sneaky way around it but it doesn't seem healthy :
->linkToUrl('the url to the desired action')
Using:
PHP 8.1
Symfony 5.4
EasyAdmin 4.x
Following #Ruban's comment, and as EasyAdminBundle's docs mention, we can generate a URL for the desired action using the AdminUrlGenerator class as follow:
class FirstEntityCrudController extends AbstractCrudController
{
private $adminUrlGenerator;
public function __construct(AdminUrlGenerator $adminUrlGenerator)
{
$this->adminUrlGenerator = $adminUrlGenerator;
}
...
public function configureActions(Actions $actions): Actions
{
//The magic happens here 👇
$url = $this->adminUrlGenerator
->setController(SecondEntityCrudController::class)
->setAction(Action::NEW)
->generateUrl();
return $actions
//->...
->add(Crud::PAGE_INDEX,
Action::new('add-second-entity', 'Add second entity')
->linkToUrl($url)
);
}
}
This worked for me.

Why do actions with class and public method don't fire __construct()

I'm trying to understand how WordPress works with actions, classes, and methods.
If there is a class "TestClass" and it has a public method 'method1'
The method can be hooked to any action as "add_action('theHook', ['TestClass', 'method1']);"
From my understanding. If you don't initialize the class, you can not access its public methods and objects. Now, I would assume that WordPress has to follow this, and it must initialize my "TestClass", which will cause for public __construct() to fire.
However, after testing this, it does not fire __construct()..
Why is this?. I know a fix would be to self initialize inside 'method1', but I'm trying to figure out why WordPress behaves this way.
Because WordPress call your method as a static function: TestClass::method()
There is various solution:
1. Init class before add Action
Initialize your class before add action, like that:
$test = new TestClass();
add_action('hook', [$test, 'method']);
2. Call hook inside your Class:
class TestClass {
public function __construct() {
// Your construct
}
public function method() {
// Your Method
}
public function call_hook() {
add_action('hook', [$this, 'method']);
}
}
$test = new TestClass();
$test->call_hook();
3. Use a singleton
And if you need to to have only one instance of your class and call it in various place, you have to take a look to Singleton design pattern.
Demonstration:
class MySingletonClass {
private static $__instance = null;
private $count = 0;
private function __construct() {
// construct
}
public static function getInstance() {
if (is_null(self::$__instance)) {
self::$__instance = new MySingletonClass();
}
return self::$__instance;
}
public function method() {
$this->count += 1;
error_log("count:".$this->count);
}
}
$singleton = MySingletonClass::getInstance();
add_action('wp_head', [$singleton, 'method']);
$singleton2 = MySingletonClass::getInstance();
add_action('wp_footer', [$singleton2, 'method']);

PostWrite event using services according to an event's property

I have an EventSubscriberInterface called PostCreation listening to the POST_WRITE event.
The method called when the event occurs must call a service according to the "type" attribute of the object which the event occured on.
The following code is a working example of what I want:
<?php
class PostCreation implements EventSubscriberInterface
{
private $processAfterPostCreationTypeAService;
private $processAfterPostCreationTypeBService;
public function __construct(
ProcessAfterPostCreationTypeAService $processAfterPostCreationTypeAService,
ProcessAfterPostCreationTypeBService $processAfterPostCreationTypeBService
) {
$this->processAfterPostCreationTypeAService = $processAfterPostCreationTypeAService;
$this->processAfterPostCreationTypeBService = $processAfterPostCreationTypeBService;
}
public static function getSubscribedEvents(): array
{
return [KernelEvents::VIEW => ['processPostCreation', EventPriorities::POST_WRITE]];
}
public function processPostCreation(GetResponseForControllerResultEvent $event): void
{
$myObject = $event->getControllerResult();
if (!$event->getRequest()->isMethod('POST') ||
!$myObject instanceof MyClass
) {
return;
}
/* some code */
if($myObject->getType() === 'A') {
$this->processAfterPostCreationTypeAService->doSomething($myObject);
} else if($myObject->getType() === 'B') {
$this->processAfterPostCreationTypeBService->doSomething($myObject);
}
/* some code */
}
}
Doing it that way is not maintainable so I would like to find another solution, but I can't find it myself so I need help:
The constructor of PostCreation can't know the object.type because it's called before the event occurs
I can't instantiate easily ProcessAfterPostCreationTypeAService or ProcessAfterPostCreationTypeBService in processPostCreation method because they are services as well (and needs dependancy injection, configured to be autowired)
I'm sure dependancy injection could help me, but I can't find how.
So how to do something maintainable?

Setting up a framework-only SilverStripe site

I'm looking to create a framework only SilverStripe website, however I have been unable to correctly set up the routing for it.
I want to have a single controller handling a handful of URLs. I want it to handle an empty URL too, i.e. '/'.
I've been unable to get my controller to differentiate between the different urls.
My routes are as follows:
---
Name: rootroutes
---
Director:
rules:
'$Action/$ID/$OtherID': 'MainController'
'': 'MainController'
and my controller:
class MainController extends Controller {
private static $url_handlers = array(
'$Action//$ID/$OtherID' => 'handleAction',
);
public function index() {
return "index";
}
public function login() {
return "login";
}
public function handleAction($request, $action) {
var_dump($action); // always 'index'
if($this->hasMethod($action)) {
return $this->$action();
}
}
}
You need to define the $allowed_actions array on your controller before actions other than index() will work.

Singleton between different controller actions in Symfony2

Assume we have singleton class
class Registry {
private static $_instance;
private function __construct() {}
private function __wakeup() {}
private function __clone() {}
private $_map = array();
public static function getInstance () {
if (self::$_instance === null)
self::$_instance = new self();
return self::$_instance;
}
public function set ($key, $val) {
self::getInstance()->_map[$key] = $val;
return self::getInstance();
}
public function get($key)
{
if (array_key_exists($key, self::getInstance()->_map))
return self::getInstance()->_map[$key];
return null;
}
}
And we have simple Symfony2 Controller with 2 actions
class IndexController {
public function indexAction () {
Registry::getInstance()->set('key',true);
return new Response(200);
}
public function secondAction () {
$val = Registry::getInstance()->get('key');
return new Response(200);
}
}
I call index action, then second action. But I can't find key, that was set in first action. I think, new instance of singleton creates in my second action. Why object is not saved in memory? What do I do wrong?
If you call indexAction and secondAction in different requests it won't work the way you want it because your Registry instance is not shared between requests.
Singleton itself does not store anything "in memory" (BTW Singleton is now considered as an anti-pattern).
What, I think, you want to achieve can be done by using session storage. Check doc for more info how to implement this.

Resources