ZF2: Custom user mapper for ZfcUser module - php

I`ve added module ZfcUser on my Zend Framework 2 application.
But I have to use existing database table,
which has slightly different column names than the default table structure for ZfcUser.
In ZfcUser wiki page it says that it is possible to use custom mapper if my model doesn`t conform to the provided interface.
And since my database table is different than default, my user entity class is also different than
standard ZfcUser\Entity\User. But I can tell ZfcUser to work with my own class easily
by overriding setting in file config/autoload/zfcuser.global.php:
'user_entity_class' => 'MyApp\Entity\MyUser',
But I`ve not found an easy way to tell ZfcUser to use my mapper class so far.
I have only found that the mapper is created by ZfcUser\Module::getServiceConfig()
inside which, I can see the mapper is returned from its factory function:
// ...
public function getServiceConfig()
{
return array(
// ...
'factories' => array(
// ...
'zfcuser_user_mapper' => function ($sm) {
$options = $sm->get('zfcuser_module_options');
$mapper = new Mapper\User();
$mapper->setDbAdapter($sm->get('zfcuser_zend_db_adapter'));
$entityClass = $options->getUserEntityClass();
$mapper->setEntityPrototype(new $entityClass);
$mapper->setHydrator(new Mapper\UserHydrator());
return $mapper;
},
// ...
Is there a way to make ZfcUser to use my custom user mapper class?

I've had the same problem as you, but managed to log in to my application at last. I followed Rob's advice and created my own service factory within my existing user module. Unfortunately Bernhard is also spot on. You kinda have to dig into the ZfcUser source code to get it to work. The project I am working on now has a MSSQL server and I must say that it's been tough getting a handle on things. I've ended up tweaking only one function in the ZfcUser source to get the login page to work.
I only need log in functionality for the current application, but the upcoming project is much more role driven. I was looking for something that wouldn't be too complicated to hook up quickly, and at the same time offer more options and possibilities for the future.
Here is what I did for now and what I've learned:
I copied the Entity and Mapper folders from the ZfcUser directory over to my existing b2bUser (my module) folder. Everything...even the Exception folder inside Mapper. It might not be necessary, but I wasn't in the mood to figure out dependencies.
In the zfcuser.global.php file, my active configuration looks as follows:
'user_entity_class' => 'b2bUser\Entity\User',
'enable_registration' => false,
'enable_username' => true,
'auth_identity_fields' => array( 'username' ),
'login_redirect_route' => 'home',
'enable_user_state' => false,
I left the rest of the settings on default. I removed the email option from auth identities because they won't use email addresses to log into the system. The user_entity_class is the one I copied over...
Module.php (b2bUser)
Copied the following to the service manager config:
'zfcuser_user_mapper' => function ($sm) {
$mapper = new Mapper\User();
$mapper->setDbAdapter($sm->get('Zend\Db\Adapter\Adapter'));
$mapper->setEntityPrototype(new Entity\User());
$mapper->setHydrator(new Mapper\UserHydrator());
return $mapper;
},
After the setup was done, I changed the namespaces, etc of the files in Entity and Mapper to reflect their new home. Changed the Entity and the Interface to reflect my own data structure. I did the same with the Mapper files and made sure the variable names in the Hydrator file is the same as my database column names.
I left the AbstractDbMapper file where it was. But this is the file I tweaked a bit.
Here is what mine looks like. The SQLSRV driver was full of pooh, complaining the whole time about an object or a string...
protected function select(Select $select, $entityPrototype = null, HydratorInterface $hydrator = null)
{
$this->initialize();
$selectString = $this->getSlaveSql()->getSqlStringForSqlObject($select);
$stmt = $this->getDbAdapter()->driver->createStatement($selectString);
$stmt->prepare();
$res = $stmt->execute($stmt);
$resultSet = new HydratingResultSet($hydrator ?: $this->getHydrator(),
$entityPrototype ?: $this->getEntityPrototype());
$resultSet->initialize($res);
return $resultSet;
}
And that is that. I hope it helps someone to get it up and running on their own system at least. I won't leave mine like this, but it was a bit of a mission to get it to work.

Ceate your own service factory for zfcuser_user_mapper and it will get used.

Currently there seems to be no easy way to change the table structure without modifying the ZfcUser source. There's a pull request on Github that should solve this problem:
https://github.com/ZF-Commons/ZfcUser/pull/174

Related

TYPO3 EXT:cart_product: extend class product with own extension

I try to extend the existing class Product from the Typo3 (Version 11) cart_products extension with my own extension. Therefor I have already extended the backend and tables to store a new value called productpackagetype.
Next step for me was to implement the new class Product in myext/Classes/Domain/Model/Product/Product.php which looks like this :
<?php
namespace Vendor\myext\Domain\Model\Product;
class Product extends \Extcode\CartProducts\Domain\Model\Product\Product
{
protected $productpackagetype = '';
public function getProductpackagetype()
{
return $this->productpackagetype;
}
public function setProductpackagetype($productpackagetype)
{
$this->productpackagetype = $productpackagetype;
}
}
To tell typo3 to use the new class definition I tried the following code in the myext/ext_localconf.php
$GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][\Extcode\CartProducts\Domain\Model\Product\Product::class] = [
'className' => \Vendor\myext\Domain\Model\Product\Product::class
];
\TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\Container\Container::class)
->registerImplementation(
\Extcode\CartProducts\Domain\Model\Product\Product::class,
\Vendor\myext\Domain\Model\Product\Product::class
);
As long as I write nothing to myext/ext_localconf.php nothing in the frontend changes. But as soon as I add the above code typo3 comes up with a Error 503
Return value of Extcode\CartProducts\Domain\Model\Product\Product::getBeVariants() must be an instance of TYPO3\CMS\Extbase\Persistence\ObjectStorage, null returned
So what would be the right way to bring up my extended class. And maybe someone can tell me how I tell typo3 to use my extensions private template show.html instead of using cart_products template.
Best regards
Johannes
You forgot to map your model to the products table of the extension cart_products. To do so, create a file myext/Configuration/Extbase/Persistence/Classes.php and paste these lines:
<?php
declare(strict_types = 1);
return [
\Vendor\myext\Domain\Model\Product\Product::class => [
'tableName' => 'tx_cartproducts_domain_model_product_product',
],
];
You need to flush all the caches to apply this change.
You can read more about persistence handling in TYPO3 here.
Concerning your second question: You have to configure the paths to your templates in TypoScript:
plugin.tx_cartproducts {
view {
templateRootPaths.10 = EXT:myext/Resources/Private/Templates/
partialRootPaths.10 = EXT:myext/Resources/Private/Partials/
layoutRootPaths.10 = EXT:myext/Resources/Private/Layouts/
}
}
Please note the index 10 here. You can configure multiple paths. TYPO3 will start looking for the template in the folder configured with the hightest numbered index. If the file does not exist at that location, it continues with the next lower index.
When you inspect the file cart_products/Configuration/TypoScript/setup.typoscript you will see that the extension uses the index 0. So if you don't provide any templates, the default ones will be used.
Make sure to use the same names for folders and files as in the original extension.
You can read more about template paths in TYPO3 here

Zend\Mvc\Controller\PluginManager::get was unable to fetch or create an instance for init

I'm maintaining a PHP Zend application. I'm attempting to add functionality to it.
I'm trying to call a Controller through a phtml file. I'm thinking I'm approaching this the wrong way, but I'm not sure what the correct way is.
I've modified three files and added another. I've added code to FileController.php.
public function getModifiedBy($filename) {
$groupFiles =$this->getServiceLocator()->get('qatools\Model \GroupFilesTable');
$modified = $groupFiles->fetch($filename);
return $modified;
}
I've also added code to job-wizard.phtml.
<?php
use qatools\Controller\FileController;
$fileControl = new FileController;
$fileControl->init();
$modified =$fileControl->getModifiedBy("addresscleaningservice.xlsx");
?>
The new file is 'GroupFileTable.php' which extend AbstractModelTable and queries a MySQL database. I added the following lines to module.config.php.
'qatools\Model\GroupFilesTable' => function($sm) {
return defModelTable($sm, 'GroupFilesTable');
},
'GroupFilesTableGateway' => function($sm) {
return defModelTableGateway($sm, 'group_files', 'GroupFiles');
},
The code is failing on $fileControl->init() in job-wizard.phtml. I tried taking out that line, but then the code failing on the getServiceLocator call.
#0 /mnt/c/git-repos/qatools/vendor/zendframework/zendframework/library /Zend/ServiceManager/AbstractPluginManager.php(103): Zend\ServiceManager\ServiceManager->get('init', true)
#1 /mnt/c/git-repos/qatools/vendor/zendframework/zendframework/library/Zend/Mvc/Controller/PluginManager.php(82): Zend\ServiceManager\AbstractPluginManager->get('init', NULL, true)
You didn't specify what version of Zend Framework are you using, anyway whatever version is, you should not create an istance of a controller within a view script.
In ZF, if you need to call a view functionality, a better approach is to create a view helper, the logic between ZF1 and ZF2/3 is the same, only the implementation changes, I'll leave to you google searches to see examples, your final view script code should be something like the follow
<?php echo $this->getModifiedBy("addresscleaningservice.xlsx"); ?>
The viewHelper will contain the logic. If at some point you'll need to reuse this logic within a controller or in whatever other place, my advice is to create a component with the logic, then create a service that returns the instance of this component and eventually create an action helper ( $this->myComponent in any controller ) and a view helper ( $this->myComponent in any view ) and inject the same component instance.

How update object with relation 1:n in a hook in TYPO3

I have an A entity and this have a property call B as relation 1:n from B to A. When I update A in TCA backend interface, when an particular field is active, the solution runs a hook of type function processDatamap_postProcessFieldArray (...)
I have to create a new element of B and save in ObjectStorage attribute of A. This works in execute time, create an objet and attaching it, but can not save in DB. I have tried with functions of extbase and Repository but it does not work. In my reserch identified the framework Doctrine for create queries, similar to persistence behavior, but in this case I could save the new object of B.
My question is: how could I use Doctrine for build query that allows to make update for object A, adding the new element B and save this element in the relation in DB.
I am working with TYPO3 7.6
You shouldn't use Extbase within the DataHandler hooks. Also plain database queries (neither with Dotrine or TYPO3_DB) are not good idea for creating entities within BE. Better way is to use TYPO3 DataHandler API. Example creation of Entity B during create/edit of Entity A could look like that.
Register hook typo3conf/ext/example/ext_localconf.php
defined('TYPO3_MODE') || die('Access denied.');
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass']['example'] = 'Vendor\\Example\\DataHandling\\DataHandler';
typo3conf/ext/example/Classes/DataHandling/DataHandler.php
namespace Vendor\Example\DataHandling;
use TYPO3\CMS\Core\SingletonInterface;
use TYPO3\CMS\Core\Utility\StringUtility;
class DataHandler implements SingletonInterface
{
public function processDatamap_afterDatabaseOperations(
string $status,
string $table,
$id,
$fieldArray,
\TYPO3\CMS\Core\DataHandling\DataHandler $dataHandler
) {
// Do nothing if other table is processed
if ($table !== 'tx_example_domain_model_entitya') {
return;
}
// Get real UID of entity A if A is new record
$idOfEntityA = $dataHandler->substNEWwithIDs[$id];
// Example fields of entity B
$entityB = [
'sys_language_uid' => '0',
'entitya' => $idOfEntityA,
'hidden' => '0',
'title' => 'I\'m entitty B',
'starttime' => '0',
'endtime' => '0',
'pid' => $fieldArray['pid'],
];
// Add entity B associated with entity A
$dataHandler->start(
[
'tx_example_domain_model_entityb' => [
StringUtility::getUniqueId('NEW') => $entityB
]
],
[]
);
$dataHandler->process_datamap();
}
}
Tested on 8.7, but will work on 7.6 too. Here you can read more about DataHandler https://docs.typo3.org/typo3cms/CoreApiReference/8.7/ApiOverview/Typo3CoreEngine/Database/
In contrary to the previous answer, I see no reason, why extbase shouldn`t be used in the DataHandler Hooks. I do it myself in an extension with dynamic objects that are being synchronized via a SOAP-Webservice.
You got to keep few things in mind (in this order inside the hooked function) :
-Obey naming policies !!
-Instantiate the ObjectManager manually via GeneralUtility::makeInstance
-Get ALL the repositories manually (with all I mean really all.. also repositories of childs of models you are working with inside the hooked function).
-Create new object instances with object manager => not with "new".
Then you can just add childs to parents as you are used to.. but dont forget to persistAll() via persistenceManager manually in the end.
Hope this could help. Basically, a function hooked via DataMap Hook acts like a static function called via ajax => you have to make sure to get all the desired utilities and managing classes manually, because typo3 doesn`t auto-inject them.
Hope this helps,
Oliver

Symfony2 / Doctrine - create entity only in the test environment

I want to create some entities that would run only in test environment, during my unit tests. I dont think that there is some ebbded solution (am I wrong?), so probably another way is to create entities in the test folderand use them.
But there is something that I dont understand. Symfony sets the BundleName\Entity folders as folders where entities are, and Tests/Entity folder will not work with my entities. So, how do I explicitly set my Tests/Entity folder to work (read/install/register entities) in my test case? I assume this is made by configurating of doctrine entity manager?
Found this function in one of my projects, may be it can help you a bit. This function create new EntityManager, where you can define your entities namespace.
/**
* #return EntityManager
*/
public static function createTestEntityManager()
{
if (!class_exists('PDO') || !in_array('sqlite', \PDO::getAvailableDrivers())) {
self::markTestSkipped('This test requires SQLite support in your environment');
}
$config = new \Doctrine\ORM\Configuration();
$config->setEntityNamespaces(array('SerializerBundleTests' => 'Top10\SerializerBundle\Tests\Entity'));
$config->setAutoGenerateProxyClasses(true);
$config->setProxyDir(\sys_get_temp_dir());
$config->setProxyNamespace('SerializerBundleTests\Entity');
$config->setMetadataDriverImpl(new AnnotationDriver(new AnnotationReader()));
$config->setQueryCacheImpl(new \Doctrine\Common\Cache\ArrayCache());
$config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ArrayCache());
$params = array(
'driver' => 'pdo_sqlite',
'memory' => true,
);
return EntityManager::create($params, $config);
}
I may be wrong, but I think that Doctrine's entity manager doesn't allow you to add mapping for additional entities on the fly.
As a "crutchy" workaround, you could create an additional bundle that contains entities you want to use with your tests, then leave your Symfony2 config for the test environment the same (if you have "auto_mapping" set to true; see this reference document), while changing you orm config for dev and prod environment. You'll need to disable auto mapping and specify the bundles explicitly, excluding the bundle with your test-only entities from "mappings".
Or maybe you don't even need to specify the value of "auto_mapping". The relevant part of your config ("entity_managers", "mappings") for the dev\prod environment should look somewhat similar to the one in the cookbook entry.
Again, I wish to emphasize that I'm not 100% sure that this answer is correct, as my knowledge of Symfony2 has become a bit fuzzy.

how to use Lithium PHP framework with enumerated list of collections and changing collection in model

I'm looking to use Lithium framework to build my application config interface as I like its minimal approach and the document-store (i.e. Mongodb) centric model.
However, (and I know its not quite released yet), there is little-to-no information, tutorials or examples out there to move you on from the simple blog tutorial.
What I am trying to do now is build an app that will show me the collections I have in Mongodb, and then let me work with which ever collection I choose. I can't seem to figure out:
a) how would I build a model that enumerates the collections - preferably according to my internal naming scheme,
b) how do I break the convention model so I can specify the name of the collection to use?
I think there are two things i'm struggling with to answer these two questions - perhaps a fundamental misunderstanding of how to move a model in MVC beyond the simple collection-model-controller-view examples, and secondly, the actual process of telling the mongo datasource what collection to use.
any pointers or examples, gratefully received.
Chris
update::
So I figured out how to set the collection - for reference you can set source in the $_meta array like this:
protected $_meta = array(
'source' => '<<collectionName>>'
);
still no idea how to use a Model that will list me all the collections I have in my DB though. Any ideas how to do that from a philosophical and also technological manner?
further update::
so I have got a bit further thanks to the comments below. At least I might now be able to re-phrase the question a bit. I can define my model something like this:
<?php
namespace app\models;
use lithium\data\Model;
class Posts extends \lithium\data\Model{
protected $_meta = array('source' => false);
public function testcolls(){
return (self::connection()->sources());
}
}
?>
then in my view I can use:
<?php foreach ($post->testcolls() as $coll): ?>
<h2><?=$coll ?></h2>
<?php endforeach; ?>
that works - however, what I really want to do is not create a 'testcolls' method in my Model but as Medhi suggested below, I need to override the find method. I can't seem to figure out how to do that and what it would need to return. The docs are not too clear on this.
final update
based on the comment below and a bit of experimentation, I came up with the following that works for being able to call find with a collection as a parameter.
model:
class Dataqueues extends \lithium\data\Model{
protected $_meta = array('source' => false);
public static function find($filter, array $options = array()) {
if (isset($options['collection'])){
self::meta('source', $options['collection']);
}
return parent::find('all',$options);
}
}
controller:
class DataqueuesController extends \lithium\action\Controller {
public function index() {
$dataqueues = Dataqueues::find('all',array('limit'=>20,'collection'=>'W501'));
return compact('dataqueues');
}
}
getting a model that returns a list of collections was also pretty simple in the end:
class Collections extends \lithium\data\Model{
protected $_meta = array('source' => false);
public static function find($filter, array $options = array()) {
return self::connection()->sources();
}
}
note that the controller won't support options or filters.
Nothing holds you from having a Collections Model, where you set $_meta['source'] = false to prevent Lithium from looking for a Collection in your database named collections.
In this model, you can call YourModel::connection()->sources() to list all your Mongo Collections.
Docs for sources(): http://li3.me/docs/lithium/data/source/MongoDb::sources(). Basically it calls listCollections() on a MongoDB instance http://php.net/manual/en/mongodb.listcollections.php
You can override your Model::find() method to return the list of collections, instead the list of documents, or pass the collection as a param Collections::find('all', array('conditions' => ..., 'collection' => 'foo'))... or wathever you want :-)
Lithium is designed to don't force that much on you !
First of all, Lithium follows the convention over configuration approach.
What this means:
Configuration: 'source' => '<< collectionName >>'
Convention: Name your model and your collection the same thing, the framework handles the rest.
IE: A "People" collection will have a "People" model
Second, connect to your database:
Configure your connections.php file in app\bootstrap\connections.php. I know I said convention over configuration, but you still need to let the framework know where the database is and what the login info is. For details look at the http://li3.me/docs/manual/quickstart. Here is the relevant code:
// MongoDB Connection
Connections::add('default', array('type' => 'MongoDb', 'database' => 'blog', 'host' => 'localhost'));
Third, get data
Create a model, matching your collection name, and then in your controller, add this line:
$model = Models::all();
where model is the singular name for what you are storing in your collection, and Models is the name of your model. That is it.
If you put a break point after this line, you will see your Models collection. For more information, see http://li3.me/docs/manual/working-with-data/using-models.wiki
Finally, to pass it to your view, simply put this line of code at the end of your controller:
return compact('model', 'model2', 'model3');
where model would be what you just pulled in the third step. The models 2 and 3 that I tacked on is how you would pass any other collections you pulled.
In your view, you would just reference $model to and assume that the relevant fields are there. You don't have to worry about putting getters or setters or anything else like that.
For example: if you want to show the data in $model:
foreach ($model as $aModel) {
echo $aModel;
}
See Accessing View Variables in: http://li3.me/docs/manual/handling-http-requests/views.wiki
Hope this helps.

Categories