SS4.4
I have two classes Member and Activity. A member has many activities. I have a class MemberExtension which extends Member. Inside MemberExtension, we have a has_many array containing ‘Activities’. How do we get the list of Activities in MemberExtension?
We have tried the following:
$this->Activities()
Error: Uncaught Error: Call to undefined method MemberExtension::Activities()
$this->getOwner()->Activities()
Error: Uncaught BadMethodCallException: Object->__call(): the method 'Activities' does not exist on 'SilverStripe\Security\Member'
We also ran dev/build?flush
// MemberExtension class
class MemberExtension extends DataExtension {
public static $has_many = [
'Activities' => Activity::class
];
}
// Activity class
class Activity extends DataObject {
private static $has_one = [
'Member' => Member::class,
];
}
// Register MemberExtension in _config.php
Member::add_extension(MemberExtension::class);
In the MemberExtension $this->owner->Activities() should work.
In Silverstripe CMS 4 you also need to use the FQCN (fully qualified class name, aka including the whole namespace. This means you need to use SilverStripe\Security\Member when adding the extension. It's also a good practice to use yml config files for adding extensions to classes, see documentation.
SilverStripe\Security\Member:
extensions:
- MemberExtension
assuming your extension doesn't have a namespace yet.
Then after running dev/build/flush you should be able to call the Activities relation as shown above:
$activities = $this->owner->Activities(); //name of the relation as always
Related
So my question is very specific but I couldn't figure out how to make it even after several months of reflection. The following topic will be about Symfony, Doctrine and generating fixtures on-the-go for the tests.
I want to generate fixtures on the go from a test. The goal is to provide a very specific set of fixtures for each tests using helpers without sacrify the readability. That is the goal, so my idea was to create a tests/Resources/EntityProxy which is a mirror of the src/Entity folder, containing the same amount of classes with the exact same name. Each EntityProxy extends from its related Entity, use a custom trait to fill the properties easily.
You guessed it, I want to only use in tests the EntityProxy and use it directly into the functions to tests them. And there is a major issue with that, as Doctrine doesn't recognize the EntityProxy as an entity even if it extends from a real Entity.
Is there a way to say to Doctrine to persist an EntityProxy as its extended Entity?
__
The following code is an example of what I want as en EntityProxy:
namespace Tests\Resources\EntityProxy;
class User extends App\Entity\User
{
use FixtureGenerationTrait;
public function static makeDefault(): self
{
return static::generate([
'username' => self::getFaker()->username,
'email' => self::getFaker()->email,
...
]);
}
public function static make(array $data = []): self
{
$entity = static::makeDefault();
$entity = static::setValues($entity, $data);
return $entity;
}
}
And can be used in the tests like following: User::make(['name' => 'John Wick']);
Hi,
I have an entity A :
id
att1
att2
Now, I'd like to add a new Bundle (which will add new functionality) and here especially I'd like to add an attribute to A. A must be like :
id
att1
att2
newAtt3
newAtt4
In order to do that, I was thinking to create a new entity which will extend A and add the new attribute.
But then, what I don't know is how can I prepare the first bundle to use the 2nd entity (and controller/view) if the 2nd bundle is installed?
I guess I need to add configuration in the first bundle, but I have no idea what to add...
Thanks !
Please read Best Practices for Reusable Bundles
and Doctrine Inheritance Mapping
You can also think about Traits but often the best way is to use Interfaces. With the help of interfaces you can make your bundle highly configurable on a clean way like this:
make a configuration variabele that tells the bundle what class should be used. This can be the default class A from the Bundle but may also be class A from your AppBundle.
$rootNode
->children()
->scalarNode('a_entity')
->defaultValue('AppBundle\\Entity\\A')
->end()
->end()
;
Then make a interface for class A with all the functions that are mandatory:
interface AInterface
{
public function setVariable($name, $var);
public function getHtml($template);
}
and implement the interface in the class:
class A implements AInterface
{
// ...
}
Every time if you pass the class as a paramter use AInterface instead of A:
class B
{
private $a;
public function __construct(AInterface $a)
{
$this->a = $a;
}
}
Right now you can change the configuration variabele a_entity to another class. This other class still needs to implement the interface AInterface.
I want to create a class that extends DataObject and simply has a Title, Desc, and Image.
class ImageBlock extends DataObject
{
private static $db = [
'Title' => 'Varchar(50)',
'Description' => 'Varchar(255)'
];
private static $has_one = [
'Image' => 'Image'
];
}
This is generic Tile to display on the Frontend and could be shown on multiple pages and within multiple DataObjects. A given page or DO can have many of these. To clarify, this is not just for pages. I have a Region DO that has_many of these ImageBlocks:
class TourRegion extends \DataObject
{
private static $db = [
'RegionName' => 'Varchar(50)',
'RegionSlug' => 'Varchar(50)',
'RegionIntro' => 'Varchar(255)',
'RegionDescription' => 'Text',
];
private static $has_many = [
'RegionHeroImages' => 'TourHeroImage',
'MainAttractions' => 'ImageBlock'
];
....
My question is, a has_many to a DataObject requires a has_one relationship on that DataObject. Since the has_one relationship could be more than one possible class, how do I create this reference?
I have tried adding a has_one to the lowest common class that these objects share (DataObject) as follows:
class ImageBlock extends DataObject
{
private static $db = [
'Title' => 'Varchar(50)',
'Description' => 'Varchar(255)'
];
private static $has_one = [
'Image' => 'Image',
'ParentObject' => 'DataObject'
];
}
But I get this error:
[User Error] Uncaught Exception: No has_one found on class
'ImageBlock', the has_many relation from 'TourRegion' to
'ImageBlock' requires a has_one on 'ImageBlock'
I get the same error when I try to omit this has_one on ImageBlock altogether. Which begs the question; Why is it I can add has_many relationships to DataObjects like Image or File without the Image or File class having a has_one reference to my Object?
It seems that it's not possible to have generic and reusable has_many related objects in Silverstripe. And that every class that needs to have this ImageBlock must duplicate the class for the sole purpose of adding the has_one reference.
To answer the last part of your question, it's important to remember that has_many is schematically meaningless. It imposes no structural changes to your database. All it does is add a magic method to the parent DataObject that looks for a has_one somewhere else. Defining a has_many is basically just for convenience, to save you the time of writing a getter.
If you're looking to define the relation in the parent, which to me makes sense, I would do that as a many_many, as that requires no reciprocity (It can be reciprocated by belongs_many_many, but that is just a convenience method, too).
For consistency and clarity, I would create an extension to inject the many_many => ImageBlock to the DO's that want it.
I'm developping a symfony2 application with the DatatableBundle (https://github.com/AliHichem/AliDatatableBundle)
I need to override the DoctrineBuilder class in the Util Directory
I've created a DtatableBundle in my src directory with all the structure of the Alidatatable Bundle
I've write a getParentMethod in the newBundle, and create my new DoctrineBuilde class
Symfony always use the vendor class and not the new one
Here,s the Bundle Structure and then class i want to override :
DatatableBundle
Util
Factory
Query
DoctrineBuilder.php
and the bundle's service definition:
parameters:
datatable.class: Ali\DatatableBundle\Util\Datatable
services:
datatable:
class: "%datatable.class%"
arguments: [ #service_container ]
scope: prototype
datatable.twig.extension:
class: Ali\DatatableBundle\Twig\Extension\AliDatatableExtension
arguments: [ #service_container ]
tags:
- { name: twig.extension }
Any Ideas ?
Thansk a lot!
In normal (extendable) bundles you always have ability to override every part of it just by replacing one parameter with your value (default class name to your class name). But the bundle that you want to extend is not intended to be extended. So to extend it properly and override some parts you will need to do many manipulations.
In your particular case to use your own query builder you need to override __construct() of Datatable.php in your class and replace in it:
//from
$this->_queryBuilder = new DoctrineBuilder($container);
//to
$this->_queryBuilder = new YourDoctrineBuilder($container);
Also to make application to use your Datatable class you need to replace default class to yours in your parameters.yml:
datatable.class: Your\Path\To\Datatable
It is not necessary to implement the same directory structure in your bundle to override some parts of bundle. It has no effect! The only thing that you need is to define your own classes and set up bundle to use yours instead of original.
getParent() for bundle only works for cases when you need to override some Resources. They need to have the same structure as in original bundle. But your case is not about Resources.
For this you cannot directly override the builder class.
You could, however, override the Datatable class and call your own version of the builder in the __construct like so..
namespace Acme\DatatableBundle\Util;
use Ali\DatatableBundle\Util\Datatable as BaseDatatable;
use Acme\DatatableBundle\Util\Factory\Query\DoctrineBuilder;
class Datatable extends BaseDatatable
{
/**
* class constructor
*
* #param ContainerInterface $container
*/
public function __construct(ContainerInterface $container)
{
parent::__construct($container);
// This would change the default for your version of the builder
$this->_queryBuilder = new DoctrineBuilder($container);
}
}
And then just set your version of the datatable class as the %datatable.class% parameter like..
datatable.class: Acme\DatatableBundle\Util\Datatable
So, I'm using the bundle class to do most of my work as I dont need controllers (src\CSFs\QuicklinksBundle\CSFsQuicklinksBundle.php).
From the FrontController of another bundle, I get the quicklinks bundle, inject the container object into the bundle class (above) and then, within the bundle class, extract templating to return HTML, this works fine. However, I'm having trouble with repositories.
/**
* Get the container object, so we can use all the symfony2 fun stuffs
*/
public function injectContainer($cont)
{
// Template
$this->tpl = $cont->get('templating');
// EM
$this->em = $cont->get('doctrine')->getEntityManager();
}
/**
*
**/
public function doStuff()
{
$products = $this->em->getRepository('QuicklinksBundle:Quicklinks')
->getUsersWithQuicklinks();
}
The error I get is:
Unknown Entity namespace alias 'QuicklinksBundle'.
I have both the generated entity file and a repository class with the getUsersWithQuicklinks() method defined.
How do I get the entity manager to know about my repositories?
Thanks,
Mike
Change:
$this->em->getRepository('QuicklinksBundle:Quicklinks')
To:
$this->em->getRepository('CSFsQuicklinksBundle:Quicklinks')
And I'm assuming you have an Entity named 'Quicklinks'