Typo3 10.4
I created Models, Repositories and Controller. Values are getting written from the backend into the database but I can't get a bunch of values to the frontend. It's not all values that don't work but some.
class Element extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
{
protected $type = "";
protected $name = "";
protected $beschreibung = "";
protected $customRender = "";
protected $mehrHtml = "";
protected $formsUsingThisElement = "";
public function __construct(string $type='', string $name='', string $beschreibung = '', string $customRender = '') {
$this->setType($type);
$this->setName($name);
$this->setBeschreibung($beschreibung);
$this->setCustomRender($customRender);
}
public function setType(string $type):void {
$this->type = $type;
}
public function getType():string {
return $this->type;
}
public function setName(string $name):void {
$this->name = $name;
}
public function getName():string {
return $this->name;
}
public function setBeschreibung(string $beschreibung):void {
$this->beschreibung = $beschreibung;
}
public function getBeschreibung():string {
return $this->beschreibung;
}
public function setCustomRender(string $customRender):void {
$this->customRender = $customRender;
}
public function getCustomRender():string {
return $this->customRender;
}
public function setMehrHtml(string $mehrHtml) {
$this->mehrHtml = $mehrHtml;
}
public function getMehrHtml() {
return $this->mehrHtml;
}
public function setFormsUsingThisElement(string $formsUsingThisElement):void {
$this->formsUsingThisElement = $formsUsingThisElement;
}
public function getFormsUsingThisElement(): string {
return $this->formsUsingThisElement;
}
}
Both $mehrHtml and $customRender actually have values but I can only get the other variables from the database.
While $type is of type select, $name and $beschreibung are of types input the two variables which I can not get the value of are config.type='text' in the TCA. But that shouldn't matter as I got another model which holds a text which is readable but an integer which I, too, can not get the value.
EDIT
Thomas remarked that the TCA might be misconfigured. Indeed were the columns in the Database in lowerCamelCase instead of snake_case which is why I didn't get any values from the database.
return [
'ctrl' => [
//... ,
],
'columns' => [
'default_options' => [ //IMPORTANT HERE. snake_case instead of camelCase like in variable names.
'exclude' => true,
'label' => 'Standardangaben',
'config' => [
//....
],
],
],
];
Related
I stumbled on a script that generates random names from two different types of words in an array. Code is following:
protected static $techTerms = array('AddOn', 'Algorithm', 'Architect', 'Array', 'Asynchronous', 'Avatar', 'Band', 'Base', 'Beta', 'Binary');
protected static $culinaryTerms = array('Appetit', 'Bake', 'Beurre', 'Bistro', 'Blend', 'Boil', 'Bouchees', 'Brew', 'Buffet', 'Caffe', 'Caffeine', 'Cake');
protected static $companyNameFormats = array(
'{{techTerm}}{{culinaryTerm}}',
'{{techTerm}}{{techTerm}}',
'{{culinaryTerm}}{{techTerm}}'
);
public static function techTerm()
{
return static::randomElement(static::$techTerms);
}
public static function culinaryTerm()
{
return static::randomElement(static::$culinaryTerms);
}
public function companyName()
{
$format = static::randomElement(static::$companyNameFormats);
return $this->generator->parse($format);
}
Basically, the script should create and return a random combination of words as defined in $companyNameFormats. This script requires Faker\Factory, but I'd like to make it independent. At this point, there are 2 problems:
randomElement as an undefined method, and generator->parse as Call to a member function parse() on null
I've managed to modify the script and make it work, but I am interested in how can I use the {{}} as given in $companyNameFormats and return the result without using an external library?
The modified script is as follows:
protected static function companyNameFormats()
{
$techArray = [];
$techArray[] = self::techTerm();
$culinaryArray = [];
$culinaryArray[] = self::culinaryTerm();
$result = array(
array_merge($techArray, $culinaryArray),
array_merge($techArray, $culinaryArray),
array_merge($culinaryArray, $techArray),
array_merge($techArray, $culinaryArray),
array_merge($culinaryArray, $techArray)
);
return $result;
}
public static function techTerm()
{
$techTermKey = array_rand(static::$techTerms, 1);
$techTermValue = static::$techTerms[$techTermKey];
return $techTermValue;
}
public static function culinaryTerm()
{
$culinaryTermsKey = array_rand(static::$culinaryTerms, 1);
$culinaryTermsValue = static::$culinaryTerms[$culinaryTermsKey];
return $culinaryTermsValue;
}
public function companyName()
{
$companyNameKey = array_rand(static::companyNameFormats(), 1);
$companyNameValue = static::companyNameFormats()[$companyNameKey];
return $companyNameValue;
}
They're probably doing something like preg_replace.
Quick and dirty example:
class Foo {
protected static array $foods = ["Pie", "Cake", "Yoghurt"];
protected static array $animals = ["Dog", "Cat", "Giraffe"];
protected static array $formats = [
"{{food}} {{animal}}",
"{{animal}} {{food}}"
];
public function get():string{
$format = $this->getRandomElement(self::$formats);
$format = preg_replace(
"/{{animal}}/",
$this->getRandomElement(self::$animals),
$format
);
return preg_replace(
"/{{food}}/",
$this->getRandomElement(self::$foods),
$format
);
}
protected function getRandomElement(array $elements):string{
return $elements[random_int(0, count($elements) - 1)];
}
}
echo (new Foo())->get(); // Cat Yoghurt
Based on the #Kyrre code, this is the final working example:
protected static $techTerms = array('AddOn', 'Algorithm', 'Architect', 'Array', 'Asynchronous', 'Avatar', 'Band', 'Base', 'Beta', 'Binary');
protected static $culinaryTerms = array('Appetit', 'Bake', 'Beurre', 'Bistro', 'Blend', 'Boil', 'Bouchees', 'Brew', 'Buffet', 'Caffe', 'Caffeine', 'Cake');
protected static $companyNameFormats = array(
'{{techTerm}} {{culinaryTerm}}',
'{{techTerm}} {{techTerm}}',
'{{culinaryTerm}} {{techTerm}}'
);
public static function techTerm()
{
return static::getRandomElement(static::$techTerms);
}
public static function culinaryTerm()
{
return static::getRandomElement(static::$culinaryTerms);
}
protected static function getRandomElement(array $elements): string
{
return $elements[random_int(0, count($elements) - 1)];
}
public function get(): string
{
$format = $this->getRandomElement(self::$companyNameFormats);
$format = preg_replace(
"/{{techTerm}}/",
$this->getRandomElement(self::$techTerms),
$format
);
return preg_replace(
"/{{culinaryTerm}}/",
$this->getRandomElement(self::$culinaryTerms),
$format
);
}
I want to make a class let's say it's called validation, it has two functions, either registrationVal or loginVal.
I want to pass data from two different pages that will use the same class. I want to initialize the variables in a constructor so it looks cleaner... how can I do this?
How can I call the constructor from one page when it doesn't have all the variables to be passed to the constructor?
For example:
registration page
$obj = new validation($password, $password2, $email, $phone);
$obj->registrationVal();
login page
$obj = new validation($email, $password);
$obj->loginVal();
You can make something like this. It's not the best code - but for start is not bad.
<?php
class Validator
{
private array $params;
private array $validated = [];
private array $errors = [];
public function __construct(array $params)
{
$this->params = $params;
$this->validate();
}
private function validate(): void
{
foreach ($this->params as $field => $param){
$validationMethod = 'validate' . ucfirst(strtolower($field));
if(! method_exists($this, $validationMethod)){
continue;
}
if($error = $this->{$validationMethod}($param)){
$this->errors[$field] = $error;
}else{
$this->validated[$field] = $param;
}
}
}
public function validateOrFail(): void
{
if($this->hasErrors()){
throw new ValidationException($this->getErrors());
}
}
public function validated(): array
{
return $this->validated;
}
private function validateLogin($value): ?string
{
//validation logic - return null on success or error string
return $validation_result;
}
public function __get($name)
{
return $this->params[$name] ?? null;
}
public function getErrors(): array
{
return $this->errors;
}
public function hasErrors(): bool
{
return count($this->errors) > 0;
}
}
You must write validation methods such validateLogin() for login field or validatePassword() for password field.
How it working:
$params = [
'login' => 'login',
'password' => 'password'
...
];
$validator = new Validator($params);
//it will throw exception when validation failing.
$validator->validateOrFail();
//or you can manually working with errors
if($validator->hasErrors()){
...
}
$password = $validator->password;
//or you can get only validated fields
$params = $validator->validated();
I have an interface with search function:
interface Searcher
{
public function search($text,$limit)
}
I have some realization based on API
class APISeach implements Searcher
{
public function search($text,$limit)
{
$params = [
'name' => $sName,
'maxRows' => $iLimit,
];
$response = Http::get('http://some_api_service/search', $params)->json();
}
}
And I have some code which used this Search:
class MyController extends Controller
{
private $seacher;
public function __construct(Searcher $mySeacher)
{
$this->seacher = $mySeacher;
}
public function search($text,$limit=10)
{
$this->seacher->search($text,$limit)
}
}
All looks fine (may be). But what if I need change API realization and it will be required another parameters. For example:
class AnotherAPISeach implements Searcher
{
public function search($text,$language,$fuzzy,$limit)
{
$ApiObjectFromLib = new ApiObjectFromLib();
$response = $ApiObjectFromLib->search($text,$language,$fuzzy,$limit)->json();
}
}
So it's can not implement Searcher interface any more.
Is it exists any way to use interfaces for API functions? All API can required various parameters and it's no good if I need change Interface or Controller code for each API.
You can use variadic arguments
interface Searcher
{
public function search($text, $limit, ...$additional);
}
If that defeats the purpose of the interface is up to you to decide 😉
​​​​​​​
How about something like this (demo)?
public function search(string $name, int $limit = \Search\Limit::DEFAULT)
{
return $this->api->search(
new \Search\Name($name),
new \Search\Limit($limit)
);
}
public function lookup(
string $name,
string $language = \Search\Language::DEFAULT,
bool $fuzzy = \Search\Fuzzy::DEFAULT,
int $limit = \Search\Limit::DEFAULT
) {
return $this->api->search(
new \Search\Name($name),
new \Search\Language($language),
new \Search\Fuzzy($fuzzy),
new \Search\Limit($limit)
);
}
These "specifications" look like this:
namespace Search
{
interface Specification
{
public function __invoke(array $params): array;
}
class Name implements Specification
{
private $name = null;
public function __construct(string $name)
{
$this->name = $name;
}
public function __invoke(array $params): array
{
return [
'name' => $this->name,
];
}
}
class Language implements Specification
{
const DEFAULT = 'en';
private $language = null;
public function __construct(string $language)
{
$this->language = $language;
}
public function __invoke(array $params): array
{
return [
'language' => $this->language ?? 'en',
];
}
}
class Fuzzy implements Specification
{
const DEFAULT = true;
private $fuzzy = null;
public function __construct(bool $fuzzy)
{
$this->fuzzy = $fuzzy;
}
public function __invoke(array $params): array
{
return [
'fuzzy' => $this->fuzzy,
];
}
}
class Limit implements Specification
{
const DEFAULT = 10;
private $max = null;
public function __construct(int $limit)
{
$this->limit = $limit;
}
public function __invoke(array $params): array
{
return [
'maxRows' => $this->limit ?: self::DEFAULT,
];
}
}
}
Which the searchable API composes like this:
interface Searchable
{
public function search(\Search\Specification... $criteria);
}
class Search implements Searchable
{
private $url = '/search';
private $defaults = [
'maxRows' => \Search\Limit::DEFAULT,
];
public function search(\Search\Specification ...$criteria)
{
return \Http::get($this->url, array_reduce(
$criteria,
fn($params, $criteria) => $criteria($params) + $params,
$this->defaults
))->json();
}
}
The Specification Pattern is interesting, since it implies that a request into a domain is really just a chain of decisions that result in a configuration that can be applied elsewhere.
For instance, note how above the $criteria($params) objects are each given the current $params for the request, for which it may override parameters, read and modify a parameter, or potentially incorporate a Specification check to validate parameters.
Note on the array + array syntax, which is a way to merge arrays:
['foo' => 'bar'] + ['foo' => 'baz'] // left takes precedence: ['foo' => 'bar']
Filter/Criteria is very similar; I tend to think of those having a tighter link to the object it's applied to (Repository, Query or Collection) than Specification, which in my mind applies more to what's to be gotten back.
I want to load the data from a JSON file into a table. (With Zend Framework)
This is my JSON file markup:
{
"wijken": {
"11": {
"coords": "3.79073170001967,51.1717753664505,0 3.79020920176376,51.1723018883706,0 3.78989543642226,51.1729670713336,0 3.78983091856725,51.1736482209016,0 3.79035112720225,51.174896701853",
"id": "kml_1",
"fid": "0",
"wijziging": "Ja",
"nieuwnr": "11",
"naam": "Noordoost",
"wijk": "Kanaaldorpen en -zone",
"wijknr": "11",
"objectid": "1",
"area": "0",
"len": "0"
}
}
I know how to do this with clear php: (and it works)
<?php
//connection to database
$$connect = mysql_connect('localhost', 'root', 'root');
$db = mysql_select_db('testdatabase');
// ALLE VELDEN LEEGMAKEN
mysql_query("TRUNCATE TABLE wijken");
// url from json file
$url = "http://data.appsforghent.be/poi/wijken.json";
//get content from json file
$json = file_get_contents($url);
// OM ALLES VAN IN DE JSON FILE TE TONENE
//var_dump(json_decode($json));
//var_dump(json_decode($json, true));
$out = json_decode($json, true);
foreach($out["wijken"] as $wijk)
{
// ID + NAAM + WIJK + WIJKNR + COORDINATEN
$coords = addslashes($wijk[coords]);
$id = addslashes($wijk[id]);
$fid = addslashes($wijk[fid]);
$wijziging = addslashes($wijk[wijziging]);
$nieuwnr = addslashes($wijk[nieuwnr]);
$naam = addslashes($wijk[naam]);
$wijk = addslashes($wijk[wijk]);
$wijknr = addslashes($wijk[wijknr]);
$objectid = addslashes($wijk[objectid]);
$area = addslashes($wijk[area]);
$len = addslashes($wijk[len]);
mysql_query("INSERT INTO wijken (coords, id, fid, wijziging, nieuwnr, naam, wijk, wijknr, objectid, area, len)
VALUES('$coords', '$id', '$fid', '$wijziging', '$nieuwnr', '$naam', '$wijk', '$wijknr', '$objectid', '$area', '$len')") or die (mysql_error());
}
?>
But how can I implement this in Zend Framework?
What I have till now in my models map in Zend Framework:
Map "DbTable" with Districts.php in it:
class Backoffice_Model_DbTable_Districts extends Zend_Db_Table_Abstract
{
protected $_name = 'Wijken';
}
District.php with getters and setters:
class Backoffice_Model_District extends Ahs_Model_Abstract
{
protected $coords;
protected $id;
protected $fid;
protected $wijziging;
protected $nieuwnr;
protected $naam;
protected $wijk;
protected $wijknr;
protected $objectid;
protected $area;
protected $len;
public function getCoords()
{
return $this->coords;
}
public function setCoords($coords)
{
$this->coords = $coords;
}
public function getId()
{
return $this->id;
}
public function setId($id)
{
$this->id = $id;
}
public function getFid()
{
return $this->fidid;
}
public function setFid($fid)
{
$this->fid = $fid;
}
public function getWijziging()
{
return $this->wijziging;
}
public function setWijziging($wijziging)
{
$this->wijziging = $wijziging;
}
public function getNieuwnr()
{
return $this->nieuwnr;
}
public function setNieuwnr($nieuwnr)
{
$this->nieuwnr = $nieuwnr;
}
public function getNaam()
{
return $this->naam;
}
public function setNaam($naam)
{
$this->naam = $naam;
}
public function getWijk()
{
return $this->wijk;
}
public function setWijk($wijk)
{
$this->wijk = $wijk;
}
public function getObjectid()
{
return $this->objectid;
}
public function setObjectid($objectid)
{
$this->objectid = $objectid;
}
public function getArea()
{
return $this->area;
}
public function setArea($area)
{
$this->area = $area;
}
public function getLen()
{
return $this->len;
}
public function setLen($len)
{
$this->len = $len;
}
}
Now I have a DistrictMapper.php, but how can I implement the code to load everything from the json in my database?
What I have till now:
protected $_dbTable;
public function __construct()
{
$this->_dbTable = new Backoffice_Model_DbTable_Districts();
}
public function fetchAll()
{
$rowset = $this->_dbTable->fetchAll();
$districts = $this->_toObjects($rowset);
return $districts;
}
And now I need to make the save and toobject.
public function save(Backoffice_Model_Admin $admin)
{
$data = array('coords' => $district->getCoords(),
'id' => $district->getId(),
'fid' => $district->getFid(),
'wijziging' => $district->getWijziging(),
'nieuwnr' => $district->getNieuwnr(),
'naam' => $district->getNaam(),
'wijk' => $district->getWijk(),
'wijknr' => $district->getWijknr(),
'objectid' => $district->getObjectid(),
'area' => $district->getArea(),
'len' => $district->getLen(),
);
}
protected function _toObject(Zend_Db_Table_Row_Abstract $row = null)
{
}
From the Zend docs, Zend_DbTable::insert(), and adapted to your other posted table info:
public function save(Backoffice_Model_Admin $admin){
$data = array(
"fid": "0",
"wijziging": "Ja",
"nieuwnr": "11",
// other data from $admin...
);
$table = new Backoffice_Model_DbTable_Districts();
$table->insert($data);
}
Construct your array; pass it in.
I have an action in my controller called createAction. I also have a model My_Application_Product, that I'm using to create the product within the system. I'm following the Architecting Your Models talk. Is this the "correct" way to save my product? Code for My_Application_Product follows below.
class ProductController extends Zend_Controller_Action {
public function createAction() {
$categoryAdapter = new Application_Model_Categories();
$categories = $categoryAdapter->fetchAll('parent_id IS NOT NULL');
$form = new My_Application_Forms_Product_Create();
$category = $form->getElement('category');
foreach ($categories as $cat) {
$category->addMultiOption($cat->id, $cat->name);
}
if ($this->getRequest()->isPost()) {
if (! $form->isValid($_POST)) {
$this->view->form = $form;
return $this->render('create');
}
$product = new My_Application_Product();
$product->name = $_POST['name'];
$product->company_id = 1;
$product->category_id = $_POST['category'];
$product->trade_names = $_POST['trade_names'];
$product->website = $_POST['website'];
$product->description = $_POST['description'];
$product->closed_loop = $_POST['closed_loop'];
$product->sold_as = $_POST['sold_as'];
$product->sold_in = $_POST['sold_in'];
$product->dilution = $_POST['dilution'];
$id = $product->save();
$url = $this->getHelper('Url')
->url(array('action' => 'registryservices', 'id' => $id));
$this->_redirect($url);
}
$this->view->form = $form;
}
}
'
class My_Application_Product implements My_Application_Product_Interface {
// declare all the internally used variables here.
// if something isn't showing up when trying to save, that's probably
// because it's missing from here
protected $_id;
protected $_name;
protected $_company_id;
protected $_trade_names;
protected $_website;
protected $_description;
protected $_closed_loop;
protected $_sold_as;
protected $_sold_in;
protected $_dilution;
protected $_category_id;
protected $_verification_level;
protected $_dfe_sccp;
protected $_dfe_siicp;
protected $_el_ccd_hsc;
public function __set($name, $value) {
$local_var_name = "_" . $name;
if (property_exists($this, $local_var_name)) {
$this->{$local_var_name} = $value;
}
}
public function __get($name) {
$local_var_name = "_" . $name;
if (property_exists($this, $local_var_name)) {
return $this->{$local_var_name};
}
}
/**
*
* #param array $data The data to save
*/
public function save() {
// this means we're editing something
if ($this->id) {
$table = new My_Application_Product_Table();
$data = $table->find($this->id)->toArray();
$data = $data[0];
foreach (get_class_vars(get_class($this)) as $key => $value) {
if (! is_null($this->$key)) {
$data[preg_replace('/^_/', '', $key)] = $this->$key;
}
}
$id = $table->update($data, sprintf('id = %d', $this->id));
// this means we're creating, and this is the data we need
} else {
$data = array(
'id' => rand(1,1000000),
'name' => $this->name,
'date_created' => date('Y-m-d H:i:s'),
);
$id = $table->insert($data);
}
return $id;
}
}
'
class My_Application_Product_Table extends Zend_Db_Table_Abstract {
protected $_name = 'products';
protected $_primary = 'id';
}
Split your model in multiple classes :
1 class representing the entity (no methods, except for accessors).
this class represents your "real-life" object, and is just a structured data container, which encapsulates data
class My_Application_Model_Product {
protected $_id;
protected $_name;
protected $_company_id;
protected $_trade_names;
protected $_website;
//...
public function __set($name, $value) {
//Implement your setter here
}
public function __get($name) {
}
}
1 class responsible of data mapping.
This class makes is the link between your data source (database, webservice, file...) and your entity.
Class My_Application_Model_DataMapper_Product {
protected $_adapter
public function __construct($adapter)
{
$this->setAdapter($adapter);
}
public function setAdapter($adapter)
{
$this->_adapter = $adapter;
}
public function save(My_Application_Model_Product $product)
{
//Perform your save operation here
}
public function fetchAll()
{
}
public function findById($id)
{
}
//You may implement specific methods for any needed specific operation (search, bulk-update...
}
a third class for data access and persistence (Zend_Db_table, Soap client...) This third class is passed to the datamapper as the adapter and is used inside the methods to getch/save data.
With this architecture, you have a clear separation of responsibilities, and may change one part without affecting the other : for example, you could switch from a database to a webservice without affecting your Product class.
A very simple example is given in the zf Quickstart