Pages

ADSENSE link unit (728 X 15px) SPACE

Countryside Gazebos

Friday, September 9, 2011

New Defragmentation Solution for SQL Server

The product is said to be the industry?s only SQL server defragmentation solution that simplifies the process of pinpointing fragmentation of hotspots through automation.  It gives users the power to defragment indexes automatically or when needed, thus helping to improve SQL Server?s overall performance.

Index fragmentation is one of the major causes of lethargic SQL Server performance.  By keeping indexes defragmented, SQL defrag manager v3.0 keeps SQL Server applications running at optimal performance levels.  This frees database administrators from having to waste precious time on manual defragmentation, allowing them to increase efficiency and giving them the opportunity to concentrate on other tasks.  Heather Sullivan, Idera?s Director of SQL Server Products, reiterated this notion in the official press release: "When SQL Server indexes become fragmented, database performance can quickly degrade and manually defragmenting can be extremely time consuming and tedious.  SQL defrag manager 3.0 provides DBAs with a defragmentation autopilot for the entire SQL Server enterprise."

The latest version of SQL defrag manager comes equipped with a host of features, some of which are new to the software.  One of the newest additions is index fill factor management, which helps to reduce the splitting and shifting of pages.  Darlene Stephens, a database administrator with WaterOne, commented on its useful nature: "The new index fill factor feature in SQL defrag manager has saved me time because I can quickly modify those parameters and visualize the history.  Also, the ability to view and independently reorganize or rebuild partitioned indexes in SQL defrag manager 3.0 has made it even easier to improve the performance of my SQL Servers."

Other features besides the newly added index fill factor management begin with complete, customized control over defragmentation.  SQL defrag manager?s management console provides users with a solid overview of defragmentation activity across multiple servers and databases.  Processes can be activated by and prioritized according to a certain fragmentation percentage, scan density, or index size.  Policy-based management is another highlight of the software?s feature set.     Detailed metrics, email notifications for DBAs, and extensive reporting are just a few more examples of what is included in the SQL defrag manager v3.0 package.

SQL defrag manager v3.0 is currently available through Idera?s website, www.idera.com.  The software costs $1,195 per instance and is free to Microsoft MVPs.  Idera offers a fully functional 14-day trial so users can test all of the features that SQL defrag manager v3.0 has to offer.

For more on this topic, visit http://www.marketwatch.com/story/idera-announces-sql-defrag-manager-30-2011-08-30

Novosoft?s Handy Backup Now Supports 64-bit Version of SQL Server

Novosoft, a global software development company and provider product solutions for businesses, recently announced that its Handy Backup software has been updated to offer backup, sync, and restore for 64-bit versions of Microsoft SQL Server and Microsoft Exchange.  Handy Backup is known across the industry for its power and efficiency when it comes to data backup in both home and business environments.  With the addition of 64-bit support, the software allows Novosoft to provide one of the most comprehensive and cost-friendly backup solutions around.

Handy Backup was originally developed to backup and restore 32-bit platforms.  The addition of 64-bit support comes via Handy Backup Workstation x64.  Those looking for 64-bit SQL Server support will need to have Workstation x64 installed in conjunction with the Handy Backup utility itself.  Workstation x64 comes equipped with multiple 64-bit backup plugins.  The MS SQL x64 plugin offers backup for SQL Server 32-bit and 64-bit versions of its 2000, 2005, and 2008 editions.  Other plugins that come with Workstation x64 include MS Exchange x64 and File System x64.

Novosoft pairs the workstation with local and network backup options for maximum flexibility.  The local backup of 64-bit applications requires the same-computer installation of Workstation x64 on top of Handy Backup.  Remote backup requires the installation of Workstation x64 on the remote computer.  In addition, the program must be configured to comply with the Backup Network Workstation policy.  There is no cap as to the amount of additional workstations that can be installed, and Novosoft offers premium and free support for those seeking help with the configuration process.

Alexander Prichalov, head of the Novosoft Development Department, commented on the latest Handy Backup improvements, saying: ?Bet you won?t guess five in a row hardcore up-to-date-actual data backup features which Handy Backup doesn?t have. Concerning those features which are being currently developed and will be released soon, I bet you won?t name three. That?s our key priority here at Novosoft: we make cool backup stuff and make it quickly. Thanks to that little alchemy formula, Handy Backup is now officially one of a few fully-functional software solutions to manage backup and restore of 64-bit server platforms. You have never seen a handier 64-bit MS SQL Server backup software solution.?

Novosoft?s suite of Handy Backup products is available through the official Handy Backup website, www.handybackup.net. 

For more on this topic, visit http://www.thehostingnews.com/handy-backup-enhances-ms-sql-backup-with-new-support-20149.html



blog comments powered by

Gazebo shed on saleDownload here
Read More ...

Clauses and Logical Operators for Retrieving Table Data

How to code the WHERE clause

The WHERE clause in a SELECT statement filters the rows in the base table so that only the rows you need are retrieved. In the topics that follow, you?ll learn a variety of ways to code this clause.

How to use the comparison operators

Figure 3-11 shows you how to use the comparison operators in the search condition of a WHERE clause. As you can see in the syntax summary at the top of this figure, you use a comparison operator to compare two expressions. If the result of the comparison is True, the row being tested is included in the query results.

The examples in this figure show how to use some of the comparison operators. The first WHERE clause, for example, uses the equal operator (=) to retrieve only those rows whose vendor_state column have a value of IA. Since the state code is a string literal, it must be enclosed in single quotes.

In contrast, a numeric literal like the one in the second WHERE clause isn?t enclosed in quotes. This clause uses the greater than (>) operator to retrieve only those rows that have a balance due greater than zero.

The third WHERE clause illustrates another way to retrieve all the invoices with a balance due. Like the second clause, it uses the greater than operator. Instead of comparing the balance due to a value of zero, however, it compares the invoice total to the total of the payments and credits that have been applied to the invoice.

The fourth WHERE clause illustrates how you can use comparison operators other than the equal operator with string data. In this example, the less than operator (<) is used to compare the value of the vendor_name column to a literal string that has the letter M in the first position. That will cause the query to return all vendors with names that begin with the letters A through L.

You can also use the comparison operators with date literals, as illustrated by the fifth and sixth WHERE clauses. The fifth clause will retrieve rows with invoice dates on or before May 31, 2008, and the sixth clause will retrieve rows with invoice dates on or after May 1, 2008. Like string literals, date literals must be enclosed in single quotes. In addition, you can use different formats to specify dates, as shown by the two date literals shown in this figure. You?ll learn more about the acceptable date formats and date comparisons in chapter 8.

The last WHERE clause shows how you can test for a not equal condition. To do that, you code a less than sign followed by a greater than sign. In this case, only rows with a credit total that?s not equal to zero will be retrieved.

Whenever possible, you should compare expressions that have similar data types. If you attempt to compare expressions that have different data types, Oracle may implicitly convert the data types for you. Although implicit conversions are often acceptable, they will occasionally yield unexpected results. In chapter 8, you?ll learn how to explicitly convert data types so your comparisons will always yield the results that you want.

The syntax of the WHERE clause with comparison operators

WHERE expression_1 operator expression_2
The comparison operators 




Examples of WHERE clauses that retrieve? 


Vendors located in Iowa 


[code]WHERE vendor_state = 'IA'

Invoices with a balance due (two variations)

WHERE invoice_total - payment_total - credit_total > 0
WHERE invoice_total > payment_total + credit_total

Vendors with names from A to L

WHERE vendor_name < 'M'

Invoices on or before a specified date

WHERE invoice_date <= '31-MAY-08'

Invoices on or after a specified date

WHERE invoice_date > = '01-MAY-08'

Invoices with credits that don?t equal zero

WHERE credit_total <> 0

Description

You can use a comparison operator to compare any two expressions that result in like data types. Although unlike data types may be converted to data types that can be compared, the comparison may produce unexpected results.
If the result of a comparison results in a True value, the row being tested is included in the result set. If it?s False or Unknown, the row isn?t included.
To use a string literal or a date literal in a comparison, enclose it in quotes. To use a numeric literal, enter the number without quotes.
Character comparisons are case-sensitive. ?CA? and ?Ca?, for example, are not equivalent. How to use the comparison operators


blog comments powered by

Gazebo shed on saleDownload here
Read More ...

Comparison of MyISAM and InnoDB MySQL Databases

It's not breaking news that MySQL is by far the most popular open-source RDBMS that exists today. The widespread adoption of the database server stems from a balance of robustness and solid performance; it doesn't hurt to that it also supports a variety of storage engines, which gives users the ability to pick the one that best suits their needs.

Unfortunately, not everyone using MySQL is an experienced DBA with a deep background on each storage engine supported by the server. This is especially evident in the terrain of web development, where (in many cases) the design of a project?s database schema is conducted by taking into account only the kind of data that will be persisted between HTTP requests.

The bright side is that in most use cases, selecting the appropriate storage engine for a particular project can be much simpler than one might think. If you?ve been working with MySQL on a frequent basis and have followed its evolution over time, you probably know that the whole selection process can be reduced to a few typical options: the older MyISAM system and the newer, flashier InnoDB.

That being said, in this tutorial I?ll be conducting a comparison between the main characteristics offered by MyISAM and InnoDB, in this way making it easier for you to spot their differences without having to get lost in the overwhelming contents of the MySQL manual.

MyISAM

Being the storage engine included by default with MySQL from its early days, MyISAM (short for My Indexed Sequential Access Method) is a relatively simple storage mechanism (especially when compared to more complex and sophisticated implementations, such as InnoDB and DB2) which has a fast performance, due to the fact that each record is organized first into  a sequential file, and then accessed via a set of small indexes or pointers annexed to the record in question.

It?s worth noting, however, that this simplicity and great performance comes with a hidden cost: first and foremost, MyISAM doesn?t provide support for transactional ACID operations, meaning that it?s not possible to run batches of operations in one go, or even to roll them back when something goes wrong. For obvious reasons, this can lead to potential inconsistencies in tables and affect the integrity of the stored data, thus making it necessary to tackle this and other related issues at application level.

In addition, when accessing a database, MyISAM uses an approach known as table-level blocking. Simply put, this implies that each time the server attempts to run an operation in the target table, it will be blocked during the execution process. While at first glance this operational mode seems to be quite efficient, it can quickly become a poor strategy, particularly when it?s necessary to attend to multiple clients trying to gain access to the same resource at the same time (aka concurrence issues).

Finally, there?s another big "NO" that MyISAM hides under the hood. Effectively, the mechanism doesn?t offer support for handling native foreign keys. Of course, I?m not saying that you won?t be able to create two or more tables and relate them manually at will through a few foreign keys. What I actually mean is that you can?t specify foreign keys in the corresponding table definitions and specify the constraints that they will behave in response to common cascade operations, such as updates and deletions.       

In summary: MyISAM will perform satisfactorily as long as you?re database requirements remain basic and limited to running traditional, non-transactional CRUD operations. If you need to take a step further into the realm of transactions (which goes hand in hand with the trends of modern application development), it should be discarded in favor of a more robust implementation, such as InnoDB.

InnoDB

Added to MySQL in 2005, InnoDB (whose manufacturer is the Finnish company Innobase Oy [http://www.innodb.com]) is a remarkable improvement of its predecessor MyISAM, which not only saves records following the same order of their primary keys (something that sometimes avoids an additional reordering process), but it also offers solid support for performing transactional CRUD operations (the details on how InnoDB achieves this are out of the scope of this article, but you can take a deeper look at them here [http://dev.mysql.com/doc/refman/5.0/en/innodb-storage-engine.html]).

While the support for transactions is hard to beat when it comes to selecting the storage engine that fits your needs most efficiently, InndoDB provides a few other robust features that turn it into an even more appealing contender.

If you?re wondering what these extra features are, here?s a quick summary of them: first off, the engine implements a row-level blocking mechanism (unlike its older brother MyISAM, which blocks the entire table being accessed), meaning that the server will be able to perform concurrent operations in the same table, as long as they don?t target the same row. This is a really clever approach, which really shines on its own, especially if you have a lot of clients accessing the same target table.

Last but not least, InnoDB will let you easily specify (in the same table definition) which column(s) will be native foreign keys, as well as the behavior that they will have when updating and deleting rows in the parent table(s) (in other words, the foreign keys constraints mentioned in the preceding section).

Don?t worry - I?m not planning to cover how to define foreign keys constraints when using InnoDB; however, if you?re interested in learning more on this topic, just check out the tutorial that I wrote here (http://www.devshed.com/c/a/PHP/Updating-and-Deleting-Records-in-MySQL-Tables-with-Foreign-Key-Constraints/).

It goes without saying that MyISAM and InnoDB implement a host of additional low-level features, which are way to extensive to dicuss here. Nevertheless, if you only need a quick guide that will show you the most relevant facilities that each storage engine brings to the table, hopefully this tutorial will assist you in the analysis process.

Final Thoughts

Over the course of this article, I made a brief summary of the most relevant features offered by the popular MyISAM and InnoDB MySQL storage engines. As you saw through this quick review, the clear winner here is undoubtedly the latter, not only for being much newer than its competitor, but due to the fact that it provides a bunch of facilities that are hard to ignore when it comes to building database applications, including the support for transactions (a key one, in my humble opinion) and the implementation of a solid row-level blocking mechanism.

Naturally, if your database requirements are rather basic and you don?t need to use transactions, MyISAM is still a good option worth noting that will yield pretty satisfactory results as well. Regardless of this, InnoDB is rapidly becoming the choice of many users, because of the aforementioned benefits, which as I said before, are definitively along the lines of modern application development.    

See you in the next MySQL tutorial!



blog comments powered by

Gazebo shed on saleDownload here
Read More ...

Free Monitoring Tool for Java Apps on Heroku

The announcement came during the Salesforce.com Dreamforce ?11 conference, which brings hundreds of companies and thousands of customers, partners, and developers together to discuss cloud computing.  The timing of the announcement could not be better, as Java support on Heroku was recently made official.  New Relic?s application performance management tool offers a comprehensive solution for monitoring web applications and is used by a host of businesses, both large and small. 


The New Relic Standard edition provides 24/7 app and user monitoring and is ideal for smaller projects.  It supplies users with detailed information on database performance through various statistical tracking measures.  Customer data is retained for 1 week, and there can be unlimited users per account.  The tool also comes equipped with a JVM performance analyzer, incident and error alerting, plus much more to ensure that web application performance is optimized.


Heroku VP of Product Management Oren Teich commented on the importance of the latest move: "As more businesses look to Heroku to enable their cloud strategy, providing application performance monitoring is critical.  We are excited to announce the availability of New Relic's add-on for our tens of thousands of developers worldwide and now growing base of Java developers with New Relic's new production profiling and monitoring capabilities integrated seamlessly with Heroku."


Bill Lapcevic, New Relic?s VP of Business Development, noted the strength of the partnership between New Relic and Heroku, stating: "New Relic and Heroku have worked together to deliver the tools, resources, and skills organizations needed to rapidly and successfully deploy business-critical apps in the cloud.  The addition of New Relic's app production profiling and monitoring capabilities to Heroku's new Cedar platform underscores our continued joint commitment to providing Java developers with the same scalability, simplicity, and reliability that Ruby programmers have enjoyed for over the last two years." 


New Relic Standard edition is available for free to Heroku?s Java customers and continues to be free of charge to those running Ruby apps on the Heroku platform.  New Relic also offers its application performance management tool in a Pro edition that offers additional features beyond those found in the Standard version.  A 14-day trial can be used to experience the Pro edition?s capabilities.  A business package is available as well for clients with more than five servers.  More information on the different offerings can be found on New Relic?s website, www.newrelic.com.


Formore on this topic, visit http://www.marketwatch.com/story/new-relic-provides-developers-with-free-24x7-web-app-monitoring-and-analysis-for-java-apps-on-heroku-2011-08-30?reflink=MW_news_stmp


Extentech Releases Update for its Java Spreadsheet Solution


Extentech, an industry leader in web spreadsheet and document technology, recently upgraded its portfolio of Java components and development tools with the announcement of its release of ExtenXLS 10.1.  The release upgrades the already solid Java spreadsheet SDK to include several new and improved features.


The ExtenXLS Java spreadsheet toolkit offers a fast and reliable platform that is ideal for reading, writing, and generating Excel spreadsheets.  Beyond its excellent performance and reliability, ExtenXLS? main advantage over competitors is that it now gives users SVG charting that is compatible with web browsers such as Google Chrome, Firefox, Internet Explorer, and Safari.  SVG is an XML graphics format that eliminates the need for any browser plugins, as the most up-to-date browsers support its output.  Thanks to SVG charting, users can experience the benefits of data rich spreadsheet reporting in real time with vector graphics that are not only fast, but scalable as well.  ExtenXLS also offers VML Chart output to provide compatibility with legacy browsers. 


In addition to the SVG charting, ExtenXLS 10.1 is highlighted by several other appealing features, beginning with the introduction of the new ERF, NORMINV, NORMSINV, NORMDIST, and NORMSDIST formula functions.  Sheet protection and locking have been upgraded for increased compatibility with Excel, and charts containing spreadsheets can be exported in PDF format. 


ExtenXLS 10.1 also comes with various improvements aimed at increasing performance.  One of those improvements focuses on the realm of XLSX and OOXML fidelity, which has been increased when working with Excel 2010 and encrypted spreadsheet files.  A new workbook parser has been included as well to offer enhanced event-driven document review for sessions that are read-only.


Extentech CEO John McMahon commented on the latest ExtenXLS release, stating: ?We are continually updating ExtenXLS to provide the most functionality for developers and to ensure that it remains the top choice of enterprises that need reliable and fast spreadsheet capabilities without vendor or platform lock-in.  With our new SVG charting output, we give customers a better way to present spreadsheet data, with rich and up-to-date web-based charts. Our system renders charts very quickly, so busy users don't need to wait for their charts and spreadsheet data to display, allowing everyone to work more efficiently."



blog comments powered by
Gazebo shed on saleDownload here
Read More ...

Steve Jobs` Era at Apple Ends

The official resignation by Jobs does not come as a complete surprise, as he had recently been plagued by various health problems.  Jobs turned over Apple?s reigns to Cook in January of this year when he was forced to take an indefinite medical leave.  It was his third such leave in recent years, but he was determined to return to his position, stating, ?I love Apple so much and hope to be back as soon as I can.?


Jobs? unfortunate medical history began in August of 2004, when he had surgery to treat a rare form of pancreatic cancer.  The procedure was a success, and Jobs returned to work the next month.  He ran into problems once again in January of 2009, when he claimed that his body?s ability to absorb proteins was being hindered due to a hormonal imbalance.  Jobs had liver transplant surgery in April of that year which kept him sidelined until July.


The string of health problems appear to have been his final undoing, as Jobs proclaimed that he was no longer able to perform as CEO in a letter to the Apple?s  board members as well as the company?s vast community.  He said, ?I have always said if there ever came a day when I could no longer meet my duties and expectations as Apple's CEO, I would be the first to let you know. Unfortunately, that day has come.? 


While his CEO days may be over, Jobs expressed the desire to continue contributing to the company as chairman of the board, director, and even as an employee.  He noted that such involvement would be subject to the board?s approval.  Jobs finished his letter on a positive note, stating, ?Apple's brightest and most innovative days are ahead of it.?  Jobs? last major public appearance as CEO of Apple occurred a couple of months ago during the Worldwide Developers Conference in June.  The conference was used as a springboard for the company?s introduction of its iCloud service and iOS5. 


Tim Cook?s career with Apple began in 1998.  Cook had served as Jobs? right-hand man for a lengthy period, and his duties included figurehead appearances at product launches, as well as leading several earning calls with investors and shareholders meetings.  The 50 year old assumed the position of chief operating officer in 2004.  Cook?s new role as CEO kicks off a new era for Apple, but the company?s booming success combined with his solid experience should make it a prosperous one. 


Apple recently held the distinction of being the world?s most valuable company thanks to its $330 billion-plus market capitalization.  Apple?s line of highly-noticeable products, including the Mac, iPhone, iPad, and iPod, has created an army of loyal followers and helped it separate itself from competitors.


 



blog comments powered by
Gazebo shed on saleDownload here
Read More ...

Heroku Adds JCloud Platform Support, Java 7 News

Heroku first introduced its Celadon Cedar stack in May.  The company claimed that the stack would be able to run any language.  With the addition of Java to the mix, Heroku?s is solidified even further.  Adam Wiggins, co-founder of Heroku, described the latest move in a recent blog post, noting: ?Java is, by many measures, the world's most popular programming language.  In addition to its large and diverse developer base, it offers a huge ecosystem of libraries and tools, an extremely well-tuned VM for fast and reliable runtime performance, and an accessible C-like syntax.?


Bill Lapcevic, VP of business development at New Relic, claimed that Heroku?s support of Java would open new doors for the platform.  He said: ?New Relic and Heroku have been long-standing partners and it's great to see them expand their multi-language platform with support for Java.  This puts Heroku in a key position to serve Salesforce.com's developer audience as their platforms are largely Java- based. It also puts Heroku on the same playing field with existing solutions like Amazon Elastic Beanstalk. I expect there will be a significant uptake from enterprise developers looking for easy deployment and multi-language support for their critical apps."


Besides its popularity, Wiggins noted various other reasons for supporting Java.  He applauded the Java Virtual Machine (JVM) for its reliable memory footprint and impressive performance, calling it one of the best runtime VMs around.  Wiggins cited Java?s massive estimated population of six million developers as another reason to support the language, and said that it?s the industry?s best developed programming language for building server-side applications.  He cited the JVM runtime environment?s wide availability across platforms as an appealing factor as well.  ?Supporting Java is what's best for the large world of Java developers; it's what's best for developers who want to use other JVM languages; and it's even good for users of other languages, who will benefit indirectly from the learning their community may gain from contact with Java,? Wiggins said.


Wiggins also promised that Heroku would help to clean up certain problems associated with Java 2 Platform Enterprise Edition (J2EE), noting that the platform offers ?the capabilities promised by J2EE application containers for managing your app include deployment, restart, logging, service binding (config), and clustering (horizontal scaling.  Running your Java app on Heroku, you achieve these ends via the platform instead.?  He added, ?Using Heroku's platform to run Java apps finally solves the impedance mismatch between application containers designed for traditional software distribution, and the modern world of software-as-a-service.?


As for what to expect from Heroku in the future, Wiggins said, ?Future language packs will span the gamut from venerable (like Java) to cutting-edge (like Clojure and Node.js) to squarely in-between (like Ruby). Our desire is to be as inclusive as possible. Choice of language is up to the developer.?
For more on this topic, visit http://www.eweek.com/c/a/Application-Development/Heroku-to-Java-Welcome-to-Our-Cloud-832656/


A Glimpse into Java 7?s Improvements


The recent release of Java Platform Standard Edition 7 (Java SE 7) was the first major Java release in over five years.  It also marked the first Java introduction under the Oracle umbrella.  While Java SE 7 did have a bit of a rough start due to some serious bugs, it has been fixed, and it offers developers a host of new features that are worthy of praise.


Although Java SE 7 comes with various upgrades, some believe that its timing is its most important feature.  ?The main thing about Java SE is that it shipped. The inertia of five years without a release had to be overcome,? said Al Hilwa, an analyst at IDC.  Some features, such as language and VM support for modular programming and the ability to add closures for multi-core programming did not make the deadline for Java 7, meaning users will have to wait for Java 8?s release in 2012 to see them.


As for what?s available now, Java 7?s new and improved feature set begins with support for dynamic languages.  According to Hilwa, such support will promote the expansion of the Java ecosystem.  Thanks to a multi-core ready API, developers can decompose problems into tasks for parallel execution via multiple processor cores more efficiently.  Another Java 7 improvement serves to help increase developer productivity.  With Project Coin, coding is reduced and syntax is clarified through various language changes. 


Java founder James Gosling cited the new NIO2 file-system capabilities as one of his favorite features of Java 7.  NIO2 supplies a solid interface for working with file systems that offers detailed error information and broad access to file attributes.  Other Java 7 highlights of note include the new Gervill sound engine and the XRender pipeline for 2D graphics rendering.


blog comments powered by
Gazebo shed on saleDownload here
Read More ...

Thursday, September 8, 2011

Using the IN and BETWEEN Operators on Tables

How to use the IN operator

Figure 3-13 shows how to code a WHERE clause that uses the IN operator. When you use this operator, the value of the test expression is compared with the list of expressions in the IN phrase. If the test expression is equal to one of the expressions in the list, the row is included in the query results. This is illustrated by the first example in this figure, which will return all rows whose terms_id column is equal to 1, 3, or 4.

You can also use the NOT operator with the IN phrase to test for a value that?s not in a list of expressions. This is illustrated by the second example in this figure. In this case, only those vendors who are not in California, Nevada, or Oregon are retrieved.

If you look at the syntax of the IN phrase shown at the top of this figure, you?ll see that you can code a subquery in place of a list of expressions. Subqueries are a powerful tool that you?ll learn about in chapter 6. For now, though, you should know that a subquery is simply a SELECT statement within another statement. In the third example in this figure, for instance, a subquery is used to return a list of vendor_id values for vendors who have invoices dated May 1, 2008. Then, the WHERE clause retrieves a vendor row only if the vendor is in that list. Note that for this to work, the subquery must return a single column, in this case, vendor_id.

The syntax of the WHERE clause with the IN operator

WHERE test_expression [NOT] IN ({subquery|expression_1 [, expression_2]...})

Examples of the IN operator

The IN operator with a list of numeric literals

WHERE terms_id IN (1, 3, 4)

The IN operator preceded by NOT

WHERE vendor_state NOT IN ('CA', 'NV', 'OR')

The IN operator with a subquery

WHERE vendor_id IN
(SELECT vendor_id
FROM invoices
WHERE invoice_date = '01-MAY-2008')

Description

You can use the IN operator to test whether an expression is equal to a value in a list of expressions. Each of the expressions in the list must evaluate to the same type of data as the test expression.
The list of expressions can be coded in any order without affecting the order of the rows in the result set.
You can use the NOT operator to test for an expression that?s not in the list of expressions.
You can also compare the test expression to the items in a list returned by a subquery as illustrated by the third example above. You?ll learn more about coding subqueries in chapter 6. --------------------------------------------Figure 3-13 How to use the IN operator


blog comments powered by

Gazebo shed on saleDownload here
Read More ...

Ten Android Apps Dominate Smartphone Usage

Nielsen Smartphone Analytics collected its data during June of this year by installing meters on thousands of smartphones running on the Android operating system.  The meters tracked data concerning usage on the phones to give Nielsen researches further insight into how they are actually used.  The study found that the average Android user in the United States interacts with the web and uses apps for approximately 56 minutes per day.  Of the 56 minutes, 67 percent involves app usage, with the remaining third going to the web in the form of browsing and more.


A closer look into the app usage shows that only a very small portion of the Android Market?s catalog of over 250,000 apps is occupying users? time.   The 10 most popular Android apps account for 43 percent of app usage.  When you bump the analysis to include the top 50 Android apps, they account for 61 percent of overall usage.  In other words, the remaining 39 percent is left for the other 249,950-plus apps to fight over. 


It?s rather interesting that Nielsen did not release an actual listing of the top Android apps, but the company did say it plans to disclose the information during an upcoming webinar.  A little Google research into the topic shows that the Android Market?s top ten app list consists of YouTube, Facebook, Skype, Google Maps, Twitter, Adobe Reader/Flash, and Barcode Scanner.  One thing that definitely helps some of the top apps achieve such popularity is the fact a few come pre-installed on Android devices.  Their free status is helpful as well.  The top ten also consists of apps that perform basic, everyday functions that users want access to, such as communication and social networking.  When you think of a site like Facebook or a service like Skype, it?s not hard to figure out why people would spend so much time on them. 


Although Nielsen?s numbers show dominance by a small sector of the Android app world, the remaining market share is certainly worth fighting over.  Developers can certainly make a name for themselves by creating apps that are free or at least very affordable, plus offer a unique way to deliver functionality for everyday tasks.


For more on this topic, visit http://www.pcworld.com/article/238583/android_smartphones_dominated_by_10_apps.html



A Glimpse into the Future of HP?s WebOS


When Hewlett-Packard announced that it would stop the production of various webOS-based devices such as the TouchPad tablet and smartphones in the form of the Veer and Pre 3, many wondered what it meant for the future of the webOS platform itself.  HP purchased the mobile operating system from Palm for $1.2 billion last year, so it makes sense that the company?s executives stated a commitment to further development of the platform.  Stephen DeWitt, head of HP?s webOS division, ensured that webOS will continue to exist as far as PCs are concerned, and will also remain as the platform of choice for printers and other appliances.  In an interview with Engadget, he said: ?We made the decision to focus on the platform.  We could bring webOS to the market and expand the ecosystem...We can look at licensing; we can look at OEM and ODM-type relationships.?


Although a commitment does exist, there is still plenty of speculation of what is in store for webOS in the future.  To help clear things up a bit, a recent PCWorld article simplified the speculation to give a clearer glimpse into what developers and HP followers can look forward to from webOS.


Future Development


In terms of continued development of webOS in the near future, things appear to be looking up.  DeWitt confirmed HP?s commitment to the evolution, updating, and support of webOS in a recent statement to Bloomberg.  Speaking of updating, DeWitt also confirmed that updates for HP?s Veer and TouchPad devices would continue.  Whether or not that lasts well into the future remains to be seen.


HP App Catalog?s Prospects


HP?s interest in keeping app developers interested in webOS is still alive and well, at least if you take what Kerris recently wrote on the HP webOS Developer Blog to heart.  He noted: ?We will continue to support, innovate, and develop the webOS App Catalog. Our intent is to enhance our merchandising and presentation of your great products and to continue to build our webOS app ecosystem.?


While such a statement is promising, attracting developer attention to a platform lacking an upcoming product line is no easy task.  It becomes infinitely more difficult with competitors like Microsoft waiting in the wings.  The software giant knows it has a ripe opportunity to snatch up talented webOS developers, and it has begun doing its best to bring them over to Windows Phone 7?s side.  In a recent posting on Twitter, Brandon Watson, Microsoft?s director of developer experience for Windows Phone 7, offered webOS developers with published apps various incentives to develop for Microsoft?s mobile platform, including free development phones.  So far, the offer seems to be working, as it has piqued the interest of many webOS developers.


webOS? Expansion


For the time being, you can expect to see continued webOS development for PCs and printers, as HP proclaimed earlier this year that it would attempt to enhance Windows via webOS.  The enhancement would use the platform as a synchronization avenue between HP PCs and mobile webOS devices.


There also appears to be a chance that HP could expand webOS? reach into other devices besides phones, printers, and tablets.  DeWitt hinted at this when he told Bloomberg, ?There are going to be appliances of so many different sizes and shapes in the future that are going to require a human interface for data.?  In order to convert the expansion hopes into reality, HP is seeking solid webOS hardware partners.  ?HP webOS is an awesome software platform and now we can explore the best hardware partner for it,? wrote HP?s VP of worldwide developer relations for webOS Richard Kerris, on Twitter. 


blog comments powered by
Gazebo shed on saleDownload here
Read More ...

Windows Phone 7 Marketplace Surpasses 30,000

Windows Phone Applist, a site that specializes in tracking statistics for the Windows Phone Marketplace, documented the smartphone platform?s latest triumph.  Microsoft initially issued tools to app developers in March of 2010, a few months prior to the October 2010 launch of various Windows Phone Devices.  The Windows Phone Marketplace reached a count of 11,500 apps in March of 2011, and has quickly exceeded more than double that total with over 30,000 apps currently. 


Although the sudden growth of Windows Phone 7?s app population does show that there is some developer interest in the devices, Microsoft still has a lot of work to do if it hopes to even sniff the success of Android and the iPhone.  Google?s Android Market reached 250,000 apps in July, and Apple?s App Store reported an inventory of over 425,000 apps in June.  When you look at the numbers of those two competitors, it certainly puts a damper on Microsoft?s recent milestone.  Microsoft has been successful in luring some of the top apps to its platform, such as Angry Birds and Netflix, just to name a couple.  However, it still must overcome a barrier that usually occurs when new apps launch.  The apps typically become available for Android or the iPhone, with Windows Phone 7 left out in the cold.


Microsoft?s Mango update for Windows Phone 7 should give the platform a boost.  The upcoming OS will come with a host of new features that Microsoft believes will attract app developers.  The ability to access the camera and home screen shortcuts to specific parts of apps are just two capabilities developers can take advantage of with Mango.  With supposedly over 500 new features on the OS horizon, Mango should spark developer interest.  On the flip side, the continuing lack of in-app purchase capabilities will still be seen as a negative mark against Windows Phone.


The late introduction of Windows Phone 7 certainly does not help its numbers, as Android and the iPhone have had extra time to establish their dominance.  According to the NPD Group, Windows Phone?s share sits at just 2 percent for the entire smartphone market in the United States.  Android holds a 52 percent share, and the iPhone trails with 29 percent.  The fact that Microsoft?s smartphone devices hold such a small share of the market yet continue to grow in their app count is promising, as developers are showing that they still have faith in the tech giant.


For more on this topic, visit http://www.pcworld.com/article/239118/windows_phone_7_hits_30000_apps.html


App Developers Should Keep Data Efficiency in Mind


As carriers continue to institute rules that cap data usage, many smartphone users are becoming more and more worried about rising costs.  Controlling usage can be hard, however, especially with the growing popularity of apps. 


Carriers such as AT&T, T-Mobile, and Verizon Wireless have begun changing the ways in which they operate to keep data usage under control.  Whether it?s through tiered data plans or throttling, the carriers are not sitting idle.  To ensure that the data restrictions do not hinder the popularity and usage of their apps, developers need to keep data efficiency in mind during the development process.  Larry Rau of Verizon cemented this notion, saying: ?Bandwidth is not infinite and it's not free.  It's something developers have to be aware of.?


With data at such a premium, there are a few things that developers can do to reduce their apps? likelihood of becoming bandwidth hogs, as explained in a recent CNet article.  The first tip mentioned deals with compression.  Videos are always a solid avenue for offering entertainment to users, but they can also hog bandwidth.  For this reason, many carriers urge video providers and developers to use compression.  Since compression can vary, carriers hope that developers will apply the correct type to their video content.  For instance, news reports or how-to videos should not require high resolutions for content such as movies or sporting events.  ?We work with developers to make sure the type of video used matches the use case,? Rau noted.


Carriers also prefer apps that send larger portions of data but with less frequency, rather than those that constantly ping the network, as excessive pinging interferes with other users? access.  Unisys VP of application services Kurt Gerstner stated that since native apps don?t require constant pinging for updates, they are often more data efficient than web-based apps.


To prevent constant network pinging, carriers suggest that developers cache or store some data (images, logos, etc.) on the phone.  Gerstner touts what is referred to as the ?store and forward? technique, which minimizes data usage by storing most of the app content on the phone and sending only actual changes to the data.


Although there are many apps on the market that tend to drain networks, Rau believes that most were not purposely created to do so.  He said: ?It's one of the biggest areas where I find developers can be assisted.  They're not all that familiar with the network usage.?



blog comments powered by
Gazebo shed on saleDownload here
Read More ...

PHP and the Law of Demeter

This might just be my opinion, but if I had to choose the flashiest star in the sky that is PHP, I'd have to mutter two words: Dependency Injection. Seated in solid logic, DI (or Inversion of Control) is the new kid on the block, which allows you to create modular, highly-testable classes that ask for their collaborators - either in their constructors or setters - instead of looking for them.

While I have to confess that I'm a big fan of DI, I admit that there are cases where it can be difficult to figure out what dependencies to inject into a given class. Further more, sometimes we're not careful enough when it comes to defining the responsibilities that classes will have in the context of an application, and end up passing them the wrong collaborators. Or - even worse - we pass in dependencies that are used internally as bridges or mediators for acquiring other objects, which makes classes "aware" of functionality that's completely unnecessary.

When this happens, it's a clear symptom of a common issue known as the "Law of Demeter" breakage. In case the name doesn't ring any bells, the "Law of Demeter" (http://en.wikipedia.org/wiki/Law_of_Demeter) - or the Principle of Least Knowledge - is a paradigm that allows to create loosely-coupled classes, based on a simple concept: each class should be designed to work properly using only the dependencies that it really needs.

This has more to do with common sense than any obscure programming principle. With a little willpower and planning, however, it's possible to create classes that adhere to the Law of Demeter.

I'll be demonstrating how the violation of this law can cause some undesired coupling effects in PHP classes, and how these artifacts can be fixed with minor hassle.

According to the formal definition excerpted from Wikipedia, a method of an object that doesn't invoke the following objects:

1) The object itself.
2) The method's parameters.
3) Any objects created/instantiated within the method. 4) The object's direct component objects. 5) A global variable, accessible by the object, in the scope of the method.

flagrantly infringes the Law of Demeter. While understanding the theoretical concepts is all well and fine, the best manner to understand the side effects of violating the law is by example. Keeping with this, I'm going to show you a use case where this occurs quite frequently: let's say that we need to implement a mapping layer that moves data between a domain model and a MySQL database, while keeping both isolated from one another.

The set of classes that compose the model are as follows:

(SampleApp/Model/AbstractEntity.php)


namespace SampleAppModel;
abstract class AbstractEntity
{
protected $_values = array();
protected $_allowedFields = array();

    /**
* Constructor
*/
public function __construct(array $fields)
{
foreach ($fields as $name => $value) {
$this->$name = $value;
}
}

    /**
* Assign a value to the specified field via the corresponding mutator (if it exists);
* otherwise, assign the value directly to the '$_values' array
*/
public function __set($name, $value)
{
if (!in_array($name, $this->_allowedFields)) {
throw new InvalidArgumentException("Setting the field '$name' is not allowed for this entity.");
}
$mutator = 'set' . ucfirst($name);
if (method_exists($this, $mutator) && is_callable(array($this, $mutator))) {
$this->$mutator($value);
}
else {
$this->_values[$name] = $value;
}
}

    /**
* Get the value of the specified field (via the getter if it exists or by getting it from the $_values array)
*/ 
public function __get($name)
{
if (!in_array($name, $this->_allowedFields)) {
throw new InvalidArgumentException("Getting the field '$name' is not allowed for this entity.");
}
$accessor = 'get' . ucfirst($name);
if (method_exists($this, $accessor) && is_callable(array($this, $accessor))) {
return $this->$accessor;
}
else if (isset($this->_values[$name])) {
return $this->_values[$name];
}
else {
throw new InvalidArgumentException("The field '$name' has not been set for this entity yet."); 
}
}

    /**
* Check if the specified field has been assigned to the entity
*/
public function __isset($name)
{
if (!in_array($name, $this->_allowedFields)) {
throw new InvalidArgumentException("The field '$name' is not allowed for this entity.");
}
return isset($this->_values[$name]);
}
/**
* Unset the specified field from the entity
*/
public function __unset($name)
{
if (!in_array($name, $this->_allowedFields)) {
throw new InvalidArgumentException("Unsetting the field '$name' is not allowed for this entity.");
}
if (isset($this->_values[$name])) {
unset($this->_values[$name]);
return true;
}
throw new InvalidArgumentException("The field '$name' has not been set for this entity yet.");
}

    /**
* Get an associative array with the values assigned to the fields of the entity
*/
public function toArray()
{
return $this->_values;
}    
}

(SampleApp/Model/User.php)


namespace SampleAppModel;
class User extends AbstractEntity
{  
protected $_allowedFields = array('id', 'name', 'email');

    /**
* Set the user's ID
*/
public function setId($id)
{
if(!filter_var($id, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 999999)))) {
throw new InvalidArgumentException('The ID of the user is invalid.');
}
$this->_values['id'] = $id;
}

    /**
* Set the user's full name 
*/ 
public function setName($name)
{
if (strlen($name)< 2 || strlen($name) > 32) {
throw new InvalidArgumentException('The name of the user is invalid.');
}
$this>_values['name'] = $name;
}

    /**
* Set the user's email address
*/ 
public function setEmail($email)
{
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException('The email address of the user is invalid.');
}
$this->_values['email'] = $email;
}         
}

You may be familiar with the above classes, as Ive used them in some previous articles. In any case, all the earlier abstract parent does is define the structure and constraints of generic entities, while its subclass does something similar, only with users.

So far, these entity classes are pretty easy to grasp, so let's make the domain model a bit more functional by adding a class that handles the collections of domain objects. Here's the interface implemented by this collection, followed by its originating class:

(SampleApp/Model/Collection/CollectionInterface.php)


namespace SampleAppModelCollection;
use SampleAppModel;
interface CollectionInterface extends Countable, IteratorAggregate, ArrayAccess
{
public function toArray();
public function clear();
public function reset();
public function add($key, ModelAbstractEntity $entity);

    public function get($key);
public function remove($key);
public function exists($key);
}

(SampleApp/Model/Collection/EntityCollection.php)


namespace SampleAppModelCollection;
use SampleAppModel;
class EntityCollection implements CollectionInterface
{
protected $_entities = array();
/**
* Constructor
*/
public function  __construct(array $entities = array())
{
$this->_entities = $entities;
$this->reset();
}
/**
* Get the entities stored in the collection
*/
public function toArray()
{
return $this->_entities;
}
/**
* Clear the collection
*/
public function clear()
{
$this->_entities = array();
}

    /**
* Rewind the collection
*/     
public function reset()
{
reset($this->_entities);
}
/**
* Add an entity to the collection
*/
public function add($key, ModelAbstractEntity $entity) {
return $this->offsetSet($key, $entity);
}
/**
* Get from the collection the entity with the specified key
*/
public function get($key)
{
return $this->offsetGet($key);
}
/**
* Remove from the collection the entity with the specified key
*/
public function remove($key)
{
return $this->offsetUnset($key);
}
/**
* Check if the entity with the specified key exists in the collection
*/
public function exists($key)
{
return $this->offsetExists($key);
}
/**
* Count the number of entities in the collection
*/
public function count()
{
return count($this->_entities);
}
/**
* Get the external array iterator
*/
public function getIterator()
{
return new ArrayIterator($this->toArray());
}
/**
* Add an entity to the collection
*/ 
public function offsetSet($key, $entity)
{
if (!$entity instanceof ModelAbstractEntity) {
throw new InvalidArgumentException('The entity to be added to the collection must be an instance of EntityAbstract.');
}
if (!isset($key)) {
$this->_entities[] = $entity;
}
else {
$this->_entities[$key] = $entity;
}
return true;
}

    /**
* Remove an entity from the collection
*/
public function offsetUnset($key)
{
if ($key instanceof ModelAbstractEntity) {
$this->_entities = array_filter($this->_entities, function ($v) use ($key) {
return $v !== $key;
});
return true;
}
if (isset($this->_entities[$key])) {
unset($this->_entities[$key]);
return true;
}
return false;
}

    /**
* Get the specified entity in the collection
*/
public function offsetGet($key)
{
return isset($this->_entities[$key])
? $this->_entities[$key] : null;
}  

    /**
* Check if the specified entity exists in the collection
*/ 
public function offsetExists($key)
{
return isset($this->_entities[$key]);
}
}

As shown in the above code snippet, the "EntityCollection" class does exactly what its name suggests: it traverses, counts, and accesses a collection of entities by using an array-like notation (along with a few concrete methods). I assume that you must have coded a class like this several times before, so I'm not going to waste time explaining its inner workings.

Instead, let's recap what's been achieved so far: at this time there's a simple model domain, where user entities live in happy isolation, completely ignorant of any type of persistence mechanism that might exist out of their boundaries.

Although this is well and fine, there's still no single sign of a violation of the Law of Demeter. Well, be patient and let me show you the layer that will be responsible for accessing the underlying database and persisting the previous model.

Here are the elements that comprise this brand new tier:

(SampleApp/Library/Database/DatabaseAdapterInterface.php)


namespace SampleAppLibraryDatabase;
interface DatabaseAdapterInterface
{
function connect();
function disconnect();
function query($query);
function fetch();
function select($table, $conditions, $fields, $order, $limit, $offset);
function insert($table, array $data);
function update($table, array $data, $conditions);
function delete($table, $conditions);
function getInsertId();
function countRows();
function getAffectedRows();
}

(SampleApp\Library/Database/MysqlAdapter)


namespace SampleAppLibraryDatabase;
use SampleAppCommon;
class MysqlAdapter implements DatabaseAdapterInterface, CommonAbstractResource
{
protected $_config = array();
protected $_link;
protected $_result;

    /**
* Constructor
*/ 
public function __construct(array $config)
{
if (count($config) !== 4) {
throw new InvalidArgumentException('Invalid number of connection parameters.');
}
$this->_config = $config;
}

    /**
* Connect to MySQL
*/
public function connect()
{
// connect only once
if ($this->_link === null) {
list($host, $user, $password, $database) = $this->_config;
if (!$this->_link = @mysqli_connect($host, $user, $password, $database)) {
throw new RunTimeException('Error connecting to the server : ' . mysqli_connect_error());
}
unset($host, $user, $password, $database);
}
return $this->_link;

/**
* Execute the specified query
*/
public function query($query)
{
if (!is_string($query) || empty($query)) {
throw new InvalidArgumentException('The specified query is not valid.');
}
// lazy connect to MySQL
$this->connect();
if (!$this->_result = mysqli_query($this->_link, $query)) {
throw new RunTimeException('Error executing the specified query : ' . $query . mysqli_error($this->_link));
}
return $this->_result;
}

    /**
* Perform a SELECT statement
*/ 
public function select($table, $where = '', $fields = '*', $order = '', $limit = null, $offset = null)
{
$query = 'SELECT ' . $fields . ' FROM ' . $table
. (($where) ? ' WHERE ' . $where : '')
. (($limit) ? ' LIMIT ' . $limit : '')
. (($offset && $limit) ? ' OFFSET ' . $offset : '')
. (($order) ? ' ORDER BY ' . $order : '');
$this->query($query);
return $this->countRows();
}

    /**
* Perform an INSERT statement
*/  
public function insert($table, array $data)
{
$fields = implode(',', array_keys($data));
$values = implode(',', array_map(array($this, 'quoteValue'), array_values($data)));
$query = 'INSERT INTO ' . $table . ' (' . $fields . ') ' . ' VALUES (' . $values . ')';
$this->query($query);
return $this->getInsertId();
}

    /**
* Perform an UPDATE statement
*/
public function update($table, array $data, $where = '')
{
$set = array();
foreach ($data as $field => $value) {
$set[] = $field . '=' . $this->quoteValue($value);
}
$set = implode(',', $set);
$query = 'UPDATE ' . $table . ' SET ' . $set 
. (($where) ? ' WHERE ' . $where : '');
$this->query($query);
return $this->getAffectedRows();
}

    /**
* Perform a DELETE statement
*/
public function delete($table, $where = '')
{
$query = 'DELETE FROM ' . $table
. (($where) ? ' WHERE ' . $where : '');
$this->query($query);
return $this->getAffectedRows();
}

    /**
* Escape the specified value
*/ 
public function quoteValue($value)
{
$this->connect();
if ($value === null) {
$value = 'NULL';
}
else if (!is_numeric($value)) {
$value = "'" . mysqli_real_escape_string($this->_link, $value) . "'";
}
return $value;
}

    /**
* Fetch a single row from the current result set
*/
public function fetch($mode = MYSQLI_ASSOC)
{
if ($this->_result === null) {
return false; 
}
if (!in_array($mode, array(MYSQLI_NUM, MYSQLI_ASSOC, MYSQLI_BOTH))) {
$mode = MYSQLI_ASSOC;
}
if (($row = mysqli_fetch_array($this->_result, $mode)) === false) {
$this->freeResult();
}
return $row;
}

    /**
* Get the insertion ID
*/
public function getInsertId()
{
return $this->_link !== null
? mysqli_insert_id($this->_link) : null;
}

    /**
* Get the number of rows returned by the current result set
*/
public function countRows()
{
return $this->_result !== null
? mysqli_num_rows($this->_result) : 0;
}

    /**
* Get the number of affected rows
*/ 
public function getAffectedRows()
{
return $this->_link !== null
? mysqli_affected_rows($this->_link) : 0;
}

    /**
* Free up the current result set
*/ 
public function freeResult()
{
if ($this->_result === null) {
return false;
}
mysqli_free_result($this->_result);
return true;
}

    /**
* Close explicitly the database connection
*/ 
public function disconnect()
{
if ($this->_link === null) {
return false;
}
mysqli_close($this->_link);
$this->_link = null;
return true;
}

    /**
* Close automatically the database connection when the instance of the class is destroyed
*/
public function __destruct()
{
$this->disconnect();
}
}

As shown above, this data access layer is a no-brainer, as it's made up of a segregated interface and one single implementer, which turns out to be a MySQL abstraction class that executes a few common database operations, such as running queries, counting and retrieving table rows.

While the driving logic of the previous class (and its associated interface) is pretty easy to follow, it's valid to point out that it doesn't violate the Law of Demeter either. As I said before though, my goal here is to implement a set of data mappers that interconnect the earlier domain model with the persistence layer just defined.

To do so, I could take a more "relaxed" approach and build a service locator, which would be responsible for providing the mappers with their collaborators. It makes sense, doesn't it? Based on this idea, here're the interfaces that are implemented by the mentioned service locator, along with its spawning class:

(SampleApp/Common/AbstractResource.php)


namespace SampleAppCommon;
interface AbstractResource
{}

(SampleApp/Common/RegistrableInterface.php)


namespace SampleAppCommon;
interface RegistrableInterface
{
public function set($key, AbstractResource $resource);
public function get($key);
public function remove($key);
public function exists($key);
}

(SampleApp/Common/ServiceLocator.php)


namespace SampleAppCommon;
class ServiceLocator implements RegistrableInterface
{
protected $_resources = array();
/**
* Set the specified resource
*/
public function set($key, AbstractResource $resource)
{
if (!isset($this->_resources[$key])) {
$this->_resources[$key] = $resource;
}
return $this;
}

    /**
* Get the specified resource
*/
public function get($key)
{
if (isset($this->_resources[$key])) {
return $this->_resources[$key];
}
throw new InvalidArgumentException('The requested resource does not exist.');
}

    /**
* Remove the specified resource
*/
public function remove($key)
{
if (isset($this->_resources[$key])) {
unset($this->_resources[$key]);
return $this;
}
throw new InvalidArgumentException('The resource to be removed does not exist.');
}

    /**
* Check if the specified resource exists
*/
public function exists($key)
{
return isset($this->_resources[$key]);
}
}

The above "ServiceLocator" class implements a couple of trivial interfaces, allowing you to save and remove generic resources from an array-based registry. While the functionality of this locator seems to be acceptable when analyzed in isolation, it's relatively easy to take it to the next level. But the question that comes up here is: how can this be done?

Well, the class could be used for providing the aforementioned mappers with the dependencies that they need to work as intended. Sounds like a decent approach that would be implemented in a few easy steps, right?

As usual, the best manner to demonstrate this concept is with a concrete example. Thus, in the following section I'll be constructing the pertaining mappers, which will exploit the functionality offered by the earlier service locator to acquire their collaborators.

Moving Data Between the Data Access and Persistence Layers: Building a Set of Mappers

If you've come to this point in the article, it's because you're really interested in seeing what's so wrong with breaking the Law of Demeter. Again, the best way to demonstrate this is a concrete code sample, so be sure to check out the definition of the following data mappers, something that hopefully will make things clear for you:

(SampleApp/Model/Mapper/AbstractMapper.php)


namespace SampleAppModelMapper;
use SampleAppLibraryDatabase,
SampleAppModelCollection,
SampleAppModel,
SampleAppCommon;
abstract class AbstractMapper
{
protected $_adapter;
protected $_entityTable;
protected $_entityClass;
/**
* Constructor
*/
public function __construct(CommonServiceLocator $serviceLocator, array $entityOptions = array())
{
// Get the database adapter via the service locator
$this->_adapter = $serviceLocator->get('adapter');

   // Set the entity table if the options has been specified
if (isset($entityOptions['entityTable'])) {
$this->setEntityTable($entityOptions['entityTable']);
}

        // Set the entity class if the options has been specified
if (isset($entityOptions['entityClass'])) {
$this->setEntityClass($entityOptions['entityClass']);
}
// check if the entity options have been set
$this->;_checkEntityOptions();
}

    /**
* Check if the entity options have been set
*/
protected function _checkEntityOptions()
{
// check if the entity table has been set
if (!isset($this->_entityTable)) {
throw new InvalidArgumentException('The entity table has been not set yet.');
}
// check if the entity class has been set
if (!isset($this->_entityClass)) {
throw new InvalidArgumentException('The entity class has been not set yet.');
}
}
/**
* Get the database adapter
*/
public function getAdapter() 

return $this->_adapter; 
}
/** 
* Set the entity table 
*/ 
public function setEntityTable($entityTable)  

if (!is_string($entityTable) || empty($entityTable)) { 
throw new InvalidArgumentException("The given entity table '$entityTable' is invalid."); 

$this->_entityTable = $entityTable; 

/** 
* Get the entity table 
*/ 
public function getEntityTable() 

return $this->_entityTable; 

/** 
* Set the entity class 
*/ 
public function setEntityClass($entityClass) 

if (!is_subclass_of($entityClass, 'SampleAppModelAbstractEntity')) { 
throw new InvalidArgumentException("The given entity class '$entityClass' is invalid. It must be a subclass of AbstractEntity."); 

$this->_entityClass = $entityClass; 

/** 
* Get the entity class 
*/ 
public function getEntityClass() 

return $this->_entityClass; 

/** 
* Find an entity by its ID 
*/ 
public function findById($id) 

$this->_adapter->select($this->_entityTable, "id = $id"); 
if (!$data = $this->_adapter->fetch()) { 
return null; 

return new $this->_entityClass($data);         
}
/** 
* Find all the entities that match the specified criteria (or all when no criteria are given) 
*/ 
public function find($criteria = '') 

$collection = new CollectionEntityCollection; 
$this->_adapter->select($this->_entityTable, $criteria); 
while ($data = $this->_adapter->fetch()) { 
$collection[] = new $this->_entityClass($data); 

return $collection; 

/** 
* Insert a new row in the table corresponding to the specified entity 
*/ 
public function insert($entity) 
{  
if (!$entity instanceof $this->_entityClass) { 
throw new InvalidArgumentException("The entity to be inserted must be an instance of '$entityClass'."); 

return $this->_adapter->insert($this->_entityTable, $entity->toArray()); 
}
/** 
* Delete the row in the table corresponding to the specified entity or ID 
*/ 
public function delete($id) 

if ($id instanceof $this->_entityClass) { 
$id = $id->id; 

return $this->_adapter->delete($this->_entityTable, "id = $id"); 
}    
}

(SampleApp/Model/Mapper/UserMapper.php)


namespace SampleAppModelMapper;
class UserMapper extends AbstractMapper 

protected $_entityTable = 'users'; 
protected $_entityClass = 'SampleAppModelUser';   
}

At a glance, the mappers look pretty good, as the first one is an abstract parent that encapsulates the functionality required to perform CRUD operations in a MySQL database. This makes it possible to fetch one or multiple entities from storage, insert new domain objects, and delete existing ones as well. On the other hand, its subclass "User" does something similar, but specifically with user entities. Again, what's the big issue with these classes?

If you look closer at the parent's constructor, you'll realize that it takes an instance of the earlier service locator, which is used to get the database adapter. While admittedly this falls within the "while list" of the Law of Demeter, from a pragmatic standpoint, it's a clear violation of it. After all, why does the mapper have to use a mediator to obtain the adapter, when it can get it directly?

Even if the locator were saved as a protected/private property, this wouldn't make any difference. In both cases, the mapper is asking for the wrong collaborator, thus gaining access to functionality that it doesn't need at all. Not to mention that this approach has introduced a strong coupling, which makes testing the mappers a painful and inflexible process.

And if all of these reasons still don't convince you from the explosion of side effects generated by the breakage of the Law of Demeter, let me show you a short script, which uses all the classes developed previously to fetch a few users from the following MySQL table:

That's the table in question. Now, here's the mentioned script:


use SampleAppLibraryLoaderAutoloader as Autoloader, 
SampleAppCommonServiceLocator as ServiceLocator, 
SampleAppLibraryDatabaseMysqlAdapter as MysqlAdapter, 
SampleAppModelMapperUserMapper as UserMapper, 
SampleAppModelUser as User; 

// include the autoloader and create an instance of it 
require_once 'Library/Loader/Autoloader.php'; 
$autoloader = new Autoloader;


// create an instance of the service locator and store the MySQL adapter in it 
$serviceLocator = new ServiceLocator; 
$serviceLocator->set('adapter', new MysqlAdapter(array( 
'host',  
'user',  
'password',  
'database' 
)) 
);
// create an instance of the user mapper (the service locator is injected) 
$userMapper = new UserMapper($serviceLocator);

// fetch all users from the storage and display their data 
$users = $userMapper->find(); 
foreach ($users as $user) { 
echo ' ID: ' . $user->id  
. ' Name: ' . $user->name  
. ' Email: ' . $user->email . ''; 
}

// insert a new user into the storage 
$user = new User(array( 
'name'  => 'Margaret Dennis',  
'email' => 'margaret@domain.com' 
));
$userMapper->insert($user);
// delete an existing user from the storage 
$userMapper->delete(1);

If you run this script, (and assuming that you created the previous "users" table), it will nicely retrieve the corresponding domain objects, then insert a new user, and finally delete the first row from the corresponding table. To do so, however, first the database adapter needs to be saved in the service locator, and finally this one injected into the user mapper.

This approach not only requires you to write more lines of code, (which is the least of the damages), but it makes the whole API confusing, as it's not clear why the mapper needs the service locator to do its business. Of course, it's relatively easy to fix these issues and make the mapper to follow the commandments of the Law of Demeter. However, the solution will be implemented in the final part of this tutorial.

Final Thoughts

In this first post, I attempted to provide you with a humble introduction to what the Law of Demeter is and how its infringement can turn our PHP classes into heavily-coupled structures that are hard to test in isolation. As I said before, though, it's fairly simple to construct classes that stick to the law's requirements. Thus, if you're interested in seeing the fix up, don't miss the final part!



blog comments powered by

Gazebo shed on saleDownload here
Read More ...

Retrieving Table Data with the LIKE Operator

How to use the LIKE operator

One final operator you can use in a search condition is the LIKE operator, shown in figure 3-15. You use this operator along with the wildcards shown at the top of this figure to specify the string pattern, or mask, that you want to match. The examples in this figure show how this works.

In the first example, the LIKE phrase specifies that all vendors in cities that start with the letters SAN should be included in the query results. Here, the percent sign (%) indicates that any characters can follow these three letters. So San Diego and Santa Ana are both included in the results.

The second example selects all vendors whose vendor name starts with the letters COMPU, followed by any one character, the letters ER, and any characters after that. Two vendor names that match that pattern are Compuserve and Computerworld.

The LIKE operator provides a powerful technique for finding information in a database that can?t be found using any other technique.

The syntax of the WHERE clause with the LIKE operator

WHERE match_expression [NOT] LIKE pattern

Wildcard symbols

Matches any string of zero or more characters.

WHERE clauses that use the LIKE operator

Results that match the mask the mask

WHERE vendor_name LIKE 'COMPU_ER%'

"Compuserve"and "Computerworld"

Description

You use the LIKE operator to retrieve rows that match a string pattern, called a mask. Within the mask, you can use special characters, called wildcard characters, that determine which values in the column satisfy the condition.
You can use the NOT operator before the LIKE operator. Then, only those rows with values that don?t match the string pattern will be included in the result set. --------------------------------------------Figure 3-15 How to use the LIKE operator


blog comments powered by

Gazebo shed on saleDownload here
Read More ...

PHP: Applying the Law of Demeter

Perhaps better known as "The Principle of Least Knowledge", the Law of Demeter (http://en.wikipedia.org/wiki/Law_of_Demeter) is a programming paradigm, which encourages the design of loosely-coupled classes that take advantage only of the functionality offered by their closest collaborators. Although this may sound logical, the truth is that sometimes we tend (either due to ignorance or as a consequence of bad design), to provide our classes with dependencies that aren?t actually required, or act as mediators to access the ones that are needed.

As with many other concepts of object-oriented programming, the best way to understand the underlying logic behind the Law of Demeter is by setting up an example that breaks the principle in question. In line with this idea, in the previous part of this tutorial, I went through the implementation of a small ? yet extendable ? PHP framework, whose set of data mappers committed a cardinal sin: they injected a service locator into their internals, only to get the database adapter they needed to run CRUD operations in the persistence layer.

Learning from our mistakes, in this final installment I?ll be refactoring the framework?s mapping layer, which this time will follow the commandments of the Law of Demeter, breaking up the undesired coupling introduced by the aforementioned service locator.

Before we begin however, if you missed the first part of this series - or need a refresher - you can find it here:

http://www.devshed.com/c/a/PHP/PHP-and-the-Law-of-Demeter-43810/

A Quick Recap: Showing the Badly-Designed Version of the Earlier Mapping Layer

If you haven't viewed the previous installment - or don't want to go back and review it - below I will reintroduce its source files, so that you can study them in detail and gain a better understanding of how they work.

Here?s the framework domain model, which is made up of some straightforward interfaces and classes, tasked with modeling generic entities, users and collections of domain objects. Check them out:

(SampleApp/Model/AbstractEntity.php)

namespace SampleAppModel;
abstract class AbstractEntity
{
protected $_values = array();
protected $_allowedFields = array();
/**
* Constructor
*/
public function __construct(array $fields)
{
foreach ($fields as $name = > $value) {
$this->$name = $value;
}
}
/**
* Assign a value to the specified field via the corresponding mutator (if it exists);
* otherwise, assign the value directly to the '$_values' array
*/
public function __set($name, $value)
{
if (!in_array($name, $this->_allowedFields)) {
throw new InvalidArgumentException("Setting the field '$name' is not allowed for this entity");
}
$mutator = 'set' . ucfirst($name);
if (method_exists($this, $mutator) && is_callable(array($this, $mutator))) {
$this->;$mutator($value);
}
else {
$this->_values[$name] = $value;
}
}
/**
* Get the value of the specified field (via the getter if it exists or by getting it from the $_values array)
*/
public function __get($name)
{
if (!in_array($name, $this->_allowedFields)) {
throw new InvalidArgumentException("Getting the field '$name' is not allowed for this entity.");
}
$accessor = 'get' . ucfirst($name);
if (method_exists($this, $accessor) && is_callable(array($this, $accessor))) {
return $this->$accessor;
}
else if (isset($this->_values[$name])) {
return $this->_values[$name];
}
else {
throw new InvalidArgumentException("The field '$name' has not been set for this entity yet.");
}
}
/**
* Check if the specified field has been assigned to the entity
*/
public function __isset($name)
{
if (!in_array($name, $this->_allowedFields)) {
throw new InvalidArgumentException("The field '$name' is not allowed for this entity.");
}
return isset($this->_values[$name]);
}
/**
* Unset the specified field from the entity
*/
public function __unset($name)
{
if (!in_array($name, $this->_allowedFields)) {
throw new InvalidArgumentException("Unsetting the field '$name' is not allowed for this entity.");
}
if (isset($this->_values[$name])) {
unset($this->_values[$name]);
return true;
}
throw new InvalidArgumentException("The field '$name' has not been set for this entity yet.");
}
/**
* Get an associative array with the values assigned to the fields of the entity
*/
public function toArray()
{
return $this->_values;
}
}

(SampleApp/Model/User.php)

namespace SampleAppModel;
class User extends AbstractEntity
{
protected $_allowedFields = array('id', 'name', 'email');
/**
* Set the user's ID
*/
public function setId($id)
{
if(!filter_var($id, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 999999)))) {
throw new InvalidArgumentException('The ID of the user is invalid.');
}
$this->_values['id'] = $id;
}
/**
* Set the user's full name
*/
public function setName($name)
{
if (strlen($name) < 2 || strlen($name) > 32) {
throw new InvalidArgumentException('The name of the user is invalid.');
}
$this->_values['name'] = $name;
}
/**
* Set the user's email address
*/
public function setEmail($email)
{
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException('The email address of the user is invalid.');
}
$this->_values['email'] = $email;
}
}

(SampleApp/Model/Collection/CollectionInterface.php)

namespace SampleAppModelCollection;
use SampleAppModel;
interface CollectionInterface extends Countable, IteratorAggregate, ArrayAccess
{
public function toArray();
public function clear();
public function reset();
public function add($key, ModelAbstractEntity $entity);
public function get($key);
public function remove($key);
public function exists($key);
}

(SampleApp/Model/Collection/EntityCollection.php)

namespace SampleAppModelCollection;
use SampleAppModel;
class EntityCollection implements CollectionInterface
{
protected $_entities = array();
/**
* Constructor
*/
public function  __construct(array $entities = array())
{
$this->_entities = $entities;
$this->reset();
}
/**
* Get the entities stored in the collection
*/
public function toArray()
{
return $this->_entities;
}
/**
* Clear the collection
*/
public function clear()
{
$this->_entities = array();
}
/**
* Rewind the collection
*/
public function reset()
{
reset($this->_entities);
}
/**
* Add an entity to the collection
*/
public function add($key, ModelAbstractEntity $entity) {
return $this->offsetSet($key, $entity);
}
/**
* Get from the collection the entity with the specified key
*/
public function get($key)
{
return $this->offsetGet($key);
}
/**
* Remove from the collection the entity with the specified key
*/
public function remove($key)
{
return $this->offsetUnset($key);
}
/**
* Check if the entity with the specified key exists in the collection
*/
public function exists($key)
{
return $this->offsetExists($key);
}
/**
* Count the number of entities in the collection
*/
public function count()
{
return count($this->_entities);
}
/**
* Get the external array iterator
*/
public function getIterator()
{
return new ArrayIterator($this->toArray());
}
/**
* Add an entity to the collection
*/
public function offsetSet($key, $entity)
{
if (!$entity instanceof ModelAbstractEntity) {
throw new InvalidArgumentException('The entity to be added to the collection must be an instance of EntityAbstract.');
}
if (!isset($key)) {
$this->_entities[] = $entity;
}
else {
$this->_entities[$key] = $entity;
}
return true;
}  
/**
* Remove an entity from the collection
*/
public function offsetUnset($key)
{
if ($key instanceof ModelAbstractEntity) {
$this->_entities = array_filter($this->_entities, function ($v) use ($key) {
return $v !== $key;
});
return true;
}
if (isset($this->_entities[$key])) {
unset($this->_entities[$key]);
return true;
}
return false;
}
/**
* Get the specified entity in the collection
*/
public function offsetGet($key)
{
return isset($this->_entities[$key])
? $this->_entities[$key] : null;
}
/**
* Check if the specified entity exists in the collection
*/
public function offsetExists($key)
{
return isset($this->_entities[$key]);
}
}

The above classes are responsible for setting up a simple domain layer where user entities and collections of domain objects live in isolation.

Having listed the framework?s domain layer, it?s time to show the next one, which is charged with accessing the underlying persistence mechanism. In this case, a MySQL database:

(SampleApp/Library/Database/DatabaseAdapterInterface.php)

namespace SampleAppLibraryDatabase;
interface DatabaseAdapterInterface
{
function connect();
function disconnect();
function query($query);
function fetch();
function select($table, $conditions, $fields, $order, $limit, $offset);
function insert($table, array $data);
function update($table, array $data, $conditions);
function delete($table, $conditions);
function getInsertId();
function countRows();
function getAffectedRows();
}

(SampleApp\Library/Database/MysqlAdapter)

namespace SampleAppLibraryDatabase;
use SampleAppCommon;
class MysqlAdapter implements DatabaseAdapterInterface, CommonAbstractResource
{
protected $_config = array();
protected $_link;
protected $_result;
/**
* Constructor
*/
public function __construct(array $config)
{
if (count($config) !== 4) {
throw new InvalidArgumentException('Invalid number of connection parameters.');
}
$this->_config = $config;
}
/**
* Connect to MySQL
*/
public function connect()
{
// connect only once
if ($this->_link === null) {
list($host, $user, $password, $database) = $this >_config;
if (!$this->_link = @mysqli_connect($host, $user, $password, $database)) {
throw new RunTimeException('Error connecting to the server : ' . mysqli_connect_error());
}
unset($host, $user, $password, $database);
}
return $this->_link;
}
/**
* Execute the specified query
*/
public function query($query)
{
if (!is_string($query) || empty($query)) {
throw new InvalidArgumentException('The specified query is not valid.');
}
// lazy connect to MySQL
$this->connect();
if (!$this->_result = mysqli_query($this->_link, $query)) {
throw new RunTimeException('Error executing the specified query : ' . $query . mysqli_error($this->_link));
}
return $this->_result;
}
/**
* Perform a SELECT statement
*/
public function select($table, $where = '', $fields = '*', $order = '', $limit = null, $offset = null)
{
$query = 'SELECT ' . $fields . ' FROM ' . $table
. (($where) ? ' WHERE ' . $where : '')
. (($limit) ? ' LIMIT ' . $limit : '')   
. (($offset && $limit) ? ' OFFSET ' . $offset : '')
. (($order) ? ' ORDER BY ' . $order : '');
$this->query($query);
return $this->countRows();
}
/**
* Perform an INSERT statement
*/
public function insert($table, array $data)
{
$fields = implode(',', array_keys($data));
$values = implode(',', array_map(array($this, 'quoteValue'), array_values($data)));
$query = 'INSERT INTO ' . $table . ' (' . $fields . ') ' . ' VALUES (' . $values . ')';
$this->query($query);
return $this->getInsertId();
}
/**
* Perform an UPDATE statement
*/
public function update($table, array $data, $where = '')
{
$set = array();
foreach ($data as $field => $value) {
$set[] = $field . '=' . $this->quoteValue($value);
}
$set = implode(',', $set);
$query = 'UPDATE ' . $table . ' SET ' . $set
. (($where) ? ' WHERE ' . $where : '');
$this->query($query);
return $this->getAffectedRows();
}
/**
* Perform a DELETE statement
*/
public function delete($table, $where = '')
{
$query = 'DELETE FROM ' . $table
. (($where) ? ' WHERE ' . $where : '');
$this->query($query);
return $this->getAffectedRows();
}
/**
* Escape the specified value
*/
public function quoteValue($value)
{
$this->connect();
if ($value === null) {
$value = 'NULL';
}
else if (!is_numeric($value)) {
$value = "'" . mysqli_real_escape_string($this->_link, $value) . "'";
}
return $value;
}
/**
* Fetch a single row from the current result set
*/
public function fetch($mode = MYSQLI_ASSOC)
{
if ($this->_result === null) {
return false;
}
if (!in_array($mode, array(MYSQLI_NUM, MYSQLI_ASSOC, MYSQLI_BOTH))) {
$mode = MYSQLI_ASSOC;
}
if (($row = mysqli_fetch_array($this->_result, $mode)) === false) {
$this->freeResult();
}
return $row;
}
/**
* Get the insertion ID
*/
public function getInsertId()
{
return $this->_link !== null
? mysqli_insert_id($this->_link) : null;
}
/**
* Get the number of rows returned by the current result set
*/
public function countRows()
{
return $this->_result !== null 
? mysqli_num_rows($this->_result) : 0;
}
/**
* Get the number of affected rows
*/
public function getAffectedRows()
{
return $this->_link !== null
? mysqli_affected_rows($this->_link) : 0;
}
/**
* Free up the current result set
*/
public function freeResult()
{
if ($this->_result === null) {
return false;
}
mysqli_free_result($this->_result);
return true;
}
/**
* Close explicitly the database connection
*/
public function disconnect()
{
if ($this->_link === null) {
return false;
}
mysqli_close($this->_link);
$this->_link = null;
return true;
}
/**
* Close automatically the database connection when the instance of the class is destroyed
*/
public function __destruct()
{
$this->disconnect();
}
}

That was fairly easy to grasp wasn?t it? However, we?re not done listing the framework?s source classes and interfaces. Below are the elements that compose the service locator mentioned at the start of this article:

(SampleApp/Common/AbstractResource.php)

namespace SampleAppCommon;
interface AbstractResource
{}

(SampleApp/Common/RegistrableInterface.php)

namespace SampleAppCommon;
interface RegistrableInterface
{
public function set($key, AbstractResource $resource);
public function get($key);
public function remove($key);
public function exists($key);
}

(SampleApp/Common/ServiceLocator.php)

namespace SampleAppCommon;
class ServiceLocator implements RegistrableInterface
{
protected $_resources = array();
/**
* Set the specified resource
*/
public function set($key, AbstractResource $resource)
{
if (!isset($this->_resources[$key])) {
$this->_resources[$key] = $resource;
}
return $this;
}
/**
* Get the specified resource
*/
public function get($key)
{
if (isset($this->_resources[$key])) {
return $this->_resources[$key];
}
throw new InvalidArgumentException('The requested resource does not exist.');
}
/**
* Remove the specified resource
*/
public function remove($key)
{
if (isset($this->_resources[$key])) {
unset($this->_resources[$key]);
return $this;
}
throw new InvalidArgumentException('The resource to be removed does not exist.');
}
/**
* Check if the specified resource exists
*/
public function exists($key)
{
return isset($this->_resources[$key]);
}
}

As you can see, the above service locator is nothing but an array-based registry, which allows you to save and remove generic resources by means of some simple methods. Of course, in a situation where dependency injection is the workhorse that pulls out the chariots of an application or framework, such a locator won?t be needed at all.

In this case - and for demonstrative purposes - I decided to inject the locator into the constructor of the following mappers (yes, the evil guys that break up the Law of Demeter): 

(SampleApp/Model/Mapper/AbstractMapper.php)

namespace SampleAppModelMapper;
use SampleAppLibraryDatabase,
SampleAppModelCollection,
SampleAppModel,
SampleAppCommon;
abstract class AbstractMapper
{
protected $_adapter;
protected $_entityTable;
protected $_entityClass;
/**
* Constructor
*/
public function __construct(CommonServiceLocator $serviceLocator, array $entityOptions = array())
{
// Get the database adapter via the service locator
$this->_adapter = $serviceLocator->get('adapter');
// Set the entity table if the options has been specified
if (isset($entityOptions['entityTable'])) {
$this->setEntityTable($entityOptions['entityTable']);
}
// Set the entity class if the options has been specified
if (isset($entityOptions['entityClass'])) {
$this->setEntityClass($entityOptions['entityClass']);
}
// check if the entity options have been set
$this->_checkEntityOptions();
}
/**
* Check if the entity options have been set
*/
protected function _checkEntityOptions()
{
// check if the entity table has been set
if (!isset($this->_entityTable)) {
throw new InvalidArgumentException('The entity table has been not set yet.');
}
// check if the entity class has been set
if (!isset($this->_entityClass)) {
throw new InvalidArgumentException('The entity class has been not set yet.');
}
}
/**
* Get the database adapter
*/
public function getAdapter()
{
return $this->_adapter;
}
/**
* Set the entity table
*/
public function setEntityTable($entityTable)
{
if (!is_string($entityTable) || empty($entityTable)) {
throw new InvalidArgumentException("The given entity table '$entityTable' is invalid.");
}
$this->_entityTable = $entityTable;
}
/**
* Get the entity table
*/
public function getEntityTable()
{
return $this->_entityTable;
}
/**
* Set the entity class
*/
public function setEntityClass($entityClass)
{
if (!is_subclass_of($entityClass, 'SampleAppModelAbstractEntity')) {
throw new InvalidArgumentException("The given entity class '$entityClass' is invalid. It must be a subclass of AbstractEntity.");
}
$this->_entityClass = $entityClass;
}
/**
* Get the entity class
*/
public function getEntityClass()
{
return $this->_entityClass;
}
/**
* Find an entity by its ID
*/
public function findById($id)
{
$this->_adapter->select($this->_entityTable, "id = $id");
if (!$data = $this->_adapter->fetch()) {
return null;
}
return new $this->_entityClass($data);
}
/**
* Find all the entities that match the specified criteria (or all when no criteria are given)
*/
public function find($criteria = '')
{
$collection = new CollectionEntityCollection;
$this->_adapter->select($this->_entityTable, $criteria);
while ($data = $this->_adapter->fetch()) {
$collection[] = new $this->_entityClass($data);
}
return $collection;
}
/**
* Insert a new row in the table corresponding to the specified entity
*/
public function insert($entity)
{
if (!$entity instanceof $this->_entityClass) {
throw new InvalidArgumentException("The entity to be inserted must be an instance of '$entityClass'.");
}
return $this->_adapter->insert($this->_entityTable, $entity->toArray());
}
/**
* Delete the row in the table corresponding to the specified entity or ID
*/
public function delete($id)
{
if ($id instanceof $this->_entityClass) {
$id = $id->id;
}
return $this->_adapter->delete($this->_entityTable, "id = $id");
}
}

(SampleApp/Model/Mapper/UserMapper.php)

namespace SampleAppModelMapper;
class UserMapper extends AbstractMapper
{
protected $_entityTable = 'users';
protected $_entityClass = 'SampleAppModelUser';
}

You don't have to be a genius to spot the flaw in the implementation of the previous mappers. Effectively, they use the service locator as a ?middleman? to gain access to the database adapter, which is in turn stored in a class property. Even though subtle, this is a violation of the commandment ?talk to your closest friends?, stated by the Law of Demeter, as the adapter should be directly passed in the constructor, instead of using an unneeded mediator.

Before I show you how simple it is to fix this issue, take a look at the following script, which shows how to use all of the classes (and interfaces) listed so far, in order to run a few CRUD operations in a ?users? MySQL table.

Here?s how this script looks:  

use SampleAppLibraryLoaderAutoloader as Autoloader,
SampleAppCommonServiceLocator as ServiceLocator,
SampleAppLibraryDatabaseMysqlAdapter as MysqlAdapter,
SampleAppModelMapperUserMapper as UserMapper,
SampleAppModelUser as User;
// include the autoloader and create an instance of it
require_once 'Library/Loader/Autoloader.php';
$autoloader = new Autoloader;
// create an instance of the service locator and store the MySQL adapter in it
$serviceLocator = new ServiceLocator;
$serviceLocator->set('adapter', new MysqlAdapter(array(
'host',
'user',
'password',
'database'
))
);
// create an instance of the user mapper (the service locator is injected)
$userMapper = new UserMapper($serviceLocator);
// fetch all users from the storage and display their data
$users = $userMapper->find();
foreach ($users as $user) {
echo ' ID: ' . $user->id
. ' Name: ' . $user->name
. ' Email: ' . $user->;email . '<; >;';
}
// insert a new user into the storage
$user = new User(array(
'name' =>; 'Margaret Dennis',
'email' => 'margaret@domain.com'
));
$userMapper->insert($user);
// delete an existing user from the storage
$userMapper->delete(1);

Even though the earlier script does a decent job when it comes to fetching, saving to and removing user entities from storage, this is done by using the service locator, which in this particular case isn?t required at all.

But fear not, as addressing this issue is a breeze. In the coming section I?ll be refactoring the troubling mappers, which this time will make use only of the dependencies that they require.

Fixing Things: Making the Data Mappers Adhere to the Law of Demeter

Turning the previous data mappers into classes that follow the commandments of the Law of Demeter is a simple process, reduced to eliminating the mediator (in this case the service locator), and injecting directly the corresponding database adapter.

As usual, the best way to demonstrate this is with concrete code. Thus, here?s how the abstract mapper looks (and hence its subclass), after providing it with the right collaborator:

(SampleApp/Model/Mapper/MapperInterface.php)

namespace SampleAppModelMapper;
interface MapperInterface
{
public function findById($id);
public function find($conditions = '');
public function insert($entity);
public function delete($id);
}

(SampleApp/Model/Mapper/AbstractMapper.php)

namespace SampleAppModelMapper;
use SampleAppLibraryDatabase,
SampleAppModelCollection,
SampleAppModel;
abstract class AbstractMapper implements MapperInterface
{
protected $_adapter;
protected $_entityTable;
protected $_entityClass;
/**
* Constructor
*/
public function __construct(DatabaseDatabaseAdapterInterface $adapter, array $entityOptions = array())
{
$this->_adapter = $adapter;
// Set the entity table if the option has been specified
if (isset($entityOptions['entityTable'])) {
$this->setEntityTable($entityOptions['entityTable']);
}
// Set the entity class if the option has been specified
if (isset($entityOptions['entityClass'])) {
$this->setEntityClass($entityOptions['entityClass']);
}
// check if the entity options have been set
$this->_checkEntityOptions();
}
/**
* Check if the entity options have been set
*/
protected function _checkEntityOptions()
{
// check if the entity table has been set
if (!isset($this->_entityTable)) {
throw new InvalidArgumentException('The entity table has been not set yet.');
}
// check if the entity class has been set
if (!isset($this->_entityClass)) {
throw new InvalidArgumentException('The entity class has been not set yet.');
}
}
/**
* Get the database adapter
*/
public function getAdapter()
{
return $this->_adapter;
}
/**
* Set the entity table
*/
public function setEntityTable($entityTable)
{
if (!is_string($entityTable) || empty($entityTable)) {
throw new InvalidArgumentException("The given entity table '$entityTable' is invalid.");
}
$this->_entityTable = $entityTable;
}
/**
* Get the entity table
*/
public function getEntityTable()
{
return $this->_entityTable;
}
/**
* Set the entity class
*/
public function setEntityClass($entityClass)
{
if (!is_subclass_of($entityClass, 'SampleAppModelAbstractEntity')) {
throw new InvalidArgumentException("The given entity class '$entityClass' is invalid. It must be a subclass of AbstractEntity.");
}
$this->_entityClass = $entityClass;
}
/**
* Get the entity class
*/
public function getEntityClass()
{
return $this->_entityClass;
}
/**
* Find an entity by its ID
*/
public function findById($id)
{
$this->_adapter->select($this->_entityTable, "id = $id");
if (!$data = $this->_adapter->fetch()) {
return null;
}
return new $this->_entityClass($data);    }
/**
* Find all the entities that match the specified criteria (or all when no criteria are given)
*/
public function find($criteria = '')
{
$collection = new CollectionEntityCollection;
$this->_adapter->select($this->_entityTable, $criteria);
while ($data = $this->_adapter->fetch()) {
$collection[] = new $this->_entityClass($data);
}
return $collection;
}
/**
* Insert a new row in the table corresponding to the specified entity
*/
public function insert($entity)
{
if (!$entity instanceof $this->_entityClass) {
throw new InvalidArgumentException("The entity to be inserted must be an instance of '$entityClass'.");
}
return $this->_adapter->insert($this->_entityTable, $entity->toArray());
}
/**
* Delete the row in the table corresponding to the specified entity or ID
*/
public function delete($id)
{
if ($id instanceof $this->_entityClass) {
$id = $id->id;
}
return $this->_adapter->delete($this->_entityTable, "id = $id");
}
}

Aside from implementing a brand new segregated interface, which allows you to easily create different data mappers, now the above ?AbstractMapper? class accepts the database adapter in its constructor. This subtle - yet relevant - not only sticks with the Law of Demeter, but it also breaks up the coupling created between the service locator and the mapper in question. In summary, we get the best of both worlds.

What?s more, if you?re curious and want to see how to consume the refactored version of the mapper, the following script will of help. Take a look at it:

use SampleAppLibraryLoaderAutoloader as Autoloader,
SampleAppLibraryDatabaseMysqlAdapter as MysqlAdapter,
SampleAppModelMapperUserMapper as UserMapper,
SampleAppModelUser as User;
// include the autoloader and create an instance of it
require_once __DIR__ . '/Library/Loader/Autoloader.php';
$autoloader = new Autoloader;
// create an instance of the user mapper and inject the MySQL adapter into it
$userMapper = new UserMapper(
new MysqlAdapter(array(
'host',
'user',
'password',
'database'
))
);
// fetch all users from the storage and display their data
$users = $userMapper->find();
foreach ($users as $user) {
echo ' ID: ' . $user->id
. ' Name: ' . $user->name
. ' Email: ' . $user->email . '< >';
}
// insert a new user into the storage
$user = new User(array(
'name'=> 'Margaret Dennis', 
'email' => 'margaret@domain.com'
));
$userMapper->insert($user);
// delete an existing user from the storage
$userMapper->delete(1);

There you have it. Now that the database adapter is injected directly into the user mapper?s internals, the whole code is easier to read and consume, not to mention the improvements achieved in terms of decoupling and testability. Moreover, even when in this case I implemented a mapping layer that deliberately violated the Law of Demeter and showed the bunch of side effects that this produces, the same concept can be applied to different use cases. So, be conscious with the dependencies that you provide to your classes.

Final Thoughts

In this programming series you learned the basic concepts that surround the application of the Law of Demeter, and how its breakage can lead to create strongly-coupled classes that don?t adhere to the principles of good object-oriented design.

In light of this, if you?re planning to use dependency injection in the construction of your own PHP classes, make sure to design them only to ask for the dependencies that they really need. By fitting this subtle - yet crucial -requirement, you?ll be able to build testable, highly-decoupled classes more easily, which will have fewer responsibilities as well. What more can you ask for?

See you in the next PHP development tutorial!



blog comments powered by

Gazebo shed on saleDownload here
Read More ...
Powered by Blogger.