I am using a command controller and the scheduler module to import a car list into TYPO3. This list contains only available cars, so if a car is removed from the list I want to set the deleted flag into database to 1 for this car, but I am missing a setter like this:
$car->setDeleted(1);
So how can I set this property manually?
Get an extbase repository for cars, and then call its remove() method with the car you want to mark as deleted.
Something along these lines:
class YourCommandController extends \TYPO3\CMS\Extbase\Mvc\Controller\CommandController {
/**
* #var \Yourvendor\Yourextkey\Domain\Repository\CarRepository
* #inject
*/
protected $carRepository;
/**
* Deletes some car.
*/
public function deleteCarCommand() {
$car = ... // get hold of the car to delete somehow, probably using the repository
$this->carRepository->remove($car); // This should suffice!
}
}
Related
The following script comes from https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/inheritance-mapping.html#mapped-superclasses, and was only changed to include a second sub-class. It is my understanding that MappedSuperclassBase cannot exist by itself but must be extended by one and only one sub-class (i.e. either EntitySubClassOne or EntitySubClassTwo), and is the same concept as supertype/subtype for SQL. Agree?
How is a super/sub type defined using either YAML or XML instead of annotation mapping?
<?php
/** #MappedSuperclass */
class MappedSuperclassBase
{
/** #Column(type="integer") */
protected $mapped1;
/** #Column(type="string") */
protected $mapped2;
/**
* #OneToOne(targetEntity="MappedSuperclassRelated1")
* #JoinColumn(name="related1_id", referencedColumnName="id")
*/
protected $mappedRelated1;
// ... more fields and methods
}
/** #Entity */
class EntitySubClassOne extends MappedSuperclassBase
{
/** #Id #Column(type="integer") */
private $id;
/** #Column(type="string") */
private $name;
// ... more fields and methods
}
/** #Entity */
class EntitySubClassTwo extends MappedSuperclassBase
{
/** #Id #Column(type="integer") */
private $id;
/** #Column(type="string") */
private $name;
// ... more fields and methods
}
Based on our comments, I think I see your confusion. Because the docs handle both "MappedSuperclass" and "Discriminator" on the same page, I think you've mixed up their uses in your head. Hopefully this can help you:
A MappedSuperclass provides properties/defaults in a re-usable way, but it can never be an Entity by itself. This is comparable to PHP's abstract classes (which cannot be instantiated on their own)
A Discriminator provides the ability to "extend" an Entity, making it another Entity. For example, having a Person Entity gives you 1 Entity. This Entity can be extended, for example by Worker and Manager.
A good use-case for a MappedSuperclass would be an AbstractEntity. Every Entity needs an ID, a unique identifier. It also gives you something common to check against in Listeners and such. So, go ahead and create:
/**
* #ORM\MappedSuperclass
*/
abstract class AbstractEntity
{
/**
* #var int
* #ORM\Id
* #ORM\Column(name="id", type="integer", options={"unsigned":true})
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
// getter / setter
}
See how this is both declared abstract and MappedSuperclass?
This is because neither (abstract class and MappedSuperclass) cannot be instantiated on their own. You cannot do $entity = new AbstractEntity() because it's an abstract PHP class. Neither will Doctrine create a separate table for AbstractEntity.
Next, create a Person:
/**
* #ORM\Entity
* #ORM\Table(name="persons")
*
* #InheritanceType("JOINED")
* #DiscriminatorColumn(name="discr", type="string")
*/
class Person extends AbstractEntity
{
/**
* #var string
* #ORM\Column(name="name", type="string", length=255, nullable=false)
*/
protected $name;
// getter / setter
}
The above, Person, Entity is setup for Class Table Inheritance through the JOINED inheritance type. Meaning that, on the database level, the table persons will be separate from any columns added by other entities, extending Person.
Notice how I did not declare DiscriminatorMap. Below from the docs, highlighted in bold by me:
Things to note:
The #InheritanceType, #DiscriminatorColumn and #DiscriminatorMap must be specified on the topmost class that is part of the mapped entity hierarchy.
The #DiscriminatorMap specifies which values of the discriminator column identify a row as being of which type. In the case above a value of "person" identifies a row as being of type Person and "employee" identifies a row as being of type Employee.
The names of the classes in the discriminator map do not need to be fully qualified if the classes are contained in the same namespace as the entity class on which the discriminator map is applied.
If no discriminator map is provided, then the map is generated automatically. The automatically generated discriminator map contains the lowercase short name of each class as key.
Now, let's create a Worker:
/**
* #ORM\Entity
* #ORM\Table(name="workers")
*/
class Worker extends Person
{
/**
* #var int
* #ORM\Column(name="worker_id", type="integer", length=11, nullable=false)
*/
protected $workerId;
// getter / setter
}
So, now we've got:
MappedSuperclass: AbstractEntity - is not a stand-alone Entity
Discriminated: Person - is a stand-alone Entity
"normal": Worker - extends Person
Things to note:
A MappedSuperclass can not be instantiated. As such: you can not create links/relations to it. Comparable with PHP's abstract class
A Discriminated Entity is one which also stands alone and can be used as a normal Entity. You can create relations to and from it, without an issue
An Entity extending a Discriminated Entity is an instance of both. In the above code these are both true: $worker instanceof Worker and $worker instanceof Person, because the Worker extends Person. However, $person instanceof Worker will be false!
$workerId = $person->getWorkerId() // generates "method does not exist" fatal
$workerId = $worker->getWorkerId() // generates integer value
Hope that managed to clear stuff up for you. If not, feel free to ask.
I have a “Check” Direct Menu Item contributed in my fragment.e4xmi. It’s selected state should reflect the value of a boolean preference. Setting the preference in the #Execute method works fine:
#Execute
public void execute(MMenuItem item, #Preference IEclipsePreferences preferences) {
preferences.putBoolean("selected", item.isSelected());
}
But initializing the DirectMenuItem’s selected state from the preference doesn’t work:
#PostConstruct
public void init(MMenuItem item, #Preference("selected") boolean selected) {
item.setSelected(selected);
}
When the #PostConstruct method is called, the MMenuItem linked with the handler is not yet present in the current context.
Also, moving the setSelected call into #CanExecute doesn’t seem to work; the change made there is not reflected in the UI.
So, how to solve this issue (linking the selected state of a menu item with a boolean preference) in e4?
Doing this in #CanExecute works when using a Handled Menu Item rather than Direct Menu Item. Some UI things don't seem to work well in Direct handlers.
Following up to your question and the discussion in https://dev.eclipse.org/mhonarc/lists/e4-dev/msg09498.html I implemented the following solution for a DirectToolItem.
#Inject
public void initialize(EModelService modelService, MPart part){
MUIElement toolItem = modelService
.find("a.b.c.d.toolitemId", part.getToolbar());
isActive = ((MDirectToolItem) toolItem).isSelected();
}
The #Inject method gets called once, and I know the location of the MDirectToolItem resp. I can inject the part. This seems to suffice to synchronize the e4 application model and my model.
For the record, I've come up with the following add-on:
/**
* The basis for an add-on which synchronizes the {#linkplain MItem#isSelected()
* selection state} of {#link MItem}s tagged with one of the tags passed to
* {#link ItemSelectionSynchronizationAddonBase#ItemSelectionSynchronizationAddonBase(String...)}
* with some external source.
* <p>
* Subclasses need to implement {#link #getSelection(String)} to retrieve the
* desired selection state for a given tag.
*/
public abstract class ItemSelectionSynchronizationAddonBase {
private EModelService modelService;
private MApplication application;
private final List<String> tags;
public ItemSelectionSynchronizationAddonBase(String... tags) {
this.tags = new ArrayList<>(asList(tags));
}
/**
* Injects all objects necessary to work with the E4 Application Model. Not done
* in the constructor simply to keep subclasses unburdened by the knowledge
* about the exact objects needed.
*/
#PostConstruct
public void injectUiModel(EModelService modelService, MApplication application) {
this.modelService = modelService;
this.application = application;
}
/**
* Synchronizes the selection state of all {#link MItem}s found in the
* Application Model when startup is complete. This does <strong>not</strong>
* include items that exist only in part descriptors, but no concrete parts yet.
* These items will be synchronized when the part gets created and
* {#link #partActivated(Event)}.
*/
#Inject
#Optional
public void applicationStartupComplete(
#EventTopic(UIEvents.UILifeCycle.APP_STARTUP_COMPLETE) #SuppressWarnings("unused") Event event) {
synchronizeSelections(application, tags,
EModelService.ANYWHERE | EModelService.IN_MAIN_MENU | EModelService.IN_PART);
}
/**
* Synchronizes the selection state of all {#link MItem}s found in the
* Application Model for the part that was just activated.
*/
#Inject
#Optional
public void partActivated(#EventTopic(UIEvents.UILifeCycle.ACTIVATE) Event event) {
MPart part = (MPart) event.getProperty(UIEvents.EventTags.ELEMENT);
synchronizeSelections(part, tags, EModelService.IN_PART);
}
/**
* Synchronizes the selection state of all {#link MItem}s with the given tags.
* Should be called by the subclass when the value changes in the external
* source.
*/
protected void synchronizeSelections(List<String> tags) {
synchronizeSelections(application, tags,
EModelService.ANYWHERE | EModelService.IN_MAIN_MENU | EModelService.IN_PART);
}
private void synchronizeSelections(MUIElement searchScope, List<String> tags, int searchFlags) {
List<MItem> items = modelService.findElements(searchScope, null, MItem.class, tags, searchFlags);
for (MItem item : items) {
for (String tag : tags) {
if (item.getTags().contains(tag)) {
item.setSelected(getSelection(tag));
}
}
}
}
/** Gets the current selection state associated with the given tag. */
protected abstract boolean getSelection(String tag);
}
Subclasses can then override getSelection to, e.g., use the tag as a basis for retrieving a preference or (as is done in my code) getting the value from a Java bean. Just note that getSelection takes only care of one sync direction (pull). The subclass will also need to call synchronizeSelections whenever an event occurs that necessitates a UI update (push, e.g., caused by a PropertyChangeEvent).
In a class file I can get all records from another repository that is not mine
$allUsergroups = $this->feGroupRepository->findAll();
How to make custom function to acomplish something like this on such a repository in the most correct way?
// magic default function that takes a uid list (or array) as argument
$someUsergroups = $this->feGroupRepository->findSomeByUidList('2,4,6,8');
Or can I extent an existing repository with my own custom functions, in this case based on $query->in(list)?
You can create your own method in your extensionRepository.php class
you can use :
in($propertyName, $operand)
or
contains($propertyName, $operand)
Contrarily, the methods in() and contains() accept multi-value data types as arguments (e.g. Array, ObjectStorage).
take a look how some other extension are doing stuff. (like the tx_news extension)
or read some docs here :
https://docs.typo3.org/typo3cms/ExtbaseFluidBook/6-Persistence/3-implement-individual-database-queries.html
Yes, you can extend another class in TYPO3 without any need to change any other code. It´s called Dependency Injection in ExtBase context.
First, create a new repository class your_ext/Classes/Domain/Repository/FrontendUserRepository.php and add below content to it:
<?php
namespace Tillebeck\YourExt\Domain\Repository;
class FrontendUserRepository extends \TYPO3\CMS\Extbase\Domain\Repository\FrontendUserRepository {
/**
* #param array $uidList
* #return \TYPO3\CMS\Extbase\Persistence\QueryResultInterface
*/
public function findByUidList(Array $uidList)
{
$query = $this->createQuery();
//$query->getQuerySettings()->setRespectStoragePage(false);
$query->matching(
$query->in('uid', $uidList)
);
return $query->execute();
}
/**
* #return string
*/
protected function getRepositoryClassName()
{
return get_parent_class($this);
}
}
Here we have implemented your method findByUidList with the required argument $uidList which needs to be an array.
Because repositories resolve their model names by their own class name, we need to change the method getRepositoryClassName to return the parent class name, in this case TYPO3\CMS\Extbase\Domain\Repository\FrontendUserRepository.
But this alone won't work. We need to tell ExtBase that every time we inject or initialize a TYPO3\CMS\Extbase\Domain\Repository\FrontendUserRepository, either by PHPDocBlock annotation #inject or by the objectManager->get, then we really want to initialize our new repository. This is done in TypoScript.
config.tx_extbase.objects {
TYPO3\CMS\Extbase\Domain\Repository\FrontendUserRepository {
className = Tillebeck\YourExt\Domain\Repository\FrontendUserRepository
}
}
You can also restrict your change to your own extension alone by replacing config.tx_extbase with plugin.tx_yourext.
Last step: clear ALL cache, and possibly delete all files in typo3temp directory.
Now in your controller (or other class) you can run below code.
$uidList = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', '2,4,6,8', true);
\TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump(
$this->frontendUserRepository->findByUidList($uidList)
);
I have tested above solution in TYPO3 7.6 and it works. Dependency Injection has existed since version 6.1.
This is by my definition the must correct way, as you asked, to implement this feature in your own TYPO3 extension.
I have created a Bundle (called MYBUNDLE) with just 2 entities: Menu and Group. Both were declared as mappedSuperclass because I need this bundle could be use for other projects. As a condition, projects will have to extend from these classes to customize them by setting the tables name or adding some fields. For instance:
Classes into MYBUNDLE:
class Group{
protected $id;
protected $menus;
}
class Menu{
protected $id;
protected $groups;
}
YML for mapping my entities from MYBUNDLE
Project\MYBUNDLE\Entity\Menu:
type: mappedSuperclass
fields:
id:
type: integer
id: true
generator:
strategy: AUTO
Project\MYBUNDLE\Entity\Group:
type: mappedSuperclass
fields:
id:
type: integer
id: true
generator:
strategy: AUTO
manyToMany:
menus:
targetEntity: Menu
joinTable:
name: sf_group_menu
joinColumns:
sf_group_id:
referencedColumnName: id
inverseJoinColumns:
sf_menu_id:
referencedColumnName: id
Classes into my child bundle:
use Project\MYBUNDLE\Entity\Group as TGroup;
/**
* #ORM\Entity
* #ORM\Table(name="sf_group")
*/
class Group extends TGroup
{ }
use Project\MYBUNDLE\Entity\Menu as TMenu;
/**
* #ORM\Entity
* #ORM\Table(name="sf_menu")
*/
class Menu extends TMenu
{ }
However each one of the two classes have a property to make a manytomany association between them (As mappedSuperclass doesn't allow an inverse side, my association is manytomany unidirectional).
I need to make a query inside MYBUNDLE. A query which join both tables with the manytomany association.
The reason i want to make this query inside MYBUNDLE, is because this bundle has a service which draws the Menu deppending in the Group or Groups. This method should be facilitate from this bundle, so other bundles could use it and i dont have to implemment in every sub-bundle.
My partial solution was to make a config part for my MYBUNDLE, something like:
mybundle:
menu_entity:
name: MyprojectChildBundle\Entity\Menu
group_entity:
name: MyprojectChildBundle\Entity\Group
With this configuration, I can use the repository of the child bundle inside MYBUNDLE, with this:
$this->em->getRepository("MyprojectChildBundle:Group")-findAll();
Everything works when I make a query without joins. On the other hand, when I do this:
$repo = $this->em->getRepository("MyprojectChildBundle:Group");
$result = $repo->createQueryBuilder("c")
->select('c, d')
->join("c.menus", "d")->getQuery()->getResult();
return $result
Everything fails, because the SQL formed try to look for a table called "Menu" which does not exist because it is called "sf_menu". The table Group is correctly changed to "sf_group" because I am using the Repository of my child. However using this repository just change the name of that class, but not of joined tables.
How could I make this kind of query inside MYBUNDLE? Thanks a lot.
Finally i found the solution :)
I had to create 2 more classes as my model. This classes should have all mapping Database and shoud be declared as single inheritance like this:
#src\Myproject\MYBUNDLE\Model\Menu
/**
* #ORM\Entity
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="discr", type="string")
* #ORM\DiscriminatorMap({"menu1" = "Myproject\MYBUNDLE\Entity\Menu", "menu2" = "Menu"})
*/
abstract class Menu {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
// Other fields
}
That you should do with both entities (Menu and Group). The advantage of this implementation is that you don't lose any associations as before, due to declare them as mappedSuperClasss.
Then you should declare one entity per each model class and declare them as MappedSuperClass. They should look like this:
#src\Myproject\MYBUNDLE\Entity\Menu
use Doctrine\ORM\Mapping as ORM;
use Tixpro\TMenuBundle\Model\Menu as BaseMenu;
/** #ORM\MappedSuperclass */
class Menu extends BaseMenu
{
}
With this implementation you make sure not to lose any association. In addition, any entity could extend from your entities class to add more fields and customize them. For instance:
#src\Project\ChildBundle\Entity\Menu
use Myproject\MYBUNDLE\Entity\Menu as TMenu;
/**
* #ORM\Entity
* #ORM\Table(name="sf_menu")
*/
class Menu extends TMenu{
//put your code here
}
Don't forget to configure the params in the config.yml to use MYBUNDLE.
mybundle:
menu_entity:
name: MyprojectChildBundle\Entity\Menu
group_entity:
name: MyprojectChildBundle\Entity\Group
if you dont do this, you couldn't know the repository inside MYBUNDLE and consequently you couldn't make joined queries into your MYBUNDLE.
Finally, after setting your params and making that implementation, you are able to use joined queries inside your MYBUNDLE, like this:
$repo = $this->em->getRepository("MyprojectChildBundle:Menu");
$result = $repo->createQueryBuilder("wa")
->select('wa, da')
->join("wa.roles", "da")
That's all.
Say i have a class hierarchy of domain objects with one base class and a couple of child classes, one level.
Let say I have a list of those objects (list of the base class) and I want to apply some logic to the classes that I feel don't really belong to the classes (eg. design/UI specific code).
What are my alternatives ?
If-is statement. Personally this one shouldn't even be considered as an alternative but i write it anyway.
Polymorphism. This one is actually an alternative in some cases but with my example above, I don't want my classes to contain any UI specifics.
Resolving some logic method via reflection/IoC container based on the type of the object.
Eg C#. Type type = typeof(ILogic<>).MakeGenericType(domainObject.GetType());
I really like this one, I don't get any compile time checks though if an implementation is missing for a sub class, or is that possible somehow?
Visitor pattern. Will work but seemes kind of overkill to apply on a structure thats only one level deep.
Anyone has any other tips or tricks to solve these kinds of problems?
Great question. Problem is that there are many solutions and most of them will work.
I work with MVC a lot, similar situation happens quite often. Especially in the view, when similar rendering needs to happen across some views... but does not really belong to the view.
Let's say we have child class ChildList that extends BaseList.
Add a property uiHandler in the child class. Overload the rendering function, let's say toString(), and use the uiHandler with your specific UI/Design things.
I wrote a little something, it is in PHP... but you should be able to get an idea. It gives you freedom to chose how your objects will be displayed and flexibility to use specific UIs for specific objects.
Look at the code below, it seems like a lot but int's not that bad.
BaseList - your base class
BaseListUIExtended - base class that uses UI, takes optional UI class as constructor parameter. In C# 4 you can use optional, otherwise use 2 constructors.
UIBase - interface for UI classes...
UIChildSpecific - UI class
ChildList - child class that can use UI or not, because of BaseListUIExtended optional constructor parameter.
Define interface
/**
* Base UI interface
*/
interface IUIBase {
/**
* Renders the Base Class
*
* #param UIBase $obj
* #return string
*/
public function render($obj);
}
Define base classes, child class
//**************************************************************
// Define Base Classes
//**************************************************************
/**
* Base Class
*/
class BaseList {
/**
* List of items
* #var array
*/
protected $_items = array();
/**
* Gets collection of items
*
* #return array
*/
public function getItems() {
return $this->_items;
}
/**
* Adds new item to the list
* #param object $item
*/
public function add($item) {
$this->_items[] = $item;
}
/**
* Displays object
*/
public function display() {
echo $this->toString();
}
/**
* To String
*/
public function __toString() {
// Will output list of elements separated by space
echo implode(' ', $this->_items);
}
}
/**
* Extended BaseList, has UI handler
* This way your base class stays the same. And you
* can chose how you create your childer, with UI or without
*/
class BaseListUIExtended extends BaseList {
/**
* UI Handler
* #var UIBase
*/
protected $_uiHandler;
/**
* Default Constructor
*
* #param UIBase Optional UI parameter
*/
public function __construct($ui = null) {
// Set the UI Handler
$this->_uiHandler = $ui;
}
/**
* Display object
*/
public function display() {
if ($this->_uiHandler) {
// Render with UI Render
$this->_uiHandler->render($this);
} else {
// Executes default BaseList display() method
// in C# you'll have base:display()
parent::display();
}
}
}
//**************************************************************
// Define UI Classe
//**************************************************************
/**
* Child Specific UI
*/
class UIChildSpecific implements UIBase {
/**
* Overload Render method
*
* Outputs the following
* <strong>Elem 1</strong><br/>
* <strong>Elem 2</strong><br/>
* <strong>Elem 3</strong><br/>
*
* #param ChildList $obj
* #return string
*/
public function render($obj) {
// Output array for data
$renderedOutput = array();
// Scan through all items in the list
foreach ($obj->getItems() as $text) {
// render item
$text = "<strong>" . strtoupper(trim($text)) . "</strong>";
// Add it to output array
$renderedOutput[] = $text;
}
// Convert array to string. With elements separated by <br />
return implode('<br />', $renderedOutput);
}
}
//**************************************************************
// Defining Children classes
//**************************************************************
/**
* Child Class
*/
class ChildList extends BaseListUIExtended {
// Implement's logic
}
Testing...
//**************************************************************
// TESTING
//**************************************************************
// Test # 1
$plainChild = new ChildList();
$plainChild->add("hairy");
$plainChild->add("girl");
// Display the object, will use BaseList::display() method
$plainChild->display();
// Output: hairy girl
// Test # 2
$uiChild = new ChildList(new UIChildSpecific());
$uiChild->add("hairy");
$uiChild->add("girl");
// Display the object, will use BaseListUIExtended::display() method
$uiChild->display();
// Output: <strong>hairy</strong><br /><strong>girl</strong>