I try to build an importer with a scheduler task.
The task creates an object manager which creates my import service.
This import service has dependencies to the repository.
I simply create instances and add them to the repository.
It works well until i tried to specify on which pid my records are supposed to be saved. I tried to configure it in setup.txt.
plugin.tx_extkey {
view {
templateRootPath = {$plugin.tx_extkey.view.templateRootPath}
partialRootPath = {$plugin.tx_extkey.view.partialRootPath}
layoutRootPath = {$plugin.tx_extkey.view.layoutRootPath}
}
persistence {
storagePid = {$plugin.tx_extkey.persistence.storagePid}
classes {
EXTNAME\EXTNAME\Domain\Model\MODELNAME {
newRecordStoragePid = {$plugin.tx_extkey.persistence.storagePid}
}
}
}
features {
# uncomment the following line to enable the new Property Mapper.
# rewrittenPropertyMapper = 1
}
}
module.tx_extkey {
persistence < plugin.tx_extkey.persistence
}
But that didn't work. Everything is still saved to pid 1.
Are there any pitfalls that I might have overlooked?
I found an ugly way. The BackendConfigurationManager does not get the extensionName when the service is executed though the scheduler. Manually setting it in the task resolves this.
$objectManager = GeneralUtility::makeInstance('TYPO3\CMS\Extbase\Object\ObjectManager');
/** #var BackendConfigurationManager $configurationManager */
$configurationManager = $objectManager->get('TYPO3\CMS\Extbase\Configuration\BackendConfigurationManager');
$configurationManager->setConfiguration(array(
'extensionName' => 'hnsenvionjob'
));
I use an ImportCommandController as recommended by lorenz in In an extbase extension, how to access the persistence layer from a scheduler task? (my actual code has changed in the meantime, let me know if you want it)
and I have to set the pid manually:
$item->setPid($storagePid);
There is an easy trick for that
add in your ts something like that
plugin.tx_extkey.settings.storagePid = {$plugin.tx_extkey.persistence.storagePid}
That will allow you to have access to your storage pid everywhere in your code where you have access to your ts. For example in controller
$this->settings['storagePid']
Related
In an cakephp app, I need to periodically retrieve data from an api. I also need the admin to be able to launch the update manually.
So I created a model accessing the data; this way I can use it in a controller and in the command/cronjob.
In the controller, no problem, it's running fine.
The problem is when I run the task in the bash : the bin/cake.php file get deleted and I can't do anything (bake, run task, etc).
Here's my code (simplified version on what's failling):
// IN Table/ApisTable.php
namespace App\Model\Table;
use Cake\ORM\Table;
use Cake\ORM\TableRegistry;
use Cake\Http\Client;
class ApisTable extends Table{
public function getUrl(){
$http = new Client();
$response = $http->get('http://api.tvmaze.com/shows/1');
return $response->getStringBody();
}
}
// IN Entity/Api.php
namespace App\Model\Entity;
use Cake\ORM\Entity;
class Api extends Entity{
}
//IN Command/TestCommand.php
namespace App\Command;
use Cake\Command\Command;
use Cake\Console\Arguments;
use Cake\Console\ConsoleIo;
class TestCommand extends Command {
public function execute(Arguments $a, ConsoleIo $io){
$this->loadModel('Apis');
$data = $this->Apis->getUrl();
$io->out($data);
}
}
The file is deleted when I run bin/cake test
Any idea ?
Solved :
After trying few thing on another computer, I solved it. I'll let the subject here, if someone else need it.
It was my antivirus who placed the file in quarantine when reaching another url.
here is a problem I encountered in TYPO3 Extension Development.
I've written an TYPO3-extension. It will display in browser the news in the DB. But I'd like to configure a scheduler task to recurrently update the news in the DB to be displayed.
In writing this scheduler task I've used a Command Controller.
namespace Vendor\Extension\Command;
class CheckNewsCommandController extends \TYPO3\CMS\Extbase\Mvc\Controller\CommandController
{
public function simpleCommand()
{
$newsRepository = $this->objectManager->get( \Vendor\Extension\Domain\Repository\NewsRepository::class );
\TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($newsRepository);
$all_news = $newsRepository->findAll();
\TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($all_news);
}
}
But the variable $all_news contains nothing, it equals to NULL !!! That means, the findAll() Function of the NewsRepository does NOT work at all !!!
In comparison, I've also used this NewsRepository in a normal Controller Class: Vendor\Extension\Controller\NewsController
namespace Vendor\Extension\Controller;
class NewsController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController
{
public function listAction()
{
$newsRepository = $this->objectManager->get( \Etagen\EtSocNewsSt\Domain\Repository\NewsRepository::class );
$all_news = $newsRepository->findAll();
\TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($all_news);
}
And, in the NewsController, the function NewsRepository::findAll() DID really work, and returned all records in the DB.
So, who can tell me, why the Repository function will ONLY work in the class Vendor\Extension\Controller\NewsController, but NOT work in the class Vendor\Extension\Command\CheckNewsCommandController ?
The answer is EASY: You need to define the storagePid for your news records in the CommandController OR change the settings of the NewsRepository to IGNORE the storagePid.
How to set the storagePid for CommandController:
https://worksonmymachine.org/blog/commandcontroller-and-storagepid
How to set the repository to ignore storagePid:
http://typo3.sascha-ende.de/docs/development/database/how-to-ignore-the-page-id-pid-in-repository-database-query/
I'm using a hook after the user unhide a record. In this hook i want to update a object.
class ProcessCmdmap {
function processDatamap_postProcessFieldArray($status, $table, $id, &$fieldArray, &$reference) {
if ($table == 'tx_oaevents_domain_model_events' && $status == 'update' && $fieldArray['hidden'] == 0) {
// Get objectmanager
$objectManager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Object\\ObjectManager');
// Get repo
$repository = $objectManager->get('Mab\\Oaevents\\Domain\\Repository\\EventsRepository');
// Get config manager
$configurationManager = $objectManager->get('TYPO3\\CMS\\Extbase\\Configuration\\ConfigurationManagerInterface');
// Get settings and storage pid
$settings = $configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT);
$storagePid = $settings['plugin.']['tx_oaevents.']['persistence.']['storagePid'];
// Build default query settings
$querySettings = $objectManager->get('TYPO3\\CMS\\Extbase\\Persistence\\Generic\\Typo3QuerySettings');
$querySettings->setStoragePageIds(array($storagePid));
$repository->setDefaultQuerySettings($querySettings);
$object = $repository->findByUid($id);
// modify object
// Update / Persist object
}
}
}
But i can't retrieve a object with "findByUid()" or "findAll()". Do i use the objectmanager in wrong scope? Or how can retrieve and update into hooks my object?
Can someone give me a hint?
Update:
Now i give up :( and not use the objectmanager and repository, i use the functions from $GLOBALS['TYPO3_DB']
I definitely recommend to follow your solution by using $GLOBALS['TYPO3_DB'] at this point. Extbase just brings in a lot of overhead here.
Without Extbase you also need less code here.
Anyway:
Most likely the generated queries by Extbase are broken. Log your queries and check the generated one. Try it out and modify it, until it works. Check the difference.
You can try without using "setStoragePageIds".
If you are looking record by uid it is useless.
My working code for processDatamap:
/** #var $objectManager \TYPO3\CMS\Extbase\Object\ObjectManager */
$objectManager = GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Object\\ObjectManager');
/** #var $orderRepository \Vendor\ExtensionName\Domain\Repository\OrderRepository */
$orderRepository = $objectManager->get('Vendor\\ExtensionName\\Domain\\Repository\\OrderRepository');
$order = $orderRepository->findByUid($id);
It is true that the use of $GLOBALS['TYPO3_DB'] is easier.
I'm currently using SOA, I've a bunch of Service, (ArticleService, CommentService, UserService, etc..)
I also have a ConfigurationService which is filled from an XML configuration file.
I'm using Zend Framework.
THis configuration service is needed in some of my service, and I'm using dependency injection, is it a good practice, to add ConfigurationService in constructor of most my Service to be able to fetch global configuration?
Thank you for your feedbacks.
I would say, no, don't pass the config container - neither as a service nor as an array nor a Zend_Config instance - in the constructor of your other services. I would keep the injection (whether by constructor or by setter) for those services focused on the actual objects/collaborators/data they actually need.
So, for example, an ArticleService might depend upon an ArticleRepository interface/object or on an ArticleMapper or on a db adapter. Let the constructor/setter signatures for the ArticleService reflect what it truly needs.
Instead, what I would do is during Bootstrap, create some kind of factory object - perhaps as an application resource - that accepts in its constructor your config data/object/service (or even better, the bootstrap instance itself, from which you could get, not just your config data, but also any application resources, like a db adapter, that were created during the bootstrap process). Then write methods on your factory object that create/deliver the other services you need. Internally, the factory maintains a registry of already created services so that it can lazy-create instances where required.
A snippet of what I have in mind might be as follows:
Bootstrap snippet:
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
protected function _initFactory()
{
$factory = new My_Factory($this);
return $factory;
}
}
Then the factory:
class My_Factory
{
protected $_registry;
protected $_bootstrap;
public function __constructor($bootstrap)
{
$this->_bootstrap = $bootstrap;
}
public function getDbAdapter()
{
if (!isset($this->_registry['dbAdapter']){
$this->_bootstrap->bootstrap('db'); // probably using app resource
$this->_registry['dbAdapter'] = $This->_bootstrap->getResource('db');
}
return $this->_registry['dbAdapter'];
}
public function getArticleService()
{
if (!isset($this->_registry['articleService']){
$dbAdapter = $this->getDbAdapter();
$this->_registry['articleService'] = new My_ArticleService($dbAdapter);
}
return $this->_registry['articleService'];
}
public function getTwitterService()
{
if (!isset($this->_registry['twitterService']){
$options = $this->_bootstrap->getOptions();
$user = $options['twitter']['user'];
$pass = $options['twitter']['pass'];
$this->_registry['twitterService'] = new My_TwitterService($user, $pass);
}
return $this->_registry['twitterService'];
}
}
Then in a controller, you could grab an ArticleService instance:
class SomeController extends Zend_Controller_Action
{
protected $_factory;
public function init()
{
$this->_factory = $this->getInvokeArg('bootstrap')->getResource('factory');
}
public function someAction()
{
$articleService = $this->_factory->getArticleService();
$this->view->articles = $articleService->getRecentArticles(5); // for example
}
}
The upshot here is that each service explicitly identifies the collaborators it needs and the factory is a single place that takes care of creating/injecting all those collaborators.
Finally, I confess that I am just spitballing here. To me, this is essentially a rudimentary dependency injection container; in that sense, using a fully-featured DIC - perhaps the Symfony DIC or the new Zend\Di package in ZF2 - might be better. But after many months of struggling with all the best-practice recommendations to inject your dependencies, this is what I have come up with. If it's goofy or just plain wrong, please (please!) straighten me out. ;-)
The title of this question was hard to come up with, it might not be the best, anyway.
I have a site with regions, categories and suppliers, the obvious solution is to use the default route of
"/:module/:controller/:action"
So that my URLs will look something like this
"/region/midlands/category/fashion"
"/region/midlands/supplier/ted-baker"
What I want to achieve is a URL format like this however, this would need to involve a database query to check for the existence of midlands, fashion and ted-baker
"/midlands/fashion"
"/midlands/ted-baker"
My original solution was to use something like this
"/region/midlands/fashion"
With a route defined as
routes.category.route = "/region/:region/:category"
routes.category.defaults.controller = category
routes.category.defaults.action = index
routes.category.defaults.module = default
routes.category.defaults.category = false
routes.category.defaults.region = false
routes.supplier.route = "/supplier/:supplier"
routes.supplier.defaults.controller = supplier
routes.supplier.defaults.action = index
routes.supplier.defaults.module = default
routes.supplier.defaults.supplier = false
But that means prefixing everything with region or supplier. I almost need to hijack the request completely with a plug in?
What is the best way of achieving this?
Thanks for any help.
Edit.
#St.Woland, the problem is that I want this route
/:region/:supplier
To work with this URL
/midlands/ted-baker
But that route effectively overrides the default router
The best way is to add a method into your Bootstrap class like this:
<?php
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
protected function _initMyRoutes()
{
// First, initialize Database resource
$this->bootstrap('db');
// Second, initialize Router resource
$this->bootstrap('router');
// Finally, instantiate your database table with routes
$m_routes = new Model_Routes() ;
// Now get the Router
$router = $this->getResource('router');
// ... and add all routes from the database
foreach( $m_routes->fetchAll() as $route ) {
$router->addRoute( $route->name, new Zend_Controller_Router_Route( $route->path, $route->toArray() ) ) ;
}
}
}
Then in your application.ini:
[production]
bootstrap.path = APPLICATION_PATH/Bootstrap.php
bootstrap.class = Bootstrap
This will initialize the router with routes from the database.
You should keep in mind, that queing the database upon every request is not efficient, so make sure to use cache.
I have used the ErrorController to do this in the end. I catch the Exception where there is controller or no route and then act accordingly.
There are calls made to a few database tables for the specific route which cannot be found rather than fetching all as in St.Woland's solution. The results are cached with tags which helps a great deal, this removes all database queries for finding routes
public function errorAction()
{
$errors = $this->_getParam('error_handler');
switch ($errors->type)
{
case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ROUTE:
case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER:
//Code for locating routes in Db goes here
}
}