i have existing website.
and i write the new back-end (in cakephp) without changing front-end programm
the discomfort that
db table has field names as
id
news_date
news_title
news_content
is it possiable to do something in cakephp model file (reindentify the field names)
so i can use model in controller as
News.date
News.title
News.content
What you need to do is setup some very basic virtual fields in your news model. Something like this should suit your needs.
public $virtualFields = array(
'title' => 'news_title',
'date' => 'news_date',
'content' => 'news_content'
);
Also do yourself a favour by checking out the other model attributes that could help you out, you'll want to set displayType as new_title I'd imagine.
Is said by Dunhamzz, virtualFields are a good solution until you want to work with these new field-names.
Since I assume your frontend needs to use the old names from the database I would go with the afterFind-callback in your model.
Let's say you've got the model news.php:
# /app/model/news.php
function afterFind($results) {
foreach ($results as $key => $val) {
if (isset($val['News']['title'])) {
$results[$key]['News']['news_title'] = $val['News']['title']);
# unset($results[$key]['News']['title']); //use this if you don't want the "new" fields in your array
}
if (isset($val['News']['date'])) {
$results[$key]['News']['news_date'] = $val['News']['date']);
# unset($results[$key]['News']['date']); //use this if you don't want the "new" fields in your array
}
if (isset($val['News']['content'])) {
$results[$key]['News']['news_content'] = $val['News']['content']);
# unset($results[$key]['News']['content']); //use this if you don't want the "new" fields in your array
}
}
return $results;
}
You need to rename the database-fields to your new wanted value. You then can use these within conditions like every other field.
Only difference is, that you get back an array where all your fields have been renamed to your frontend-fields.
For more information about the available callback-methods have a look here: Callback Methods
Related
I am trying to add a function to that strips the html from a field in drupal views. I found a function for sql server called "udf_StripHTML" that does that.
http://blog.sqlauthority.com/2007/06/16/sql-server-udf-user-defined-function-to-strip-html-parse-html-no-regular-expression/
I am using the following code:
/**
* Implements hook_views_query_alter().
*/
function cviews_views_query_alter(&$view, &$query) {
// Add a strip html tags from content.
$fields = array('field_data_body.body_value');
foreach ($query->where as $key1 => $value) {
foreach ($value['conditions'] as $key2 => $coditions) {
if (in_array($coditions['field'], $fields)) {
$query->where[$key1]['conditions'][$key2]['field'] = 'dbo.udf_StripHTML(' . $coditions['field'] . ')';
}
}
}
}
When views module converts the query object to a string the field become
from:
'dbo.udf_StripHTML(field_data_body.body_value)';
to:
[dbo].[udf_StripHTMLfield_data_body.body_value]
My question is, how can I add a function there?
Thank you,
You are going way too deep here friend. I'm going to assume that you're using Drupal 7, but for Drupal 8 this should be similar (since views is in core for both).
A few things about your approach:
That function is a user defined function which means that it needs to be defined at a much lower-level (in the SQL database) before you can use it in your query.
This is a red-herring approach, however, because you don't need to even touch the SQL to accomplish what you want (you can do this with PHP with strip_tags!)
You don't need a query alter hook here (we don't need to go to the database to do this). You could do this with one of the preprocess or field hooks from the field API or the views API using the function linked in my previous point.
Even better, you don't even have to touch the code to accomplish this. You can do it right in the Drupal UI.
Under the field settings for the view, select rewrite results and then Strip HTML tags. Presto, no more HTML tags in that field.
Image source: https://www.drupal.org/node/750172
Here is the solution that worked for me:
// Traverse through the 'where' part of the query.
foreach ($query->where as &$condition_group) {
foreach ($condition_group['conditions'] as &$condition) {
if (in_array($condition['field'], $fields)) {
$value = $condition['value'];
$field = $condition['field'];
$condition = array(
'value' => array(),
'field' => t('dbo.udf_StripHTML(!field) like \'#value\'', array(
'!field' => $field,
'#value' => $value)),
'operator' => 'formula',);
}
}
}
I'm using CakePHP's translatable behavior. I have a few existing fields working fine, but I'm having trouble adding a new translatable field to my model.
CakePHP uses an INNER JOIN to fetch all translatable fields from the database.
Now, if I add an extra translatable field to my model, all the translation records for that field won't exist in the database. And because of the inner join, whenever it tries to fetch ANY existing records from the database, it will return blank - because the INNER JOIN on the new field fails, and so the entire query returns nothing.
Surely people must have come accross this situation before. Is there an easy solution?
One solution would be to edit/override the core and make all the INNER JOIN's into LEFT OUTER JOIN's. Is there anything wrong with that?
Another solution would be to run an update on the translations table to create all the extra records for the new field, every time you add a new translatable field - but I hate that solution.
Is there a better solution? How have others dealt with this problem?
Thanks in advance.
OK, here's a way of making sure the records exist after each time you add a new translatable field. If you've got a better answer, add it, and I'll mark yours as correct.
PS - this is tested for my purposes. I'm using multiple translation tables (http://book.cakephp.org/2.0/en/core-libraries/behaviors/translate.html#multiple-translation-tables). I think it should work for most situations, but if not, it should at least be a good starting point.
In your model (the model that actsAs Translatable), add the following method. What it does is takes an array of locales, and then for every record in the table, and for every translatable field, and for every locale (ie, 3 loops), it checks that a translation record exists. If a translation doesn't exist, it adds a blank one, so at least the INNER JOIN won't fail.
It returns an array of all the records it added, so you can then go through and check them or change their content or whatever.
Here's the model method:
function ensureTranslationIntegrity($localesToCheck){
$allRows = $this->find('all', array('fields' => array('id')));
$fieldsToCheck = array();
$translatableFields = $this->actsAs['Translate'];
foreach($translatableFields as $key => $value){
// actsAs Translatabe can take field names only, or Key => Value pairs - see http://book.cakephp.org/2.0/en/core-libraries/behaviors/translate.html#retrieve-all-translation-records-for-a-field
if(is_numeric($key)){
$field = $value;
} else {
$field = $key;
}
array_push($fieldsToCheck, $field);
}
$translateModel = $this->translateModel();
$addedRows = array(); // This will contain all the rows we have to add
foreach ($allRows as $row){
foreach($fieldsToCheck as $field){
foreach($localesToCheck as $locale){
$conditions = array(
'model' => $this->name,
'foreign_key' => $row[$this->name]['id'],
'field' => $field,
'locale' => $locale
);
$translation = $translateModel->find('first',array('conditions' => $conditions));
if(!$translation){
$data = $conditions; // The data we want to insert will mostly just match the conditions of the failed find
$data['content'] = ''; // add it as empty
$translateModel->create();
$translateModel->save($data);
array_push($addedRows, $data);
}
} // END foreach($localesToCheck as $locale){
} // END foreach($fieldsToCheck as $field){
} // END foreach ($allRows as $row){
return $addedRows;
}
And in your controller, you'd call it something like this:
public function ensure_translation_integrity(){
$locales = array('en_au','en_gb','en_nz','pt_br','xh_za');
$addedRows = $this->YourModel->ensureTranslationIntegrity($locales);
debug($addedRows);
}
Hope that helps someone, but like I said, I'd love to see a better solution if someone has one.
I have custom validation rule:
public function customRule($check)
{
}
Inside this rule I would like to access some model data (in database). Of course I can do it like this:
$this->id = 23;
$this->read();
But then all the data in current model will be overidden by read function (I mean $this->data[$this->alias][...] is overridden.
How I can get this data?
Use a regular
$result = $this->find('first', array('conditions' => array($this->alias . '.' . $this->primaryKey => $id));
with the id in the find conditions. And work with the result, it is not overriding the data property.
Just to note that if you want to get the full record of the data that is currently being validated it is always accessible in $this->data inside the validation rule as opposed to $check which contains only the data in the currently validated field.
If you need to validate based on something that is stored in the DB, you can use $this->find() or any of the Model's functions as you are in the Model.
I support #burzum 's answer +1.
In one of my models, I have a "LONGTEXT" field that has a big dump of a bunch of stuff that I never care to read, and it slows things down, since I'm moving much more data between the DB and the web app.
Is there a way to specify in the model that I want CakePHP to simply ignore that field, and never read it or do anything with it?
I really want to avoid the hassle of creating a separate table and a separate model, only for this field.
Thanks!
Daniel
As #SpawnCxy said, you'll need to use the 'fields' => array(...) option in a find to limit the data you want to retrieve. If you don't want to do this every time you write a find, you can add something like this to your models beforeFind() callback, which will automatically populate the fields options with all fields except the longtext field:
function beforeFind($query) {
if (!isset($query['fields'])) {
foreach ($this->_schema as $field => $foo) {
if ($field == 'longtextfield') {
continue;
}
$query['fields'][] = $this->alias . '.' . $field;
}
}
return $query;
}
Regarding comment:
That's true… The easiest way in this case is probably to unset the field from the schema.
unset($this->Model->_schema['longtextfield']);
I haven't tested it, but this should prevent the field from being included in the query. If you want to make this switchable for each query, you could move it to another variable like $Model->_schemaInactiveFields and move it back when needed. You could even make a Behavior for this.
The parameter fields may help you.It doesn't ignore fields but specifies fields you want:
array(
'conditions' => array('Model.field' => $thisValue), //array of conditions
'fields' => array('Model.field1', 'Model.field2'), //list columns you want
)
You can get more information of retrieving data in the cookbook .
Another idea:
Define your special query in the model:
function myfind($type,$params)
{
$params['fields'] = array('Model.field1','Model.field2',...);
return $this->find($type,$params);
}
Then use it in the controller
$this->Model->myfind($type,$params);
Also try containable behaviour will strip out all unwanted fields and works on model associations as well.
Containable
class Post extends AppModel { <br>
var $actsAs = array('Containable'); <br>
}
where Post is your model?
You can add a beforeFilter function in your Table and add a select to the query
Excample:
public function beforeFind(Event $event, Query $query){
$protected = $this->newEntity()->hidden;
$tableSchema = $event->subject()->schema();
$fields = $tableSchema->columns();
foreach($fields as $key => $name){
if(in_array($name,$protected)){
unset($fields[$key]);
}
}
$query->select($fields);
return $event;
}
In this excample I took the hidden fields from the ModelClass to exclude from result.
Took it from my answer to a simular question here : Hidden fields are still listed from database in cakephp 3
If I have a person model with first_name and last_name, how do I create and display a full_name? I would like to display it at the top of my Edit and View views (i.e. "Edit Frank Luke") and other places. Simply dropping echoes to first_name and last_name isn't DRY.
I'm sorry if this is a very simple question, but nothing has yet worked.
Thank you,
Frank Luke
Edit for clarity: Okay, I have a function on the person model.
function full_name() {
return $this->Person->first_name . ' ' . $this->Person->last_name;
}
In the view, I call
echo $person['Person']['full_name']
This gives me a notice that I have an undefined index. What is the proper way to call the function from the view? Do I have to do it in the controller or elsewhere?
If what you are wanting is just to display a full name, and never need to do any database actions (comparisons, lookups), I think you should just concatenate your fields in the view.
This would be more aligned with the MVC design pattern. In your example you just want to view information in your database in a different way.
Since the action of concatenating is simple you probably don't save much code by placing it in a separate function. I think its easiest to do just in the view file.
If you want to do more fancy things ( ie Change the caps, return a link to the user ) I would recommend creating an element which you call with the Users data.
The arrays set by the save() method only return fields in the datbase, they do not call model functions. To properly use the function above (located in your model), you will need to add the following:
to the controller, in the $action method:
$this->set( 'fullname', $this->Person->full_name();
// must have $this-Person->id set, or redefine the method to include $person_id
in the view,
echo $fullname;
Basically, you need to use the controller to gather the data from the model, and assign it to the controller. It's the same process as you have before, where you assign the returned data from the find() call to the variable in the view, except youre getting the data from a different source.
There are multiple ways of doing this. One way is to use the afterFind-function in a model-class.
See: http://book.cakephp.org/view/681/afterFind.
BUT, this function does not handle nested data very well, instead, it doesn't handles it al all!
Therefore I use the afterfind-function in the app_model that walks through the resultset
function afterFind($results, $primary=false){
$name = isset($this->alias) ? $this->alias : $this->name;
// see if the model wants to attach attributes
if (method_exists($this, '_attachAttributes')){
// check if array is not multidimensional by checking the key 'id'
if (isset($results['id'])) {
$results = $this->_attachAttributes($results);
} else {
// we want each row to have an array of required attributes
for ($i = 0; $i < sizeof($results); $i++) {
// check if this is a model, or if it is an array of models
if (isset($results[$i][$name]) ){
// this is the model we want, see if it's a single or array
if (isset($results[$i][$name][0]) ){
// run on every model
for ($j = 0; $j < sizeof($results[$i][$name]); $j++) {
$results[$i][$name][$j] = $this->_attachAttributes($results[$i][$name][$j]);
}
} else {
$results[$i][$name] = $this->_attachAttributes($results[$i][$name]);
}
} else {
if (isset($results[$i]['id'])) {
$results[$i] = $this->_attachAttributes($results[$i]);
}
}
}
}
}
return $results;
}
And then I add a _attachAttributes-function in the model-class, for e.g. in your Person.php
function _attachAttributes($data) {
if (isset($data['first_name']) && isset($data['last_name'])) {
$data['full_name'] = sprintf("%s %s %s", $data['first_name'], $data['last_name']);
}
return $data;
}
This method can handle nested modelData, for e.g. Person hasMany Posts then this method can also attachAttributes inside the Post-model.
This method also keeps in mind that the linked models with other names than the className are fixed, because of the use of the alias and not only the name (which is the className).
You must use afterFind callback for it.
You would probably need to take the two fields that are returned from your database and concatenate them into one string variable that can then be displayed.
http://old.nabble.com/Problems-with-CONCAT-function-td22640199.html
http://teknoid.wordpress.com/2008/09/29/dealing-with-calculated-fields-in-cakephps-find/
Read the first one to find out how to use the 'fields' key i.e. find( 'all', array( 'fields' => array( )) to pass a CONCAT to the CakePHP query builder.
The second link shows you how to merge the numeric indexes that get returned when you use custom fields back into the appropriate location in the returned results.
This should of course be placed in a model function and called from there.