I am a little confused in this Doctrine model concept , lets say we a table called "article"
Doctrine will generate class called
i am using Zend framework and Doctrine 1.2
models/generated/BaseArticle.php
models/ArticleTable.php
models/Article.php
Is it true to call the ArticleTable in the controller in this way
$tableArticle = Doctrine::getTable('Article');
then to save it in the Article Object like this
$article = new Article();
$fArticles = $tableArticle->getFeaturedArticles();
foreach ($fArticles as $fArticle) {
$article->fromArray($fArticle);
echo $article->title
}
Or I have to let the Article.php to call the ArticleTable ?
then to initiate an Article.php object in the controller ?
class Article extends BaseArticle
{
public function getFArticles()
{
$tableArticle = Doctrine::getTable('Article');
$obj = $tableArticle->getFeaturedArticles();
return $obj;
}
Article.php should not call ArticleTable.php unless really, really needed. In table classes you will only hold queries called by controller like:
$featuredArticles = ArticleTable::getInstance()->getFeatured() ;
Above code is simpler and you will have autocompletion in any IDE.
The reason not to call queries in Article.php is that you will have easier transition to Doctrine2 one day.
For a table call tbl_article or just article, doctrine will generate Article.php and BaseArticle.php. Base classes must not be changed manually.
Article class is where your logic goes. For example, you fetch list of ALL articles in database. When you display them, you want feature articles to have a star (just an example):
controller:
$allArticles = ArticleTable::getInstance()->findAll() ;
template (Smarty version here):
{foreach $allArticles as $article}
{if $article->isFeatured()} <img src=.. some image ..>{/if}
<h5>{$article->title}
{/foreach}
and the model class
class Article extends BaseArticle
{
const STATUS_FEATURED = 1 ;
public function isFeatured()
{
return $this->status == self::STATUS_FEATURED ;
}
}
All these are just some examples, in real life it is much more usefull.
And what are you actually trying to do with this fromArray($fArticle)? I don't see any point of that code.
Related
In my previous protractor JS project (This new one I will do it with TS) I created one class for all my elements and another one for my functions, something like this:
specs
|_reportPage
|_lib
|_pageElements.js
|_pageFunctions.js
Then I was importing the files as necessary, in this way was easy to find the info since the element list was long.
So far all examples online for protractor TS projects are short pageObject files with a couple of elements and methods, but I would like to know how to correctly proceed when the page requires a lot of elements and functions/methods.
For example, lets say we have 5 specs under the same folder that test the same page and this page is full of fields and tables.
What would be the best practice here? create 1 pageobject for each spec, create one long class with all the elements and functions...?
Thanks for your time!
To Extend my answer you can add additional layer as a service which can execute several actions from the flow in different pages.
Code example:
export class E2EService {
mainPage: MainPage = new MainPage();
innerPage: InnerPage = new InnerPage();
doSomethingE2E() {
this.mainPage.headerPage.weDoSomething();
this.mainPage.contentPage.weDoSomething()
this.innerPage.somethingComplicated();
}
}
export class MainPage {
public readonly headerPage: HeaderPage;
public readonly contentPage: ContentPage;
}
export class InnerPage {
headerPage: InnerHeaderPage;
contentPage: InnerContentPage;
public somethingComplicated() {
this.headerPage.weDoSomething();
this.contentPage.weDoSomething();
}
}
export class ContentPage {
private readonly elements = {
// elements
};
public weDoSomething() {
// code
}
public getElements() {
return this.elements;
}
}
export class HeaderPage {
private readonly elements = {
btn1: element(by.id('')),
div: element(by.id('')),
h2: element(by.id(''))
};
public weDoSomething() {
// code
}
public getElements() {
return this.elements;
}
}
Based on Infern0's answer, I did dependency injection to the classes:
class HeaderElements {
foo = element(by.id("foo"));
//List goes on...
}
class HomePageElements {
foo = element(by.id("foo"));
//List goes on...
}
export class MainCommonElementsPage {
headerElements: HeaderElements;
homePageElements: HomePageElements;
constructor() {
this.headerElements = new HeaderElements();
this.homePageElements = new HomePageElements();
}
}
Best practices, even for large Page Objects is this:
Each page should only have 1 page object class. All of the tools needed to access that page should be located here. Think of your page object as an API.
Don't break the PO into different parts, especially for large pages. You'll eventually need to modify the PO to adjust for content changes. Would you rather change 1 file or 12? This also ensures that each of your e2e tests will remain functional after you update the PO.
I have one PO that handles a page with a lengthy form. The form has 12 controls and three buttons (cancel, reset, and submit). I have about 30 functions that deal with the form. I don't like having more than 1-2 methods in my test, so if it gets more complicated, I add to the PO.
I'm trying to fetch covers of the books that belongs to an author. So far, so good. But it generates a separate query for each book and takes 2 seconds to load a page, I think I'm doing something wrong.
I use eager loading with my comments table (a comment belongs to a user), but since I use polymorphic relations with images table (a image can belong to different kind of other tables, such as user, thing, or group, so I can't use foreign keys in images table since it's not a right convention), I couldn't find a way to achieve the same thing this time.
Image Model
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Image extends Model
{
public function imageable()
{
return $this->morphTo();
}
}
Person Model (Author)
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Person extends Model {
public function books()
{
return $this->belongsToMany('\App\Models\Thing', 'person_thing');
}
Thing Model (Books)
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Thing extends Model {
public function cover() {
return $this->morphMany('\App\Models\Image', 'imageable');
}
}
Controller
$findBooks = Person::with(array('books' => function($query)
{
$query->groupBy('original_name');
}))->find(52957);
$allbooks = $findBooks->books;
return view('frontend.index')->with('allbooks', $allbooks)
}
Current View
#foreach($allbooks as $allBooks)
#foreach($allBooks->cover as $value)
<img class="hund" src="{{$value->link}}" alt="">
#endforeach
#endforeach
Image:
From this post on Laracasts
$query->with([
'child' => function ($q) {
$q->where(’someCol', ’someVal’); //constraint on child
},'child.grandchild' => function ($q) {
$q->where(‘someOtherCol’, ‘someOtherVal’); //constraint on grandchild
}
]);
I don't think your problem is with eager loading or morph to many if your page takes 2s to load, ,Did you install laravel/debugbar to see exactly what takes 2 sec or how many queries u run?
I have this code in my controller, it takes 'procedure_type' from the request and checks to see if a ProcedureType with that name exists. If it does it uses the object, if not it creates a new ProcedureType, then return the new object to use.
// Check the typed in ProcedureType against existing types.
$procedureTypes = $entityManager->getRepository('IncompassSurgeryBundle:ProcedureType')->findBy(array('name' => $request->request->get('procedure_type'), 'vendor' => $vendorId));
if (empty($procedureTypes)) {
// Create Procedure Type
$procedureType = new ProcedureType();
$procedureType->setVendor($vendor)
->setName($request->request->get('procedure_type'))
->setCreated(new \DateTime())
->setUpdated($procedureType->getCreated());
$entityManager->persist($procedureType);
} else {
$procedureType = $procedureTypes[0];
}
I don't think this is the best way to do this, I'd like to move the code into a function, say checkProcedureType(), but I don't know where the best place is to put that. I don't think it could go in the Entity or Repository classes, and moving it to a private function in the controller doesn't feel right.
I'm sure there is a class type that I'm not aware of, that extends the Entity. Or maybe I should just put these functions in my entity classes.
Service are the answer to almost everything in Symfony 2. Create a service like this :
namespace Your\Bundle\Service;
class ProcedureService // Call this the way you want
{
protected $entityManager;
public function __construct($entityManager)
{
$this->entityManager = $entityManager;
}
public function callMeTheWayYouWant($vendorId, $vendor)
{
// Check the typed in ProcedureType against existing types.
$procedureTypes = $this->entityManager->getRepository('IncompassSurgeryBundle:ProcedureType')->findBy(array('name' => $request->request->get('procedure_type'), 'vendor' => $vendorId));
if (empty($procedureTypes)) {
// Create Procedure Type
$procedureType = new ProcedureType();
$procedureType->setVendor($vendor)
->setName($request->request->get('procedure_type'))
->setCreated(new \DateTime())
->setUpdated($procedureType->getCreated());
$this->entityManager->persist($procedureType);
} else {
$procedureType = $procedureTypes[0];
}
// The rest of your code
}
}
In your services.yml file :
your_service:
class: Your\Bundle\Service\ProcedureService
arguments: [#doctrine.orm.entity_manager]
Then use it in your controller :
$this->get('your_service')->callMeTheWayYouWant($vendorId, $vendor);
If logic is somehow related to acessing database I always go for repository. However, if cases like yours, I tend to analyze it's dependency map.
Does your code repeats in some other method within same class, only?
If so, go for private method.
Is this part of code reused somewhere else but does not rely on some services?
You could externalize logic by creating separate class and static method which executes the code. Beware: Tends to get messy really quick
Finally, does your code rely on services/configuration?
Create a separate service, inject the services/configuration and invoke it's method. Adds a bit of overhead, if your abuse it, but you should be fine
Personally, in your example, I would go for private method, but that's just my opinion.
I am trying to use the autocomplete addon in agile toolkit (I am still very much new to this, but it seems to be very well suited for my needs). Autocomplete Basic works, but when I use Plus and press the plus-button I get an error connected to no model set. In the Plus source the self-model should be used - but I don't understand how I should set the model of the autocomplete form.
This is the important part of the stack trace, I think:
Frontend_page_form: Form->setModel(Null)
Frontend_createquestions_form_question_id:
autocomplete\Form_Field_Plus->autocomplete{closure}(Object(Page))
This is my model:
class Model_QuestionInCollection extends Model_Table {
public $entity_code='questionincollection';
function init(){
parent::init();
$this->hasOne('Question')->display(array('form'=>'autocomplete/Plus'));
This is the code:
$form=$this->add('Form');
$form->setModel('QuestionInCollection');
--- EDIT
I ended up changing the model in autocomplete, and now it works, showing that something is wrong in the original "$self->model" - but of course it cannot be generalized. I did some extra changes (to make the new record show up in the autocomplete-field), so Autocomplete/Plus now is like this:
<?php
namespace autocomplete;
class Form_Field_Plus extends Form_Field_Basic
{
function init()
{
parent::init();
$self = $this;
$f = $this->other_field;
// Add buttonset to name field
$bs = $f->afterField()->add('ButtonSet');
// Add button - open dialog for adding new element
$bs->add('Button')
->set('+')
->add('VirtualPage')
->bindEvent('Add New Record', 'click')
->set(function($page)use($self) {
$model=$this->add('Model_Question');
$form = $page->add('Form');
$form->setModel($model); //Was: $self->model
//Would be nice if it worked...: $form->getElement($model->title_field)->set($self->other_field->js()->val());
if ($form->isSubmitted()) {
$form->update();
$js = array();
$js[] = $self->js()->val($form->model[$model->id_field]);
$js[] = $self->other_field->js()->val($form->model[$model->title_field]);
$form->js(null, $js)->univ()->closeDialog()->execute();
}
});
}
}
Here is an example on using autocomplete: http://codepad.demo.agiletech.ie/interactive-views/autocomplete
You can manually create a form and link the field with Model.
If that works fine, you can move on to trying and get your own example working. It seems OK, and should work in theory.
I have the following model:
class Model_GrantMinimal extends Model_Table {
public $table='grant';
function init() {
parent::init();
$this->hasOne('User');
$this->getField('id')->hidden(true);
$this->getField('user_id')->hidden(true);
$this->addField('grant_number');
$this->addField('grant_name');
}
}
And inside the page I have the following code:
$grant=$this->add('Model_GrantMinimal');
$grant->load($id);
$user=$grant->ref('user_id');
$field = $grantForm->addField('Dropdown','Manager');
$field->setModel($user);
$field
->validateNotNull()
->add('Icon',null,'after_field')
->set('arrows-left3')
->addStyle('cursor','pointer')
->js('click',$grantForm->js()->reload())
;
And everything works almost perfectly - how do I make sure the Dropdown ($field in php) is linked to the overall form, i.e. when I change the value in the dropdown that value is passed into the $grantForm->onSubmit - and how do I ensure the the defaultValue (pre-selected value) of the dropdown is the User that is set by user_id inside GrantMinimal
I'm loving the framework so far - its really impressive and coming from the .NET framework where MVVM and MVC are so common, specially with the latest WPF related. It has been a treat compared to the old way of writing HTML/PHP, just taking a while to fully understand whats what.
Figured it out after a couple of hours of debug tracing:
class Model_GrantMinimal extends Model_Table {
public $table='grant';
function init() {
parent::init();
$this->hasOne('User');
$this->getField('id')->hidden(true);
$this->getField('user_id')->hidden(true);
$this->addField('grant_number');
$this->addField('grant_name');
$this->hasOne('User');
$this->getField('user_id')->caption('Manager')->hidden(false);
}
}