Recently I was given a work on a project that is being made in CakePHP 3. The project has some basic function working already and I was asked to create some new functionalities.
My understanding of PHP and MVC comes from CodeIgniter, where I have used Active Record to work with the database.
In CI, the Model was just Model. Here in Cake, I see an Entity, Table and Behavior. I somehow understood this (for me) new concept by asking around my colleagues. Now I just want to finish my assignment without asking them anything further.
So, it is a project management portal. As an Admin, you log in and you manage projects. People then come and join the project.
There are two tables I need to use:
users Table with info such as firstname, lastname, phone, address etc.
users_projects Table that stores user_id and report_id [they are Foreign Keys to users Table and reports Table respectively]
My task is to create a page where you can see who has joined in a particular project in a paginated table fashion with info from Users table such as name, surname, phone, etc..
I have prepared a new Controller called ReportsController in which I have created new participants method, so when you visit /admin/reports/participants/$project_id, it gives you the table with users joined in particular project.
ReportsController
public function participants($project_id = null)
{
$this->loadModel('Projects');
$participants = $this->Projects->selectAllProjectParticipantsByID($project_id);
foreach ($participants as $participant){
echo $participant->users[0]->firstname;
//dump($participant);
}
//dump($participants);
}
ProjectsTable
public function selectAllProjectParticipantsByID($project_id){
// return $this->find()->where(['project_id' => $project_id]);
// return $this->find()->where(['project_id' => $project_id]);
return $this->find('all')->contain([$project_id]);
}
Reports/participants template
<div class="col-xs-9">
<h3><?= __('Participants Report for') ?>
<small><?= __('Participants Report') ?></small>
</h3>
<table class="table table-bordered" cellpadding="0" cellspacing="0">
<thead>
<thead>
<tr>
<!-- <th colspan="2">--><?//= $this->Paginator->sort('id') ?><!--</th>-->
<th><?= $this->Paginator->sort('firstname', __('First Name')) ?></th>
<th><?= $this->Paginator->sort('lastname', __('Last Name')) ?></th>
<th><?= $this->Paginator->sort('phone', __('Phone')) ?></th>
<th><?= $this->Paginator->sort('address', __('Address')) ?></th>
<th><?= $this->Paginator->sort('personType', __('Person Type')) ?></th>
<th><?= $this->Paginator->sort('status', __('Status')) ?></th>
<th class="actions text-right"><?= __('More Info') ?></th>
</tr>
</thead>
<tbody class="sortable" data-url="<?= $this->Url->build(['controller' => 'QuestionSets', 'action' => 'reorder']) ?>">
</tbody>
</table>
<ul class="pagination">
</ul>
The result I get so far is following:
Error PrintScreen png
Now I have been reading about CakePHP's Pagination, Data Model, Templating etc for 16 hours now but I still am unable to get the results, which is really frustrating since I know I would have done this in another framework in a matter of minutes.
I best learn by example, sometimes I get lost by reading all the techy words in documentations, so you helping me is the best way you can teach me something.
Well, this question looks like little broad but still I want to point out following things:
Codeigniter vs CakePHP
Just as you mentioned as CakePHP covers many things/features,learning curve of cakephp is not as easy as CodeIgniter. And I would say you need to spend little more time for CakePHP documentation (CakePHP Cookbook).
One of the key advantages of CakePHP is that it provides ready to use templates. When you start to understand CakePHP relations and conventions, you will certainly feel more comfortable.
And I bet when you understand things, you can do much things with CakePHP than you do with CodeIgniter in easier fashion.
Where to start ?
Have you ever done CakePHP blog tutorials? If not this is the best place to start CakePHP. When you finished you will get basics things in CakePHP well. If you escape these basics things it would be hard to understand cakephp conventions,relations and MVC. See here (Cakephp3 blog tutorial.)
The things you did in CodeIgniter model,is basically done with table objects (eg.UsersTable.php) in CakePHP. These objects provide access to collections of data. They allow you to save new records, modify/delete existing ones, define relations, and perform bulk operations.
Talking about the error:
You need to know about what is contain and what to contain:
$this->find('all')->contain([$project_id]);
You need to contain tables (like Users) not the any field of tables as you did above. Again you will understand these things when you complete blog tutorial and which is pretty straightforward.
Related
I'm having to move a project from rails to angular, Much to my disgrace.
How would i go about changing this
<% if souporder.datefrom > Time.now %>
into angular terms?
heres my code
<tr ng-repeat="soup in souporders">
<td>{{soup.id}}</td>
<td>{{soup.soup}}</td>
<td>{{soup.datefrom}}</td>
<td>{{soup.dateto}}</td>
</tr>
Edit
This is in my view
<div ng-repeat="soup in souporders">
<tr ng-if="date == soup.datefrom">
And this is in my controller
$scope.date = new Date();
You clarified in the comments that you only need this calculated on page refresh, which is pretty simple and doesn't require much angular magic.
On initializing the view's controller, simply set a variable whether or not the order date is in the future or not and expose it on the scope. Then bind directly to it, and use the bindonce functionality {{:myvariable}} to improve performance because it won't be changing anyway.
Depending on your needs, you can even have the server calculate that value and return it as a property of each order in your JSON array of orders. (I'm guessing a bit at what you're doing, but you can adapt the answer if you're not doing what I think you're doing.)
Update: I just realized that perhaps your objective is not to do something special with each row based on the date, but to filter the list based on the date. Most of my answer still stands, but you can forget about the bindonce spiel. What you need is called an angular filter on ng-repeat and is done using the pipe (|) character. You can read all about it by searching the topic. There are examples in the angularjs docs. But basically, instead of:
<tr ng-repeat="soup in souporders">
<td>{{soup.id}}</td>
<td>{{soup.soup}}</td>
<td>{{soup.datefrom}}</td>
<td>{{soup.dateto}}</td>
<td>{{:soup.active}}</> <!--do something nice here instead of just displaying true/false --->
</tr>
You might want:
<tr ng-repeat="soup in souporders | filter:{current: true}">
...etc
</tr>
Either on the server or on the client, when you load the data from the server, you can add a boolean property to the items in the souporders array that says whether it's current or not.
What this accomplishes:
You have a filtered list of items based on a property that does NOT change dynamically and requires a page reload to update. But it's fast (but not the fastest it could be if you really need to go crazy - don't prematurely optimize, you'll know what to do when the time comes to do it, and that time isn't now).
Why it's not that great:
It isn't dynamic and it isn't angular-y or declarative. But based on your requirements, it's a solution. I'd spend some time considering if you want this to be dynamic, and if not, why the items that aren't showing are even returned from the server. Are they used elsewhere on the page?
I have an app I am working to re-create in angular. One section is a list of items that currently has 140 different items. Each item has a table with 1-4 rows, and differing numbers of columns (usually 6-20).
The way the information works, we really need to show all the items to start. However, the processing time to render the tables using ng-repeat, as well as the massively exponential number of scopes is prohibitive, especially since some users will have older computers (although likely running FF or Chrome, so I'm not worried about IE issues).
I have all the data locally in a json array, and I can format it as needed. Of course, there is filtering, so things will be shown/hidden all over. But the data in the tables is one time bound, and doesn't change.
I'd really prefer to make this something that is in a template. I could either loop it in a javascript array, or create the table data myself that way. However, in angular, templates are not allowed to have logic, so I can't do that.
I'd prefer to do everything rendering client side for speed, so I'd prefer not to have to call up the server to render it.
I could also pre-render it in the json, and use sanitize I believe, but that seems back door and against the separation of interests which is core to angular.
What would be the best practices in this case? I think I've tried about everything, and nothing within angular seems perfomant enough for this case. I'd prefer not to have to use paging, since the users would want to scroll multiple filtered long lists. Any suggestions?
Edit: Now with some sample code:
<table id="{{::item.name}}}" class="table table-striped table-bordered table-hover table-condensed table-responsive text-center" >
<thead>
<tr>
<td ng-repeat="header in ::item.tableHeader">{{::header}}</td>
</tr>
</thead>
<tbody>
<tr ng-repeat="diff in ::item.tableInfo track by $index">
<td ng-repeat="cell in ::diff track by $index">{{::cell}}</td>
</tr>
</tbody>
</table>
I used arrays because the order matters, and objects don't always follow the order. Again, with over 140 items, 1-4 rows with 10-20 columns, the scopes become quite costly.
So im trying to populate a dropdown with entries from a specific database. Right nwo it works, however it puts in the ENTIRE entry which has 10+ attributes, where i only need a couple of them. Is there any way to specify which columns get passed back and displayed in the dropdown?
<tr>
<TD><span class="required">*</span> CMS Group ID:</TD>
<td><form:select path="cmsGroupId">
<form:options items="${list}" itemValue="id" />
</form:select>
</td>
<td><form:errors path="cmsGroupId" cssClass="required" /></td>
</tr>
Ideally id like to only get the first 4 columns from here. (ID, Version, Name, Entity ID) but dont really know how to make it work. Ive found bits on doing a c:foreach loop but havent gotten that working right either...
Thanks!
Build the appropriate display values on the Java side, not in JSP.
Don't try to do this in the view layer–IMO it's not the right place for such logic.
I'm trying to fix my code posted for this question VisualForce: convert carriage returns to html line-breaks in a long text field
As you can see in that posting there is a custom controller for a Visualforce page that has tabbed content for Case. The only way to list CaseComments is with custom Apex. The controller performs a simple query to retrieve all the CaseComments for the current case.
I want to only show an "Edit" link if the current user has permission to edit the instance of a particular case comment.
I found a good blog about rendering components dynamically based on object level security here:
http://forcearchitects.deliveredinnovation.com/2011/02/05/render-visualforce-components-dynamically-based-on-object-level-security/
Based on that posting I tried using
$ObjectType.CaseComment.updateable
As in the following:
<apex:repeat value="{!comments}" var="c">
<tr>
<td class="commentsActionColumn">
<!-- open the case comment for edit -->
<apex:outputLink title=""
rendered="{!$ObjectType.CaseComment.updateable}"
value="/{!c.id}/e?parent_id={!c.parentId}&retURL=/apex/{!$CurrentPage.Name}%3Fid={!case.id}" style="font-weight:bold">Edit</apex:outputLink>
</td>
<td>
<!-- display the case comment formatted using the apex outputField -->
<div class="commentTdClass">
<apex:outputField value="{!c.commentbody}"></apex:outputField>
</div>
</td>
</tr>
</apex:repeat>
Unfortunately, the test for updateable is on the Object level so it returns true on all CaseComments if the current user has permission to edit any CaseComment.
I need record level validation to only show the Edit action if the particular row can be modified by the current user.
Any ideas?
Update
As I reread my question I see that it wasn't as clear as it should be.
As I iterate over the set of CaseComments, for a given Case, I need to know if the current user can safely edit a particular CaseComment without seeing the "you don't have permission page". This test must be on a CaseComment by CaseComment record level basis because any given Case will have many CaseComments all contributed by different Users
You can do this in a couple of ways (I think).
If you want to go down a purely programmatic route then look here which discusses using the sharing object to allow record sharing with particular users or groups.
Otherwise, if possible you could use Roles and Hierarchy management to organise this in a way so that only people within a particular role of a particular level or above can see a record and access it?
Paul
Update:
Based upon your comment below I think you want to use apex sharing. If you look at this useful developerforce wiki you can see that you should access the sharing object to check whether or not the user has a record in this object to view the particular record instance. Something like:
MyObject__Share share1 = [Select ParentId, UserOrGroupId, AccessLevel from MyObject__Share where ParentId = :myrecordId And UserOrGroupId = :UserId];
if(share1 != null)
{ code to allow the record to be viewed}
else
{code to deny access}
I haven't tested or tried that so you will want to tweak it but that should be the general idea.
Paul
Have a look at the below URL.
https://developer.salesforce.com/forums/?id=906F00000008w0gIAA
Looks like we can use something like below. however ihaven't tested yet.
SELECT RecordId, [HasReadAccess, HasEditAccess, HasAllAccess, MaxAccessLevel] FROM UserRecordAccess
WHERE UserId = [single ID]
AND RecordId = [single ID]
//or Record IN [list of IDs]
Tl;Dr: How to create one array; from two tables; with hasMany and belongsTo associations; to foreach through; with CakePHP; optimally.
EDIT: From(http://book.cakephp.org/view/872/Joining-tables) This is what I'm trying to learn how to do although it says "hasOne" and not hasMany....
"In CakePHP some associations (belongsTo and hasOne) performs automatic joins to retrieve data, so you can issue queries to retrieve models based on data in the related one."
I'm trying to foreach through an array that will ideally contain data from two tables that have a hasMany and belongsTo relationship. I know that CakePHP has functionality built in to make this very simple and would like to use it:
trips hasMany legs
legs belongsTo trips
So, I want to make an array containing data from both tables so I can easily display the data in the view. Notice there is nothing about joining in the array that contains find parameters. I think CakePHP joins tables when I create associations; I'm just not sure how to access those associations when creating an array.
(I've spent hours on this already and couldn't find an example of how to do it on the web or in books so thanks for your help). I could hack my way around this (by creating a separate array and having a much more convoluted view file) but I'm just starting to learn to program and would like to do it somewhat optimally and use CakePHP's functionality.
Thanks in advance for your help! Am I not right that with these associations (belongsTo and hasMany) I don't have to explicitly declare the joins in the controller/find()? I might, for now, try and manually make the joins to see what happens.
Existing code (this works fine):
trips_controllers:
function view() {
if(empty($this->data)){
$this->Session->setFlash('You forgot to put stuff in the form fields!');
$this->redirect(array('action'=>'index'));
}
$price=$this->data['Trip']['price'];
$origin=$this->data['Trip']['origin_airport'];
$this->set('trips', $this->Trip->find('all', array('conditions'=>array('Trip.price <='=>$price, 'Trip.origin_airport'=>$origin), 'limit'=>30, 'recursive=>2')));
view:
<th>Trip ID</th>
<th>Price</th>
<th>Origin</th
</tr>
<? foreach($trips as $trip):?>
<tr>
<td><?=$trip['Trip']['trip_id'];?></td>
<td><?=$html->link($trip['Trip']['url']); ?>
<td><?=$trip['Trip']['origin_airport'];?></td>
<td><?=$trip['Trip']['price'];?></td>
</tr>
<? endforeach;?>
Thanks again!
If Trip hasMany Legs, you should be able to loop through the data like so:
foreach ($trips as $trip) {
echo $trip['Trip']['id'];
…
foreach ($trip['Leg'] as $leg) {
echo $leg['id'];
…
}
}
debug($trips) is your friend. The default Cake data array layout is very sensible. Try to get used to it, there should be very little need to modify the structure in 99.99% of applications. On the contrary, there's a strong case to be made for keeping it consistent everywhere.