PHP array of objects. How to find a specific object?

Discussion in 'Scripts, 3rd Party Apps, and Programming' started by Tarzan, Aug 15, 2012.

  1. Tarzan

    Tarzan New Member

    Messages:
    66
    Likes Received:
    0
    Trophy Points:
    0
    I'm starting to moving into a more object oriented approach when creating my PHP scripts.

    However, there is a scenario that probably is quite common, but I don't know the best way to solve: Finding a specific element in an array. (Before OOP I didn't need to worry since I could find it with SQL in the database.)
    Let's say I have a supermarket with different products. I would create a Supermarket object, and add the products from the database as an array in the supermarket:

    Code:
    class Supermarket {
    
       function Supermarket($ownerId) {
         $results = 'SELECT * FROM products WHERE ownerId = ' . $ownerId;
         foreach($results AS $resultItem) {
            $this->products[] = new Product($resultItem);
         }
       }
    }
    Now I have an array of products that can be looped through with foreach($market->products AS $product). That's all good.
    But how do I access a specific product? Let's say I want to change the price for the product with ID = 40. How do I access this product (to call its "setPrice()" function)?

    I can see two solutions:
    1. I implement a "getProduct($id)" function in the supermarket that loops through all products and returns the one with the correct $id. Seems inefficient.

    2. I use the product ids as keys in the array. So in the foreach loop above, I do
    Code:
    $this->products[$resultItem['id']] = new Product($resultItem);
    Then I can access the object directly, just calling "$market->products[$id]" (or via a getProduct() function for those who fancy that). However, now I won't have a nice array of 0, 1 ,2 ,3 ,4 etc. Instead the keys will be something like 5, 8, 12, 40 56 etc. Are there any drawbacks of this? (Memory, or access speed or something...)

    So, is this the way to go, or is there another "best practice" solution that I haven't thought of?

    (Don't worry about programming misstakes since it's the concept I'm after.)
     
  2. misson

    misson Community Paragon Community Support

    Messages:
    2,572
    Likes Received:
    72
    Trophy Points:
    48
    Option 2.

    Looping through an array is indeed too inefficient. In tech speak, it's O(n), meaning it takes on the order of the size of the array to find any element in it (average case is n/2, but we don't care about coefficients in Big-O notation; O(n/2) = O(n)). In some cases (not this one), you can use array_keys and array_search to search an array; they will still be O(n), but (since they're compiled into PHP and run natively on the processor), they'll run faster than a PHP implementation.

    Since arrays in PHP aren't actually arrays (objects stored in a contiguous region of memory, accessed by integer index) but hash maps, there's no advantage to having the keys be contiguous. You can even mix integers and strings and it shouldn't have a noticeable performance impact.

    On the conceptual side, data structures are all about the basic operations (adding, removing, accessing) and the performance characteristics for these operations, which a good textbook (or online reference) on data structures will cover. Hashes are generally quick for all operations. For more specialized requirements, PHP offers other data structures as part of the SPL (which comes with PHP).

    Note that you can use
    PHP:
     tags to mark PHP in postswhich will colorize it syntactically.

    [
    quote="Tarzan, post: 893012"][PHP]
    class 
    Supermarket {

       function 
    Supermarket($ownerId) {
    [/QUOTE]
    Is this supposed to be a constructor? If so, you should name it __construct. Constructors named after the class were a PHP 4 thing. One issue with them is it can make it tricky to manage calling the appropriate parent constructor. Consider:

    PHP:
    class {
        function 
    A($a) {
            
    # do something important
        
    }
    }

    class 
    extends {...}

    class 
    extends {
        function 
    C($x$y) {
            
    # there's no B::B(), so call A::A()
            
    parent::A($x);
            
    # do other important stuff
        
    }
    }
    If a constructor is ever added to B, there's a good chance its children's constructors won't get updated, leading to bugs, causing some developer to hunt them down and fix them. However, if you use __construct, there isn't even the possibility of forgetting to update the child constructors, hence no time will be wasted down the road.

    Hopefully you only wrote this to illustrate that you're using an SQL statement and aren't actually adding the data directly to the statement. If that's what you're doing in production code, look into prepared statements.

    If you're putting the database access code (including the SQL statements) in each model class, you may someday discover that it becomes too difficult to manage all that DB access code, as it's scattered about. At that point, look into separating it out into a Database Access Layer (DAL); "10 orm patterns: components of a object-relational mapper" lists some approaches. This comes from the Single Responsibility Principle, the first of the SOLID principles of OOP design.
     
    Last edited: Aug 17, 2012
  3. essellar

    essellar Community Advocate Community Support

    Messages:
    3,295
    Likes Received:
    227
    Trophy Points:
    63
    In addition to what misson posted, try to keep in mind that the common definition of an object as "a data structure that knows what to do" is a vast simplification.

    In your example, either the supermarket would be a very poor example of the species (a small convenience store with delusions of grandeur) or it would be an enormous memory hog. There is absolutely no reason why the supermarket, either in a programmatic representation or in the real world, should be "aware" of its entire stock of inventory. There's no reason not to have a "stock boy" go to look for things, count things, update the prices of things, etc., on demand as required.

    In this case, there's no need for the supermarket to have very much in the way of static data associated with it. If all it needs at construction time is the store id, then that would be an adequate minimum representation. If there are other data that are used frequently (things like a specific store name, say "Eddies Red & White" rather than just a chain name that can be hard-coded or put in a config file, the address and telephone number, and so on), then it may be cheaper to add that info at construction. Most of the data associated with the store would be virtual -- its inventory, employees, suppliers and so forth would be accessed only when required. The object serves as a proxy to the inquiries and activities. Your external code asks the store how many tins of tuna it has, and the store replies with the number. The external code does not have to know that the guy who answered the phone had to send somebody scrambling for the shelves and somebody else running to the storeroom in the basement in order to come up with the answer -- your database queries can be as specific as they were in your purely procedural code; you're just moving the knowledge required to carry out those queries into the supermarket object.

    In the same way, you can create, say, an object that represents a list of all of the natural numbers. You are never going to be able to store that list in an object. What you can do, though, is create code that can spit out a list on demand. Tell it that you want all of the natural numbers between 1 and 10, and it can do that. tell it you want all of the numbers between 167,084,911,330 and 167,084,911,575, and it will happily spit out the entire list of those numbers. The external code doesn't have to know that the list didn't exist until it was asked for, and to that external code it sure looks like the whole list was there all along.

    That's just something to keep in mind. You aren't really trading space for time (since an "omniscient" constructor would take nearly the same amount of time as all of the queries it needs to service), but you can defer activities until they are needed. The ones you don't need are never carried out, and you can always cache the results of the activities you do carry out in order to improve performance next time around.
     

Share This Page