TYPO3 extbase: Get parent object in model object - php

I have got two (2) classes:
Person model class
<?php
class Person extends BaseDto
{
/**
* #var array|PostalAddress
*/
protected $postalAddresses = array();
/**
* #param array|PostalAddress $postalAddresses
*/
public function setPostalAddresses($postalAddresses)
{
$this->postalAddresses = $postalAddresses;
}
/**
* #return array|PostalAddress[]
*/
public function getPostalAddresses()
{
return $this->postalAddresses;
}
}
PostalAddress model class
<?php
class PostalAddress
{
/**
* #var string $privatePersonFirstName
*/
protected $privatePersonFirstName;
/**
* #var string $privatePersonName
*/
protected $privatePersonName;
/**
* #return string
*/
public function getPrivatePersonFirstName()
{
return $this->privatePersonFirstName;
}
/**
* #param string $privatePersonFirstName
*/
public function setPrivatePersonFirstName($privatePersonFirstName)
{
$this->privatePersonFirstName = $privatePersonFirstName;
}
/**
* #return string
*/
public function getPrivatePersonName()
{
return $this->privatePersonName;
}
/**
* #param string $privatePersonName
*/
public function setPrivatePersonName($privatePersonName)
{
$this->privatePersonName = $privatePersonName;
}
}
In the controller PostalAddressConroller I have got an action which creates the form to edit a single address.
I would like to make some fields editable only if certain conditions are met. Example: The organization fields on the address are only editable in case the person is of type private person and the address is of type employer.
To implement such a condition check, I would like to create a method on the PostalAddress model. But for this, It would require to have a reference back to the parent object inside the controller.
I would like to avoid to put all the logic inside the templates to keep the templates clean and easy to understand.
Is there support on extbase level for such back references?
In case I have to implement such a back reference myself: How do I prevent circular references in general (for example on object serialization)?

I would handle the problem differently. This is no controller job imho. This is definetly a template/view job. I'd use if conditions in the template to show the correct layout (field editable or not). Afterwards you have to make sure that nobody can just make the fields editable via developer tools for example.
That would be achievable by adding conditions in the backend logic, for example:
if($model->isAllowedProperty) { AddFieldToResultArrOrSimilar() }

As I understand the MVC pattern, the model should only carry data, no logic and no dependencies of any kind.
So to solve you problem I would use two different model classes, based on the same table, carying only those properites and validation metadata which applies to that particular model.
Imagine those two models below:
namespace Acme\MyPlugin\Domain\Model;
class PostalAddressPrivate
{
/**
* #var string $privatePersonFirstName
*/
protected $privatePersonFirstName;
/**
* #var string $privatePersonName
*/
protected $privatePersonName;
[...]
}
namespace Acme\MyPlugin\Domain\Model;
class PostalAddressCommercial
{
/**
* #var string $privatePersonFirstName
*/
protected $companyName;
[...]
}
Now you must tell the persistence layer, that those models goes to the same table. You do this in typoscript setup for that plugin.
plugin.tx_myplugin {
persistence {
classes {
Acme\MyPlugin\Domain\Model\PostalAddressPrivate {
mapping {
tableName = tx_myplugin_domain_model_postal_address
}
}
Acme\MyPlugin\Domain\Model\PostalAddressCommercial {
mapping {
tableName = tx_myplugin_domain_model_postal_address
}
}
}
}
Now you can transfer the logic into the controller and decide there which model to use. You can extend this simple case using a common Interface or abstract class, etc.
This "choose the right model" logic in the controller may be a little bit tricky at times. In general you will need to place some code dealing with the extbase "property mapper" inside the appropriate "initializeXxxAction" method.
At the begining I was inspired by this article in german (for an older version extbase!): https://jweiland.net/typo3/codebeispiele/extension-programmierung/extbase-dynamische-validierung.html
Hope google translate will give you some hints to solve upcomming problems.
On top you can assist the server based validation and processing by some frontend work. E.g. JavaScript tricks to enable or disable certain formular fields depending on the private/commercial status chosen.
You can as well tune the fluid templates to render/not render certain parts depending of the model variant used in controller.

Related

Create Doctrine entity from JSON column

I'm new to using Doctrine and am struggling to find the best way to handle the following scenario.
I have a table payment_gateways, which stores payment gateway config for users. Most of the data is common between payment gateways but there is also a JSON column config, the purpose of this column is to store configuration which is unique to specific payment gateways since I can't guarantee all payment gateways will share the same configuration fields.
I want to create a Doctrine ORM entity for my payment_gateways table, but I also want the config property to be its own entity where I can use its own getters and setters instead of accessing and setting properties directly from the config array.
Is single table inheritance a good way to approach this? I have tried this by creating a separate entity for each payment gateway I have integrated with and have extended the base entity PaymentGateway. In these entities I define the properties that I expect to be in the config property. Then I get/set the properties like so:
/**
* #ORM\Entity
*/
class PaypalGateway extends PaymentGateway
{
/**
* #return string|null
*/
public function getApiKey(): ?string
{
return $this->getConfig()['apiKey'] ?? null;
}
/**
* #param string $apiKey
*/
public function setApiKey(string $apiKey)
{
$data = $this->getConfig();
$data['apiKey'] = $apiKey;
$this->setConfig($data);
}
The parent PaymentGateway class looks like the following:
/**
* #ORM\Entity
* #ORM\Table(name="payment_gateways")
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="provider_type", type="string")
* #ORM\DiscriminatorMap({
* "paypal" = "PaypalGateway",
* "stripe" = "StripeGateway"
* })
*/
abstract class PaymentGateway
{
**
* #ORM\Column(type="json")
*/
private ?array $config = null;
public function getConfig()
{
return $this->config;
}
public function setConfig($config)
{
$this->config = $config;
}
As far as I can tell, this is working correctly for me but I'm not sure if it's a good way to go about it. I was wondering if this is the correct approach or if there's something I'm missing completely?

phpstan:: Method (methodname) return type with generic interface (interfacename) does not specify its types

I have defined an interface for an ordered list. The class docblock looks like this:
/**
* Interface ListOrderedInterface
* #template ListOrderedElement
*/
Within the method docblocks for this interface, ListOrderedElement is used to ensure that the type of thing being added to the list is consistent. PHPStan runs clean on ListOrderedInterface.php. So far so good.
Next I defined an interface for a factory that makes an ordered list. The definition looks like this:
/**
* Class ListFactoryInterface
*/
interface ListOrderedFactoryInterface
{
/**
* makeList
* #return ListOrderedInterface
*/
public function makeList(): ListOrderedInterface;
}
phpstan is complaining with the warning message "phpstan:: Method makeList return type with generic interface ListOrderedInterface does not specify its types". I don't know how to specify the types for the interface.
Thanks for helping with this.
You need provide a specialization for your ListOrderedInterface in the makeList phpdoc in the #return part.
interface ListOrderedFactoryInterface
{
/**
* This is an example with a concrete type, syntax is <type>
* #return ListOrderedInterface<string>
*/
public function makeList(): ListOrderedInterface;
}
If you need this to generic as well, you would need to add the #template on the factory also and return a generic type from makeList.
/**
* #template T
*/
interface ListOrderedFactoryInterface
{
/**
* #return ListOrderedInterface<T>
*/
public function makeList(): ListOrderedInterface;
}
And in the classes that implement FactoryInterface, add the #implements phpdoc.
/**
* Instead of string put the actual type it should return
* #implements ListOrderedFactoryInterface<string>
*/
class ConcreteFactory implements ListOrderedFactoryInterface
{
}
You can find more examples in the official docs: https://phpstan.org/blog/generics-by-examples

Custom functions in Doctrine2 auto generated classes

Is there a way to extend classes auto-generated from database by Doctrine2 ?
Example: I have this User class generated by Doctrine.
<?php
namespace Entities;
/**
* User
*/
class User
{
/**
* #var integer
*/
private $id;
/**
* #var string
*/
private $firstName;
/**
* #var string
*/
private $lastName;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set firstName
*
* #param string $firstName
*
* #return User
*/
public function setFirstName($firstName)
{
$this->firstName = $firstName;
return $this;
}
/**
* Get firstName
*
* #return string
*/
public function getFirstName()
{
return $this->firstName;
}
/**
* Set lastName
*
* #param string $lastName
*
* #return User
*/
public function setLastName($lastName)
{
$this->lastName = $lastName;
return $this;
}
/**
* Get lastName
*
* #return string
*/
public function getLastName()
{
return $this->lastName;
}
I would like to add this function :
public function getFullName()
{
return $this->getFirstName().' '.$this->getLastname();
}
Is there a cleaner way than adding it directly into this class?
I tried to create another class (Test) in libraries and extends it, then add it in autoload (which is working), but i get an error when I try to save object :
class Test extends Entities\User {
public function getFullName() {
return $this->getFirstName().' '.$this->getLastname();
}
}
Message: No mapping file found named 'Test.dcm.yml' for class 'Test'.
I'm using Doctrine2 in CodeIgniter3.
Thanks.
As explained in the Doctrine 2 FAQ:
The EntityGenerator is not a full fledged code-generator that solves all tasks. [...] The EntityGenerator is supposed to kick-start you, but not towards 100%.
In plain English this means you ask Doctrine to generate the Entity files only once. After that, you are on your own and do whatever changes you like (or it needs) to them.
Because an Entity is not just a container for some properties but it's where the entire action happens, this is how the flow should happen, Doctrine cannot write more code for you.
The only way to add functionality to the stub Entities generated by Doctrine is to complete the generated classes by writing the code that implements the functionality of each Entity according to its role in your Domain Model.
Regarding the other issue, on the Test class, the error message is self-explanatory: any class passed to the EntityManager for handling needs to be mapped.
Take a look at the help page about Inheritance Mapping. You can either map class User as a Mapped Superclass (it acts like a template for the derived classes and its instances are not persisted in the database) or you can use Single Table Inheritance to store the instances of all classes derived from User in a single table (useful when they have the same properties but different behaviour).
Or, in case you created class Test just because you were afraid to modify the code generated by Doctrine, put the behaviour you need in class User and drop class Test.
Seems you are having trouble while accessing the user entity class. You mentioned that test is a library class. Why not try to access the User entity class from a controller. If can do this then may be something is wrong with the configuration of test file. Besides, you need to map you doctrine entity class properly. You can have a look here to learn about doctrine mapping using yml: http://doctrine-orm.readthedocs.org/en/latest/reference/yaml-mapping.html
you can do this:
<?php
namespace Entities;
/**
* User
*/
class User extends Test
{
//... and extends Test
}
or
<?php
namespace Entities;
/**
* User
*/
class User
{
//...
public function getFullName() {
return $this->getFirstName().' '.$this->getLastname();
}
}
view more
Symfony 2 - Extending generated Entity class
http://www.theodo.fr/blog/2013/11/dynamic-mapping-in-doctrine-and-symfony-how-to-extend-entities/
http://doctrine-orm.readthedocs.org/en/latest/reference/inheritance-mapping.html
Annotation allows you to specify repository class to add more methods to entity class.
/**
* #ORM\Entity(repositoryClass="App\Entity\UserRepository")
*/
class User
{
}
class UserRepository extends EntityRepository
{
public function getFullName() {
return $this->getFirstName().' '.$this->getLastname();
}
}
// calling repository method
$entityManager->getRepository('User')->getFullName();
Here's a link [http://doctrine-orm.readthedocs.org/en/latest/reference/working-with-objects.html]
7.8.8. Custom Repositories

Yii2: Post complex models

I have a complex entity, looking similar like this:
class Article extends \yii\db\ActiveRecord {
public $id;
public $name;
/** #var ArticleAspectValue[] */
public $aspects;
public function getArticleAspectValues() {
return $this->hasMany(ArticleAspectValue::className(), ['article_id' => $this->id]);
}
}
And I have an entity serving as part of the more complex entity above.
class ArticleAspectValue extends \yii\db\ActiveRecord {
public $aspect_id; // <--- Two-attributes identifier (two-col PK in db)
public $article_id; // <----'
public $value;
}
While every ArticleAspectValue is assigned to ArticleAspect and Article as well, one article only has its own ArticleAspectValues.
The model Article consists of an id, a name and an array of sub-entities called ArticleAspectValues.
I solved the creation of input fields for each ArticleAspectValue, but since this is a simple for-each on the frontend with no connection to the model behind.
Question: How has the form and the receiving controller method to look like in order to post new values on the sub-entities, but according to their superior model, the Article?
PS the doc on complex models is TBD
The solution is to save the inferior model in the controller method as well as the superior, if they are ActiveRecords as well.
Following the appreciated comment from Mihai P., I repost my corrected example code from above and the solution.
The major model of the complex structure looks like this:
/**
* The superior class in a complex model.
* #property int $id
* #property string $name
*
* #property ArticleAspectValues $aspects
*/
class Article extends \yii\db\ActiveRecord
{
/**
* #return \yii\db\ActiveQuery
*/
public function getArticleAspectValues() {
return $this->hasMany(ArticleAspectValue::className(), ['article_id' => 'id']);
}
}
In Yii, properties of an ActiveRecord are created of database table columns. Yii provides magic getters to obtain their values. To work with properties which aren't actually existing, you can annotate them in the class. Most IDE's would parse these annotations and provide them as regular items of a class. Same works for methods.
The inferior class of the complex model looks like this:
/**
* The inferior class of the complex model.
* #property int $aspect_id
* #property int $article_id
* #property string $value
*/
class ArticleAspectValue extends \yii\db\ActiveRecord
{
/**
* #return \yii\db\ActiveQuery
*/
public function getArticle() {
return $this->hasOne(Article::className(), ['article_id' => 'id]);
}
}
In the controller of the superior model, the inferiors are saved within at the same time.
class ArticleController extends \yii\db\ActiveRecord
{
// ...
// Exemplary method. Goes for create action as well.
public function actionUpdate($id) {
$model = $this->findModel($id);
if ($model->load(Yii::$app->request->post()) && $model->save()) {
if ($model->aspects->load(Yii::$app->request->post()) && $model->aspects->save()) {
return $this->redirect(['view', 'id' => $model->id]);
}
}
}
The load method of ActiveRecords is able to parse a whole post data body to obtain its own values.
It's probably better to extend load and save methods of your complex model to process the inferiors, rather than instruct the controller for each inferior model.

zf2 mvc event-listener or strategy

im new to Zf2, i recently upgraded from zf1 and need help and advice on this problem.
Here the fact :
I'm working on medical project (which is an upgrade to a zf1 version) in some controller (page) i need to have the patient's info and current visitation in sidebar panel...
I know i'm new to zf2 but i don't want to do redundant things like having in every action the getvisiteService() and patientService() retrieve info and passing these results to view over and over.
I thought about a plugin but again i have to pass from controller to view and supercharge my view with partials and placeholder helper (grr!!!)
Thinkin' about Strategy and eventlistener but i don't know how these work and i need to inject result to a partial.
So there is a simple and/or complicated way to achieve that? Thank you in advance any hint and code will be appreciated and sorry for my poor english i speak french (such a typical excuse :) )
There's a ton of approaches you could use here, but sticking to your original question, it's quite easy to inject things into your layout model, with something like this:
Module.php
/**
* On bootstrap event
*
* #param \Zend\Mvc\MvcEvent $e
*/
public function onBootstrap(MvcEvent $e)
{
// Inject something, like a nav into your Layout view model
$viewModel = $e->getViewModel(); // Layout View Model
$navigation= new ViewModel(array(
'username' => 'Bob' // Dynamically set some variables..
));
$navigation->setTemplate('navigation/mynav');
$viewModel->addChild($navigation, 'navigation');
}
You could also create a custom view Helper to do the work for you if you wanted
<?php
/**
* MyHelper.php
*/
namespace Application\View\Helper;
use Zend\View\Helper\AbstractHelper;
use Zend\ServiceManager\ServiceManagerAwareInterface;
use Zend\ServiceManager\ServiceManager;
class MyHelper extends AbstractHelper implements ServiceManagerAwareInterface
{
/**
* Invoke
*
* #return string
*/
public function __invoke()
{
// Dynamically build your nav or what ever.
$patientService = $this->getServiceManager()->get('PatientService');
return 'soemthing';
}
/**
* #var ServiceManager
*/
protected $serviceManager;
/**
* Retrieve service manager instance
*
* #return ServiceManager
*/
public function getServiceManager()
{
return $this->serviceManager;
}
/**
* Set service manager instance
*
* #param ServiceManager $locator
* #return User
*/
public function setServiceManager(ServiceManager $serviceManager)
{
$this->serviceManager = $serviceManager;
return $this;
}
}

Categories