How to override the existing method of an existing class, FlysystemAssetStore that is part of the framework in SilverStripe - silverstripe

I am working on a SilverStripe project, in my project, I need to change the behavior of a class that is part of the SilverStripe framework. The class I need to modify is SilverStripe\Assets\Flysystem\FlysystemAssetStore. For example, I am now trying to modify the exists method of the class. I tried using two options Injector and Extension. Both did not work.
The first option I tried is using the Injector. This is what I did.
First, I created a class called, CustomFlysystemAssetStore.
Then I added the following code to the mysite.yml
SilverStripe\Assets\Flysystem\FlysystemAssetStore:
class: CustomFlysystemAssetStore
I declared the public function called exists in the CustomFlysystemAssetStore class to override the existing behavior. But it did not work. The new method within the new class was not executed at all.
The second option I tried is using the Extension. This is what I did.
First, I created a class called CustomFlysystemAssetStore that is extending the DataExtension class.
Then, I added the following code snippet into the mysite.yml.
SilverStripe\Assets\Flysystem\FlysystemAssetStore:
extensions:
- CustomFlysystemAssetStore
Then I declared a public method in the new class called exists to see if the new method is called.
Unfortunately, the second approach did not work either. How can I override the methods of the SilverStripe\Assets\Flysystem\FlysystemAssetStore class that is part of the framework?
This is my assets.yml file
---
Name: silverstripes3-flysystem
Only:
envvarset: AWS_BUCKET_NAME
After:
- '#assetsflysystem'
---
SilverStripe\Core\Injector\Injector:
Aws\S3\S3Client:
constructor:
configuration:
region: '`AWS_REGION`'
version: latest
League\Flysystem\Adapter\Local:
class: League\Flysystem\Adapter\Local
constructor:
root: '`TEMP_PATH`'
SilverStripe\S3\Adapter\PublicAdapter:
constructor:
s3Client: '%$Aws\S3\S3Client'
bucket: '`AWS_BUCKET_NAME`'
prefix: '`AWS_PUBLIC_BUCKET_PREFIX`'
League\Flysystem\Cached\Storage\Memory.public:
class: League\Flysystem\Cached\Storage\Memory
League\Flysystem\Cached\Storage\Adapter.public:
class: League\Flysystem\Cached\Storage\Adapter
constructor:
adapter: '%$League\Flysystem\Adapter\Local'
file: 's3metadata/public'
expire: 259200
SilverStripe\Assets\Flysystem\PublicAdapter:
class: SilverStripe\S3\Adapter\PublicCachedAdapter
constructor:
adapter: '%$SilverStripe\S3\Adapter\PublicAdapter'
cache: '%$League\Flysystem\Cached\Storage\Adapter.public'
SilverStripe\S3\Adapter\ProtectedAdapter:
constructor:
s3Client: '%$Aws\S3\S3Client'
bucket: '`AWS_BUCKET_NAME`'
prefix: '`AWS_PROTECTED_BUCKET_PREFIX`'
League\Flysystem\Cached\Storage\Adapter.protected:
class: League\Flysystem\Cached\Storage\Adapter
constructor:
adapter: '%$League\Flysystem\Adapter\Local'
file: 's3metadata/protected'
expire: 259200
SilverStripe\Assets\Flysystem\ProtectedAdapter:
class: SilverStripe\S3\Adapter\ProtectedCachedAdapter
constructor:
adapter: '%$SilverStripe\S3\Adapter\ProtectedAdapter'
cache: '%$League\Flysystem\Cached\Storage\Adapter.protected'
#---
Name: silverstripes3-assetscore
Only:
envvarset: AWS_BUCKET_NAME
After:
- '#assetscore'
---
SilverStripe\Core\Injector\Injector:
SilverStripe\Assets\Storage\AssetStore:
class: CustomFlysystemAssetStore

In Silverstripe 4.5 we can extend FlysystemAssetStore and define our own exists method.
First we create a CustomFlysystemAssetStore.php file in our project:
app/src/CustomFlysystemAssetStore.php
use SilverStripe\Assets\Flysystem\FlysystemAssetStore;
class CustomFlysystemAssetStore extends FlysystemAssetStore {
public function exists($filename, $hash, $variant = null)
{
// Custom logic goes here
// ...
// Fallback to the parent exists function
return parent::exists($filename, $hash, $variant);
}
}
We then set this as the AssetStore we want the system to use through a yml config file. We create an assets.yml file:
app/_config/assets.yml
---
Name: app-assetscore
After:
- '#assetscore'
---
SilverStripe\Core\Injector\Injector:
SilverStripe\Assets\Storage\AssetStore:
class: CustomFlysystemAssetStore

Related

How to use libphonenumber.phone_number_util in Symfony 4

To parse phone number I need to use libphonenumber.phone_number_util in my controller ( Symfony 4) as like as :
$parsed = $this->get('libphonenumber.phone_number_util')->parse($phoneNo);
as we have libphonenumber.phone_number_util in private I wanted to make it public by adding this helper in service as below:
services:
libphonenumber\PhoneNumberUtil:
alias: libphonenumber.phone_number_util
public: true
But this returns Exception and message:
"message": "The \"libphonenumber.phone_number_util\" service or alias has been removed or inlined when the container was compiled. You should either make it public, or stop using the container directly and use dependency injection instead.",
"class": "Symfony\\Component\\DependencyInjection\\Exception\\ServiceNotFoundException",
If you are using this in a controller method (which I presume you do based on $this->get(...)), you need to
1) Declare your controller as a service and tag it with controller.service_arguments tag
2) Make sure your util service id matches the class name (I suppose it does already). You don't need it to be public - that's and ancient approach
3) Require the util as a parameter to your controller's action method.
E.g.
services:
libphonenumber\PhoneNumberUtil:
alias: libphonenumber.phone_number_util
AppBundle\Controller\MyController:
tags: ['controller.service_arguments']
and
public function validatePhoneAction(Request $request, PhoneNumberUtil $phoneNumberUtil)
{
...
$phoneNumberUtil->parse($request->request->get('phone_number');
...
}
There is a nice Symfony blog post about these changes in dependency management: https://symfony.com/blog/new-in-symfony-3-4-services-are-private-by-default

add a virtual property in easy-admin bundle

I'm currently working with the 2.3 version of the easy-admin bundle in Symfony 4.
I try to create a virtual property for the new view.
I have the following configuration
#config/packages/easy_admin.yaml
easy_admin:
entities:
Field:
class: App\Entity\Field
form:
fields:
- { type: tab, label: initial information, icon: pencil-alt }
- name
new:
fields:
- { property: toto, type: file }
and my entity file:
//src/Entity/Field.php
/**
* #ORM\Entity(repositoryClass="App\Repository\FieldRepository")
*/
class Field
{
public function setToto(?File $file): self
{
$this->setImage(new Image);
$this->getImage()->setImageFile($file);
}
as explain in the documentation the setter should be sufficient.
but when I reach the new page I get the following error:
Neither the property "toto" nor one of the methods "getToto()", "toto()", "isToto()", "hasToto()", "__get()" exist and have public access in class "App\Entity\Field".
which means that the page is looking for getter and not setter. Is it normal or did I make something wrong ?
I have just ran into this issue and I have solved it by adding the getter.
As you said, it is looking for getter but also setter.

Read from parameters in Symfony 3.4 getParameter null

I can't read a parameter from parameters.yml in my controller.
I want to do this:
//My Controller
class ExampleController extends Controller
{
function someMethod($argument)
{
dump($this->getParameter('free_proxy'));die();
and in parameters.yml I got:
parameters:
free_proxy: "http://xxx:8080"
I get an error: Call to a member function getParameter() on null
I've tested some solutions like adding some services and using get and stuff but nothing works.
EDIT: also, I tried this:
services:
_defaults:
autowire: true
autoconfigure: true
public: false
bind:
$freeProxy: '%free_proxy%'
Then using:
$this->container->getParameter('free_proxy');
But I got an error: Unused binding "$freeProxy" in service...
So there are two mysteries here. First is why is the container not being injected which in turn causes getParameter to fail. And second, why does bind generate that unused binding error.
You did not show your routing but I suspect that somewhere along the line you actually have:
$exampleController = new ExampleController();
If so then this explains why getParameter is failing. You really need to let Symfony create the controller based on the route. Otherwise the container is not injected and other controller magic is skipped.
I installed a fresh 3.4 app with the old directory structure and added a parameter
composer create-project symfony/framework-standard-edition s34
# app/config/parameters.yml
parameters:
free_proxy: "http://xxx:8080"
I then tweaked the default controller using the default route annotation:
class DefaultController extends Controller
{
/**
* #Route("/", name="homepage")
*/
public function indexAction(Request $request)
{
$freeProxy = $this->getParameter('free_proxy');
// replace this example code with whatever you need
return $this->render('default/index.html.twig', [
'base_dir' => realpath($this->getParameter('kernel.project_dir')).DIRECTORY_SEPARATOR.$freeProxy,
]);
}
}
And everything worked as expected. The Symfony request handler takes care of injecting the container and thus gives you access to the parameters. If you cannot get this working then please update your question with your routing information.
I then took a look at the bind issue. You really want to inject these parameters instead of pulling them. I updated services.yml
# app/config/services.yml
services:
bind:
$freeProxy: '%free_proxy%'
And started getting those unused binding errors. It turns out that bind does not work for action injection. Not really sure why. I don't use it much but I really would have expected that just adding $freeProxy to your action method would work. In any event, here is a working example of the proper way to do things.
class ExampleController extends Controller
{
private $freeProxy;
public function __construct($freeProxy)
{
$this->freeProxy = $freeProxy;
}
/**
* #Route("/example", name="example")
*/
function someMethod()
{
dump($this->freeProxy);
dump($this->getParameter('free_proxy'));die();
}
}
I then went to a fresh 4.2 project and tried action injection:
class IndexController extends AbstractController
{
public function index($freeProxy)
{
return new Response("Index $freeProxy");
}
}
Action injection works as expected for 4.2 but not 3.4. Constructor injection works fine in either version.
documentation show like this :
parameters.yml :
parameters:
mailer.transport: sendmail
to set :
$container->setParameter('mailer.transport', 'sendmail');
to get :
$container->getParameter('mailer.transport');

Integrating Symfony Routing and Twig using Symfony Twig Bridge

I use in my code Twig and Symfony routing which I would like to integrate with Twig using Symfony Twig Bridge.
I have them both installed and what I need to do is to add to Twig extensions Symfony\Bridge\Twig\Extension\RoutingExtension which requires Symfony\Component\Routing\Generator\UrlGenerator.
UrlGenerator requires 2 arguments:
routes collection
request context
So in my yaml services file I have:
router:
class: Symfony\Component\Routing\Router
arguments:
- '#yaml.file.loader'
- '%routing.file%'
- { 'cache_dir' : '%cache.dir%' }
- '#request.context'
twig:
class: Twig_Environment
calls:
- ['addExtension', ['#twig.extensions.debug']]
- ['addExtension', ['#twig.extensions.translate']]
- ['addExtension', ['#twig.extensions.routing']]
arguments:
- '#twig.loader'
- '%twig.options%'
twig.extensions.routing:
class: Symfony\Bridge\Twig\Extension\RoutingExtension
public: false
arguments:
- '#twig.url.generator'
And finally UrlGenerator:
twig.url.generator:
class: Symfony\Component\Routing\Generator\UrlGenerator
public: false
arguments:
- '#router'
- '#request.context'
Unfortunatelly #router is not route collection type. It has method getRouteCollection which allows to get data required by UrlGenerator and it works if I add extension manually eg. from controller. But I don't want to split services definition between different files and prefer to keep them in yaml services definition.
So the question is: how to pass as an argument to UrlGenerator not the raw object Router but result of getRouteCollection?
There are multiple ways to do this:
Using Symfony expression language
If you have Symfony Expression Language component installed, you can do this in your service definition:
twig.url.generator:
class: Symfony\Component\Routing\Generator\UrlGenerator
public: false
arguments:
- "#=service('router').getRouteCollection()"
- "#request.context"
Using factory
If for some reason you don't want to use Symfony Expression Language, you can do it using a factory class which is responsible for instantiating your url generator.
class UrlGeneratorFactory
{
private $router;
private $requestContext;
public function __construct($router, $requestContext)
{
$this->router = $router;
$this->requestContext = $requestContext;
}
public function create()
{
return new UrlGenerator($this->router->getRouteCollection(), $this->requestContext);
}
}
And in your yaml set url generator definition to:
twig.url.generator.factory:
class: UrlGeneratorFactory
arguments: ["#router", "#request.context"]
twig.url.generator:
class: Symfony\Component\Routing\Generator\UrlGenerator
factory: ["#twig.url.generator.factory", create]

symfony2 custom repository extending EntityRepository

I am trying to implement a custom repository class in symfony2, and I want it to extend EntityRepository class. I am having trouble with passing the getting arguments to the parent (i.e. EntityRepository) constructor. This is the signiture of parent constructor:
public function __construct($em, Mapping\ClassMetadata $class)
So I had to add this to my services.yml file, in order to get the arguments:
parameters:
user_provider.class: Untitled\F5Bundle\Security\UserRepository
services:
user_meta_data:
class: Doctrine\ORM\Mapping\ClassMetaData
arguments:
name: "Untitled\F5Bundle\Entity\User"
user_provider:
class: "%user_provider.class%"
arguments:
entityManager: "#doctrine.orm.entity_manager"
meta_data: "#user_meta_data"
And I also added the annotation tag to my User class (which I'm not sure if it was neccessary)
Now when I run it, it raises an error. the message says:
FatalErrorException: Error: Class 'Doctrine\ORM\Mapping\ClassMetaData' not found
in /mnt/data/Projects/F5/app/cache/dev/appDevDebugProjectContainer.php line 2749
(/mnt/data/Projects/F5/ is where I keep the code)
I don't get it. What's wrong here? What am I doing wrong?
Metadata is obtained with the MetadataFactory. As an example you can see how it works in EntityManager.
public function getClassMetadata($className)
{
return $this->metadataFactory->getMetadataFor($className);
}
You can retrieve you repository as service as well. Look at this question.
You don't need to inject these constructor arguments yourself, just specify which repository class you want to use:
/**
* #Entity(repositoryClass="MyProject\UserRepository")
*/
class User
{
...
}
See also http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/annotations-reference.html#entity
You miss typed classname "ClassMetaData" should be ClassMetadata
class: Doctrine\ORM\Mapping\ClassMetadata

Resources