Need help extending a class in a Drupal 8 custom module - drupal

I'm using the elasticsearch connector module for Drupal 8 to manage indexes. https://www.drupal.org/project/elasticsearch_connector
One thing I need to happen on index creation time is to add some analyzers and filters to the elasticsearch index mapping.
I've tracked down the place that's happening here in the create() method for one of their classes:
modules\contrib\elasticsearch_connector\src\ElasticSearch\Parameters\Factory\IndexFactory.php
I have a custom module that I'm trying to extend that method and add to it but can't seem to get it to fire. I have the module enabled properly as it shows up on the extend page. So far my folder structure for my custom module looks like this:
modules\custom\elasticsearch_analyzer\src\elasticsearch_analyzer.php
<?php
namespace Drupal\elasticsearch_analyzer;
use Drupal\search_api\IndexInterface;
use Drupal\elasticsearch_connector\ElasticSearch\Parameters\Factory\IndexFactory;
class elasticsearch_analyzer extends IndexFactory {
/**
* {#inheritdoc}
*/
public static function create(IndexInterface $index) {
die('does this work?');
return [
'index' => IndexFactory::getIndexName($index),
'body' => [
'settings' => [
'number_of_shards' => $index->getOption('number_of_shards', 5),
'number_of_replicas' => $index->getOption('number_of_replicas', 1),
],
],
];
}
}
I've tried a bunch of different combinations surrounding the PSR-4 standard, but can't seem to get it to run. Is this how we'd normally go about extending things in Drupal 8? Should I just be putting this class in the .module file at the root of my module? Should I be registering this as a service?
Based on the tutorials I've read, I haven't been able to see any examples of extending contributed modules or core outside of creating block plugins or simple route pages. I thought now that we're out of the hook world I should be able to examine code within an existing class and override it using OOP inheritance. Or is that not the case?

You cannot just extend a class. How is the module supposed to call this new class? How to you communicate its existence to other modules so they are able to call it? Your class in isolation does not do anything.
I was reading through the source code of this module. This IndexFactory class is used in the SearchApiElasticsearchBackend class which is registered as a backend to the Search API module (i.e. the search api knows that this backend exists and calls it).
Sadly this IndexManager class is just hardcoded into the backend and there is no way to inject your own. The class has static methods only so they are called directly. Even if you would create your own search backend and extend SearchApiElasticsearchBackend you would have to replace IndexManager with YourCustomIndexManager everywhere.
You will have to change the create() method directly in module to do what you want.

Related

How to check if Laravel blade component is loaded?

I'm contributing to a package that will provide some blade components. So, the users of this package may use the components on a blade template as:
<x-mypackage-component-a/>
The components are located under the src/Components folder of my package. These components are loaded in the package service provider using the loadViewComponentsAs() method as explained here:
$this->loadViewComponentsAs('mypackage', [
Components\ComponentA::class,
...
]);
Now, I need to make some tests for phpunit that should check that the components are loaded by the package service provider, something like next:
public function testComponentsAreLoaded()
{
$this->assertTrue(/*code that check 'x-mypackage-component-a' exists*/);
}
Is there any way (using the Laravel framework) to check a blade component name exists and/or is loaded?
I have manage to do something similar for a set of blade views provided by the package with next code:
// Views are loaded on the package service provider as:
$this->loadViewsFrom($viewsPath, 'mypackage');
// The phpunit test method is:
public function testViewsAreLoaded()
{
$this->assertTrue(View::exists('mypackage::view-a'));
$this->assertTrue(View::exists('mypackage::view-b'));
...
}
Thanks in advance!
Finally managed to find a way to solve this, I'm going to explain this because it may be useful for other readers. First, you need to load the set of views that are used by the component classes (the ones you usually use on the render() method). In my particular case the component views are located on the resources/components folder, so I had to insert next code on the boot() method of my package's service provider:
// Load the blade views used by the components.
$viewsPath = $this->packagePath('resources/components');
$this->loadViewsFrom($viewsPath, 'mypackage');
Where packagePath() is a method that returns the fully qualified path (from the package root folder) to the received argument.
Next, again in the boot() method, I had to load the components as explained on the question:
$this->loadViewComponentsAs('mypackage', [
Components\ComponentA::class,
Components\ComponentB::class,
...
]);
Finally, in order to make a test that asserts the views and the components are loaded correctly by the service provider, I have created the next method to be used with phpunit:
public function testComponentsAreLoaded()
{
// Check that the blade component views are loaded.
$this->assertTrue(View::exists('mypackage::component-a'));
$this->assertTrue(View::exists('mypackage::component-b'));
...
// Now, check that the class components aliases are registered.
$aliases = Blade::getClassComponentAliases();
$this->assertTrue(isset($aliases['mypackage-component-a']));
$this->assertTrue(isset($aliases['mypackage-component-b']));
...
}
As an additional information, I must say that my phpunit test classes inherits from Orchestral/testbench TestCase class, and you may need to include the View and Blade facades on your test file. I'm also using the next method to ensure the boot() method of my package's service provider executes on my test environment before running the tests:
protected function getPackageProviders($app)
{
return ['Namespace\To\MyPackageServiceProvider'];
}
there is no method or helper for checking that a component exists or not but from then blade components are class in laravel, so you can check that your specific component class exists or not:
// application namespaces
namespace App\View\Components;
use Illuminate\View\Component;
// define component
class mypackage extends Component { ... }
// check component
public function testViewsAreLoaded(){
$this->assertTrue(class_exists('\Illuminate\View\Component\mypackage'));
...
}

Differences between different methods of Symfony service collection

For those of you that are familiar with the building of the Symfony container, do you know what is the differences (if any) between
Tagged service Collector using a Compiler pass
Tagged service Collector using the supported shortcut
Service Locator especially, one that collects services by tags
Specifically, I am wondering about whether these methods differ on making these collected services available sooner or later in the container build process. Also I am wondering about the ‘laziness’ of any of them.
It can certainly be confusing when trying to understand the differences. Keep in mind that the latter two approaches are fairly new. The documentation has not quite caught up. You might actually consider making a new project and doing some experimenting.
Approach 1 is basically an "old school" style. You have:
class MyCollector {
private $handlers = [];
public function addHandler(MyHandler $hamdler) {
$handlers[] = $handler;
# compiler pass
$myCollectorDefinition->addMethodCall('addHandler', [new Reference($handlerServiceId)]);
So basically the container will instantiate MyCollector then explicitly call addHandler for each handler service. In doing so, the handler services will be instantiated unless you do some proxy stuff. So no lazy creation.
The second approach provides a somewhat similar capability but uses an iterable object instead of a plain php array:
class MyCollection {
public function __construct(iterable $handlers)
# services.yaml
App\MyCollection:
arguments:
- !tagged_iterator my.handler
One nice thing about this approach is that the iterable actually ends up connecting to the container via closures and will only instantiate individual handlers when they are actually accessed. So lazy handler creation. Also, there are some variations on how you can specify the key.
I might point out that typically you auto-tag your individual handlers with:
# services.yaml
services:
_instanceof:
App\MyHandlerInterface:
tags: ['my.handler']
So no compiler pass needed.
The third approach is basically the same as the second except that handler services can be accessed individually by an index. This is useful when you need one out of all the possible services. And of course the service selected is only created when you ask for it.
class MyCollection {
public function __construct(ServiceLocator $locator) {
$this->locator = $locator;
}
public function doSomething($handlerKey) {
/** #var MyHandlerInterface $handler */
$handler = $serviceLocator->get($handlerKey);
# services.yaml
App\MyCollection:
arguments: [!tagged_locator { tag: 'app.handler', index_by: 'key' }]
I should point out that in all these cases, the code does not actually know the class of your handler service. Hence the var comment to keep the IDE happy.
There is another approach which I like in which you make your own ServiceLocator and then specify the type of object being located. No need for a var comment. Something like:
class MyHandlerLocator extends ServiceLocator
{
public function get($id) : MyHandlerInterface
{
return parent::get($id);
}
}
The only way I have been able to get this approach to work is a compiler pass. I won't post the code here as it is somewhat outside the scope of the question. But in exchange for a few lines of pass code you get a nice clean custom locator which can also pick up handlers from other bundles.

Symfony calling functions between controllers

Can anyone give me direction on how to accomplish cross controller variable exchange and/or function calls?
I'm new to Symfony and I have a reasonably complex practice sample site which has two controllers - PageController and BlogController.
PageController has actions to generate my home, about and contact page. The home page simply has a list of blogs.
The BlogController has all the CRUD related functions - create, delete etc
My issue is that I want to call my BlogController:createAction function from the PageController so I can present a blog create form above the blog listings on the homepage OR just pass the variable containing the new blog form data.
In addition, I need to find a solution which will allow the form to submit and the listings to refresh via AJAX.
Although #Tom Toms answer is correct, I would recommend another approach in order to minimize dependencies:
Just create a route for your Blog function and then use $this->redirectToRoute in your PageController thereby the action will be simply redirected to the assigned route, so you have no forwarding (although I think that the forwad action will create nothing more then a redirect) or service implementation with injections just a simple redirect
Either using forward method directy
$response = $this->forward('AcmeDemoBundle:Blog:myFunction', array(
'name' => $name,
));
http://symfony.com/doc/current/book/controller.html#forwarding-to-another-controller
Or alternatively, you could define the blog controller as a service.
service.yml
services:
your_service:
class: Acme\DemoBundle\Controller\BlogController
Then you can use
$service = $this->get('your_service');
$service->myFunction();
You could also look into setting up a form factory service (bit more advanced)
UPDATE:
Following your post I implemented the a service but got the following error:
Error: Call to a member function get() on a non-object
This got fixed when I added the following line to the service declaration:
calls:
- [ setContainer, [ #service_container ]]

Silverstripe's Versioned feature for dataobjects in new release (3.2)

I want to audit-trail all changes made to dataobjects. Say I have Event dataobject and I want to know who changed it, when changed, what is changed etc. (Similar to Pages).
Silverstripe site recommends the use of Verioned but I can't find any examples of implementation. It says the best example is Pages which is is already comes with Versioned implemented. The basic rule is to define an augmentDatabase() method on your decorator.
So, I want to use DataExtention for dataobject (extension) and then use the extended one for my Event dataobject. But is there any simple example?
Assuming you want to manage and monitor multiple versions of the event DataObject, you simply need to declare that you want to use the versioned extension for thatDataObject
Class Event extends DataObject{
static $extensions = array(
"Versioned('Stage', 'Live')"
);
...
}
Then run a dev/build
You should now have a Event, Event_Live, and Event_versions tables.
You can then have a look at the methods available in Versioned.php, and use them with Event, ie publish(). This should get you started.
"Versioning in SilverStripe is handled through the Versioned class. It's a DataExtension, which allow it to be applied to any DataObject subclass."
"Similarly, any subclass you create on top of a versioned base will trigger the creation of additional tables, which are automatically joined as required."
Here is link to read further with examples
Versioning of Database Content

Flex: load parent class into module

My main application contains a ClassA. The main application then loads a module and in that module I would like would to do:
var classA:InterfaceClassA = new ClassA();
It compiles fine but I get this warning:
Warning: YourApplication is a module or application that is directly referenced. This will cause YourApplication and all of its dependencies to be linked in with module.YourModule:Module. Using an interface is the recommended practice to avoid this.
I can't use an interface to generate the new instance, so what is the correct way to do this?
I found an answer in Accessing the parent application from the modules . I created a helper class in the main Application that contains instances of the classes I want to access. In the module I then use:
parentApplication.myModuleHelper.**myClassInstance**.myMethod();
for instance methods and for static class level methods I use:
parentApplication.myModuleHelper.**MyClassInstance**.myMethod()
To get an instance of my class in a module I use this in MyModuleHelper
public function getFullScreenUtil(iFullScreenUtil:IFullScreenUtil , iSystemManager:ISystemManager):FullScreenUtil {
return new FullScreenUtil(iFullScreenUtil , iSystemManager);
}
and this in MyModule:
var _fullScreenUtil:* = parentApplication.moduleHelper.getFullScreenUtil(this , systemManager);
which is all I need for now. I am not sure how I could cast the result of getFullScreenUtil(..) to an actual instance of FullScreenUtil but I expect that it can not be done in modules. Maybe using an interface would provide the solution to that.

Resources