Getting this error
Call to a member function attributes() on a non-object
I have found multiple answers to this on SO, but none of them seem to solve my problem?
Here is the XML:
<Routes>
<Route type="source" name="incoming">
</Route>
<Routes>
Here is the PHP:
$doc = new SimpleXMLElement('routingConfig.xml', null, true);
class traverseXML {
function getData() {
global $doc;
$routeCount = count($doc -> xpath("Route")); //this value returns correctly
$routeArr = array();
for ($i = 1; $i <= $routeCount; $i++) {
$name = $doc -> Route[$i] -> attributes() -> name;
array_push($routeArr, $name);
}
return $routeArr;
}
}
$traverseXML = new traverseXML;
var_dump($traverseXML -> getData());
I understand what the error means, but how is it a non-object? How do I return the name attribute of Routes/Route[1] ?
Your $doc is <Routes>. Trying to get ->Routes from it is trying to get
<Routes>
<Routes>
You need to do $doc->Route[$i]. Errors like this are less frequent when you name your variable after the document root:
$Routes = new SimpleXMLElement('routingConfig.xml', null, true);
Also, your XML is invalid. The Routes element is not closed.
In addition, you don't need the XPath. SimpleXML is traversable, so you can foreach over all the routes by doing
foreach ($Routes->Route as $route) {
And attributes() returns an array, so you cannot chain ->name off it but must access it with square brackets. But it's not necessary to use attributes() anyway because you can get attributes from SimpleXmlElements directly via square brackets, e.g.
echo $route['name'];
Here is an example that will print "incoming":
$xml = <<< XML
<Routes>
<Route type="source" name="incoming"/>
</Routes>
XML;
$routes = simplexml_load_string($xml);
foreach ($routes->Route as $route) {
echo $route['name'];
}
demo
If you want to do it with XPath, you can collect all the attributes in an array like this:
$routeNames = array_map('strval', $Routes->xpath('/Routes/Route/#name'));
Yes, it's just that one line :)
As for your class:
Don't use global. Forget it exists. If you want to have a class, inject the dependency, e.g. do
class Routes
{
private $routes;
public function __construct(SimpleXmlElement $routes)
{
$this->routes = $routes;
}
public function getRouteNames()
{
return array_map('strval', $this->routes->xpath('/Routes/Route/#name'));
}
}
$routes = new Routes(simplexml_load_string($xml));
print_r($routes->getRouteNames());
demo
Related
I am using method post to create new data in xml file but The function c_element cannot be used in the function store
$DeTai = c_element('DeTai', $root);
This is my current code:
public function c_element($e_name, $parent)
{
global $xml;
$node = $xml->createElement($e_name);
$parent->appendChild($node);
return $node;
}
public function c_value($value, $parent)
{
global $xml;
$value = $xml->createTextNode($value);
$parent->appendChild($value);
return $value;
}
public function store(Request $request)
{
$xml = new DOMDocument("1.0","UTF-8");
$xml->load('../xml/QuanLyDoAnTotNghiep.xml');
if ($request->isMethod('post'))
{
$madt= $request->madt;
$noidungdetai = $request->noidungdetai;
$root=$xml->getElementsByTagName("QuanLyDoAnTotNghiep")->item(0);
$DeTai = c_element("DeTai", $root); //error in here
$s_madt = c_element('MaDT', $DeTai);
c_value("$madt", $s_madt);
$s_noidungdetai = c_element('NoiDungDeTai', $DeTai);
c_value("$noidungdetai", $s_noidungdetai);
$xml->formatOutput=true;
$xml->save('../xml/QuanLyDoAnTotNghiep.xml');
echo "Thêm mới thành công!!!";
}
}
use this keyword to call one method in different method of same class
$DeTai = $this->c_element('DeTai', $root);
to know more about it please visit this
Thanks..
I have a JSON field in my MySQL table column which has an JSON array with part of URLs.
["products/1.jpg", "products/2.jpg", "products/3.jpg"]
I want to get the array with appending a Base URL for each of the values of the array.
["www.example.com/images/products/1.jpg", "www.example.com/images/products/2.jpg", "www.example.com/images/products/3.jpg"]
I have tried with getAttribute() function like nelow code. But was not succeeded.
public function getImagesAttribute(){
$images = json_decode($this->attributes['images']);
$imageP = [];
foreach ($images as $image) {
$imageP[] = "www.example.com/images/" . $image;
}
return $imageP;
}
can you help me.
You probably want to use $this->images instead of $this->attributes['images'].
In this case, I would use Collections like so:
public function getImagesAttribute(){
return collect(json_decode($this->images))
->map(function ($image) {
return "www.example.com/images/" . $image;
})
->all();
}
I am trying to implement a widgets library using load->view. I know I can use include to call directly the file and avoid the vars cache issues but just wondering why it does not work.
Here is how I have structured my code:
My Controller:
class Page extends MY_Controller {
public $data = array();
public function __construct() {
parent::__construct();
...
$this->load->library('widgetmanager');
}
public function index($slug = '') {
echo $this->widgetmanager->show(2);
echo $this->widgetmanager->show(1);
}
}
My Library
class WidgetManager
{
private $CI;
public function __construct()
{
$this->CI = & get_instance();
}
public function show($widget_id) {
$data = array();
$widget_id = (int)$widget_id;
$this->CI->db->select('*');
$this->CI->db->from('widget');
$this->CI->db->where('id', $widget_id);
$query = $this->CI->db->get();
$item = $query->row_array();
$data['widget_title'] = $item['title'];
$data['widget_content'] = $item['content'];
$widget = $this->CI->load->view('widget/'.$item['source'], $data, TRUE);
$data['widget_title'] = '';
$data['widget_content'] = '';
$this->CI->load->view('widget/'.$item['source'], $data);
return $widget;
}
}
widget 1: Calls widget/content
widget 2: Calls widget/banner
What is happening is, the vars set on the first widget call (they are same name as second widget call), get cached, meaning values from the first call are passed to same call. It is weird because are different views.
I have tried:
Using clear_vars(): $this->CI->load->clear_vars(), before and after doing load->view on the library.
Calling load->view with empty array, null, etc
Tried to add a prefix with the widget slug to the vars (that works, but I have to send in some way the prefix to the view, so it is not possible due cache issue)
Any ideas?
Here is what should work.
(I took the liberty of simplifying your database call making it require much less processing.)
public function show($widget_id)
{
$data = array();
$widget_id = (int) $widget_id;
$item = $this->CI->db
->get_where('widget', array('id' => $widget_id))
->row_array();
$data['widget_title'] = $item['title'];
$data['widget_content'] = $item['content'];
$widget = $this->CI->load->view('widget/'.$item['source'], $data, TRUE);
//clear the cached variables so the next call to 'show()' is clean
$this->CI->load->clear_vars();
return $widget;
}
On further consideration The call $this->CI->load->clear_vars(); is probably pointless because each time WidgetManager::show() is called the $data var is recreated with exactly the same keys. When the $data var is passed to load->view the new values of $data['widget_title'] and $data['widget_content'] will replace the values in the cached vars using those keys.
I have inputs array and i need to make a foreach but laravel $request->all() only return last one:
url:
http://localhost:8000/api/ofertas?filter_pais=1&filter_pais=2&filter_pais=3
controller:
public function filtroOfertas(Request $request){
return $request->all();
}
result:
{"filter_pais":"3"}
result should return 1, 2 and 3 and i need to make a foreach in filter_pais.
Any solution? Thanks
Use [] at the key of query string.
http://localhost:8000/api/ofertas?filter_pais[]=1&filter_pais[]=2&filter_pais[]=3
It will be parsed as array.
Repeated parameters make no sense and should be avoided without exception.
But looking at other solutions, there are several:
routes.php
Route::get('/api/ofertas/{r}', 'Controller#index');
Controller:
public function index($r)
{
$query = explode('&', $r);
$params = array();
foreach($query as $param)
{
list($name, $value) = explode('=', $param);
$params[urldecode($name)][] = urldecode($value);
}
// $params contains all parameters
}
Given that the URL has no question marks:
http://localhost:8000/api/ofertas/filter_pais=1&filter_pais=2&filter_pais=3
I have this code
//ImageableTrait
trait ImageableTrait
{
public function images()
{
return $this->morphMany(Image::class, 'imageable')
->orderBy('order', 'ASC');
}
}
//User
class User extend Model
{
use ImageableTrait;
}
//Post
class Post extend Model
{
use ImageableTrait;
}
class ImageCollection extends Collection
{
public function firstOrDefault()
{
if ($this->count() === 0) {
$image = new Image();
$image->id = 'default';
$image->imageable_type = '/* I need the parent className here */';
$image->imageable_id = '.';
}
return $this->first();
}
}
//Image
class Image extend Model
{
public function imageable
{
return $this->morphTo();
}
public function newCollection(array $models = [])
{
return new ImageCollection($models);
}
public function path($size)
{
//Here, there is some logic to build the image path and it needs
//the imageable_type attribute no matter if there is
//an image record in the database or not
return;
}
}
I want to be able to do so
$path = User::find($id)->images->firstOrDefault()->path('large');
But I can't figure out how to get the parent class name to build the path properly...
I tried with $morphClass or getMorphClass() but can't figure out how to use it properly or if it is even the right way to do it.
Any thoughts on that?
I think you can keep it simple and drop the ImageCollection class because there is already a firstOrNew method that seems to be what you're looking for.
The firstOrNew method accepts an array of attributes that you want to match. If you don't care about the attributes, you can pass an empty array. If there are no matches in the database, it'll make a new instance with the proper parent type.
$path = User::find($id)->images()->firstOrNew([])->path('large');
Note: I am calling the images() method to get the MorphMany object so that I can call the firstOrNew method. In other words, you need to add the parentheses. Otherwise, you get a Collection.
Edit: If you want to make things a bit simpler by automatically setting some default attributes, you can add this to your ImageableTrait:
public function imagesOrDefault()
{
$defaultAttributes = ['id' => 'default'];
return $this->images()->firstOrNew($defaultAttributes);
}
Then, you can do something like this: $path = User::find($id)->imagesOrDefault()->path('large');
Note that your default attributes must be fillable for this to work. Also, imageable_id and imageable_type will automatically be set to your parent's id and type.
If you want to set the default value for imageable_id to a period and not the parent's id, then you have to alter it a bit, and it will look a lot like your original code except this will go inside your ImageableTrait.
public function imagesOrDefault()
{
// First only gets one image.
// If you want to get all images, then change it to get.
// But if you do that, change the conditional check to a count.
$image = $this->images()->first();
if (is_null($image))
{
$image = new Image();
$image->id = 'default';
$image->imageable_type = $this->getMorphClass();
$image->imageable_id = '.';
}
return $image;
}
Ok guys I've found something that seems to work pretty good for now so I'll stick with that.
In the Image model, I've added some code when I make the new collection:
public function newCollection(array $models = [])
{
$morphClass = '';
$parent = debug_backtrace(false, 2)[1];
if (isset($parent['function']) AND $parent['function'] === 'initRelation') {
if (isset($parent['args'][0][0])) {
$morphClass = get_class($parent['args'][0][0]);
}
}
return new ImageCollection($models, $morphClass);
}
I then simply retrieve the morphClass through the constructor of the ImageCollection
private $morphClass;
public function __construct($items = [], $morphClass)
{
parent::__construct($items);
$this->morphClass = $morphClass;
}
public function firstOrDefault()
{
if ($this->count() === 0) {
$image = new Image();
$image->id = 'default';
$image->imageable_type = $this->morphClass;
$image->imageable_id = '.';
}
return $this->first();
}
This way, I can simply call the method like that
User::with('images')->get()->images->firstOrDefault()
This seems to work really great in many cases, if I have some issues at some times, I'll update this post.
i may be late for the party, but i kinda did a small trick for morph relationships where i had "media" as morph, i get the parent since "model_type" has the full string parent class string.
$model = new $media->model_type;
$media->model = $model->findOrFail($media->model_id);