Using ZF2 with Doctrine 2.
For the needs of the application i passed objectmanager as a variable ($om) in return new ViewModel
so i can use it in my view:
$om->getRepository('\Schema\Entity\Category')->findBy(.....
The thing that confuses me is:
1) Do I break MVC rules? (Because some say that is a bad practice and some others not)
2) Am I going to face any problems with the development of the application using this tactic?
yes you do
violation of coding standards always has negative side-effects
The Job of the Controller is to pass the Data you need into the View. For this Purpose the Controller interacts with the Model.
public function fooAction()
{
return new ViewModel([
'entities' => $this->getObjectManager()->findBy([
'foo' => 'bar'
])
]);
}
That's pretty much how you will do it. Next question for you should be: What does getObjectManager() do or how do i properly inject the ObjectManager into the Controller?
The answer is as simple: You have to use Controller-Factories
//module.config.php
'controllers' => [
'factories' => [
'YourNamespace\Controller\FooController' => function($cpm) {
$ctrl = new FooController();
$ctrl->setObjectManager(
$cpm->getServiceLocator()->get('Doctrine\ORM\EntityManager')
);
return $ctrl;
}
]
]
And that's all there is to it. Now you have a very clean setup of what you want to achieve ;)
Related
I currently have nested routes.
Let's say I have
Route::resource('a', 'AController');
Route::resource('a.b', 'BController');
Route::resource('a.b.c', 'CController');
In my views, is there a better way to generate URL than doing :
route('a.b.c.show', ['a'=>$aId, 'b'=>$bId, 'c'=>$cId])
It's getting a little annoying for nested urls. Why can't we just pass an Eloquent model instance? Like route('a.b.c.show', $cInstance)
Thank you
I often pass the responsibility of generating the url to the model, e.g. with getters:
$cInstance->url
public function getUrlAttribute() {
return route('a.b.c.show', $this);
// or:
// return action([CController::class, 'show'], ['a' => $this]);
}
But it depends on your use case. Normal methods work just as well, like if you want to add more parameters than just what the model knows about already. For example:
public function url($b, $c) {
return route('a.b.c', ['a' => $this, 'b' => $b, 'c' => $c]);
}
However, if you're talking about just getting the ID from a model before using it, like in this case:
$aId = $a->id;
return route('a', ['a' => $aId]);
Then Laravel actually does support that already: route('a.b.c.show', compact('a', 'b', 'c')) should work fine. Eloquent models implement the Illuminate\Contracts\Routing\UrlRoutable interface, which has a getRouteKey() method that the router understands how to use.
In a function in my controller I call this:
$item = Item::where('i_id', $Id)->where('type', 1)->first();
$firebaseData = app('firebase')->getDatabase()->getReference('items/'.$Id)->getSnapshot()->getValue();
Then I do a lot of "validation" between the data from the two sources above like:
if ($item->time_expires < strtotime(Carbon::now()) && $firebaseData['active'] == 1) {
return response()->json(['errors' => [trans('api.pleaserenew')]], 422);
}
And since this is not data coming from a user/request I cant use Laravels validate method
I dont want to keep this kind of logic inside my controller but where should I put it? Since part of my data is coming from Firebase I cant setup a Eloquent model to handle it either.
I recommend to receive the firebase data via a method within the model:
public function getFirebaseData()
{
app('firebase')->getDatabase()->getReference('items'/ . $this->i_id)->getSnapshot()->getValue();
}
That way you have the logic to receive the data decoupled from controller logic and moved it to where it makes more sense. Adding a validation method could work similarily within the model then:
public function validateData()
{
$combined = array_merge($this->toArray(), $this->getFirebaseData());
Validator::make($combined, [
'active' => 'in:1',
'time_expires' => 'before:' . Carbon::now(),
]);
}
The caveat with this is that the validation error will be thrown within the model instead of the controller, but that shouldn't really be an issue I don't think.
For any data you have in your application you can use Laravel validation.
You can merge your data and process it using Validator facade like this:
$combinedData = array_merge($item->toArray(), $firebaseData);
Validator::make($combinedData, [
'active' => 'required|in:1',
'time_expires' => 'required|before:' . Carbon::now()->toDateTimeString()
], $customMessageArray);
I think the best place for this code is some kind of service class you will inject to controller or other service class using Laravel dependency injection.
I have the following array:
$arr = array(
'Profile' => 'myClient',
'Vendor' => 'myVendor',
);
And the following class
class MyModel
{
protected $client;
protected $vendor;
getClient()
{
return $this->client;
}
setClient($client)
{
$this->client = $client;
}
getVendor()
{
return $this->vendor;
}
setVendor($vendor)
{
$this->vendor = $vendor;
}
}
NOTICE that both the array and the class have "vendor" but the array has "profile" while the class has "client".
I'd like to use a hydrator to populate MyModel objects in Zend Framework 2 with the hydrate method (example assumes a hydrator and a MyModel prototype have been injected into my class):
$obj = $this->hydrator->hydrate($arr, $this->myModelPrototype)
Doing a dump of $obj you would expect to see this:
MyModel Object
(
[client:protected] =>
[vendor:protected] => myVendor
)
However, my desired result would be this:
MyModel Object
(
[client:protected] => myClient
[vendor:protected] => myVendor
)
My Question is, is there any way to customize the hydrator so it will populate the "client" property with the "Profile" value?
At first, I thought I could use a hydrator strategy but that can only alter the values, not the keys. Or, maybe this is some kind of design flaw in my application and there should never be a reason to have to do this?
Thanks for any help / advice.
if i understand you correct, you can solve you're Problem with a NamingStrategy. ZF2 ships some Naming Strategies which are used in several hydrators. unfortunately i did not find any documentation about this. You can find existing Naming Strategies https://github.com/zendframework/zend-stdlib/tree/master/src/Hydrator/NamingStrategy here.
An example how you set your own, custom naming strategy is available here http://blog.igorvorobiov.com/2014/12/29/advanced-hydrator-usage-in-zend-framework-2/ written by Igor Vorobiov.
P.S.: there is documentation for the current naming strategies. I think the MapNamingStrategy fits your needs. See at http://framework.zend.com/manual/current/en/modules/zend.stdlib.hydrator.namingstrategy.mapnamingstrategy.html
HTH
I'm working on a Laravel 5 app using the jenssegers\laravel-mongodb package and I'm trying to get up and running with Factory Muffin to help with rapidly testing and seeding etc.
I have defined relationships in my factory definitions (code below) - and they work when I run seeds to create new records in the database. By "work", I mean they attach related data correctly to the parent model and persist all those records to the database. But they do not work when I run the $muffin->instance('My\Object') method, which creates a new instance without persisting it. All the relations come back as null.
In a way this makes sense. When the models are stored in the database, they are related by the object _id key. That key doesn't exist until the model is stored. So when I call the instance method, I actually can see via debugging that it does generate the models that would be related, but it does not yet have any key to establish the relation, so that data just kinda goes poof.
This is a bummer because I'd like to be able to generate a fully fleshed-out model with its relations and see for example whether it is saved or passes validation and whatnot when I submit its data to my routes and things. I.e., right now I would not be able to check that the contact's email was valid, etc.
Simplified code samples are below - am I going about this in an entirely wrong way? This is my first time working with Mongo and Factory Muffin. Should this even be able to work the way I want it to?
// factories/all.php
$fm->define('My\Models\Event', [
'title' => Faker::text(75),
'contact' => 'factory|My\Models\Contact'
]);
$fm->define('My\Models\Contact', [
'first_name' => Faker::firstName(),
'email' => Faker::email(),
... etc
]);
// EventControllerTest.php
...
/**
* #var League\FactoryMuffin\FactoryMuffin
*/
protected $muffin;
public function setUp()
{
parent::setUp();
$this->muffin = new FactoryMuffin();
$this->muffin->loadFactories(base_path('tests/factories'));
}
...
public function testCanStoreEvent()
{
$event = $this->muffin->instance('Quirks\Models\Event');
echo $event->contact; // returns null
$event_data = $event->toArray();
$this->call('POST', 'events', $event_data);
$retrieved_event = Event::where('title', '=', $event->title)->get()->first();
$this->assertNotNull($retrieved_event); // passes
$this->assertRedirectedToRoute('events.edit', $retrieved_event->id); // passes
// So, the event is persisted and the controller redirects etc
// BUT, related data is not persisted
}
Ok, in order to get factory muffin to properly flesh out mongo embedded relationships, you have to do like this:
// factories/all.php
$embedded_contact = function() {
$definitions = [
'first_name' => Faker::firstName(),
'email' => Faker::email(),
// ... etc
];
return array_map(
function($item) { return $item(); },
$definitions
);
}
$fm->define('My\Models\Event', [
'title' => Faker::text(75),
'contact' => $embedded_contact
]);
If that seems onerous and hacky ... I agree! I'm definitely interested to hear of easier methods for this. For now I'm proceeding as above and it's working for both testing and seeding.
let me explain my problem, hope you get an idea of what it is.
I have a web service which hide from public access, and have designed a secure way of mysql sql querying using to the service across the websites. so i dont think i can really use the current model layer of Yii2, and that also means i hardly can use activeDataProvider as no database present.
Currently what i do is to write raw sqls and get all the results and then feed into dataprovider using ArrayDataProvider.
e.g.
$sql="select * from a_table";
$result=$remote->select($sql);
$dataProvider = new ArrayDataProvider([
'allModels' => $result,
'sort' => [
'attributes' => ['date', 'name'],
],
'pagination' => [
'pageSize' => 10,
],
]);
return $this->render('index', [
'dataProvider' => $dataProvider,
]);
that pose a problem, everytime i need to query the full table. This is not idea if the table is very large. It is better to query in the size of 10 something, however if i do
$sql="select * from a_table LIMIT 10";
no pagination will appear in my case...How do i solve this problem? And if this is not an idea way to talk to external data services, what is ur suggestion?
The pagination won't appear because you only feed 10 rows to the ArrayDataProvider and it has no way of knowing there is more.
If you want to use DataProvider with remote fetching I would advise you to create your own MyRemoteDataProvider class by extending BaseDataProvider and overriding at least these four methods:
use yii\data\BaseDataProvider;
class MyRemoteDataProvider extends BaseDataProvider
{
protected function prepareModels()
{
}
protected function prepareKeys($models)
{
}
protected function prepareTotalCount()
{
}
protected function sortModels($models, $sort)
{
}
}
.. and then of course use your MyRemoteDataProvider instead of ArrayDataProvider. If you don't know what the methods should return, please read documentation for BaseDataProvider or get inspired by ArrayDataProvider implementation.